spirv: define and use extended instruction set opcodes

This commit is contained in:
Ali Cheraghi 2025-08-03 17:02:51 +03:30
parent 246e1de554
commit cd4b03c5ed
No known key found for this signature in database
GPG Key ID: C25ECEF06C762AE6
7 changed files with 977 additions and 3679 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -125,9 +125,9 @@ pub const Decl = struct {
/// - For `invocation_global`, this is the result-id of the associated InvocationGlobal instruction. /// - For `invocation_global`, this is the result-id of the associated InvocationGlobal instruction.
result_id: Id, result_id: Id,
/// The offset of the first dependency of this decl in the `decl_deps` array. /// The offset of the first dependency of this decl in the `decl_deps` array.
begin_dep: u32, begin_dep: usize = 0,
/// The past-end offset of the dependencies of this decl in the `decl_deps` array. /// The past-end offset of the dependencies of this decl in the `decl_deps` array.
end_dep: u32, end_dep: usize = 0,
}; };
/// This models a kernel entry point. /// This models a kernel entry point.
@ -258,7 +258,6 @@ pub fn resolveNav(module: *Module, ip: *InternPool, nav_index: InternPool.Nav.In
.generic => .invocation_global, .generic => .invocation_global,
else => .global, else => .global,
}; };
entry.value_ptr.* = try module.allocDecl(kind); entry.value_ptr.* = try module.allocDecl(kind);
} }
@ -782,15 +781,15 @@ pub fn builtin(
const gop = try module.cache.builtins.getOrPut(module.gpa, .{ spirv_builtin, storage_class }); const gop = try module.cache.builtins.getOrPut(module.gpa, .{ spirv_builtin, storage_class });
if (!gop.found_existing) { if (!gop.found_existing) {
const decl_index = try module.allocDecl(.global); const decl_index = try module.allocDecl(.global);
const result_id = module.declPtr(decl_index).result_id; const decl = module.declPtr(decl_index);
gop.value_ptr.* = decl_index; gop.value_ptr.* = decl_index;
try module.sections.globals.emit(module.gpa, .OpVariable, .{ try module.sections.globals.emit(module.gpa, .OpVariable, .{
.id_result_type = result_ty_id, .id_result_type = result_ty_id,
.id_result = result_id, .id_result = decl.result_id,
.storage_class = storage_class, .storage_class = storage_class,
}); });
try module.decorate(result_id, .{ .built_in = .{ .built_in = spirv_builtin } }); try module.decorate(decl.result_id, .{ .built_in = .{ .built_in = spirv_builtin } });
try module.declareDeclDeps(decl_index, &.{});
} }
return gop.value_ptr.*; return gop.value_ptr.*;
} }
@ -847,8 +846,6 @@ pub fn allocDecl(module: *Module, kind: Decl.Kind) !Decl.Index {
try module.decls.append(module.gpa, .{ try module.decls.append(module.gpa, .{
.kind = kind, .kind = kind,
.result_id = module.allocId(), .result_id = module.allocId(),
.begin_dep = undefined,
.end_dep = undefined,
}); });
return @as(Decl.Index, @enumFromInt(@as(u32, @intCast(module.decls.items.len - 1)))); return @as(Decl.Index, @enumFromInt(@as(u32, @intCast(module.decls.items.len - 1))));
@ -858,17 +855,6 @@ pub fn declPtr(module: *Module, index: Decl.Index) *Decl {
return &module.decls.items[@intFromEnum(index)]; return &module.decls.items[@intFromEnum(index)];
} }
/// Declare ALL dependencies for a decl.
pub fn declareDeclDeps(module: *Module, decl_index: Decl.Index, deps: []const Decl.Index) !void {
const begin_dep: u32 = @intCast(module.decl_deps.items.len);
try module.decl_deps.appendSlice(module.gpa, deps);
const end_dep: u32 = @intCast(module.decl_deps.items.len);
const decl = module.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 /// 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 /// 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. /// wrapper is to allocate and initialize the structure holding the instance globals.

View File

@ -21,7 +21,7 @@ pub fn deinit(section: *Section, allocator: Allocator) void {
} }
pub fn reset(section: *Section) void { pub fn reset(section: *Section) void {
section.instructions.items.len = 0; section.instructions.clearRetainingCapacity();
} }
pub fn toWords(section: Section) []Word { pub fn toWords(section: Section) []Word {
@ -86,16 +86,6 @@ pub fn emit(
section.writeOperands(opcode.Operands(), operands); section.writeOperands(opcode.Operands(), operands);
} }
pub fn emitBranch(
section: *Section,
allocator: Allocator,
target_label: spec.Id,
) !void {
try section.emit(allocator, .OpBranch, .{
.target_label = target_label,
});
}
pub fn writeWord(section: *Section, word: Word) void { pub fn writeWord(section: *Section, word: Word) void {
section.instructions.appendAssumeCapacity(word); section.instructions.appendAssumeCapacity(word);
} }

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@ const Linker = @This();
base: link.File, base: link.File,
module: Module, module: Module,
cg: CodeGen,
pub fn createEmpty( pub fn createEmpty(
arena: Allocator, arena: Allocator,
@ -63,6 +64,16 @@ pub fn createEmpty(
.arena = arena, .arena = arena,
.zcu = comp.zcu.?, .zcu = comp.zcu.?,
}, },
.cg = .{
// These fields are populated in generate()
.pt = undefined,
.air = undefined,
.liveness = undefined,
.owner_nav = undefined,
.module = undefined,
.control_flow = .{ .structured = .{} },
.base_line = undefined,
},
}; };
errdefer linker.deinit(); errdefer linker.deinit();
@ -84,6 +95,7 @@ pub fn open(
} }
pub fn deinit(linker: *Linker) void { pub fn deinit(linker: *Linker) void {
linker.cg.deinit();
linker.module.deinit(); linker.module.deinit();
} }
@ -99,29 +111,41 @@ fn generate(
const gpa = zcu.gpa; const gpa = zcu.gpa;
const structured_cfg = zcu.navFileScope(nav_index).mod.?.structured_cfg; const structured_cfg = zcu.navFileScope(nav_index).mod.?.structured_cfg;
var cg: CodeGen = .{ linker.cg.control_flow.deinit(gpa);
linker.cg.args.clearRetainingCapacity();
linker.cg.inst_results.clearRetainingCapacity();
linker.cg.id_scratch.clearRetainingCapacity();
linker.cg.prologue.reset();
linker.cg.body.reset();
linker.cg = .{
.pt = pt, .pt = pt,
.module = &linker.module,
.owner_nav = nav_index,
.air = air, .air = air,
.liveness = liveness, .liveness = liveness,
.owner_nav = nav_index,
.module = &linker.module,
.control_flow = switch (structured_cfg) { .control_flow = switch (structured_cfg) {
true => .{ .structured = .{} }, true => .{ .structured = .{} },
false => .{ .unstructured = .{} }, false => .{ .unstructured = .{} },
}, },
.base_line = zcu.navSrcLine(nav_index), .base_line = zcu.navSrcLine(nav_index),
};
defer cg.deinit();
cg.genNav(do_codegen) catch |err| switch (err) { .args = linker.cg.args,
error.CodegenFail => switch (zcu.codegenFailMsg(nav_index, cg.error_msg.?)) { .inst_results = linker.cg.inst_results,
.id_scratch = linker.cg.id_scratch,
.prologue = linker.cg.prologue,
.body = linker.cg.body,
};
linker.cg.genNav(do_codegen) catch |err| switch (err) {
error.CodegenFail => switch (zcu.codegenFailMsg(nav_index, linker.cg.error_msg.?)) {
error.CodegenFail => {}, error.CodegenFail => {},
error.OutOfMemory => |e| return e, error.OutOfMemory => |e| return e,
}, },
else => |other| { else => |other| {
// There might be an error that happened *after* linker.error_msg // There might be an error that happened *after* linker.error_msg
// was already allocated, so be sure to free it. // was already allocated, so be sure to free it.
if (cg.error_msg) |error_msg| { if (linker.cg.error_msg) |error_msg| {
error_msg.deinit(gpa); error_msg.deinit(gpa);
} }

View File

@ -12,6 +12,7 @@ const ExtendedStructSet = std.StringHashMap(void);
const Extension = struct { const Extension = struct {
name: []const u8, name: []const u8,
opcode_name: []const u8,
spec: ExtensionRegistry, spec: ExtensionRegistry,
}; };
@ -44,23 +45,11 @@ const OperandKindMap = std.ArrayHashMap(StringPair, OperandKind, StringPairConte
/// Khronos made it so that these names are not defined explicitly, so /// Khronos made it so that these names are not defined explicitly, so
/// we need to hardcode it (like they did). /// we need to hardcode it (like they did).
/// See https://github.com/KhronosGroup/SPIRV-Registry/ /// See https://github.com/KhronosGroup/SPIRV-Registry
const set_names = std.StaticStringMap([]const u8).initComptime(.{ const set_names = std.StaticStringMap(struct { []const u8, []const u8 }).initComptime(.{
.{ "opencl.std.100", "OpenCL.std" }, .{ "opencl.std.100", .{ "OpenCL.std", "OpenClOpcode" } },
.{ "glsl.std.450", "GLSL.std.450" }, .{ "glsl.std.450", .{ "GLSL.std.450", "GlslOpcode" } },
.{ "opencl.debuginfo.100", "OpenCL.DebugInfo.100" }, .{ "zig", .{ "zig", "Zig" } },
.{ "spv-amd-shader-ballot", "SPV_AMD_shader_ballot" },
.{ "nonsemantic.shader.debuginfo.100", "NonSemantic.Shader.DebugInfo.100" },
.{ "nonsemantic.vkspreflection", "NonSemantic.VkspReflection" },
.{ "nonsemantic.clspvreflection", "NonSemantic.ClspvReflection.6" }, // This version needs to be handled manually
.{ "spv-amd-gcn-shader", "SPV_AMD_gcn_shader" },
.{ "spv-amd-shader-trinary-minmax", "SPV_AMD_shader_trinary_minmax" },
.{ "debuginfo", "DebugInfo" },
.{ "nonsemantic.debugprintf", "NonSemantic.DebugPrintf" },
.{ "spv-amd-shader-explicit-vertex-parameter", "SPV_AMD_shader_explicit_vertex_parameter" },
.{ "nonsemantic.debugbreak", "NonSemantic.DebugBreak" },
.{ "tosa.001000.1", "SPV_EXT_INST_TYPE_TOSA_001000_1" },
.{ "zig", "zig" },
}); });
var arena = std.heap.ArenaAllocator.init(std.heap.smp_allocator); var arena = std.heap.ArenaAllocator.init(std.heap.smp_allocator);
@ -78,7 +67,7 @@ pub fn main() !void {
const dir = try std.fs.cwd().openDir(json_path, .{ .iterate = true }); const dir = try std.fs.cwd().openDir(json_path, .{ .iterate = true });
const core_spec = try readRegistry(CoreRegistry, dir, "spirv.core.grammar.json"); const core_spec = try readRegistry(CoreRegistry, dir, "spirv.core.grammar.json");
std.sort.block(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt); std.mem.sortUnstable(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt);
var exts = std.ArrayList(Extension).init(allocator); var exts = std.ArrayList(Extension).init(allocator);
@ -134,14 +123,24 @@ fn readExtRegistry(exts: *std.ArrayList(Extension), dir: std.fs.Dir, sub_path: [
const name = filename["extinst.".len .. filename.len - ".grammar.json".len]; const name = filename["extinst.".len .. filename.len - ".grammar.json".len];
const spec = try readRegistry(ExtensionRegistry, dir, sub_path); const spec = try readRegistry(ExtensionRegistry, dir, sub_path);
const set_name = set_names.get(name) orelse {
std.log.info("ignored instruction set '{s}'", .{name});
return;
};
std.sort.block(Instruction, spec.instructions, CmpInst{}, CmpInst.lt); std.sort.block(Instruction, spec.instructions, CmpInst{}, CmpInst.lt);
try exts.append(.{ .name = set_names.get(name).?, .spec = spec }); try exts.append(.{
.name = set_name.@"0",
.opcode_name = set_name.@"1",
.spec = spec,
});
} }
fn readRegistry(comptime RegistryType: type, dir: std.fs.Dir, path: []const u8) !RegistryType { fn readRegistry(comptime RegistryType: type, dir: std.fs.Dir, path: []const u8) !RegistryType {
const spec = try dir.readFileAlloc(allocator, path, std.math.maxInt(usize)); const spec = try dir.readFileAlloc(allocator, path, std.math.maxInt(usize));
// Required for json parsing. // Required for json parsing.
// TODO: ALI
@setEvalBranchQuota(10000); @setEvalBranchQuota(10000);
var scanner = std.json.Scanner.initCompleteInput(allocator, spec); var scanner = std.json.Scanner.initCompleteInput(allocator, spec);
@ -191,7 +190,11 @@ fn tagPriorityScore(tag: []const u8) usize {
} }
} }
fn render(writer: *std.io.Writer, registry: CoreRegistry, extensions: []const Extension) !void { fn render(
writer: *std.io.Writer,
registry: CoreRegistry,
extensions: []const Extension,
) !void {
try writer.writeAll( try writer.writeAll(
\\//! This file is auto-generated by tools/gen_spirv_spec.zig. \\//! This file is auto-generated by tools/gen_spirv_spec.zig.
\\ \\
@ -317,13 +320,18 @@ fn render(writer: *std.io.Writer, registry: CoreRegistry, extensions: []const Ex
// Note: extensions don't seem to have class. // Note: extensions don't seem to have class.
try renderClass(writer, registry.instructions); try renderClass(writer, registry.instructions);
try renderOperandKind(writer, all_operand_kinds.values()); try renderOperandKind(writer, all_operand_kinds.values());
try renderOpcodes(writer, registry.instructions, extended_structs);
try renderOpcodes(writer, "Opcode", true, registry.instructions, extended_structs);
for (extensions) |ext| {
try renderOpcodes(writer, ext.opcode_name, false, ext.spec.instructions, extended_structs);
}
try renderOperandKinds(writer, all_operand_kinds.values(), extended_structs); try renderOperandKinds(writer, all_operand_kinds.values(), extended_structs);
try renderInstructionSet(writer, registry, extensions, all_operand_kinds); try renderInstructionSet(writer, registry, extensions, all_operand_kinds);
} }
fn renderInstructionSet( fn renderInstructionSet(
writer: anytype, writer: *std.io.Writer,
core: CoreRegistry, core: CoreRegistry,
extensions: []const Extension, extensions: []const Extension,
all_operand_kinds: OperandKindMap, all_operand_kinds: OperandKindMap,
@ -358,7 +366,7 @@ fn renderInstructionSet(
} }
fn renderInstructionsCase( fn renderInstructionsCase(
writer: anytype, writer: *std.io.Writer,
set_name: []const u8, set_name: []const u8,
instructions: []const Instruction, instructions: []const Instruction,
all_operand_kinds: OperandKindMap, all_operand_kinds: OperandKindMap,
@ -405,7 +413,7 @@ fn renderInstructionsCase(
); );
} }
fn renderClass(writer: anytype, instructions: []const Instruction) !void { fn renderClass(writer: *std.io.Writer, instructions: []const Instruction) !void {
var class_map = std.StringArrayHashMap(void).init(allocator); var class_map = std.StringArrayHashMap(void).init(allocator);
for (instructions) |inst| { for (instructions) |inst| {
@ -454,7 +462,7 @@ fn formatId(identifier: []const u8) std.fmt.Alt(Formatter, Formatter.format) {
return .{ .data = .{ .data = identifier } }; return .{ .data = .{ .data = identifier } };
} }
fn renderOperandKind(writer: anytype, operands: []const OperandKind) !void { fn renderOperandKind(writer: *std.io.Writer, operands: []const OperandKind) !void {
try writer.writeAll( try writer.writeAll(
\\pub const OperandKind = enum { \\pub const OperandKind = enum {
\\ opcode, \\ opcode,
@ -510,7 +518,7 @@ fn renderOperandKind(writer: anytype, operands: []const OperandKind) !void {
try writer.writeAll("};\n}\n};\n"); try writer.writeAll("};\n}\n};\n");
} }
fn renderEnumerant(writer: anytype, enumerant: Enumerant) !void { fn renderEnumerant(writer: *std.io.Writer, enumerant: Enumerant) !void {
try writer.print(".{{.name = \"{s}\", .value = ", .{enumerant.enumerant}); try writer.print(".{{.name = \"{s}\", .value = ", .{enumerant.enumerant});
switch (enumerant.value) { switch (enumerant.value) {
.bitflag => |flag| try writer.writeAll(flag), .bitflag => |flag| try writer.writeAll(flag),
@ -527,7 +535,9 @@ fn renderEnumerant(writer: anytype, enumerant: Enumerant) !void {
} }
fn renderOpcodes( fn renderOpcodes(
writer: anytype, writer: *std.io.Writer,
opcode_type_name: []const u8,
want_operands: bool,
instructions: []const Instruction, instructions: []const Instruction,
extended_structs: ExtendedStructSet, extended_structs: ExtendedStructSet,
) !void { ) !void {
@ -538,7 +548,9 @@ fn renderOpcodes(
try aliases.ensureTotalCapacity(instructions.len); try aliases.ensureTotalCapacity(instructions.len);
for (instructions, 0..) |inst, i| { for (instructions, 0..) |inst, i| {
if (std.mem.eql(u8, inst.class.?, "@exclude")) continue; if (inst.class) |class| {
if (std.mem.eql(u8, class, "@exclude")) continue;
}
const result = inst_map.getOrPutAssumeCapacity(inst.opcode); const result = inst_map.getOrPutAssumeCapacity(inst.opcode);
if (!result.found_existing) { if (!result.found_existing) {
@ -562,29 +574,29 @@ fn renderOpcodes(
const instructions_indices = inst_map.values(); const instructions_indices = inst_map.values();
try writer.writeAll("pub const Opcode = enum(u16) {\n"); try writer.print("\npub const {f} = enum(u16) {{\n", .{std.zig.fmtId(opcode_type_name)});
for (instructions_indices) |i| { for (instructions_indices) |i| {
const inst = instructions[i]; const inst = instructions[i];
try writer.print("{f} = {},\n", .{ std.zig.fmtId(inst.opname), inst.opcode }); try writer.print("{f} = {},\n", .{ std.zig.fmtId(inst.opname), inst.opcode });
} }
try writer.writeAll( try writer.writeAll("\n");
\\
);
for (aliases.items) |alias| { for (aliases.items) |alias| {
try writer.print("pub const {f} = Opcode.{f};\n", .{ try writer.print("pub const {f} = {f}.{f};\n", .{
formatId(instructions[alias.inst].opname), formatId(instructions[alias.inst].opname),
std.zig.fmtId(opcode_type_name),
formatId(instructions[alias.alias].opname), formatId(instructions[alias.alias].opname),
}); });
} }
try writer.writeAll( if (want_operands) {
try writer.print(
\\ \\
\\pub fn Operands(comptime self: Opcode) type { \\pub fn Operands(comptime self: {f}) type {{
\\ return switch (self) { \\ return switch (self) {{
\\ \\
); , .{std.zig.fmtId(opcode_type_name)});
for (instructions_indices) |i| { for (instructions_indices) |i| {
const inst = instructions[i]; const inst = instructions[i];
@ -594,11 +606,15 @@ fn renderOpcodes(
try writer.writeAll( try writer.writeAll(
\\ }; \\ };
\\} \\}
\\pub fn class(self: Opcode) Class {
\\ return switch (self) {
\\ \\
); );
try writer.print(
\\pub fn class(self: {f}) Class {{
\\ return switch (self) {{
\\
, .{std.zig.fmtId(opcode_type_name)});
for (instructions_indices) |i| { for (instructions_indices) |i| {
const inst = instructions[i]; const inst = instructions[i];
try writer.print(".{f} => .{f},\n", .{ std.zig.fmtId(inst.opname), formatId(inst.class.?) }); try writer.print(".{f} => .{f},\n", .{ std.zig.fmtId(inst.opname), formatId(inst.class.?) });
@ -607,13 +623,18 @@ fn renderOpcodes(
try writer.writeAll( try writer.writeAll(
\\ }; \\ };
\\} \\}
\\
);
}
try writer.writeAll(
\\}; \\};
\\ \\
); );
} }
fn renderOperandKinds( fn renderOperandKinds(
writer: anytype, writer: *std.io.Writer,
kinds: []const OperandKind, kinds: []const OperandKind,
extended_structs: ExtendedStructSet, extended_structs: ExtendedStructSet,
) !void { ) !void {
@ -627,7 +648,7 @@ fn renderOperandKinds(
} }
fn renderValueEnum( fn renderValueEnum(
writer: anytype, writer: *std.io.Writer,
enumeration: OperandKind, enumeration: OperandKind,
extended_structs: ExtendedStructSet, extended_structs: ExtendedStructSet,
) !void { ) !void {
@ -705,7 +726,7 @@ fn renderValueEnum(
} }
fn renderBitEnum( fn renderBitEnum(
writer: anytype, writer: *std.io.Writer,
enumeration: OperandKind, enumeration: OperandKind,
extended_structs: ExtendedStructSet, extended_structs: ExtendedStructSet,
) !void { ) !void {
@ -788,7 +809,7 @@ fn renderBitEnum(
} }
fn renderOperand( fn renderOperand(
writer: anytype, writer: *std.io.Writer,
kind: enum { kind: enum {
@"union", @"union",
instruction, instruction,
@ -872,7 +893,7 @@ fn renderOperand(
try writer.writeAll(",\n"); try writer.writeAll(",\n");
} }
fn renderFieldName(writer: anytype, operands: []const Operand, field_index: usize) !void { fn renderFieldName(writer: *std.io.Writer, operands: []const Operand, field_index: usize) !void {
const operand = operands[field_index]; const operand = operands[field_index];
derive_from_kind: { derive_from_kind: {