diff options
| author | Ali Cheraghi <alichraghi@proton.me> | 2025-08-02 04:16:01 +0330 |
|---|---|---|
| committer | Ali Cheraghi <alichraghi@proton.me> | 2025-08-02 04:16:01 +0330 |
| commit | 31de2c873fa206a8fd491f7c9c959845fb86b0a1 (patch) | |
| tree | bea6a7f47e58b557e0d2483d707f9e2af1c2f459 /src/codegen/spirv/Module.zig | |
| parent | 982c387753c33a9eb42349c109fc4a6ed0675165 (diff) | |
| download | zig-31de2c873fa206a8fd491f7c9c959845fb86b0a1.tar.gz zig-31de2c873fa206a8fd491f7c9c959845fb86b0a1.zip | |
spirv: refactor
Diffstat (limited to 'src/codegen/spirv/Module.zig')
| -rw-r--r-- | src/codegen/spirv/Module.zig | 782 |
1 files changed, 0 insertions, 782 deletions
diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig deleted file mode 100644 index ffcff0eab4..0000000000 --- a/src/codegen/spirv/Module.zig +++ /dev/null @@ -1,782 +0,0 @@ -//! This structure represents a SPIR-V (sections) module being compiled, and keeps track of all relevant information. -//! That includes the actual instructions, the current result-id bound, and data structures for querying result-id's -//! of data which needs to be persistent over different calls to Decl code generation. -//! -//! A SPIR-V binary module supports both little- and big endian layout. The layout is detected by the magic word in the -//! header. Therefore, we can ignore any byte order throughout the implementation, and just use the host byte order, -//! and make this a problem for the consumer. -const Module = @This(); - -const std = @import("std"); -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; -const autoHashStrat = std.hash.autoHashStrat; -const Wyhash = std.hash.Wyhash; - -const spec = @import("spec.zig"); -const Word = spec.Word; -const Id = spec.Id; - -const Section = @import("Section.zig"); - -/// This structure represents a function that isc in-progress of being emitted. -/// Commonly, the contents of this structure will be merged with the appropriate -/// sections of the module and re-used. Note that the SPIR-V module system makes -/// no attempt of compacting result-id's, so any Fn instance should ultimately -/// be merged into the module it's result-id's are allocated from. -pub const Fn = struct { - /// The prologue of this function; this section contains the function's - /// OpFunction, OpFunctionParameter, OpLabel and OpVariable instructions, and - /// is separated from the actual function contents as OpVariable instructions - /// must appear in the first block of a function definition. - prologue: Section = .{}, - /// The code of the body of this function. - /// This section should also contain the OpFunctionEnd instruction marking - /// the end of this function definition. - body: Section = .{}, - /// The decl dependencies that this function depends on. - decl_deps: std.AutoArrayHashMapUnmanaged(Decl.Index, void) = .empty, - - /// Reset this function without deallocating resources, so that - /// it may be used to emit code for another function. - pub fn reset(self: *Fn) void { - self.prologue.reset(); - self.body.reset(); - self.decl_deps.clearRetainingCapacity(); - } - - /// Free the resources owned by this function. - pub fn deinit(self: *Fn, a: Allocator) void { - self.prologue.deinit(a); - self.body.deinit(a); - self.decl_deps.deinit(a); - self.* = undefined; - } -}; - -/// Declarations, both functions and globals, can have dependencies. These are used for 2 things: -/// - Globals must be declared before they are used, also between globals. The compiler processes -/// globals unordered, so we must use the dependencies here to figure out how to order the globals -/// in the final module. The Globals structure is also used for that. -/// - Entry points must declare the complete list of OpVariable instructions that they access. -/// For these we use the same dependency structure. -/// In this mechanism, globals will only depend on other globals, while functions may depend on -/// globals or other functions. -pub const Decl = struct { - /// Index to refer to a Decl by. - pub const Index = enum(u32) { _ }; - - /// 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: Id, - /// The offset of the first dependency of this decl in the `decl_deps` array. - begin_dep: u32, - /// The past-end offset of the dependencies of this decl in the `decl_deps` array. - end_dep: u32, -}; - -/// This models a kernel entry point. -pub const EntryPoint = struct { - /// The declaration that should be exported. - decl_index: ?Decl.Index = null, - /// The name of the kernel to be exported. - name: ?[]const u8 = null, - /// Calling Convention - exec_model: ?spec.ExecutionModel = null, - exec_mode: ?spec.ExecutionMode = null, -}; - -/// A general-purpose allocator which may be used to allocate resources for this module -gpa: Allocator, - -/// Arena for things that need to live for the length of this program. -arena: std.heap.ArenaAllocator, - -/// Target info -target: *const std.Target, - -/// The target SPIR-V version -version: spec.Version, - -/// Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module". -sections: struct { - /// Capability instructions - capabilities: Section = .{}, - /// OpExtension instructions - extensions: Section = .{}, - /// OpExtInstImport - extended_instruction_set: Section = .{}, - /// memory model defined by target - memory_model: Section = .{}, - /// OpEntryPoint instructions - Handled by `self.entry_points`. - /// OpExecutionMode and OpExecutionModeId instructions. - execution_modes: Section = .{}, - /// OpString, OpSourcExtension, OpSource, OpSourceContinued. - debug_strings: Section = .{}, - // OpName, OpMemberName. - debug_names: Section = .{}, - // OpModuleProcessed - skip for now. - /// Annotation instructions (OpDecorate etc). - annotations: Section = .{}, - /// Type declarations, constants, global variables - /// From this section, OpLine and OpNoLine is allowed. - /// According to the SPIR-V documentation, this section normally - /// also holds type and constant instructions. These are managed - /// via the cache instead, which is the sole structure that - /// manages that section. These will be inserted between this and - /// the previous section when emitting the final binary. - /// TODO: Do we need this section? Globals are also managed with another mechanism. - types_globals_constants: Section = .{}, - // Functions without a body - skip for now. - /// Regular function definitions. - functions: Section = .{}, -} = .{}, - -/// SPIR-V instructions return result-ids. This variable holds the module-wide counter for these. -next_result_id: Word, - -/// Cache for results of OpString instructions. -strings: std.StringArrayHashMapUnmanaged(Id) = .empty, - -/// Some types shouldn't be emitted more than one time, but cannot be caught by -/// the `intern_map` during codegen. Sometimes, IDs are compared to check if -/// types are the same, so we can't delay until the dedup pass. Therefore, -/// this is an ad-hoc structure to cache types where required. -/// According to the SPIR-V specification, section 2.8, this includes all non-aggregate -/// non-pointer types. -/// Additionally, this is used for other values which can be cached, for example, -/// built-in variables. -cache: struct { - bool_type: ?Id = null, - void_type: ?Id = null, - int_types: std.AutoHashMapUnmanaged(std.builtin.Type.Int, Id) = .empty, - float_types: std.AutoHashMapUnmanaged(std.builtin.Type.Float, Id) = .empty, - vector_types: std.AutoHashMapUnmanaged(struct { Id, u32 }, Id) = .empty, - array_types: std.AutoHashMapUnmanaged(struct { Id, Id }, Id) = .empty, - - capabilities: std.AutoHashMapUnmanaged(spec.Capability, void) = .empty, - extensions: std.StringHashMapUnmanaged(void) = .empty, - extended_instruction_set: std.AutoHashMapUnmanaged(spec.InstructionSet, Id) = .empty, - decorations: std.AutoHashMapUnmanaged(struct { Id, spec.Decoration }, void) = .empty, - builtins: std.AutoHashMapUnmanaged(struct { Id, spec.BuiltIn }, Decl.Index) = .empty, - - bool_const: [2]?Id = .{ null, null }, -} = .{}, - -/// Set of Decls, referred to by Decl.Index. -decls: std.ArrayListUnmanaged(Decl) = .empty, - -/// List of dependencies, per decl. This list holds all the dependencies, sliced by the -/// begin_dep and end_dep in `self.decls`. -decl_deps: std.ArrayListUnmanaged(Decl.Index) = .empty, - -/// The list of entry points that should be exported from this module. -entry_points: std.AutoArrayHashMapUnmanaged(Id, EntryPoint) = .empty, - -pub fn init(gpa: Allocator, target: *const std.Target) Module { - const version_minor: u8 = blk: { - // Prefer higher versions - if (target.cpu.has(.spirv, .v1_6)) break :blk 6; - if (target.cpu.has(.spirv, .v1_5)) break :blk 5; - if (target.cpu.has(.spirv, .v1_4)) break :blk 4; - if (target.cpu.has(.spirv, .v1_3)) break :blk 3; - if (target.cpu.has(.spirv, .v1_2)) break :blk 2; - if (target.cpu.has(.spirv, .v1_1)) break :blk 1; - break :blk 0; - }; - - return .{ - .gpa = gpa, - .arena = std.heap.ArenaAllocator.init(gpa), - .target = target, - .version = .{ .major = 1, .minor = version_minor }, - .next_result_id = 1, // 0 is an invalid SPIR-V result id, so start counting at 1. - }; -} - -pub fn deinit(self: *Module) void { - self.sections.capabilities.deinit(self.gpa); - self.sections.extensions.deinit(self.gpa); - self.sections.extended_instruction_set.deinit(self.gpa); - self.sections.memory_model.deinit(self.gpa); - self.sections.execution_modes.deinit(self.gpa); - self.sections.debug_strings.deinit(self.gpa); - self.sections.debug_names.deinit(self.gpa); - self.sections.annotations.deinit(self.gpa); - self.sections.types_globals_constants.deinit(self.gpa); - self.sections.functions.deinit(self.gpa); - - self.strings.deinit(self.gpa); - - self.cache.int_types.deinit(self.gpa); - self.cache.float_types.deinit(self.gpa); - self.cache.vector_types.deinit(self.gpa); - self.cache.array_types.deinit(self.gpa); - self.cache.capabilities.deinit(self.gpa); - self.cache.extensions.deinit(self.gpa); - self.cache.extended_instruction_set.deinit(self.gpa); - self.cache.decorations.deinit(self.gpa); - self.cache.builtins.deinit(self.gpa); - - self.decls.deinit(self.gpa); - self.decl_deps.deinit(self.gpa); - self.entry_points.deinit(self.gpa); - - self.arena.deinit(); - - self.* = undefined; -} - -pub const IdRange = struct { - base: u32, - len: u32, - - pub fn at(range: IdRange, i: usize) Id { - assert(i < range.len); - return @enumFromInt(range.base + i); - } -}; - -pub fn allocIds(self: *Module, n: u32) IdRange { - defer self.next_result_id += n; - return .{ - .base = self.next_result_id, - .len = n, - }; -} - -pub fn allocId(self: *Module) Id { - return self.allocIds(1).at(0); -} - -pub fn idBound(self: Module) Word { - return self.next_result_id; -} - -pub fn hasFeature(self: *Module, feature: std.Target.spirv.Feature) bool { - return self.target.cpu.has(.spirv, feature); -} - -fn addEntryPointDeps( - self: *Module, - decl_index: Decl.Index, - seen: *std.DynamicBitSetUnmanaged, - interface: *std.ArrayList(Id), -) !void { - 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 (decl.kind == .global) { - try interface.append(decl.result_id); - } - - for (deps) |dep| { - try self.addEntryPointDeps(dep, seen, interface); - } -} - -fn entryPoints(self: *Module) !Section { - var entry_points = Section{}; - errdefer entry_points.deinit(self.gpa); - - var interface = std.ArrayList(Id).init(self.gpa); - defer interface.deinit(); - - var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len); - defer seen.deinit(self.gpa); - - for (self.entry_points.keys(), self.entry_points.values()) |entry_point_id, entry_point| { - interface.items.len = 0; - seen.setRangeValue(.{ .start = 0, .end = self.decls.items.len }, false); - - try self.addEntryPointDeps(entry_point.decl_index.?, &seen, &interface); - try entry_points.emit(self.gpa, .OpEntryPoint, .{ - .execution_model = entry_point.exec_model.?, - .entry_point = entry_point_id, - .name = entry_point.name.?, - .interface = interface.items, - }); - - if (entry_point.exec_mode == null and entry_point.exec_model == .fragment) { - switch (self.target.os.tag) { - .vulkan, .opengl => |tag| { - try self.sections.execution_modes.emit(self.gpa, .OpExecutionMode, .{ - .entry_point = entry_point_id, - .mode = if (tag == .vulkan) .origin_upper_left else .origin_lower_left, - }); - }, - .opencl => {}, - else => unreachable, - } - } - } - - return entry_points; -} - -pub fn finalize(self: *Module, a: Allocator) ![]Word { - // Emit capabilities and extensions - switch (self.target.os.tag) { - .opengl => { - try self.addCapability(.shader); - try self.addCapability(.matrix); - }, - .vulkan => { - try self.addCapability(.shader); - try self.addCapability(.matrix); - if (self.target.cpu.arch == .spirv64) { - try self.addExtension("SPV_KHR_physical_storage_buffer"); - try self.addCapability(.physical_storage_buffer_addresses); - } - }, - .opencl, .amdhsa => { - try self.addCapability(.kernel); - try self.addCapability(.addresses); - }, - else => unreachable, - } - if (self.target.cpu.arch == .spirv64) try self.addCapability(.int64); - if (self.target.cpu.has(.spirv, .int64)) try self.addCapability(.int64); - if (self.target.cpu.has(.spirv, .float16)) try self.addCapability(.float16); - if (self.target.cpu.has(.spirv, .float64)) try self.addCapability(.float64); - if (self.target.cpu.has(.spirv, .generic_pointer)) try self.addCapability(.generic_pointer); - if (self.target.cpu.has(.spirv, .vector16)) try self.addCapability(.vector16); - if (self.target.cpu.has(.spirv, .storage_push_constant16)) { - try self.addExtension("SPV_KHR_16bit_storage"); - try self.addCapability(.storage_push_constant16); - } - if (self.target.cpu.has(.spirv, .arbitrary_precision_integers)) { - try self.addExtension("SPV_INTEL_arbitrary_precision_integers"); - try self.addCapability(.arbitrary_precision_integers_intel); - } - if (self.target.cpu.has(.spirv, .variable_pointers)) { - try self.addExtension("SPV_KHR_variable_pointers"); - try self.addCapability(.variable_pointers_storage_buffer); - try self.addCapability(.variable_pointers); - } - // These are well supported - try self.addCapability(.int8); - try self.addCapability(.int16); - - // Emit memory model - const addressing_model: spec.AddressingModel = switch (self.target.os.tag) { - .opengl => .logical, - .vulkan => if (self.target.cpu.arch == .spirv32) .logical else .physical_storage_buffer64, - .opencl => if (self.target.cpu.arch == .spirv32) .physical32 else .physical64, - .amdhsa => .physical64, - else => unreachable, - }; - try self.sections.memory_model.emit(self.gpa, .OpMemoryModel, .{ - .addressing_model = addressing_model, - .memory_model = switch (self.target.os.tag) { - .opencl => .open_cl, - .vulkan, .opengl => .glsl450, - else => unreachable, - }, - }); - - // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction" - // TODO: Audit calls to allocId() in this function to make it idempotent. - var entry_points = try self.entryPoints(); - defer entry_points.deinit(self.gpa); - - const header = [_]Word{ - spec.magic_number, - self.version.toWord(), - spec.zig_generator_id, - self.idBound(), - 0, // Schema (currently reserved for future use) - }; - - var source = Section{}; - defer source.deinit(self.gpa); - try self.sections.debug_strings.emit(self.gpa, .OpSource, .{ - .source_language = .zig, - .version = 0, - // We cannot emit these because the Khronos translator does not parse this instruction - // correctly. - // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/2188 - .file = null, - .source = null, - }); - - // Note: needs to be kept in order according to section 2.3! - const buffers = &[_][]const Word{ - &header, - self.sections.capabilities.toWords(), - self.sections.extensions.toWords(), - self.sections.extended_instruction_set.toWords(), - self.sections.memory_model.toWords(), - entry_points.toWords(), - self.sections.execution_modes.toWords(), - source.toWords(), - self.sections.debug_strings.toWords(), - self.sections.debug_names.toWords(), - self.sections.annotations.toWords(), - self.sections.types_globals_constants.toWords(), - self.sections.functions.toWords(), - }; - - 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. -pub fn addFunction(self: *Module, decl_index: Decl.Index, func: Fn) !void { - try self.sections.functions.append(self.gpa, func.prologue); - try self.sections.functions.append(self.gpa, func.body); - try self.declareDeclDeps(decl_index, func.decl_deps.keys()); -} - -pub fn addCapability(self: *Module, cap: spec.Capability) !void { - const entry = try self.cache.capabilities.getOrPut(self.gpa, cap); - if (entry.found_existing) return; - try self.sections.capabilities.emit(self.gpa, .OpCapability, .{ .capability = cap }); -} - -pub fn addExtension(self: *Module, ext: []const u8) !void { - const entry = try self.cache.extensions.getOrPut(self.gpa, ext); - if (entry.found_existing) return; - try self.sections.extensions.emit(self.gpa, .OpExtension, .{ .name = ext }); -} - -/// Imports or returns the existing id of an extended instruction set -pub fn importInstructionSet(self: *Module, set: spec.InstructionSet) !Id { - assert(set != .core); - - const gop = try self.cache.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 = @tagName(set), - }); - gop.value_ptr.* = result_id; - - return result_id; -} - -/// Fetch the result-id of an instruction corresponding to a string. -pub fn resolveString(self: *Module, string: []const u8) !Id { - if (self.strings.get(string)) |id| { - return id; - } - - const id = self.allocId(); - try self.strings.put(self.gpa, try self.arena.allocator().dupe(u8, string), id); - - try self.sections.debug_strings.emit(self.gpa, .OpString, .{ - .id_result = id, - .string = string, - }); - - return id; -} - -pub fn structType(self: *Module, result_id: Id, types: []const Id, maybe_names: ?[]const []const u8) !void { - try self.sections.types_globals_constants.emit(self.gpa, .OpTypeStruct, .{ - .id_result = result_id, - .id_ref = types, - }); - - if (maybe_names) |names| { - assert(names.len == types.len); - for (names, 0..) |name, i| { - try self.memberDebugName(result_id, @intCast(i), name); - } - } -} - -pub fn boolType(self: *Module) !Id { - if (self.cache.bool_type) |id| return id; - - const result_id = self.allocId(); - try self.sections.types_globals_constants.emit(self.gpa, .OpTypeBool, .{ - .id_result = result_id, - }); - self.cache.bool_type = result_id; - return result_id; -} - -pub fn voidType(self: *Module) !Id { - if (self.cache.void_type) |id| return id; - - const result_id = self.allocId(); - try self.sections.types_globals_constants.emit(self.gpa, .OpTypeVoid, .{ - .id_result = result_id, - }); - self.cache.void_type = result_id; - try self.debugName(result_id, "void"); - return result_id; -} - -pub fn intType(self: *Module, signedness: std.builtin.Signedness, bits: u16) !Id { - assert(bits > 0); - const entry = try self.cache.int_types.getOrPut(self.gpa, .{ .signedness = signedness, .bits = bits }); - if (!entry.found_existing) { - const result_id = self.allocId(); - entry.value_ptr.* = result_id; - try self.sections.types_globals_constants.emit(self.gpa, .OpTypeInt, .{ - .id_result = result_id, - .width = bits, - .signedness = switch (signedness) { - .signed => 1, - .unsigned => 0, - }, - }); - - switch (signedness) { - .signed => try self.debugNameFmt(result_id, "i{}", .{bits}), - .unsigned => try self.debugNameFmt(result_id, "u{}", .{bits}), - } - } - return entry.value_ptr.*; -} - -pub fn floatType(self: *Module, bits: u16) !Id { - assert(bits > 0); - const entry = try self.cache.float_types.getOrPut(self.gpa, .{ .bits = bits }); - if (!entry.found_existing) { - const result_id = self.allocId(); - entry.value_ptr.* = result_id; - try self.sections.types_globals_constants.emit(self.gpa, .OpTypeFloat, .{ - .id_result = result_id, - .width = bits, - }); - try self.debugNameFmt(result_id, "f{}", .{bits}); - } - return entry.value_ptr.*; -} - -pub fn vectorType(self: *Module, len: u32, child_ty_id: Id) !Id { - const entry = try self.cache.vector_types.getOrPut(self.gpa, .{ child_ty_id, len }); - if (!entry.found_existing) { - const result_id = self.allocId(); - entry.value_ptr.* = result_id; - try self.sections.types_globals_constants.emit(self.gpa, .OpTypeVector, .{ - .id_result = result_id, - .component_type = child_ty_id, - .component_count = len, - }); - } - return entry.value_ptr.*; -} - -pub fn arrayType(self: *Module, len_id: Id, child_ty_id: Id) !Id { - const entry = try self.cache.array_types.getOrPut(self.gpa, .{ child_ty_id, len_id }); - if (!entry.found_existing) { - const result_id = self.allocId(); - entry.value_ptr.* = result_id; - try self.sections.types_globals_constants.emit(self.gpa, .OpTypeArray, .{ - .id_result = result_id, - .element_type = child_ty_id, - .length = len_id, - }); - } - return entry.value_ptr.*; -} - -pub fn functionType(self: *Module, return_ty_id: Id, param_type_ids: []const Id) !Id { - const result_id = self.allocId(); - try self.sections.types_globals_constants.emit(self.gpa, .OpTypeFunction, .{ - .id_result = result_id, - .return_type = return_ty_id, - .id_ref_2 = param_type_ids, - }); - return result_id; -} - -pub fn constant(self: *Module, result_ty_id: Id, value: spec.LiteralContextDependentNumber) !Id { - const result_id = self.allocId(); - const section = &self.sections.types_globals_constants; - try section.emit(self.gpa, .OpConstant, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - .value = value, - }); - return result_id; -} - -pub fn constBool(self: *Module, value: bool) !Id { - if (self.cache.bool_const[@intFromBool(value)]) |b| return b; - - const result_ty_id = try self.boolType(); - const result_id = self.allocId(); - self.cache.bool_const[@intFromBool(value)] = result_id; - - switch (value) { - inline else => |value_ct| try self.sections.types_globals_constants.emit( - self.gpa, - if (value_ct) .OpConstantTrue else .OpConstantFalse, - .{ - .id_result_type = result_ty_id, - .id_result = result_id, - }, - ), - } - - return result_id; -} - -/// Return a pointer to a builtin variable. `result_ty_id` must be a **pointer** -/// with storage class `.Input`. -pub fn builtin(self: *Module, result_ty_id: Id, spirv_builtin: spec.BuiltIn) !Decl.Index { - const entry = try self.cache.builtins.getOrPut(self.gpa, .{ result_ty_id, spirv_builtin }); - if (!entry.found_existing) { - const decl_index = try self.allocDecl(.global); - const result_id = self.declPtr(decl_index).result_id; - entry.value_ptr.* = decl_index; - try self.sections.types_globals_constants.emit(self.gpa, .OpVariable, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - .storage_class = .input, - }); - try self.decorate(result_id, .{ .built_in = .{ .built_in = spirv_builtin } }); - try self.declareDeclDeps(decl_index, &.{}); - } - return entry.value_ptr.*; -} - -pub fn constUndef(self: *Module, ty_id: Id) !Id { - const result_id = self.allocId(); - try self.sections.types_globals_constants.emit(self.gpa, .OpUndef, .{ - .id_result_type = ty_id, - .id_result = result_id, - }); - return result_id; -} - -pub fn constNull(self: *Module, ty_id: Id) !Id { - const result_id = self.allocId(); - try self.sections.types_globals_constants.emit(self.gpa, .OpConstantNull, .{ - .id_result_type = ty_id, - .id_result = result_id, - }); - return result_id; -} - -/// Decorate a result-id. -pub fn decorate( - self: *Module, - target: Id, - decoration: spec.Decoration.Extended, -) !void { - const entry = try self.cache.decorations.getOrPut(self.gpa, .{ target, decoration }); - if (!entry.found_existing) { - try self.sections.annotations.emit(self.gpa, .OpDecorate, .{ - .target = target, - .decoration = decoration, - }); - } -} - -/// Decorate a result-id which is a member of some struct. -/// We really don't have to and shouldn't need to cache this. -pub fn decorateMember( - self: *Module, - structure_type: Id, - member: u32, - decoration: spec.Decoration.Extended, -) !void { - try self.sections.annotations.emit(self.gpa, .OpMemberDecorate, .{ - .structure_type = structure_type, - .member = member, - .decoration = decoration, - }); -} - -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, - }); - - 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)]; -} - -/// Declare ALL dependencies for a decl. -pub fn declareDeclDeps(self: *Module, decl_index: Decl.Index, deps: []const Decl.Index) !void { - const begin_dep: u32 = @intCast(self.decl_deps.items.len); - try self.decl_deps.appendSlice(self.gpa, deps); - const end_dep: u32 = @intCast(self.decl_deps.items.len); - - const decl = self.declPtr(decl_index); - decl.begin_dep = begin_dep; - decl.end_dep = end_dep; -} - -/// 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, - name: []const u8, - exec_model: spec.ExecutionModel, - exec_mode: ?spec.ExecutionMode, -) !void { - const gop = try self.entry_points.getOrPut(self.gpa, self.declPtr(decl_index).result_id); - gop.value_ptr.decl_index = decl_index; - gop.value_ptr.name = try self.arena.allocator().dupe(u8, name); - gop.value_ptr.exec_model = exec_model; - // Might've been set by assembler - if (!gop.found_existing) gop.value_ptr.exec_mode = exec_mode; -} - -pub fn debugName(self: *Module, target: Id, name: []const u8) !void { - try self.sections.debug_names.emit(self.gpa, .OpName, .{ - .target = target, - .name = name, - }); -} - -pub fn debugNameFmt(self: *Module, target: Id, comptime fmt: []const u8, args: anytype) !void { - const name = try std.fmt.allocPrint(self.gpa, fmt, args); - defer self.gpa.free(name); - try self.debugName(target, name); -} - -pub fn memberDebugName(self: *Module, target: Id, member: u32, name: []const u8) !void { - try self.sections.debug_names.emit(self.gpa, .OpMemberName, .{ - .type = target, - .member = member, - .name = name, - }); -} |
