aboutsummaryrefslogtreecommitdiff
path: root/src/codegen/spirv/Module.zig
diff options
context:
space:
mode:
authorRobin Voetter <robin@voetter.nl>2023-04-09 01:29:39 +0200
committerRobin Voetter <robin@voetter.nl>2023-04-09 01:51:53 +0200
commit8bbfbfc956af163434c734e196d5c2a77e77ff07 (patch)
tree8ed95f240d2736a4d82e915ecc9269f15ba834e2 /src/codegen/spirv/Module.zig
parent80b84355692606ac840584baa62aaafdd8ecd425 (diff)
downloadzig-8bbfbfc956af163434c734e196d5c2a77e77ff07.tar.gz
zig-8bbfbfc956af163434c734e196d5c2a77e77ff07.zip
spirv: improve linking globals
SPIR-V globals must be emitted in order, so that any declaration precedes usage. Zig, however, generates globals in random order. To this end we keep for each global a list of dependencies and perform a topological sort when flushing the module.
Diffstat (limited to 'src/codegen/spirv/Module.zig')
-rw-r--r--src/codegen/spirv/Module.zig127
1 files changed, 126 insertions, 1 deletions
diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig
index dc5e8053c1..7501ec5d92 100644
--- a/src/codegen/spirv/Module.zig
+++ b/src/codegen/spirv/Module.zig
@@ -55,6 +55,27 @@ pub const Fn = struct {
}
};
+/// Globals must be kept in order: operations involving globals must be ordered
+/// so that the global declaration precedes any usage.
+pub const Global = struct {
+ /// Index type to refer to a global by.
+ pub const Index = enum(u32) { _ };
+
+ /// The result-id to be used for this global declaration. Note that this does not
+ /// necessarily refer to an OpVariable instruction - it may also be the final result
+ /// id of a number of OpSpecConstantOp instructions.
+ result_id: IdRef,
+ /// The offset into `self.globals.section` of the first instruction of this global
+ /// declaration.
+ begin_inst: u32,
+ /// The past-end offset into `self.flobals.section`.
+ end_inst: u32,
+ /// The first dependency in the `self.globals.dependencies` array list.
+ begin_dep: u32,
+ /// The past-end dependency in `self.globals.dependencies`.
+ end_dep: u32,
+};
+
/// A general-purpose allocator which may be used to allocate resources for this module
gpa: Allocator,
@@ -102,6 +123,20 @@ source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},
/// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref).
type_cache: TypeCache = .{},
+/// The fields in this structure help to maintain the required order for global variables.
+globals: struct {
+ /// The graph nodes of global variables present in the module.
+ nodes: std.ArrayListUnmanaged(Global) = .{},
+ /// This pseudo-section contains the initialization code for all the globals. Instructions from
+ /// here are reordered when flushing the module. Its contents should be part of the
+ /// `types_globals_constants` SPIR-V section.
+ section: Section = .{},
+ /// Holds a list of dependent global variables for each global variable.
+ dependencies: std.ArrayListUnmanaged(Global.Index) = .{},
+ /// The global that initialization code/dependencies are currently being generated for, if any.
+ current_global: ?Global.Index = null,
+} = .{},
+
pub fn init(gpa: Allocator, arena: Allocator) Module {
return .{
.gpa = gpa,
@@ -124,6 +159,10 @@ pub fn deinit(self: *Module) void {
self.source_file_names.deinit(self.gpa);
self.type_cache.deinit(self.gpa);
+ self.globals.nodes.deinit(self.gpa);
+ self.globals.section.deinit(self.gpa);
+ self.globals.dependencies.deinit(self.gpa);
+
self.* = undefined;
}
@@ -141,18 +180,60 @@ pub fn idBound(self: Module) Word {
return self.next_result_id;
}
+fn orderGlobalsInto(
+ self: Module,
+ global_index: Global.Index,
+ section: *Section,
+ seen: *std.DynamicBitSetUnmanaged,
+) !void {
+ const node = self.globals.nodes.items[@enumToInt(global_index)];
+ const deps = self.globals.dependencies.items[node.begin_dep .. node.end_dep];
+ const insts = self.globals.section.instructions.items[node.begin_inst .. node.end_inst];
+
+ seen.set(@enumToInt(global_index));
+
+ for (deps) |dep| {
+ if (!seen.isSet(@enumToInt(dep))) {
+ try self.orderGlobalsInto(dep, section, seen);
+ }
+ }
+
+ try section.instructions.appendSlice(self.gpa, insts);
+}
+
+fn orderGlobals(self: Module) !Section {
+ const nodes = self.globals.nodes.items;
+
+ var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, nodes.len);
+ defer seen.deinit(self.gpa);
+
+ var ordered_globals = Section{};
+
+ for (0..nodes.len) |global_index| {
+ if (!seen.isSet(global_index)) {
+ try self.orderGlobalsInto(@intToEnum(Global.Index, @intCast(u32, global_index)), &ordered_globals, &seen);
+ }
+ }
+
+ return ordered_globals;
+}
+
/// 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,
- (1 << 16) | (5 << 8),
+ (1 << 16) | (4 << 8), // TODO: From cpu features
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);
+
// Note: needs to be kept in order according to section 2.3!
const buffers = &[_][]const Word{
&header,
@@ -164,6 +245,7 @@ pub fn flush(self: Module, file: std.fs.File) !void {
self.sections.debug_names.toWords(),
self.sections.annotations.toWords(),
self.sections.types_globals_constants.toWords(),
+ globals.toWords(),
self.sections.functions.toWords(),
};
@@ -279,6 +361,8 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType {
.i64,
.int,
=> {
+ // TODO: Kernels do not support OpTypeInt that is signed. We can probably
+ // can get rid of the signedness all together, in Shaders also.
const bits = ty.intFloatBits();
const signedness: spec.LiteralInteger = switch (ty.intSignedness()) {
.unsigned => 0,
@@ -634,3 +718,44 @@ pub fn decorateMember(
.decoration = decoration,
});
}
+
+pub fn allocGlobal(self: *Module) !Global.Index {
+ try self.globals.nodes.append(self.gpa, .{
+ .result_id = self.allocId(),
+ .begin_inst = undefined,
+ .end_inst = undefined,
+ .begin_dep = undefined,
+ .end_dep = undefined,
+ });
+ return @intToEnum(Global.Index, @intCast(u32, self.globals.nodes.items.len - 1));
+}
+
+pub fn globalPtr(self: *Module, index: Global.Index) *Global {
+ return &self.globals.nodes.items[@enumToInt(index)];
+}
+
+/// Begin generating the global for `index`. The previous global is finalized
+/// at this point, and the global for `index` is made active. Any new calls to
+/// `addGlobalDependency` will affect this global. After a new call to this function,
+/// the prior active global cannot be modified again.
+pub fn beginGlobal(self: *Module, index: Global.Index) IdRef {
+ const global = self.globalPtr(index);
+ global.begin_inst = @intCast(u32, self.globals.section.instructions.items.len);
+ global.begin_dep = @intCast(u32, self.globals.dependencies.items.len);
+ self.globals.current_global = index;
+ return global.result_id;
+}
+
+/// Finalize the global. After this point, the current global cannot be modified anymore.
+pub fn endGlobal(self: *Module) void {
+ const global = self.globalPtr(self.globals.current_global.?);
+ global.end_inst = @intCast(u32, self.globals.section.instructions.items.len);
+ global.end_dep = @intCast(u32, self.globals.dependencies.items.len);
+ self.globals.current_global = null;
+}
+
+pub fn addGlobalDependency(self: *Module, dependency: Global.Index) !void {
+ assert(self.globals.current_global != null);
+ assert(self.globals.current_global.? != dependency);
+ try self.globals.dependencies.append(self.gpa, dependency);
+}