spirv: generate module initializer

This commit is contained in:
Robin Voetter
2023-09-18 22:39:44 +02:00
committed by Andrew Kelley
parent 5d844faf7c
commit b845c9d532
4 changed files with 97 additions and 39 deletions

View File

@@ -94,6 +94,8 @@ pub const Global = struct {
begin_inst: u32,
/// The past-end offset into `self.flobals.section`.
end_inst: u32,
/// The result-id of the function that initializes this value.
initializer_id: IdRef,
};
/// This models a kernel entry point.
@@ -174,9 +176,6 @@ globals: struct {
section: Section = .{},
} = .{},
/// The function IDs of global variable initializers
initializers: std.ArrayListUnmanaged(IdRef) = .{},
pub fn init(gpa: Allocator, arena: Allocator) Module {
return .{
.gpa = gpa,
@@ -205,8 +204,6 @@ pub fn deinit(self: *Module) void {
self.globals.globals.deinit(self.gpa);
self.globals.section.deinit(self.gpa);
self.initializers.deinit(self.gpa);
self.* = undefined;
}
@@ -289,6 +286,10 @@ fn addEntryPointDeps(
const decl = self.declPtr(decl_index);
const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep];
if (seen.isSet(@intFromEnum(decl_index))) {
return;
}
seen.set(@intFromEnum(decl_index));
if (self.globalPtr(decl_index)) |global| {
@@ -296,9 +297,7 @@ fn addEntryPointDeps(
}
for (deps) |dep| {
if (!seen.isSet(@intFromEnum(dep))) {
try self.addEntryPointDeps(dep, seen, interface);
}
try self.addEntryPointDeps(dep, seen, interface);
}
}
@@ -330,20 +329,76 @@ fn entryPoints(self: *Module) !Section {
return entry_points;
}
/// Generate a function that calls all initialization functions,
/// in unspecified order (an order should not be required here).
/// It generated as follows:
/// %init = OpFunction %void None
/// foreach %initializer:
/// OpFunctionCall %initializer
/// OpReturn
/// OpFunctionEnd
fn initializer(self: *Module, entry_points: *Section) !Section {
var section = Section{};
errdefer section.deinit(self.gpa);
// const void_ty_ref = try self.resolveType(Type.void, .direct);
const void_ty_ref = try self.resolve(.void_type);
const void_ty_id = self.resultId(void_ty_ref);
const init_proto_ty_ref = try self.resolve(.{ .function_type = .{
.return_type = void_ty_ref,
.parameters = &.{},
} });
const init_id = self.allocId();
try section.emit(self.gpa, .OpFunction, .{
.id_result_type = void_ty_id,
.id_result = init_id,
.function_control = .{},
.function_type = self.resultId(init_proto_ty_ref),
});
try section.emit(self.gpa, .OpLabel, .{
.id_result = self.allocId(),
});
var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len);
defer seen.deinit(self.gpa);
var interface = std.ArrayList(IdRef).init(self.gpa);
defer interface.deinit();
for (self.globals.globals.keys(), self.globals.globals.values()) |decl_index, global| {
try self.addEntryPointDeps(decl_index, &seen, &interface);
try section.emit(self.gpa, .OpFunctionCall, .{
.id_result_type = void_ty_id,
.id_result = self.allocId(),
.function = global.initializer_id,
});
}
try section.emit(self.gpa, .OpReturn, {});
try section.emit(self.gpa, .OpFunctionEnd, {});
try entry_points.emit(self.gpa, .OpEntryPoint, .{
// TODO: Rusticl does not support this because its poorly defined.
// Do we need to generate a workaround here?
.execution_model = .Kernel,
.entry_point = init_id,
.name = "zig global initializer",
.interface = interface.items,
});
try self.sections.execution_modes.emit(self.gpa, .OpExecutionMode, .{
.entry_point = init_id,
.mode = .Initializer,
});
return section;
}
/// Emit this module as a spir-v binary.
pub fn flush(self: *Module, file: std.fs.File) !void {
// See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
const header = [_]Word{
spec.magic_number,
// TODO: From cpu features
// Emit SPIR-V 1.4 for now. This is the highest version that Intel's CPU OpenCL supports.
(1 << 16) | (4 << 8),
0, // TODO: Register Zig compiler magic number.
self.idBound(),
0, // Schema (currently reserved for future use)
};
// TODO: Perform topological sort on the globals.
var globals = try self.orderGlobals();
defer globals.deinit(self.gpa);
@@ -354,6 +409,19 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
var types_constants = try self.cache.materialize(self);
defer types_constants.deinit(self.gpa);
var init_func = try self.initializer(&entry_points);
defer init_func.deinit(self.gpa);
const header = [_]Word{
spec.magic_number,
// TODO: From cpu features
// Emit SPIR-V 1.4 for now. This is the highest version that Intel's CPU OpenCL supports.
(1 << 16) | (4 << 8),
0, // TODO: Register Zig compiler magic number.
self.idBound(),
0, // Schema (currently reserved for future use)
};
// Note: needs to be kept in order according to section 2.3!
const buffers = &[_][]const Word{
&header,
@@ -368,6 +436,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
self.sections.types_globals_constants.toWords(),
globals.toWords(),
self.sections.functions.toWords(),
init_func.toWords(),
};
var iovc_buffers: [buffers.len]std.os.iovec_const = undefined;
@@ -529,6 +598,7 @@ pub fn allocDecl(self: *Module, kind: DeclKind) !Decl.Index {
.result_id = undefined,
.begin_inst = undefined,
.end_inst = undefined,
.initializer_id = undefined,
}),
}
@@ -558,10 +628,14 @@ pub fn beginGlobal(self: *Module) u32 {
return @as(u32, @intCast(self.globals.section.instructions.items.len));
}
pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32) void {
pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32, result_id: IdRef, initializer_id: IdRef) void {
const global = self.globalPtr(global_index).?;
global.begin_inst = begin_inst;
global.end_inst = @as(u32, @intCast(self.globals.section.instructions.items.len));
global.* = .{
.result_id = result_id,
.begin_inst = begin_inst,
.end_inst = @intCast(self.globals.section.instructions.items.len),
.initializer_id = initializer_id,
};
}
pub fn declareEntryPoint(self: *Module, decl_index: Decl.Index, name: []const u8) !void {