mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
Merge pull request #24661 from alichraghi/spv4
spirv: refactor and remove deduplication ISel
This commit is contained in:
commit
3fb86841cc
17 changed files with 8245 additions and 12002 deletions
|
|
@ -553,11 +553,6 @@ set(ZIG_STAGE2_SOURCES
|
||||||
src/codegen/c/Type.zig
|
src/codegen/c/Type.zig
|
||||||
src/codegen/llvm.zig
|
src/codegen/llvm.zig
|
||||||
src/codegen/llvm/bindings.zig
|
src/codegen/llvm/bindings.zig
|
||||||
src/codegen/spirv.zig
|
|
||||||
src/codegen/spirv/Assembler.zig
|
|
||||||
src/codegen/spirv/Module.zig
|
|
||||||
src/codegen/spirv/Section.zig
|
|
||||||
src/codegen/spirv/spec.zig
|
|
||||||
src/crash_report.zig
|
src/crash_report.zig
|
||||||
src/dev.zig
|
src/dev.zig
|
||||||
src/libs/freebsd.zig
|
src/libs/freebsd.zig
|
||||||
|
|
@ -620,11 +615,6 @@ set(ZIG_STAGE2_SOURCES
|
||||||
src/link/Plan9.zig
|
src/link/Plan9.zig
|
||||||
src/link/Plan9/aout.zig
|
src/link/Plan9/aout.zig
|
||||||
src/link/Queue.zig
|
src/link/Queue.zig
|
||||||
src/link/SpirV.zig
|
|
||||||
src/link/SpirV/BinaryModule.zig
|
|
||||||
src/link/SpirV/deduplicate.zig
|
|
||||||
src/link/SpirV/lower_invocation_globals.zig
|
|
||||||
src/link/SpirV/prune_unused.zig
|
|
||||||
src/link/StringTable.zig
|
src/link/StringTable.zig
|
||||||
src/link/Wasm.zig
|
src/link/Wasm.zig
|
||||||
src/link/Wasm/Archive.zig
|
src/link/Wasm/Archive.zig
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,13 @@ const Os = switch (builtin.os.tag) {
|
||||||
const gop = try w.dir_table.getOrPut(gpa, path);
|
const gop = try w.dir_table.getOrPut(gpa, path);
|
||||||
if (!gop.found_existing) {
|
if (!gop.found_existing) {
|
||||||
var mount_id: MountId = undefined;
|
var mount_id: MountId = undefined;
|
||||||
const dir_handle = try Os.getDirHandle(gpa, path, &mount_id);
|
const dir_handle = Os.getDirHandle(gpa, path, &mount_id) catch |err| switch (err) {
|
||||||
|
error.FileNotFound => {
|
||||||
|
std.debug.assert(w.dir_table.swapRemove(path));
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
const fan_fd = blk: {
|
const fan_fd = blk: {
|
||||||
const fd_gop = try w.os.poll_fds.getOrPut(gpa, mount_id);
|
const fd_gop = try w.os.poll_fds.getOrPut(gpa, mount_id);
|
||||||
if (!fd_gop.found_existing) {
|
if (!fd_gop.found_existing) {
|
||||||
|
|
|
||||||
|
|
@ -3651,9 +3651,8 @@ pub fn errorSetBits(zcu: *const Zcu) u16 {
|
||||||
|
|
||||||
if (zcu.error_limit == 0) return 0;
|
if (zcu.error_limit == 0) return 0;
|
||||||
if (target.cpu.arch.isSpirV()) {
|
if (target.cpu.arch.isSpirV()) {
|
||||||
if (!target.cpu.has(.spirv, .storage_push_constant16)) {
|
// As expected by https://github.com/Snektron/zig-spirv-test-executor
|
||||||
return 32;
|
if (zcu.comp.config.is_test) return 32;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return @as(u16, std.math.log2_int(ErrorInt, zcu.error_limit)) + 1;
|
return @as(u16, std.math.log2_int(ErrorInt, zcu.error_limit)) + 1;
|
||||||
|
|
|
||||||
|
|
@ -4459,13 +4459,10 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e
|
||||||
|
|
||||||
const lf = comp.bin_file orelse return error.NoLinkFile;
|
const lf = comp.bin_file orelse return error.NoLinkFile;
|
||||||
|
|
||||||
// TODO: self-hosted codegen should always have a type of MIR; codegen should produce that MIR,
|
// Just like LLVM, the SPIR-V backend can't multi-threaded due to SPIR-V design limitations.
|
||||||
// and the linker should consume it. However, our SPIR-V backend is currently tightly coupled
|
|
||||||
// with our SPIR-V linker, so needs to work more like the LLVM backend. This should be fixed to
|
|
||||||
// unblock threaded codegen for SPIR-V.
|
|
||||||
if (lf.cast(.spirv)) |spirv_file| {
|
if (lf.cast(.spirv)) |spirv_file| {
|
||||||
assert(pt.tid == .main); // SPIR-V has a lot of shared state
|
assert(pt.tid == .main); // SPIR-V has a lot of shared state
|
||||||
spirv_file.object.updateFunc(pt, func_index, air, &liveness) catch |err| {
|
spirv_file.updateFunc(pt, func_index, air, &liveness) catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
error.OutOfMemory => comp.link_diags.setAllocFailure(),
|
error.OutOfMemory => comp.link_diags.setAllocFailure(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ fn importBackend(comptime backend: std.builtin.CompilerBackend) type {
|
||||||
.stage2_powerpc => unreachable,
|
.stage2_powerpc => unreachable,
|
||||||
.stage2_riscv64 => @import("arch/riscv64/CodeGen.zig"),
|
.stage2_riscv64 => @import("arch/riscv64/CodeGen.zig"),
|
||||||
.stage2_sparc64 => @import("arch/sparc64/CodeGen.zig"),
|
.stage2_sparc64 => @import("arch/sparc64/CodeGen.zig"),
|
||||||
.stage2_spirv => @import("codegen/spirv.zig"),
|
.stage2_spirv => @import("codegen/spirv/CodeGen.zig"),
|
||||||
.stage2_wasm => @import("arch/wasm/CodeGen.zig"),
|
.stage2_wasm => @import("arch/wasm/CodeGen.zig"),
|
||||||
.stage2_x86, .stage2_x86_64 => @import("arch/x86_64/CodeGen.zig"),
|
.stage2_x86, .stage2_x86_64 => @import("arch/x86_64/CodeGen.zig"),
|
||||||
_ => unreachable,
|
_ => unreachable,
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
6188
src/codegen/spirv/CodeGen.zig
Normal file
6188
src/codegen/spirv/CodeGen.zig
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -13,8 +13,6 @@ const Log2Word = std.math.Log2Int(Word);
|
||||||
|
|
||||||
const Opcode = spec.Opcode;
|
const Opcode = spec.Opcode;
|
||||||
|
|
||||||
/// The instructions in this section. Memory is owned by the Module
|
|
||||||
/// externally associated to this Section.
|
|
||||||
instructions: std.ArrayListUnmanaged(Word) = .empty,
|
instructions: std.ArrayListUnmanaged(Word) = .empty,
|
||||||
|
|
||||||
pub fn deinit(section: *Section, allocator: Allocator) void {
|
pub fn deinit(section: *Section, allocator: Allocator) void {
|
||||||
|
|
@ -22,9 +20,8 @@ pub fn deinit(section: *Section, allocator: Allocator) void {
|
||||||
section.* = undefined;
|
section.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear the instructions in this section
|
|
||||||
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 {
|
||||||
|
|
@ -36,9 +33,12 @@ pub fn append(section: *Section, allocator: Allocator, other_section: Section) !
|
||||||
try section.instructions.appendSlice(allocator, other_section.instructions.items);
|
try section.instructions.appendSlice(allocator, other_section.instructions.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure capacity of at least `capacity` more words in this section.
|
pub fn ensureUnusedCapacity(
|
||||||
pub fn ensureUnusedCapacity(section: *Section, allocator: Allocator, capacity: usize) !void {
|
section: *Section,
|
||||||
try section.instructions.ensureUnusedCapacity(allocator, capacity);
|
allocator: Allocator,
|
||||||
|
words: usize,
|
||||||
|
) !void {
|
||||||
|
try section.instructions.ensureUnusedCapacity(allocator, words);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write an instruction and size, operands are to be inserted manually.
|
/// Write an instruction and size, operands are to be inserted manually.
|
||||||
|
|
@ -46,7 +46,7 @@ pub fn emitRaw(
|
||||||
section: *Section,
|
section: *Section,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
opcode: Opcode,
|
opcode: Opcode,
|
||||||
operand_words: usize, // opcode itself not included
|
operand_words: usize,
|
||||||
) !void {
|
) !void {
|
||||||
const word_count = 1 + operand_words;
|
const word_count = 1 + operand_words;
|
||||||
try section.instructions.ensureUnusedCapacity(allocator, word_count);
|
try section.instructions.ensureUnusedCapacity(allocator, word_count);
|
||||||
|
|
@ -64,6 +64,16 @@ pub fn emitRawInstruction(
|
||||||
section.writeWords(operands);
|
section.writeWords(operands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn emitAssumeCapacity(
|
||||||
|
section: *Section,
|
||||||
|
comptime opcode: spec.Opcode,
|
||||||
|
operands: opcode.Operands(),
|
||||||
|
) !void {
|
||||||
|
const word_count = instructionSize(opcode, operands);
|
||||||
|
section.writeWord(@as(Word, @intCast(word_count << 16)) | @intFromEnum(opcode));
|
||||||
|
section.writeOperands(opcode.Operands(), operands);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn emit(
|
pub fn emit(
|
||||||
section: *Section,
|
section: *Section,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
@ -76,35 +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 emitSpecConstantOp(
|
|
||||||
section: *Section,
|
|
||||||
allocator: Allocator,
|
|
||||||
comptime opcode: spec.Opcode,
|
|
||||||
operands: opcode.Operands(),
|
|
||||||
) !void {
|
|
||||||
const word_count = operandsSize(opcode.Operands(), operands);
|
|
||||||
try section.emitRaw(allocator, .OpSpecConstantOp, 1 + word_count);
|
|
||||||
section.writeOperand(spec.Id, operands.id_result_type);
|
|
||||||
section.writeOperand(spec.Id, operands.id_result);
|
|
||||||
section.writeOperand(Opcode, opcode);
|
|
||||||
|
|
||||||
const fields = @typeInfo(opcode.Operands()).@"struct".fields;
|
|
||||||
// First 2 fields are always id_result_type and id_result.
|
|
||||||
inline for (fields[2..]) |field| {
|
|
||||||
section.writeOperand(field.type, @field(operands, field.name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writeWord(section: *Section, word: Word) void {
|
pub fn writeWord(section: *Section, word: Word) void {
|
||||||
section.instructions.appendAssumeCapacity(word);
|
section.instructions.appendAssumeCapacity(word);
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +107,6 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
|
||||||
.void => return,
|
.void => return,
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
|
|
||||||
inline for (fields) |field| {
|
inline for (fields) |field| {
|
||||||
section.writeOperand(field.type, @field(operands, field.name));
|
section.writeOperand(field.type, @field(operands, field.name));
|
||||||
}
|
}
|
||||||
|
|
@ -134,30 +114,18 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
|
||||||
|
|
||||||
pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
|
pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
|
||||||
switch (Operand) {
|
switch (Operand) {
|
||||||
|
spec.LiteralSpecConstantOpInteger => unreachable,
|
||||||
spec.Id => section.writeWord(@intFromEnum(operand)),
|
spec.Id => section.writeWord(@intFromEnum(operand)),
|
||||||
|
|
||||||
spec.LiteralInteger => section.writeWord(operand),
|
spec.LiteralInteger => section.writeWord(operand),
|
||||||
|
|
||||||
spec.LiteralString => section.writeString(operand),
|
spec.LiteralString => section.writeString(operand),
|
||||||
|
|
||||||
spec.LiteralContextDependentNumber => section.writeContextDependentNumber(operand),
|
spec.LiteralContextDependentNumber => section.writeContextDependentNumber(operand),
|
||||||
|
|
||||||
spec.LiteralExtInstInteger => section.writeWord(operand.inst),
|
spec.LiteralExtInstInteger => section.writeWord(operand.inst),
|
||||||
|
|
||||||
// TODO: Where this type is used (OpSpecConstantOp) is currently not correct in the spec json,
|
|
||||||
// so it most likely needs to be altered into something that can actually describe the entire
|
|
||||||
// instruction in which it is used.
|
|
||||||
spec.LiteralSpecConstantOpInteger => section.writeWord(@intFromEnum(operand.opcode)),
|
|
||||||
|
|
||||||
spec.PairLiteralIntegerIdRef => section.writeWords(&.{ operand.value, @enumFromInt(operand.label) }),
|
spec.PairLiteralIntegerIdRef => section.writeWords(&.{ operand.value, @enumFromInt(operand.label) }),
|
||||||
spec.PairIdRefLiteralInteger => section.writeWords(&.{ @intFromEnum(operand.target), operand.member }),
|
spec.PairIdRefLiteralInteger => section.writeWords(&.{ @intFromEnum(operand.target), operand.member }),
|
||||||
spec.PairIdRefIdRef => section.writeWords(&.{ @intFromEnum(operand[0]), @intFromEnum(operand[1]) }),
|
spec.PairIdRefIdRef => section.writeWords(&.{ @intFromEnum(operand[0]), @intFromEnum(operand[1]) }),
|
||||||
|
|
||||||
else => switch (@typeInfo(Operand)) {
|
else => switch (@typeInfo(Operand)) {
|
||||||
.@"enum" => section.writeWord(@intFromEnum(operand)),
|
.@"enum" => section.writeWord(@intFromEnum(operand)),
|
||||||
.optional => |info| if (operand) |child| {
|
.optional => |info| if (operand) |child| section.writeOperand(info.child, child),
|
||||||
section.writeOperand(info.child, child);
|
|
||||||
},
|
|
||||||
.pointer => |info| {
|
.pointer => |info| {
|
||||||
std.debug.assert(info.size == .slice); // Should be no other pointer types in the spec.
|
std.debug.assert(info.size == .slice); // Should be no other pointer types in the spec.
|
||||||
for (operand) |item| {
|
for (operand) |item| {
|
||||||
|
|
@ -178,18 +146,14 @@ pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeString(section: *Section, str: []const u8) void {
|
fn writeString(section: *Section, str: []const u8) void {
|
||||||
// TODO: Not actually sure whether this is correct for big-endian.
|
|
||||||
// See https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#Literal
|
|
||||||
const zero_terminated_len = str.len + 1;
|
const zero_terminated_len = str.len + 1;
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < zero_terminated_len) : (i += @sizeOf(Word)) {
|
while (i < zero_terminated_len) : (i += @sizeOf(Word)) {
|
||||||
var word: Word = 0;
|
var word: Word = 0;
|
||||||
|
|
||||||
var j: usize = 0;
|
var j: usize = 0;
|
||||||
while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) {
|
while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) {
|
||||||
word |= @as(Word, str[i + j]) << @as(Log2Word, @intCast(j * @bitSizeOf(u8)));
|
word |= @as(Word, str[i + j]) << @as(Log2Word, @intCast(j * @bitSizeOf(u8)));
|
||||||
}
|
}
|
||||||
|
|
||||||
section.instructions.appendAssumeCapacity(word);
|
section.instructions.appendAssumeCapacity(word);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -233,20 +197,19 @@ fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeExtendedUnion(section: *Section, comptime Operand: type, operand: Operand) void {
|
fn writeExtendedUnion(section: *Section, comptime Operand: type, operand: Operand) void {
|
||||||
const tag = std.meta.activeTag(operand);
|
return switch (operand) {
|
||||||
section.writeWord(@intFromEnum(tag));
|
inline else => |op, tag| {
|
||||||
|
section.writeWord(@intFromEnum(tag));
|
||||||
inline for (@typeInfo(Operand).@"union".fields) |field| {
|
section.writeOperands(
|
||||||
if (@field(Operand, field.name) == tag) {
|
@FieldType(Operand, @tagName(tag)),
|
||||||
section.writeOperands(field.type, @field(operand, field.name));
|
op,
|
||||||
return;
|
);
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
unreachable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instructionSize(comptime opcode: spec.Opcode, operands: opcode.Operands()) usize {
|
fn instructionSize(comptime opcode: spec.Opcode, operands: opcode.Operands()) usize {
|
||||||
return 1 + operandsSize(opcode.Operands(), operands);
|
return operandsSize(opcode.Operands(), operands) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operandsSize(comptime Operands: type, operands: Operands) usize {
|
fn operandsSize(comptime Operands: type, operands: Operands) usize {
|
||||||
|
|
@ -266,28 +229,14 @@ fn operandsSize(comptime Operands: type, operands: Operands) usize {
|
||||||
|
|
||||||
fn operandSize(comptime Operand: type, operand: Operand) usize {
|
fn operandSize(comptime Operand: type, operand: Operand) usize {
|
||||||
return switch (Operand) {
|
return switch (Operand) {
|
||||||
spec.Id,
|
spec.LiteralSpecConstantOpInteger => unreachable,
|
||||||
spec.LiteralInteger,
|
spec.Id, spec.LiteralInteger, spec.LiteralExtInstInteger => 1,
|
||||||
spec.LiteralExtInstInteger,
|
spec.LiteralString => std.math.divCeil(usize, operand.len + 1, @sizeOf(Word)) catch unreachable,
|
||||||
=> 1,
|
|
||||||
|
|
||||||
spec.LiteralString => std.math.divCeil(usize, operand.len + 1, @sizeOf(Word)) catch unreachable, // Add one for zero-terminator
|
|
||||||
|
|
||||||
spec.LiteralContextDependentNumber => switch (operand) {
|
spec.LiteralContextDependentNumber => switch (operand) {
|
||||||
.int32, .uint32, .float32 => 1,
|
.int32, .uint32, .float32 => 1,
|
||||||
.int64, .uint64, .float64 => 2,
|
.int64, .uint64, .float64 => 2,
|
||||||
},
|
},
|
||||||
|
spec.PairLiteralIntegerIdRef, spec.PairIdRefLiteralInteger, spec.PairIdRefIdRef => 2,
|
||||||
// TODO: Where this type is used (OpSpecConstantOp) is currently not correct in the spec
|
|
||||||
// json, so it most likely needs to be altered into something that can actually
|
|
||||||
// describe the entire insturction in which it is used.
|
|
||||||
spec.LiteralSpecConstantOpInteger => 1,
|
|
||||||
|
|
||||||
spec.PairLiteralIntegerIdRef,
|
|
||||||
spec.PairIdRefLiteralInteger,
|
|
||||||
spec.PairIdRefIdRef,
|
|
||||||
=> 2,
|
|
||||||
|
|
||||||
else => switch (@typeInfo(Operand)) {
|
else => switch (@typeInfo(Operand)) {
|
||||||
.@"enum" => 1,
|
.@"enum" => 1,
|
||||||
.optional => |info| if (operand) |child| operandSize(info.child, child) else 0,
|
.optional => |info| if (operand) |child| operandSize(info.child, child) else 0,
|
||||||
|
|
@ -299,133 +248,25 @@ fn operandSize(comptime Operand: type, operand: Operand) usize {
|
||||||
}
|
}
|
||||||
break :blk total;
|
break :blk total;
|
||||||
},
|
},
|
||||||
.@"struct" => |info| if (info.layout == .@"packed") 1 else extendedMaskSize(Operand, operand),
|
.@"struct" => |struct_info| {
|
||||||
.@"union" => extendedUnionSize(Operand, operand),
|
if (struct_info.layout == .@"packed") return 1;
|
||||||
|
|
||||||
|
var total: usize = 0;
|
||||||
|
inline for (@typeInfo(Operand).@"struct".fields) |field| {
|
||||||
|
switch (@typeInfo(field.type)) {
|
||||||
|
.optional => |info| if (@field(operand, field.name)) |child| {
|
||||||
|
total += operandsSize(info.child, child);
|
||||||
|
},
|
||||||
|
.bool => {},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total + 1; // Add one for the mask itself.
|
||||||
|
},
|
||||||
|
.@"union" => switch (operand) {
|
||||||
|
inline else => |op, tag| operandsSize(@FieldType(Operand, @tagName(tag)), op) + 1,
|
||||||
|
},
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extendedMaskSize(comptime Operand: type, operand: Operand) usize {
|
|
||||||
var total: usize = 0;
|
|
||||||
var any_set = false;
|
|
||||||
inline for (@typeInfo(Operand).@"struct".fields) |field| {
|
|
||||||
switch (@typeInfo(field.type)) {
|
|
||||||
.optional => |info| if (@field(operand, field.name)) |child| {
|
|
||||||
total += operandsSize(info.child, child);
|
|
||||||
any_set = true;
|
|
||||||
},
|
|
||||||
.bool => if (@field(operand, field.name)) {
|
|
||||||
any_set = true;
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return total + 1; // Add one for the mask itself.
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extendedUnionSize(comptime Operand: type, operand: Operand) usize {
|
|
||||||
const tag = std.meta.activeTag(operand);
|
|
||||||
inline for (@typeInfo(Operand).@"union".fields) |field| {
|
|
||||||
if (@field(Operand, field.name) == tag) {
|
|
||||||
// Add one for the tag itself.
|
|
||||||
return 1 + operandsSize(field.type, @field(operand, field.name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
test "SPIR-V Section emit() - no operands" {
|
|
||||||
var section = Section{};
|
|
||||||
defer section.deinit(std.testing.allocator);
|
|
||||||
|
|
||||||
try section.emit(std.testing.allocator, .OpNop, {});
|
|
||||||
|
|
||||||
try testing.expect(section.instructions.items[0] == (@as(Word, 1) << 16) | @intFromEnum(Opcode.OpNop));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "SPIR-V Section emit() - simple" {
|
|
||||||
var section = Section{};
|
|
||||||
defer section.deinit(std.testing.allocator);
|
|
||||||
|
|
||||||
try section.emit(std.testing.allocator, .OpUndef, .{
|
|
||||||
.id_result_type = @enumFromInt(0),
|
|
||||||
.id_result = @enumFromInt(1),
|
|
||||||
});
|
|
||||||
|
|
||||||
try testing.expectEqualSlices(Word, &.{
|
|
||||||
(@as(Word, 3) << 16) | @intFromEnum(Opcode.OpUndef),
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
}, section.instructions.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "SPIR-V Section emit() - string" {
|
|
||||||
var section = Section{};
|
|
||||||
defer section.deinit(std.testing.allocator);
|
|
||||||
|
|
||||||
try section.emit(std.testing.allocator, .OpSource, .{
|
|
||||||
.source_language = .Unknown,
|
|
||||||
.version = 123,
|
|
||||||
.file = @enumFromInt(256),
|
|
||||||
.source = "pub fn main() void {}",
|
|
||||||
});
|
|
||||||
|
|
||||||
try testing.expectEqualSlices(Word, &.{
|
|
||||||
(@as(Word, 10) << 16) | @intFromEnum(Opcode.OpSource),
|
|
||||||
@intFromEnum(spec.SourceLanguage.Unknown),
|
|
||||||
123,
|
|
||||||
456,
|
|
||||||
std.mem.bytesToValue(Word, "pub "),
|
|
||||||
std.mem.bytesToValue(Word, "fn m"),
|
|
||||||
std.mem.bytesToValue(Word, "ain("),
|
|
||||||
std.mem.bytesToValue(Word, ") vo"),
|
|
||||||
std.mem.bytesToValue(Word, "id {"),
|
|
||||||
std.mem.bytesToValue(Word, "}\x00\x00\x00"),
|
|
||||||
}, section.instructions.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "SPIR-V Section emit() - extended mask" {
|
|
||||||
var section = Section{};
|
|
||||||
defer section.deinit(std.testing.allocator);
|
|
||||||
|
|
||||||
try section.emit(std.testing.allocator, .OpLoopMerge, .{
|
|
||||||
.merge_block = @enumFromInt(10),
|
|
||||||
.continue_target = @enumFromInt(20),
|
|
||||||
.loop_control = .{
|
|
||||||
.Unroll = true,
|
|
||||||
.DependencyLength = .{
|
|
||||||
.literal_integer = 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try testing.expectEqualSlices(Word, &.{
|
|
||||||
(@as(Word, 5) << 16) | @intFromEnum(Opcode.OpLoopMerge),
|
|
||||||
10,
|
|
||||||
20,
|
|
||||||
@as(Word, @bitCast(spec.LoopControl{ .Unroll = true, .DependencyLength = true })),
|
|
||||||
2,
|
|
||||||
}, section.instructions.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "SPIR-V Section emit() - extended union" {
|
|
||||||
var section = Section{};
|
|
||||||
defer section.deinit(std.testing.allocator);
|
|
||||||
|
|
||||||
try section.emit(std.testing.allocator, .OpExecutionMode, .{
|
|
||||||
.entry_point = @enumFromInt(888),
|
|
||||||
.mode = .{
|
|
||||||
.LocalSize = .{ .x_size = 4, .y_size = 8, .z_size = 16 },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try testing.expectEqualSlices(Word, &.{
|
|
||||||
(@as(Word, 6) << 16) | @intFromEnum(Opcode.OpExecutionMode),
|
|
||||||
888,
|
|
||||||
@intFromEnum(spec.ExecutionMode.LocalSize),
|
|
||||||
4,
|
|
||||||
8,
|
|
||||||
16,
|
|
||||||
}, section.instructions.items);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -191,6 +191,7 @@ pub const Env = enum {
|
||||||
.spirv => switch (feature) {
|
.spirv => switch (feature) {
|
||||||
.spirv_backend,
|
.spirv_backend,
|
||||||
.spirv_linker,
|
.spirv_linker,
|
||||||
|
.legalize,
|
||||||
=> true,
|
=> true,
|
||||||
else => Env.sema.supports(feature),
|
else => Env.sema.supports(feature),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,36 @@
|
||||||
//! SPIR-V Spec documentation: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html
|
|
||||||
//! According to above documentation, a SPIR-V module has the following logical layout:
|
|
||||||
//! Header.
|
|
||||||
//! OpCapability instructions.
|
|
||||||
//! OpExtension instructions.
|
|
||||||
//! OpExtInstImport instructions.
|
|
||||||
//! A single OpMemoryModel instruction.
|
|
||||||
//! All entry points, declared with OpEntryPoint instructions.
|
|
||||||
//! All execution-mode declarators; OpExecutionMode and OpExecutionModeId instructions.
|
|
||||||
//! Debug instructions:
|
|
||||||
//! - First, OpString, OpSourceExtension, OpSource, OpSourceContinued (no forward references).
|
|
||||||
//! - OpName and OpMemberName instructions.
|
|
||||||
//! - OpModuleProcessed instructions.
|
|
||||||
//! All annotation (decoration) instructions.
|
|
||||||
//! All type declaration instructions, constant instructions, global variable declarations, (preferably) OpUndef instructions.
|
|
||||||
//! All function declarations without a body (extern functions presumably).
|
|
||||||
//! All regular functions.
|
|
||||||
|
|
||||||
// Because SPIR-V requires re-compilation anyway, and so hot swapping will not work
|
|
||||||
// anyway, we simply generate all the code in flush. This keeps
|
|
||||||
// things considerably simpler.
|
|
||||||
|
|
||||||
const SpirV = @This();
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const Path = std.Build.Cache.Path;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const log = std.log.scoped(.link);
|
const log = std.log.scoped(.link);
|
||||||
const Path = std.Build.Cache.Path;
|
|
||||||
|
|
||||||
const Zcu = @import("../Zcu.zig");
|
const Zcu = @import("../Zcu.zig");
|
||||||
const InternPool = @import("../InternPool.zig");
|
const InternPool = @import("../InternPool.zig");
|
||||||
const Compilation = @import("../Compilation.zig");
|
const Compilation = @import("../Compilation.zig");
|
||||||
const link = @import("../link.zig");
|
const link = @import("../link.zig");
|
||||||
const codegen = @import("../codegen/spirv.zig");
|
|
||||||
const trace = @import("../tracy.zig").trace;
|
|
||||||
const build_options = @import("build_options");
|
|
||||||
const Air = @import("../Air.zig");
|
const Air = @import("../Air.zig");
|
||||||
const Type = @import("../Type.zig");
|
const Type = @import("../Type.zig");
|
||||||
const Value = @import("../Value.zig");
|
const BinaryModule = @import("SpirV/BinaryModule.zig");
|
||||||
|
const CodeGen = @import("../codegen/spirv/CodeGen.zig");
|
||||||
|
const Module = @import("../codegen/spirv/Module.zig");
|
||||||
|
const trace = @import("../tracy.zig").trace;
|
||||||
|
|
||||||
const SpvModule = @import("../codegen/spirv/Module.zig");
|
|
||||||
const Section = @import("../codegen/spirv/Section.zig");
|
|
||||||
const spec = @import("../codegen/spirv/spec.zig");
|
const spec = @import("../codegen/spirv/spec.zig");
|
||||||
const Id = spec.Id;
|
const Id = spec.Id;
|
||||||
const Word = spec.Word;
|
const Word = spec.Word;
|
||||||
|
|
||||||
const BinaryModule = @import("SpirV/BinaryModule.zig");
|
const Linker = @This();
|
||||||
|
|
||||||
base: link.File,
|
base: link.File,
|
||||||
|
module: Module,
|
||||||
object: codegen.Object,
|
cg: CodeGen,
|
||||||
|
|
||||||
pub fn createEmpty(
|
pub fn createEmpty(
|
||||||
arena: Allocator,
|
arena: Allocator,
|
||||||
comp: *Compilation,
|
comp: *Compilation,
|
||||||
emit: Path,
|
emit: Path,
|
||||||
options: link.File.OpenOptions,
|
options: link.File.OpenOptions,
|
||||||
) !*SpirV {
|
) !*Linker {
|
||||||
const gpa = comp.gpa;
|
const gpa = comp.gpa;
|
||||||
const target = &comp.root_mod.resolved_target.result;
|
const target = &comp.root_mod.resolved_target.result;
|
||||||
|
|
||||||
|
|
@ -72,8 +46,8 @@ pub fn createEmpty(
|
||||||
else => unreachable, // Caught by Compilation.Config.resolve.
|
else => unreachable, // Caught by Compilation.Config.resolve.
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = try arena.create(SpirV);
|
const linker = try arena.create(Linker);
|
||||||
self.* = .{
|
linker.* = .{
|
||||||
.base = .{
|
.base = .{
|
||||||
.tag = .spirv,
|
.tag = .spirv,
|
||||||
.comp = comp,
|
.comp = comp,
|
||||||
|
|
@ -85,17 +59,30 @@ pub fn createEmpty(
|
||||||
.file = null,
|
.file = null,
|
||||||
.build_id = options.build_id,
|
.build_id = options.build_id,
|
||||||
},
|
},
|
||||||
.object = codegen.Object.init(gpa, comp.getTarget()),
|
.module = .{
|
||||||
|
.gpa = gpa,
|
||||||
|
.arena = arena,
|
||||||
|
.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 self.deinit();
|
errdefer linker.deinit();
|
||||||
|
|
||||||
// TODO: read the file and keep valid parts instead of truncating
|
linker.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
|
||||||
self.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
|
|
||||||
.truncate = true,
|
.truncate = true,
|
||||||
.read = true,
|
.read = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return self;
|
return linker;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(
|
pub fn open(
|
||||||
|
|
@ -103,27 +90,90 @@ pub fn open(
|
||||||
comp: *Compilation,
|
comp: *Compilation,
|
||||||
emit: Path,
|
emit: Path,
|
||||||
options: link.File.OpenOptions,
|
options: link.File.OpenOptions,
|
||||||
) !*SpirV {
|
) !*Linker {
|
||||||
return createEmpty(arena, comp, emit, options);
|
return createEmpty(arena, comp, emit, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *SpirV) void {
|
pub fn deinit(linker: *Linker) void {
|
||||||
self.object.deinit();
|
linker.cg.deinit();
|
||||||
|
linker.module.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateNav(self: *SpirV, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
|
fn generate(
|
||||||
if (build_options.skip_non_native) {
|
linker: *Linker,
|
||||||
@panic("Attempted to compile for architecture that was disabled by build configuration");
|
pt: Zcu.PerThread,
|
||||||
}
|
nav_index: InternPool.Nav.Index,
|
||||||
|
air: Air,
|
||||||
|
liveness: Air.Liveness,
|
||||||
|
do_codegen: bool,
|
||||||
|
) !void {
|
||||||
|
const zcu = pt.zcu;
|
||||||
|
const gpa = zcu.gpa;
|
||||||
|
const structured_cfg = zcu.navFileScope(nav_index).mod.?.structured_cfg;
|
||||||
|
|
||||||
|
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,
|
||||||
|
.air = air,
|
||||||
|
.liveness = liveness,
|
||||||
|
.owner_nav = nav_index,
|
||||||
|
.module = &linker.module,
|
||||||
|
.control_flow = switch (structured_cfg) {
|
||||||
|
true => .{ .structured = .{} },
|
||||||
|
false => .{ .unstructured = .{} },
|
||||||
|
},
|
||||||
|
.base_line = zcu.navSrcLine(nav_index),
|
||||||
|
|
||||||
|
.args = linker.cg.args,
|
||||||
|
.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.OutOfMemory => |e| return e,
|
||||||
|
},
|
||||||
|
else => |other| {
|
||||||
|
// There might be an error that happened *after* linker.error_msg
|
||||||
|
// was already allocated, so be sure to free it.
|
||||||
|
if (linker.cg.error_msg) |error_msg| {
|
||||||
|
error_msg.deinit(gpa);
|
||||||
|
}
|
||||||
|
|
||||||
|
return other;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateFunc(
|
||||||
|
linker: *Linker,
|
||||||
|
pt: Zcu.PerThread,
|
||||||
|
func_index: InternPool.Index,
|
||||||
|
air: *const Air,
|
||||||
|
liveness: *const ?Air.Liveness,
|
||||||
|
) !void {
|
||||||
|
const nav = pt.zcu.funcInfo(func_index).owner_nav;
|
||||||
|
// TODO: Separate types for generating decls and functions?
|
||||||
|
try linker.generate(pt, nav, air.*, liveness.*.?, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateNav(linker: *Linker, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
|
||||||
const ip = &pt.zcu.intern_pool;
|
const ip = &pt.zcu.intern_pool;
|
||||||
log.debug("lowering nav {f}({d})", .{ ip.getNav(nav).fqn.fmt(ip), nav });
|
log.debug("lowering nav {f}({d})", .{ ip.getNav(nav).fqn.fmt(ip), nav });
|
||||||
|
try linker.generate(pt, nav, undefined, undefined, false);
|
||||||
try self.object.updateNav(pt, nav);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateExports(
|
pub fn updateExports(
|
||||||
self: *SpirV,
|
linker: *Linker,
|
||||||
pt: Zcu.PerThread,
|
pt: Zcu.PerThread,
|
||||||
exported: Zcu.Exported,
|
exported: Zcu.Exported,
|
||||||
export_indices: []const Zcu.Export.Index,
|
export_indices: []const Zcu.Export.Index,
|
||||||
|
|
@ -134,13 +184,13 @@ pub fn updateExports(
|
||||||
.nav => |nav| nav,
|
.nav => |nav| nav,
|
||||||
.uav => |uav| {
|
.uav => |uav| {
|
||||||
_ = uav;
|
_ = uav;
|
||||||
@panic("TODO: implement SpirV linker code for exporting a constant value");
|
@panic("TODO: implement Linker linker code for exporting a constant value");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const nav_ty = ip.getNav(nav_index).typeOf(ip);
|
const nav_ty = ip.getNav(nav_index).typeOf(ip);
|
||||||
const target = zcu.getTarget();
|
const target = zcu.getTarget();
|
||||||
if (ip.isFunctionType(nav_ty)) {
|
if (ip.isFunctionType(nav_ty)) {
|
||||||
const spv_decl_index = try self.object.resolveNav(zcu, nav_index);
|
const spv_decl_index = try linker.module.resolveNav(ip, nav_index);
|
||||||
const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
|
const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
|
||||||
const exec_model: spec.ExecutionModel = switch (target.os.tag) {
|
const exec_model: spec.ExecutionModel = switch (target.os.tag) {
|
||||||
.vulkan, .opengl => switch (cc) {
|
.vulkan, .opengl => switch (cc) {
|
||||||
|
|
@ -162,7 +212,7 @@ pub fn updateExports(
|
||||||
|
|
||||||
for (export_indices) |export_idx| {
|
for (export_indices) |export_idx| {
|
||||||
const exp = export_idx.ptr(zcu);
|
const exp = export_idx.ptr(zcu);
|
||||||
try self.object.spv.declareEntryPoint(
|
try linker.module.declareEntryPoint(
|
||||||
spv_decl_index,
|
spv_decl_index,
|
||||||
exp.opts.name.toSlice(ip),
|
exp.opts.name.toSlice(ip),
|
||||||
exec_model,
|
exec_model,
|
||||||
|
|
@ -175,7 +225,7 @@ pub fn updateExports(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flush(
|
pub fn flush(
|
||||||
self: *SpirV,
|
linker: *Linker,
|
||||||
arena: Allocator,
|
arena: Allocator,
|
||||||
tid: Zcu.PerThread.Id,
|
tid: Zcu.PerThread.Id,
|
||||||
prog_node: std.Progress.Node,
|
prog_node: std.Progress.Node,
|
||||||
|
|
@ -185,35 +235,29 @@ pub fn flush(
|
||||||
// InternPool.
|
// InternPool.
|
||||||
_ = tid;
|
_ = tid;
|
||||||
|
|
||||||
if (build_options.skip_non_native) {
|
|
||||||
@panic("Attempted to compile for architecture that was disabled by build configuration");
|
|
||||||
}
|
|
||||||
|
|
||||||
const tracy = trace(@src());
|
const tracy = trace(@src());
|
||||||
defer tracy.end();
|
defer tracy.end();
|
||||||
|
|
||||||
const sub_prog_node = prog_node.start("Flush Module", 0);
|
const sub_prog_node = prog_node.start("Flush Module", 0);
|
||||||
defer sub_prog_node.end();
|
defer sub_prog_node.end();
|
||||||
|
|
||||||
const comp = self.base.comp;
|
const comp = linker.base.comp;
|
||||||
const spv = &self.object.spv;
|
|
||||||
const diags = &comp.link_diags;
|
const diags = &comp.link_diags;
|
||||||
const gpa = comp.gpa;
|
const gpa = comp.gpa;
|
||||||
|
|
||||||
// We need to export the list of error names somewhere so that we can pretty-print them in the
|
// We need to export the list of error names somewhere so that we can pretty-print them in the
|
||||||
// executor. This is not really an important thing though, so we can just dump it in any old
|
// executor. This is not really an important thing though, so we can just dump it in any old
|
||||||
// nonsemantic instruction. For now, just put it in OpSourceExtension with a special name.
|
// nonsemantic instruction. For now, just put it in OpSourceExtension with a special name.
|
||||||
var error_info: std.io.Writer.Allocating = .init(self.object.gpa);
|
var error_info: std.io.Writer.Allocating = .init(linker.module.gpa);
|
||||||
defer error_info.deinit();
|
defer error_info.deinit();
|
||||||
|
|
||||||
error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;
|
error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;
|
||||||
const ip = &self.base.comp.zcu.?.intern_pool;
|
const ip = &linker.base.comp.zcu.?.intern_pool;
|
||||||
for (ip.global_error_set.getNamesFromMainThread()) |name| {
|
for (ip.global_error_set.getNamesFromMainThread()) |name| {
|
||||||
// Errors can contain pretty much any character - to encode them in a string we must escape
|
// Errors can contain pretty much any character - to encode them in a string we must escape
|
||||||
// them somehow. Easiest here is to use some established scheme, one which also preseves the
|
// them somehow. Easiest here is to use some established scheme, one which also preseves the
|
||||||
// name if it contains no strange characters is nice for debugging. URI encoding fits the bill.
|
// name if it contains no strange characters is nice for debugging. URI encoding fits the bill.
|
||||||
// We're using : as separator, which is a reserved character.
|
// We're using : as separator, which is a reserved character.
|
||||||
|
|
||||||
error_info.writer.writeByte(':') catch return error.OutOfMemory;
|
error_info.writer.writeByte(':') catch return error.OutOfMemory;
|
||||||
std.Uri.Component.percentEncode(
|
std.Uri.Component.percentEncode(
|
||||||
&error_info.writer,
|
&error_info.writer,
|
||||||
|
|
@ -228,36 +272,34 @@ pub fn flush(
|
||||||
}.isValidChar,
|
}.isValidChar,
|
||||||
) catch return error.OutOfMemory;
|
) catch return error.OutOfMemory;
|
||||||
}
|
}
|
||||||
try spv.sections.debug_strings.emit(gpa, .OpSourceExtension, .{
|
try linker.module.sections.debug_strings.emit(gpa, .OpSourceExtension, .{
|
||||||
.extension = error_info.getWritten(),
|
.extension = error_info.getWritten(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const module = try spv.finalize(arena);
|
const module = try linker.module.finalize(arena);
|
||||||
errdefer arena.free(module);
|
errdefer arena.free(module);
|
||||||
|
|
||||||
const linked_module = self.linkModule(arena, module, sub_prog_node) catch |err| switch (err) {
|
const linked_module = linker.linkModule(arena, module, sub_prog_node) catch |err| switch (err) {
|
||||||
error.OutOfMemory => return error.OutOfMemory,
|
error.OutOfMemory => return error.OutOfMemory,
|
||||||
else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}),
|
else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.base.file.?.writeAll(std.mem.sliceAsBytes(linked_module)) catch |err|
|
linker.base.file.?.writeAll(std.mem.sliceAsBytes(linked_module)) catch |err|
|
||||||
return diags.fail("failed to write: {s}", .{@errorName(err)});
|
return diags.fail("failed to write: {s}", .{@errorName(err)});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn linkModule(self: *SpirV, a: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {
|
fn linkModule(linker: *Linker, arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {
|
||||||
_ = self;
|
_ = linker;
|
||||||
|
|
||||||
const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
|
const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
|
||||||
const prune_unused = @import("SpirV/prune_unused.zig");
|
const prune_unused = @import("SpirV/prune_unused.zig");
|
||||||
const dedup = @import("SpirV/deduplicate.zig");
|
|
||||||
|
|
||||||
var parser = try BinaryModule.Parser.init(a);
|
var parser = try BinaryModule.Parser.init(arena);
|
||||||
defer parser.deinit();
|
defer parser.deinit();
|
||||||
var binary = try parser.parse(module);
|
var binary = try parser.parse(module);
|
||||||
|
|
||||||
try lower_invocation_globals.run(&parser, &binary, progress);
|
try lower_invocation_globals.run(&parser, &binary, progress);
|
||||||
try prune_unused.run(&parser, &binary, progress);
|
try prune_unused.run(&parser, &binary, progress);
|
||||||
try dedup.run(&parser, &binary, progress);
|
|
||||||
|
|
||||||
return binary.finalize(a);
|
return binary.finalize(arena);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,553 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const log = std.log.scoped(.spirv_link);
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
|
|
||||||
const BinaryModule = @import("BinaryModule.zig");
|
|
||||||
const Section = @import("../../codegen/spirv/Section.zig");
|
|
||||||
const spec = @import("../../codegen/spirv/spec.zig");
|
|
||||||
const Opcode = spec.Opcode;
|
|
||||||
const ResultId = spec.Id;
|
|
||||||
const Word = spec.Word;
|
|
||||||
|
|
||||||
fn canDeduplicate(opcode: Opcode) bool {
|
|
||||||
return switch (opcode) {
|
|
||||||
.OpTypeForwardPointer => false, // Don't need to handle these
|
|
||||||
.OpGroupDecorate, .OpGroupMemberDecorate => {
|
|
||||||
// These are deprecated, so don't bother supporting them for now.
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
// Debug decoration-style instructions
|
|
||||||
.OpName, .OpMemberName => true,
|
|
||||||
else => switch (opcode.class()) {
|
|
||||||
.type_declaration,
|
|
||||||
.constant_creation,
|
|
||||||
.annotation,
|
|
||||||
=> true,
|
|
||||||
else => false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ModuleInfo = struct {
|
|
||||||
/// This models a type, decoration or constant instruction
|
|
||||||
/// and its dependencies.
|
|
||||||
const Entity = struct {
|
|
||||||
/// The type that this entity represents. This is just
|
|
||||||
/// the instruction opcode.
|
|
||||||
kind: Opcode,
|
|
||||||
/// The offset of this entity's operands, in
|
|
||||||
/// `binary.instructions`.
|
|
||||||
first_operand: u32,
|
|
||||||
/// The number of operands in this entity
|
|
||||||
num_operands: u16,
|
|
||||||
/// The (first_operand-relative) offset of the result-id,
|
|
||||||
/// or the entity that is affected by this entity if this entity
|
|
||||||
/// is a decoration.
|
|
||||||
result_id_index: u16,
|
|
||||||
/// The first decoration in `self.decorations`.
|
|
||||||
first_decoration: u32,
|
|
||||||
|
|
||||||
fn operands(self: Entity, binary: *const BinaryModule) []const Word {
|
|
||||||
return binary.instructions[self.first_operand..][0..self.num_operands];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Maps result-id to Entity's
|
|
||||||
entities: std.AutoArrayHashMapUnmanaged(ResultId, Entity),
|
|
||||||
/// A bit set that keeps track of which operands are result-ids.
|
|
||||||
/// Note: This also includes any result-id!
|
|
||||||
/// Because we need these values when recoding the module anyway,
|
|
||||||
/// it contains the status of ALL operands in the module.
|
|
||||||
operand_is_id: std.DynamicBitSetUnmanaged,
|
|
||||||
/// Store of decorations for each entity.
|
|
||||||
decorations: []const Entity,
|
|
||||||
|
|
||||||
pub fn parse(
|
|
||||||
arena: Allocator,
|
|
||||||
parser: *BinaryModule.Parser,
|
|
||||||
binary: BinaryModule,
|
|
||||||
) !ModuleInfo {
|
|
||||||
var entities = std.AutoArrayHashMap(ResultId, Entity).init(arena);
|
|
||||||
var id_offsets = std.ArrayList(u16).init(arena);
|
|
||||||
var operand_is_id = try std.DynamicBitSetUnmanaged.initEmpty(arena, binary.instructions.len);
|
|
||||||
var decorations = std.MultiArrayList(struct { target_id: ResultId, entity: Entity }){};
|
|
||||||
|
|
||||||
var it = binary.iterateInstructions();
|
|
||||||
while (it.next()) |inst| {
|
|
||||||
id_offsets.items.len = 0;
|
|
||||||
try parser.parseInstructionResultIds(binary, inst, &id_offsets);
|
|
||||||
|
|
||||||
const first_operand_offset: u32 = @intCast(inst.offset + 1);
|
|
||||||
for (id_offsets.items) |offset| {
|
|
||||||
operand_is_id.set(first_operand_offset + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canDeduplicate(inst.opcode)) continue;
|
|
||||||
|
|
||||||
const result_id_index: u16 = switch (inst.opcode.class()) {
|
|
||||||
.type_declaration, .annotation, .debug => 0,
|
|
||||||
.constant_creation => 1,
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result_id: ResultId = @enumFromInt(inst.operands[id_offsets.items[result_id_index]]);
|
|
||||||
const entity = Entity{
|
|
||||||
.kind = inst.opcode,
|
|
||||||
.first_operand = first_operand_offset,
|
|
||||||
.num_operands = @intCast(inst.operands.len),
|
|
||||||
.result_id_index = result_id_index,
|
|
||||||
.first_decoration = undefined, // Filled in later
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (inst.opcode.class()) {
|
|
||||||
.annotation, .debug => {
|
|
||||||
try decorations.append(arena, .{
|
|
||||||
.target_id = result_id,
|
|
||||||
.entity = entity,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
.type_declaration, .constant_creation => {
|
|
||||||
const entry = try entities.getOrPut(result_id);
|
|
||||||
if (entry.found_existing) {
|
|
||||||
log.err("type or constant {f} has duplicate definition", .{result_id});
|
|
||||||
return error.DuplicateId;
|
|
||||||
}
|
|
||||||
entry.value_ptr.* = entity;
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort decorations by the index of the result-id in `entities.
|
|
||||||
// This ensures not only that the decorations of a particular reuslt-id
|
|
||||||
// are continuous, but the subsequences also appear in the same order as in `entities`.
|
|
||||||
|
|
||||||
const SortContext = struct {
|
|
||||||
entities: std.AutoArrayHashMapUnmanaged(ResultId, Entity),
|
|
||||||
ids: []const ResultId,
|
|
||||||
|
|
||||||
pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
|
|
||||||
// If any index is not in the entities set, its because its not a
|
|
||||||
// deduplicatable result-id. Those should be considered largest and
|
|
||||||
// float to the end.
|
|
||||||
const entity_index_a = ctx.entities.getIndex(ctx.ids[a_index]) orelse return false;
|
|
||||||
const entity_index_b = ctx.entities.getIndex(ctx.ids[b_index]) orelse return true;
|
|
||||||
|
|
||||||
return entity_index_a < entity_index_b;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
decorations.sort(SortContext{
|
|
||||||
.entities = entities.unmanaged,
|
|
||||||
.ids = decorations.items(.target_id),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now go through the decorations and add the offsets to the entities list.
|
|
||||||
var decoration_i: u32 = 0;
|
|
||||||
const target_ids = decorations.items(.target_id);
|
|
||||||
for (entities.keys(), entities.values()) |id, *entity| {
|
|
||||||
entity.first_decoration = decoration_i;
|
|
||||||
|
|
||||||
// Scan ahead to the next decoration
|
|
||||||
while (decoration_i < target_ids.len and target_ids[decoration_i] == id) {
|
|
||||||
decoration_i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.entities = entities.unmanaged,
|
|
||||||
.operand_is_id = operand_is_id,
|
|
||||||
// There may be unrelated decorations at the end, so make sure to
|
|
||||||
// slice those off.
|
|
||||||
.decorations = decorations.items(.entity)[0..decoration_i],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn entityDecorationsByIndex(self: ModuleInfo, index: usize) []const Entity {
|
|
||||||
const values = self.entities.values();
|
|
||||||
const first_decoration = values[index].first_decoration;
|
|
||||||
if (index == values.len - 1) {
|
|
||||||
return self.decorations[first_decoration..];
|
|
||||||
} else {
|
|
||||||
const next_first_decoration = values[index + 1].first_decoration;
|
|
||||||
return self.decorations[first_decoration..next_first_decoration];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const EntityContext = struct {
|
|
||||||
a: Allocator,
|
|
||||||
ptr_map_a: std.AutoArrayHashMapUnmanaged(ResultId, void) = .empty,
|
|
||||||
ptr_map_b: std.AutoArrayHashMapUnmanaged(ResultId, void) = .empty,
|
|
||||||
info: *const ModuleInfo,
|
|
||||||
binary: *const BinaryModule,
|
|
||||||
|
|
||||||
fn deinit(self: *EntityContext) void {
|
|
||||||
self.ptr_map_a.deinit(self.a);
|
|
||||||
self.ptr_map_b.deinit(self.a);
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn equalizeMapCapacity(self: *EntityContext) !void {
|
|
||||||
const cap = @max(self.ptr_map_a.capacity(), self.ptr_map_b.capacity());
|
|
||||||
try self.ptr_map_a.ensureTotalCapacity(self.a, cap);
|
|
||||||
try self.ptr_map_b.ensureTotalCapacity(self.a, cap);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash(self: *EntityContext, id: ResultId) !u64 {
|
|
||||||
var hasher = std.hash.Wyhash.init(0);
|
|
||||||
self.ptr_map_a.clearRetainingCapacity();
|
|
||||||
try self.hashInner(&hasher, id);
|
|
||||||
return hasher.final();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hashInner(self: *EntityContext, hasher: *std.hash.Wyhash, id: ResultId) error{OutOfMemory}!void {
|
|
||||||
const index = self.info.entities.getIndex(id) orelse {
|
|
||||||
// Index unknown, the type or constant may depend on another result-id
|
|
||||||
// that couldn't be deduplicated and so it wasn't added to info.entities.
|
|
||||||
// In this case, just has the ID itself.
|
|
||||||
std.hash.autoHash(hasher, id);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
const entity = self.info.entities.values()[index];
|
|
||||||
|
|
||||||
// If the current pointer is recursive, don't immediately add it to the map. This is to ensure that
|
|
||||||
// if the current pointer is already recursive, it gets the same hash a pointer that points to the
|
|
||||||
// same child but has a different result-id.
|
|
||||||
if (entity.kind == .OpTypePointer) {
|
|
||||||
// This may be either a pointer that is forward-referenced in the future,
|
|
||||||
// or a forward reference to a pointer.
|
|
||||||
// Note: We use the **struct** here instead of the pointer itself, to avoid an edge case like this:
|
|
||||||
//
|
|
||||||
// A - C*'
|
|
||||||
// \
|
|
||||||
// C - C*'
|
|
||||||
// /
|
|
||||||
// B - C*"
|
|
||||||
//
|
|
||||||
// In this case, hashing A goes like
|
|
||||||
// A -> C*' -> C -> C*' recursion
|
|
||||||
// And hashing B goes like
|
|
||||||
// B -> C*" -> C -> C*' -> C -> C*' recursion
|
|
||||||
// The are several calls to ptrType in codegen that may C*' and C*" to be generated as separate
|
|
||||||
// types. This is not a problem for C itself though - this can only be generated through resolveType()
|
|
||||||
// and so ensures equality by Zig's type system. Technically the above problem is still present, but it
|
|
||||||
// would only be present in a structure such as
|
|
||||||
//
|
|
||||||
// A - C*' - C'
|
|
||||||
// \
|
|
||||||
// C*" - C - C*
|
|
||||||
// /
|
|
||||||
// B
|
|
||||||
//
|
|
||||||
// where there is a duplicate definition of struct C. Resolving this requires a much more time consuming
|
|
||||||
// algorithm though, and because we don't expect any correctness issues with it, we leave that for now.
|
|
||||||
|
|
||||||
// TODO: Do we need to mind the storage class here? Its going to be recursive regardless, right?
|
|
||||||
const struct_id: ResultId = @enumFromInt(entity.operands(self.binary)[2]);
|
|
||||||
const entry = try self.ptr_map_a.getOrPut(self.a, struct_id);
|
|
||||||
if (entry.found_existing) {
|
|
||||||
// Pointer already seen. Hash the index instead of recursing into its children.
|
|
||||||
std.hash.autoHash(hasher, entry.index);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.hashEntity(hasher, entity);
|
|
||||||
|
|
||||||
// Process decorations.
|
|
||||||
const decorations = self.info.entityDecorationsByIndex(index);
|
|
||||||
for (decorations) |decoration| {
|
|
||||||
try self.hashEntity(hasher, decoration);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity.kind == .OpTypePointer) {
|
|
||||||
const struct_id: ResultId = @enumFromInt(entity.operands(self.binary)[2]);
|
|
||||||
assert(self.ptr_map_a.swapRemove(struct_id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hashEntity(self: *EntityContext, hasher: *std.hash.Wyhash, entity: ModuleInfo.Entity) !void {
|
|
||||||
std.hash.autoHash(hasher, entity.kind);
|
|
||||||
// Process operands
|
|
||||||
const operands = entity.operands(self.binary);
|
|
||||||
for (operands, 0..) |operand, i| {
|
|
||||||
if (i == entity.result_id_index) {
|
|
||||||
// Not relevant, skip...
|
|
||||||
continue;
|
|
||||||
} else if (self.info.operand_is_id.isSet(entity.first_operand + i)) {
|
|
||||||
// Operand is ID
|
|
||||||
try self.hashInner(hasher, @enumFromInt(operand));
|
|
||||||
} else {
|
|
||||||
// Operand is merely data
|
|
||||||
std.hash.autoHash(hasher, operand);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eql(self: *EntityContext, a: ResultId, b: ResultId) !bool {
|
|
||||||
self.ptr_map_a.clearRetainingCapacity();
|
|
||||||
self.ptr_map_b.clearRetainingCapacity();
|
|
||||||
|
|
||||||
return try self.eqlInner(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eqlInner(self: *EntityContext, id_a: ResultId, id_b: ResultId) error{OutOfMemory}!bool {
|
|
||||||
const maybe_index_a = self.info.entities.getIndex(id_a);
|
|
||||||
const maybe_index_b = self.info.entities.getIndex(id_b);
|
|
||||||
|
|
||||||
if (maybe_index_a == null and maybe_index_b == null) {
|
|
||||||
// Both indices unknown. In this case the type or constant
|
|
||||||
// may depend on another result-id that couldn't be deduplicated
|
|
||||||
// (so it wasn't added to info.entities). In this case, that particular
|
|
||||||
// result-id should be the same one.
|
|
||||||
return id_a == id_b;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index_a = maybe_index_a orelse return false;
|
|
||||||
const index_b = maybe_index_b orelse return false;
|
|
||||||
|
|
||||||
const entity_a = self.info.entities.values()[index_a];
|
|
||||||
const entity_b = self.info.entities.values()[index_b];
|
|
||||||
|
|
||||||
if (entity_a.kind != entity_b.kind) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity_a.kind == .OpTypePointer) {
|
|
||||||
// May be a forward reference, or should be saved as a potential
|
|
||||||
// forward reference in the future. Whatever the case, it should
|
|
||||||
// be the same for both a and b.
|
|
||||||
const struct_id_a: ResultId = @enumFromInt(entity_a.operands(self.binary)[2]);
|
|
||||||
const struct_id_b: ResultId = @enumFromInt(entity_b.operands(self.binary)[2]);
|
|
||||||
|
|
||||||
const entry_a = try self.ptr_map_a.getOrPut(self.a, struct_id_a);
|
|
||||||
const entry_b = try self.ptr_map_b.getOrPut(self.a, struct_id_b);
|
|
||||||
|
|
||||||
if (entry_a.found_existing != entry_b.found_existing) return false;
|
|
||||||
if (entry_a.index != entry_b.index) return false;
|
|
||||||
|
|
||||||
if (entry_a.found_existing) {
|
|
||||||
// No need to recurse.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!try self.eqlEntities(entity_a, entity_b)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare decorations.
|
|
||||||
const decorations_a = self.info.entityDecorationsByIndex(index_a);
|
|
||||||
const decorations_b = self.info.entityDecorationsByIndex(index_b);
|
|
||||||
if (decorations_a.len != decorations_b.len) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (decorations_a, decorations_b) |decoration_a, decoration_b| {
|
|
||||||
if (!try self.eqlEntities(decoration_a, decoration_b)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity_a.kind == .OpTypePointer) {
|
|
||||||
const struct_id_a: ResultId = @enumFromInt(entity_a.operands(self.binary)[2]);
|
|
||||||
const struct_id_b: ResultId = @enumFromInt(entity_b.operands(self.binary)[2]);
|
|
||||||
|
|
||||||
assert(self.ptr_map_a.swapRemove(struct_id_a));
|
|
||||||
assert(self.ptr_map_b.swapRemove(struct_id_b));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eqlEntities(self: *EntityContext, entity_a: ModuleInfo.Entity, entity_b: ModuleInfo.Entity) !bool {
|
|
||||||
if (entity_a.kind != entity_b.kind) {
|
|
||||||
return false;
|
|
||||||
} else if (entity_a.result_id_index != entity_a.result_id_index) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const operands_a = entity_a.operands(self.binary);
|
|
||||||
const operands_b = entity_b.operands(self.binary);
|
|
||||||
|
|
||||||
// Note: returns false for operands that have explicit defaults in optional operands... oh well
|
|
||||||
if (operands_a.len != operands_b.len) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (operands_a, operands_b, 0..) |operand_a, operand_b, i| {
|
|
||||||
const a_is_id = self.info.operand_is_id.isSet(entity_a.first_operand + i);
|
|
||||||
const b_is_id = self.info.operand_is_id.isSet(entity_b.first_operand + i);
|
|
||||||
if (a_is_id != b_is_id) {
|
|
||||||
return false;
|
|
||||||
} else if (i == entity_a.result_id_index) {
|
|
||||||
// result-id for both...
|
|
||||||
continue;
|
|
||||||
} else if (a_is_id) {
|
|
||||||
// Both are IDs, so recurse.
|
|
||||||
if (!try self.eqlInner(@enumFromInt(operand_a), @enumFromInt(operand_b))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (operand_a != operand_b) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// This struct is a wrapper around EntityContext that adapts it for
|
|
||||||
/// use in a hash map. Because EntityContext allocates, it cannot be
|
|
||||||
/// used. This wrapper simply assumes that the maps have been allocated
|
|
||||||
/// the max amount of memory they are going to use.
|
|
||||||
/// This is done by pre-hashing all keys.
|
|
||||||
const EntityHashContext = struct {
|
|
||||||
entity_context: *EntityContext,
|
|
||||||
|
|
||||||
pub fn hash(self: EntityHashContext, key: ResultId) u64 {
|
|
||||||
return self.entity_context.hash(key) catch unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eql(self: EntityHashContext, a: ResultId, b: ResultId) bool {
|
|
||||||
return self.entity_context.eql(a, b) catch unreachable;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Progress.Node) !void {
|
|
||||||
const sub_node = progress.start("deduplicate", 0);
|
|
||||||
defer sub_node.end();
|
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(parser.a);
|
|
||||||
defer arena.deinit();
|
|
||||||
const a = arena.allocator();
|
|
||||||
|
|
||||||
const info = try ModuleInfo.parse(a, parser, binary.*);
|
|
||||||
|
|
||||||
// Hash all keys once so that the maps can be allocated the right size.
|
|
||||||
var ctx = EntityContext{
|
|
||||||
.a = a,
|
|
||||||
.info = &info,
|
|
||||||
.binary = binary,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (info.entities.keys()) |id| {
|
|
||||||
_ = try ctx.hash(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hash only uses ptr_map_a, so allocate ptr_map_b too
|
|
||||||
try ctx.equalizeMapCapacity();
|
|
||||||
|
|
||||||
// Figure out which entities can be deduplicated.
|
|
||||||
var map = std.HashMap(ResultId, void, EntityHashContext, 80).initContext(a, .{
|
|
||||||
.entity_context = &ctx,
|
|
||||||
});
|
|
||||||
var replace = std.AutoArrayHashMap(ResultId, ResultId).init(a);
|
|
||||||
for (info.entities.keys()) |id| {
|
|
||||||
const entry = try map.getOrPut(id);
|
|
||||||
if (entry.found_existing) {
|
|
||||||
try replace.putNoClobber(id, entry.key_ptr.*);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub_node.setEstimatedTotalItems(binary.instructions.len);
|
|
||||||
|
|
||||||
// Now process the module, and replace instructions where needed.
|
|
||||||
var section = Section{};
|
|
||||||
var it = binary.iterateInstructions();
|
|
||||||
var new_functions_section: ?usize = null;
|
|
||||||
var new_operands = std.ArrayList(u32).init(a);
|
|
||||||
var emitted_ptrs = std.AutoHashMap(ResultId, void).init(a);
|
|
||||||
while (it.next()) |inst| {
|
|
||||||
defer sub_node.setCompletedItems(inst.offset);
|
|
||||||
|
|
||||||
// Result-id can only be the first or second operand
|
|
||||||
const inst_spec = parser.getInstSpec(inst.opcode).?;
|
|
||||||
|
|
||||||
const maybe_result_id_offset: ?u16 = for (0..2) |i| {
|
|
||||||
if (inst_spec.operands.len > i and inst_spec.operands[i].kind == .id_result) {
|
|
||||||
break @intCast(i);
|
|
||||||
}
|
|
||||||
} else null;
|
|
||||||
|
|
||||||
if (maybe_result_id_offset) |offset| {
|
|
||||||
const result_id: ResultId = @enumFromInt(inst.operands[offset]);
|
|
||||||
if (replace.contains(result_id)) continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (inst.opcode) {
|
|
||||||
.OpFunction => if (new_functions_section == null) {
|
|
||||||
new_functions_section = section.instructions.items.len;
|
|
||||||
},
|
|
||||||
.OpTypeForwardPointer => continue, // We re-emit these where needed
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (inst.opcode.class()) {
|
|
||||||
.annotation, .debug => {
|
|
||||||
// For decoration-style instructions, only emit them
|
|
||||||
// if the target is not removed.
|
|
||||||
const target: ResultId = @enumFromInt(inst.operands[0]);
|
|
||||||
if (replace.contains(target)) continue;
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-emit the instruction, but replace all the IDs.
|
|
||||||
|
|
||||||
new_operands.items.len = 0;
|
|
||||||
try new_operands.appendSlice(inst.operands);
|
|
||||||
|
|
||||||
for (new_operands.items, 0..) |*operand, i| {
|
|
||||||
const is_id = info.operand_is_id.isSet(inst.offset + 1 + i);
|
|
||||||
if (!is_id) continue;
|
|
||||||
|
|
||||||
if (replace.get(@enumFromInt(operand.*))) |new_id| {
|
|
||||||
operand.* = @intFromEnum(new_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maybe_result_id_offset == null or maybe_result_id_offset.? != i) {
|
|
||||||
// Only emit forward pointers before type, constant, and global instructions.
|
|
||||||
// Debug and Annotation instructions don't need the forward pointer, and it
|
|
||||||
// messes up the logical layout of the module.
|
|
||||||
switch (inst.opcode.class()) {
|
|
||||||
.type_declaration, .constant_creation, .memory => {},
|
|
||||||
else => continue,
|
|
||||||
}
|
|
||||||
|
|
||||||
const id: ResultId = @enumFromInt(operand.*);
|
|
||||||
const index = info.entities.getIndex(id) orelse continue;
|
|
||||||
const entity = info.entities.values()[index];
|
|
||||||
if (entity.kind == .OpTypePointer and !emitted_ptrs.contains(id)) {
|
|
||||||
// Grab the pointer's storage class from its operands in the original
|
|
||||||
// module.
|
|
||||||
const storage_class: spec.StorageClass = @enumFromInt(entity.operands(binary)[1]);
|
|
||||||
try section.emit(a, .OpTypeForwardPointer, .{
|
|
||||||
.pointer_type = id,
|
|
||||||
.storage_class = storage_class,
|
|
||||||
});
|
|
||||||
try emitted_ptrs.put(id, {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inst.opcode == .OpTypePointer) {
|
|
||||||
const result_id: ResultId = @enumFromInt(new_operands.items[maybe_result_id_offset.?]);
|
|
||||||
try emitted_ptrs.put(result_id, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
try section.emitRawInstruction(a, inst.opcode, new_operands.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (replace.keys()) |key| {
|
|
||||||
_ = binary.ext_inst_map.remove(key);
|
|
||||||
_ = binary.arith_type_width.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
binary.instructions = try parser.a.dupe(Word, section.toWords());
|
|
||||||
binary.sections.functions = new_functions_section orelse binary.instructions.len;
|
|
||||||
}
|
|
||||||
|
|
@ -140,6 +140,7 @@ test "packed union initialized with a runtime value" {
|
||||||
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
||||||
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
||||||
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
|
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
|
||||||
|
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
|
||||||
|
|
||||||
const Fields = packed struct {
|
const Fields = packed struct {
|
||||||
timestamp: u50,
|
timestamp: u50,
|
||||||
|
|
|
||||||
|
|
@ -1036,6 +1036,8 @@ test "sentinel-terminated 0-length slices" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "peer slices keep abi alignment with empty struct" {
|
test "peer slices keep abi alignment with empty struct" {
|
||||||
|
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
|
||||||
|
|
||||||
var cond: bool = undefined;
|
var cond: bool = undefined;
|
||||||
cond = false;
|
cond = false;
|
||||||
const slice = if (cond) &[1]u32{42} else &.{};
|
const slice = if (cond) &[1]u32{42} else &.{};
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
\\
|
\\
|
||||||
|
|
@ -221,6 +224,16 @@ fn render(writer: *std.io.Writer, registry: CoreRegistry, extensions: []const Ex
|
||||||
\\ }
|
\\ }
|
||||||
\\};
|
\\};
|
||||||
\\
|
\\
|
||||||
|
\\pub const IdRange = struct {
|
||||||
|
\\ base: u32,
|
||||||
|
\\ len: u32,
|
||||||
|
\\
|
||||||
|
\\ pub fn at(range: IdRange, i: usize) Id {
|
||||||
|
\\ std.debug.assert(i < range.len);
|
||||||
|
\\ return @enumFromInt(range.base + i);
|
||||||
|
\\ }
|
||||||
|
\\};
|
||||||
|
\\
|
||||||
\\pub const LiteralInteger = Word;
|
\\pub const LiteralInteger = Word;
|
||||||
\\pub const LiteralFloat = Word;
|
\\pub const LiteralFloat = Word;
|
||||||
\\pub const LiteralString = []const u8;
|
\\pub const LiteralString = []const u8;
|
||||||
|
|
@ -307,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,
|
||||||
|
|
@ -324,7 +342,7 @@ fn renderInstructionSet(
|
||||||
);
|
);
|
||||||
|
|
||||||
for (extensions) |ext| {
|
for (extensions) |ext| {
|
||||||
try writer.print("{f},\n", .{formatId(ext.name)});
|
try writer.print("{f},\n", .{std.zig.fmtId(ext.name)});
|
||||||
}
|
}
|
||||||
|
|
||||||
try writer.writeAll(
|
try writer.writeAll(
|
||||||
|
|
@ -348,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,
|
||||||
|
|
@ -357,7 +375,7 @@ fn renderInstructionsCase(
|
||||||
// but there aren't so many total aliases and that would add more overhead in total. We will
|
// but there aren't so many total aliases and that would add more overhead in total. We will
|
||||||
// just filter those out when needed.
|
// just filter those out when needed.
|
||||||
|
|
||||||
try writer.print(".{f} => &.{{\n", .{formatId(set_name)});
|
try writer.print(".{f} => &.{{\n", .{std.zig.fmtId(set_name)});
|
||||||
|
|
||||||
for (instructions) |inst| {
|
for (instructions) |inst| {
|
||||||
try writer.print(
|
try writer.print(
|
||||||
|
|
@ -395,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| {
|
||||||
|
|
@ -444,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,
|
||||||
|
|
@ -500,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),
|
||||||
|
|
@ -517,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 {
|
||||||
|
|
@ -528,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) {
|
||||||
|
|
@ -552,58 +574,67 @@ 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 {
|
\\
|
||||||
\\ return switch (self) {
|
\\pub fn Operands(comptime self: {f}) type {{
|
||||||
\\
|
\\ 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 renderOperand(writer, .instruction, inst.opname, inst.operands, extended_structs, false);
|
try renderOperand(writer, .instruction, inst.opname, inst.operands, extended_structs, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll(
|
||||||
|
\\ };
|
||||||
|
\\}
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
|
||||||
|
try writer.print(
|
||||||
|
\\pub fn class(self: {f}) Class {{
|
||||||
|
\\ return switch (self) {{
|
||||||
|
\\
|
||||||
|
, .{std.zig.fmtId(opcode_type_name)});
|
||||||
|
|
||||||
|
for (instructions_indices) |i| {
|
||||||
|
const inst = instructions[i];
|
||||||
|
try writer.print(".{f} => .{f},\n", .{ std.zig.fmtId(inst.opname), formatId(inst.class.?) });
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll(
|
||||||
|
\\ };
|
||||||
|
\\}
|
||||||
|
\\
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try writer.writeAll(
|
try writer.writeAll(
|
||||||
\\ };
|
|
||||||
\\}
|
|
||||||
\\pub fn class(self: Opcode) Class {
|
|
||||||
\\ return switch (self) {
|
|
||||||
\\
|
|
||||||
);
|
|
||||||
|
|
||||||
for (instructions_indices) |i| {
|
|
||||||
const inst = instructions[i];
|
|
||||||
try writer.print(".{f} => .{f},\n", .{ std.zig.fmtId(inst.opname), formatId(inst.class.?) });
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
|
|
@ -617,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 {
|
||||||
|
|
@ -695,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 {
|
||||||
|
|
@ -778,7 +809,7 @@ fn renderBitEnum(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderOperand(
|
fn renderOperand(
|
||||||
writer: anytype,
|
writer: *std.io.Writer,
|
||||||
kind: enum {
|
kind: enum {
|
||||||
@"union",
|
@"union",
|
||||||
instruction,
|
instruction,
|
||||||
|
|
@ -862,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: {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue