diff options
| author | Robin Voetter <robin@voetter.nl> | 2024-03-02 13:08:21 +0100 |
|---|---|---|
| committer | Robin Voetter <robin@voetter.nl> | 2024-03-18 19:13:50 +0100 |
| commit | 9b18125562b2402cae8450253decd906f09e4dc6 (patch) | |
| tree | 5525843896ccb333dfeb4fe832c565f4e1cd3b49 /src/codegen/spirv/Module.zig | |
| parent | 20d7bb68ac7043e7d4ec8f0653ec73a1090187da (diff) | |
| download | zig-9b18125562b2402cae8450253decd906f09e4dc6.tar.gz zig-9b18125562b2402cae8450253decd906f09e4dc6.zip | |
spirv: make generic globals invocation-local
Diffstat (limited to 'src/codegen/spirv/Module.zig')
| -rw-r--r-- | src/codegen/spirv/Module.zig | 270 |
1 files changed, 43 insertions, 227 deletions
diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 23b0d5d0db..b650566890 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -72,9 +72,20 @@ pub const Decl = struct { /// Index to refer to a Decl by. pub const Index = enum(u32) { _ }; - /// The result-id to be used for this declaration. This is the final result-id - /// of the decl, which may be an OpFunction, OpVariable, or the result of a sequence - /// of OpSpecConstantOp operations. + /// Useful to tell what kind of decl this is, and hold the result-id or field index + /// to be used for this decl. + pub const Kind = enum { + func, + global, + invocation_global, + }; + + /// See comment on Kind + kind: Kind, + /// The result-id associated to this decl. The specific meaning of this depends on `kind`: + /// - For `func`, this is the result-id of the associated OpFunction instruction. + /// - For `global`, this is the result-id of the associated OpVariable instruction. + /// - For `invocation_global`, this is the result-id of the associated InvocationGlobal instruction. result_id: IdRef, /// The offset of the first dependency of this decl in the `decl_deps` array. begin_dep: u32, @@ -82,20 +93,6 @@ pub const Decl = struct { end_dep: u32, }; -/// Globals must be kept in order: operations involving globals must be ordered -/// so that the global declaration precedes any usage. -pub const Global = struct { - /// This is the result-id of the OpVariable instruction that declares the global. - 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 result-id of the function that initializes this value. - initializer_id: ?IdRef, -}; - /// This models a kernel entry point. pub const EntryPoint = struct { /// The declaration that should be exported. @@ -165,18 +162,8 @@ decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{}, /// The list of entry points that should be exported from this module. entry_points: std.ArrayListUnmanaged(EntryPoint) = .{}, -/// The fields in this structure help to maintain the required order for global variables. -globals: struct { - /// Set of globals, referred to by Decl.Index. - globals: std.AutoArrayHashMapUnmanaged(Decl.Index, 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 when the module is emitted. - section: Section = .{}, -} = .{}, - /// The list of extended instruction sets that should be imported. -extended_instruction_set: std.AutoHashMapUnmanaged(ExtendedInstructionSet, IdRef) = .{}, +extended_instruction_set: std.AutoHashMapUnmanaged(spec.InstructionSet, IdRef) = .{}, pub fn init(gpa: Allocator) Module { return .{ @@ -205,9 +192,6 @@ pub fn deinit(self: *Module) void { self.entry_points.deinit(self.gpa); - self.globals.globals.deinit(self.gpa); - self.globals.section.deinit(self.gpa); - self.extended_instruction_set.deinit(self.gpa); self.* = undefined; @@ -243,46 +227,6 @@ pub fn resolveString(self: *Module, str: []const u8) !CacheString { return try self.cache.addString(self, str); } -fn orderGlobalsInto( - self: *Module, - decl_index: Decl.Index, - section: *Section, - seen: *std.DynamicBitSetUnmanaged, -) !void { - const decl = self.declPtr(decl_index); - const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep]; - const global = self.globalPtr(decl_index).?; - const insts = self.globals.section.instructions.items[global.begin_inst..global.end_inst]; - - seen.set(@intFromEnum(decl_index)); - - for (deps) |dep| { - if (!seen.isSet(@intFromEnum(dep))) { - try self.orderGlobalsInto(dep, section, seen); - } - } - - try section.instructions.appendSlice(self.gpa, insts); -} - -fn orderGlobals(self: *Module) !Section { - const globals = self.globals.globals.keys(); - - var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len); - defer seen.deinit(self.gpa); - - var ordered_globals = Section{}; - errdefer ordered_globals.deinit(self.gpa); - - for (globals) |decl_index| { - if (!seen.isSet(@intFromEnum(decl_index))) { - try self.orderGlobalsInto(decl_index, &ordered_globals, &seen); - } - } - - return ordered_globals; -} - fn addEntryPointDeps( self: *Module, decl_index: Decl.Index, @@ -298,8 +242,8 @@ fn addEntryPointDeps( seen.set(@intFromEnum(decl_index)); - if (self.globalPtr(decl_index)) |global| { - try interface.append(global.result_id); + if (decl.kind == .global) { + try interface.append(decl.result_id); } for (deps) |dep| { @@ -335,81 +279,9 @@ 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); - if (global.initializer_id) |initializer_id| { - try section.emit(self.gpa, .OpFunctionCall, .{ - .id_result_type = void_ty_id, - .id_result = self.allocId(), - .function = 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, target: std.Target) !void { +pub fn finalize(self: *Module, a: Allocator, target: std.Target) ![]Word { // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction" - - // TODO: Perform topological sort on the globals. - var globals = try self.orderGlobals(); - defer globals.deinit(self.gpa); + // TODO: Audit calls to allocId() in this function to make it idempotent. var entry_points = try self.entryPoints(); defer entry_points.deinit(self.gpa); @@ -417,13 +289,6 @@ pub fn flush(self: *Module, file: std.fs.File, target: std.Target) !void { var types_constants = try self.cache.materialize(self); defer types_constants.deinit(self.gpa); - // // TODO: Pass global variables as function parameters - // var init_func = if (target.os.tag != .vulkan) - // try self.initializer(&entry_points) - // else - // Section{}; - // defer init_func.deinit(self.gpa); - const header = [_]Word{ spec.magic_number, // TODO: From cpu features @@ -436,7 +301,7 @@ pub fn flush(self: *Module, file: std.fs.File, target: std.Target) !void { else => 4, }, }), - 0, // TODO: Register Zig compiler magic number. + spec.zig_generator_id, self.idBound(), 0, // Schema (currently reserved for future use) }; @@ -468,30 +333,23 @@ pub fn flush(self: *Module, file: std.fs.File, target: std.Target) !void { self.sections.annotations.toWords(), types_constants.toWords(), self.sections.types_globals_constants.toWords(), - globals.toWords(), self.sections.functions.toWords(), }; - if (builtin.zig_backend == .stage2_x86_64) { - for (buffers) |buf| { - try file.writeAll(std.mem.sliceAsBytes(buf)); - } - } else { - // miscompiles with x86_64 backend - var iovc_buffers: [buffers.len]std.os.iovec_const = undefined; - var file_size: u64 = 0; - for (&iovc_buffers, 0..) |*iovc, i| { - // Note, since spir-v supports both little and big endian we can ignore byte order here and - // just treat the words as a sequence of bytes. - const bytes = std.mem.sliceAsBytes(buffers[i]); - iovc.* = .{ .iov_base = bytes.ptr, .iov_len = bytes.len }; - file_size += bytes.len; - } - - try file.seekTo(0); - try file.setEndPos(file_size); - try file.pwritevAll(&iovc_buffers, 0); + var total_result_size: usize = 0; + for (buffers) |buffer| { + total_result_size += buffer.len; + } + const result = try a.alloc(Word, total_result_size); + errdefer a.free(result); + + var offset: usize = 0; + for (buffers) |buffer| { + @memcpy(result[offset..][0..buffer.len], buffer); + offset += buffer.len; } + + return result; } /// Merge the sections making up a function declaration into this module. @@ -501,23 +359,17 @@ pub fn addFunction(self: *Module, decl_index: Decl.Index, func: Fn) !void { try self.declareDeclDeps(decl_index, func.decl_deps.keys()); } -pub const ExtendedInstructionSet = enum { - glsl, - opencl, -}; - /// Imports or returns the existing id of an extended instruction set -pub fn importInstructionSet(self: *Module, set: ExtendedInstructionSet) !IdRef { +pub fn importInstructionSet(self: *Module, set: spec.InstructionSet) !IdRef { + assert(set != .core); + const gop = try self.extended_instruction_set.getOrPut(self.gpa, set); if (gop.found_existing) return gop.value_ptr.*; const result_id = self.allocId(); try self.sections.extended_instruction_set.emit(self.gpa, .OpExtInstImport, .{ .id_result = result_id, - .name = switch (set) { - .glsl => "GLSL.std.450", - .opencl => "OpenCL.std", - }, + .name = @tagName(set), }); gop.value_ptr.* = result_id; @@ -631,40 +483,21 @@ pub fn decorateMember( }); } -pub const DeclKind = enum { - func, - global, -}; - -pub fn allocDecl(self: *Module, kind: DeclKind) !Decl.Index { +pub fn allocDecl(self: *Module, kind: Decl.Kind) !Decl.Index { try self.decls.append(self.gpa, .{ + .kind = kind, .result_id = self.allocId(), .begin_dep = undefined, .end_dep = undefined, }); - const index = @as(Decl.Index, @enumFromInt(@as(u32, @intCast(self.decls.items.len - 1)))); - switch (kind) { - .func => {}, - // If the decl represents a global, also allocate a global node. - .global => try self.globals.globals.putNoClobber(self.gpa, index, .{ - .result_id = undefined, - .begin_inst = undefined, - .end_inst = undefined, - .initializer_id = undefined, - }), - } - return index; + return @as(Decl.Index, @enumFromInt(@as(u32, @intCast(self.decls.items.len - 1)))); } pub fn declPtr(self: *Module, index: Decl.Index) *Decl { return &self.decls.items[@intFromEnum(index)]; } -pub fn globalPtr(self: *Module, index: Decl.Index) ?*Global { - return self.globals.globals.getPtr(index); -} - /// Declare ALL dependencies for a decl. pub fn declareDeclDeps(self: *Module, decl_index: Decl.Index, deps: []const Decl.Index) !void { const begin_dep = @as(u32, @intCast(self.decl_deps.items.len)); @@ -676,26 +509,9 @@ pub fn declareDeclDeps(self: *Module, decl_index: Decl.Index, deps: []const Decl decl.end_dep = end_dep; } -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, - result_id: IdRef, - initializer_id: ?IdRef, -) void { - const global = self.globalPtr(global_index).?; - global.* = .{ - .result_id = result_id, - .begin_inst = begin_inst, - .end_inst = @intCast(self.globals.section.instructions.items.len), - .initializer_id = initializer_id, - }; -} - +/// Declare a SPIR-V function as an entry point. This causes an extra wrapper +/// function to be generated, which is then exported as the real entry point. The purpose of this +/// wrapper is to allocate and initialize the structure holding the instance globals. pub fn declareEntryPoint( self: *Module, decl_index: Decl.Index, |
