mirror of
https://github.com/ziglang/zig.git
synced 2026-02-13 04:48:20 +00:00
Merge pull request #7827 from Snektron/spirv-setup
Stage 2: SPIR-V setup
This commit is contained in:
commit
102d954220
@ -57,6 +57,9 @@ pub const Target = struct {
|
||||
wasi,
|
||||
emscripten,
|
||||
uefi,
|
||||
opencl,
|
||||
glsl450,
|
||||
vulkan,
|
||||
other,
|
||||
|
||||
pub fn isDarwin(tag: Tag) bool {
|
||||
@ -248,6 +251,9 @@ pub const Target = struct {
|
||||
.wasi,
|
||||
.emscripten,
|
||||
.uefi,
|
||||
.opencl, // TODO: OpenCL versions
|
||||
.glsl450, // TODO: GLSL versions
|
||||
.vulkan,
|
||||
.other,
|
||||
=> return .{ .none = {} },
|
||||
|
||||
@ -403,6 +409,9 @@ pub const Target = struct {
|
||||
.wasi,
|
||||
.emscripten,
|
||||
.uefi,
|
||||
.opencl,
|
||||
.glsl450,
|
||||
.vulkan,
|
||||
.other,
|
||||
=> false,
|
||||
};
|
||||
@ -421,6 +430,7 @@ pub const Target = struct {
|
||||
pub const powerpc = @import("target/powerpc.zig");
|
||||
pub const riscv = @import("target/riscv.zig");
|
||||
pub const sparc = @import("target/sparc.zig");
|
||||
pub const spirv = @import("target/spirv.zig");
|
||||
pub const systemz = @import("target/systemz.zig");
|
||||
pub const wasm = @import("target/wasm.zig");
|
||||
pub const x86 = @import("target/x86.zig");
|
||||
@ -493,6 +503,10 @@ pub const Target = struct {
|
||||
.wasi,
|
||||
.emscripten,
|
||||
=> return .musl,
|
||||
.opencl, // TODO: SPIR-V ABIs with Linkage capability
|
||||
.glsl450,
|
||||
.vulkan,
|
||||
=> return .none,
|
||||
}
|
||||
}
|
||||
|
||||
@ -528,6 +542,7 @@ pub const Target = struct {
|
||||
macho,
|
||||
wasm,
|
||||
c,
|
||||
spirv,
|
||||
hex,
|
||||
raw,
|
||||
};
|
||||
@ -744,6 +759,8 @@ pub const Target = struct {
|
||||
// Stage1 currently assumes that architectures above this comment
|
||||
// map one-to-one with the ZigLLVM_ArchType enum.
|
||||
spu_2,
|
||||
spirv32,
|
||||
spirv64,
|
||||
|
||||
pub fn isARM(arch: Arch) bool {
|
||||
return switch (arch) {
|
||||
@ -857,6 +874,8 @@ pub const Target = struct {
|
||||
.s390x => ._S390,
|
||||
.ve => ._NONE,
|
||||
.spu_2 => ._SPU_2,
|
||||
.spirv32 => ._NONE,
|
||||
.spirv64 => ._NONE,
|
||||
};
|
||||
}
|
||||
|
||||
@ -914,6 +933,8 @@ pub const Target = struct {
|
||||
.s390x => .Unknown,
|
||||
.ve => .Unknown,
|
||||
.spu_2 => .Unknown,
|
||||
.spirv32 => .Unknown,
|
||||
.spirv64 => .Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
@ -957,6 +978,9 @@ pub const Target = struct {
|
||||
.shave,
|
||||
.ve,
|
||||
.spu_2,
|
||||
// GPU bitness is opaque. For now, assume little endian.
|
||||
.spirv32,
|
||||
.spirv64,
|
||||
=> .Little,
|
||||
|
||||
.arc,
|
||||
@ -1012,6 +1036,7 @@ pub const Target = struct {
|
||||
.wasm32,
|
||||
.renderscript32,
|
||||
.aarch64_32,
|
||||
.spirv32,
|
||||
=> return 32,
|
||||
|
||||
.aarch64,
|
||||
@ -1035,6 +1060,7 @@ pub const Target = struct {
|
||||
.sparcv9,
|
||||
.s390x,
|
||||
.ve,
|
||||
.spirv64,
|
||||
=> return 64,
|
||||
}
|
||||
}
|
||||
@ -1057,6 +1083,7 @@ pub const Target = struct {
|
||||
.i386, .x86_64 => "x86",
|
||||
.nvptx, .nvptx64 => "nvptx",
|
||||
.wasm32, .wasm64 => "wasm",
|
||||
.spirv32, .spirv64 => "spir-v",
|
||||
else => @tagName(arch),
|
||||
};
|
||||
}
|
||||
@ -1347,6 +1374,9 @@ pub const Target = struct {
|
||||
.uefi,
|
||||
.windows,
|
||||
.emscripten,
|
||||
.opencl,
|
||||
.glsl450,
|
||||
.vulkan,
|
||||
.other,
|
||||
=> return false,
|
||||
else => return true,
|
||||
@ -1482,6 +1512,8 @@ pub const Target = struct {
|
||||
.nvptx64,
|
||||
.spu_2,
|
||||
.avr,
|
||||
.spirv32,
|
||||
.spirv64,
|
||||
=> return result,
|
||||
|
||||
// TODO go over each item in this list and either move it to the above list, or
|
||||
@ -1524,6 +1556,9 @@ pub const Target = struct {
|
||||
.windows,
|
||||
.emscripten,
|
||||
.wasi,
|
||||
.opencl,
|
||||
.glsl450,
|
||||
.vulkan,
|
||||
.other,
|
||||
=> return result,
|
||||
|
||||
|
||||
@ -141,6 +141,7 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro
|
||||
.Lib => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}),
|
||||
},
|
||||
.c => return std.fmt.allocPrint(allocator, "{s}.c", .{root_name}),
|
||||
.spirv => return std.fmt.allocPrint(allocator, "{s}.spv", .{root_name}),
|
||||
.hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}),
|
||||
.raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}),
|
||||
}
|
||||
|
||||
@ -130,6 +130,9 @@ pub const CrossTarget = struct {
|
||||
.wasi,
|
||||
.emscripten,
|
||||
.uefi,
|
||||
.opencl,
|
||||
.glsl450,
|
||||
.vulkan,
|
||||
.other,
|
||||
=> {
|
||||
self.os_version_min = .{ .none = {} };
|
||||
@ -730,6 +733,9 @@ pub const CrossTarget = struct {
|
||||
.wasi,
|
||||
.emscripten,
|
||||
.uefi,
|
||||
.opencl,
|
||||
.glsl450,
|
||||
.vulkan,
|
||||
.other,
|
||||
=> return error.InvalidOperatingSystemVersion,
|
||||
|
||||
|
||||
@ -1673,7 +1673,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void
|
||||
// in `Decl` to notice that the line number did not change.
|
||||
self.comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl });
|
||||
},
|
||||
.c, .wasm => {},
|
||||
.c, .wasm, .spirv => {},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -1906,6 +1906,7 @@ fn allocateNewDecl(
|
||||
.macho => .{ .macho = link.File.MachO.TextBlock.empty },
|
||||
.c => .{ .c = link.File.C.DeclBlock.empty },
|
||||
.wasm => .{ .wasm = {} },
|
||||
.spirv => .{ .spirv = {} },
|
||||
},
|
||||
.fn_link = switch (mod.comp.bin_file.tag) {
|
||||
.coff => .{ .coff = {} },
|
||||
@ -1913,6 +1914,7 @@ fn allocateNewDecl(
|
||||
.macho => .{ .macho = link.File.MachO.SrcFn.empty },
|
||||
.c => .{ .c = link.File.C.FnBlock.empty },
|
||||
.wasm => .{ .wasm = null },
|
||||
.spirv => .{ .spirv = .{} },
|
||||
},
|
||||
.generation = 0,
|
||||
.is_pub = false,
|
||||
@ -2010,6 +2012,7 @@ pub fn analyzeExport(
|
||||
.macho => .{ .macho = link.File.MachO.Export{} },
|
||||
.c => .{ .c = {} },
|
||||
.wasm => .{ .wasm = {} },
|
||||
.spirv => .{ .spirv = {} },
|
||||
},
|
||||
.owner_decl = owner_decl,
|
||||
.exported_decl = exported_decl,
|
||||
|
||||
@ -69,6 +69,8 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 {
|
||||
.renderscript64 => "renderscript64",
|
||||
.ve => "ve",
|
||||
.spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII,
|
||||
.spirv32 => return error.LLVMBackendDoesNotSupportSPIRV,
|
||||
.spirv64 => return error.LLVMBackendDoesNotSupportSPIRV,
|
||||
};
|
||||
// TODO Add a sub-arch for some architectures depending on CPU features.
|
||||
|
||||
@ -109,6 +111,9 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 {
|
||||
.wasi => "wasi",
|
||||
.emscripten => "emscripten",
|
||||
.uefi => "windows",
|
||||
.opencl => return error.LLVMBackendDoesNotSupportOpenCL,
|
||||
.glsl450 => return error.LLVMBackendDoesNotSupportGLSL450,
|
||||
.vulkan => return error.LLVMBackendDoesNotSupportVulkan,
|
||||
.other => "unknown",
|
||||
};
|
||||
|
||||
|
||||
51
src/codegen/spirv.zig
Normal file
51
src/codegen/spirv.zig
Normal file
@ -0,0 +1,51 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const spec = @import("spirv/spec.zig");
|
||||
const Module = @import("../Module.zig");
|
||||
const Decl = Module.Decl;
|
||||
|
||||
pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []const u32) !void {
|
||||
const word_count = @intCast(u32, args.len + 1);
|
||||
try code.append((word_count << 16) | @enumToInt(instr));
|
||||
try code.appendSlice(args);
|
||||
}
|
||||
|
||||
pub const SPIRVModule = struct {
|
||||
next_id: u32 = 0,
|
||||
free_id_list: std.ArrayList(u32),
|
||||
|
||||
pub fn init(allocator: *Allocator) SPIRVModule {
|
||||
return .{
|
||||
.free_id_list = std.ArrayList(u32).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *SPIRVModule) void {
|
||||
self.free_id_list.deinit();
|
||||
}
|
||||
|
||||
pub fn allocId(self: *SPIRVModule) u32 {
|
||||
if (self.free_id_list.popOrNull()) |id| return id;
|
||||
|
||||
defer self.next_id += 1;
|
||||
return self.next_id;
|
||||
}
|
||||
|
||||
pub fn freeId(self: *SPIRVModule, id: u32) void {
|
||||
if (id + 1 == self.next_id) {
|
||||
self.next_id -= 1;
|
||||
} else {
|
||||
// If no more memory to append the id to the free list, just ignore it.
|
||||
self.free_id_list.append(id) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idBound(self: *SPIRVModule) u32 {
|
||||
return self.next_id;
|
||||
}
|
||||
|
||||
pub fn genDecl(self: SPIRVModule, id: u32, code: *std.ArrayList(u32), decl: *Decl) !void {
|
||||
|
||||
}
|
||||
};
|
||||
1645
src/codegen/spirv/spec.zig
Normal file
1645
src/codegen/spirv/spec.zig
Normal file
File diff suppressed because it is too large
Load Diff
29
src/link.zig
29
src/link.zig
@ -139,6 +139,7 @@ pub const File = struct {
|
||||
macho: MachO.TextBlock,
|
||||
c: C.DeclBlock,
|
||||
wasm: void,
|
||||
spirv: void,
|
||||
};
|
||||
|
||||
pub const LinkFn = union {
|
||||
@ -147,6 +148,7 @@ pub const File = struct {
|
||||
macho: MachO.SrcFn,
|
||||
c: C.FnBlock,
|
||||
wasm: ?Wasm.FnData,
|
||||
spirv: SpirV.FnData,
|
||||
};
|
||||
|
||||
pub const Export = union {
|
||||
@ -155,6 +157,7 @@ pub const File = struct {
|
||||
macho: MachO.Export,
|
||||
c: void,
|
||||
wasm: void,
|
||||
spirv: void,
|
||||
};
|
||||
|
||||
/// For DWARF .debug_info.
|
||||
@ -183,6 +186,7 @@ pub const File = struct {
|
||||
.macho => &(try MachO.createEmpty(allocator, options)).base,
|
||||
.wasm => &(try Wasm.createEmpty(allocator, options)).base,
|
||||
.c => unreachable, // Reported error earlier.
|
||||
.spirv => &(try SpirV.createEmpty(allocator, options)).base,
|
||||
.hex => return error.HexObjectFormatUnimplemented,
|
||||
.raw => return error.RawObjectFormatUnimplemented,
|
||||
};
|
||||
@ -198,6 +202,7 @@ pub const File = struct {
|
||||
.macho => &(try MachO.createEmpty(allocator, options)).base,
|
||||
.wasm => &(try Wasm.createEmpty(allocator, options)).base,
|
||||
.c => unreachable, // Reported error earlier.
|
||||
.spirv => &(try SpirV.createEmpty(allocator, options)).base,
|
||||
.hex => return error.HexObjectFormatUnimplemented,
|
||||
.raw => return error.RawObjectFormatUnimplemented,
|
||||
};
|
||||
@ -213,6 +218,7 @@ pub const File = struct {
|
||||
.macho => &(try MachO.openPath(allocator, sub_path, options)).base,
|
||||
.wasm => &(try Wasm.openPath(allocator, sub_path, options)).base,
|
||||
.c => &(try C.openPath(allocator, sub_path, options)).base,
|
||||
.spirv => &(try SpirV.openPath(allocator, sub_path, options)).base,
|
||||
.hex => return error.HexObjectFormatUnimplemented,
|
||||
.raw => return error.RawObjectFormatUnimplemented,
|
||||
};
|
||||
@ -242,7 +248,7 @@ pub const File = struct {
|
||||
.mode = determineMode(base.options),
|
||||
});
|
||||
},
|
||||
.c, .wasm => {},
|
||||
.c, .wasm, .spirv => {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,7 +293,7 @@ pub const File = struct {
|
||||
f.close();
|
||||
base.file = null;
|
||||
},
|
||||
.c, .wasm => {},
|
||||
.c, .wasm, .spirv => {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,6 +306,7 @@ pub const File = struct {
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),
|
||||
.c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
|
||||
.wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl),
|
||||
.spirv => return @fieldParentPtr(SpirV, "base", base).updateDecl(module, decl),
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,7 +316,7 @@ pub const File = struct {
|
||||
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),
|
||||
.c => return @fieldParentPtr(C, "base", base).updateDeclLineNumber(module, decl),
|
||||
.wasm => {},
|
||||
.wasm, .spirv => {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,7 +328,7 @@ pub const File = struct {
|
||||
.elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
|
||||
.c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl),
|
||||
.wasm => {},
|
||||
.wasm, .spirv => {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,6 +375,11 @@ pub const File = struct {
|
||||
parent.deinit();
|
||||
base.allocator.destroy(parent);
|
||||
},
|
||||
.spirv => {
|
||||
const parent = @fieldParentPtr(SpirV, "base", base);
|
||||
parent.deinit();
|
||||
base.allocator.destroy(parent);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -401,6 +413,7 @@ pub const File = struct {
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).flush(comp),
|
||||
.c => return @fieldParentPtr(C, "base", base).flush(comp),
|
||||
.wasm => return @fieldParentPtr(Wasm, "base", base).flush(comp),
|
||||
.spirv => return @fieldParentPtr(SpirV, "base", base).flush(comp),
|
||||
}
|
||||
}
|
||||
|
||||
@ -413,6 +426,7 @@ pub const File = struct {
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).flushModule(comp),
|
||||
.c => return @fieldParentPtr(C, "base", base).flushModule(comp),
|
||||
.wasm => return @fieldParentPtr(Wasm, "base", base).flushModule(comp),
|
||||
.spirv => return @fieldParentPtr(SpirV, "base", base).flushModule(comp),
|
||||
}
|
||||
}
|
||||
|
||||
@ -424,6 +438,7 @@ pub const File = struct {
|
||||
.macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),
|
||||
.c => @fieldParentPtr(C, "base", base).freeDecl(decl),
|
||||
.wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl),
|
||||
.spirv => @fieldParentPtr(SpirV, "base", base).freeDecl(decl),
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,7 +448,7 @@ pub const File = struct {
|
||||
.elf => return @fieldParentPtr(Elf, "base", base).error_flags,
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).error_flags,
|
||||
.c => return .{ .no_entry_point_found = false },
|
||||
.wasm => return ErrorFlags{},
|
||||
.wasm, .spirv => return ErrorFlags{},
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,6 +466,7 @@ pub const File = struct {
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),
|
||||
.c => return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl, exports),
|
||||
.wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports),
|
||||
.spirv => return @fieldParentPtr(SpirV, "base", base).updateDeclExports(module, decl, exports),
|
||||
}
|
||||
}
|
||||
|
||||
@ -461,6 +477,7 @@ pub const File = struct {
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).getDeclVAddr(decl),
|
||||
.c => unreachable,
|
||||
.wasm => unreachable,
|
||||
.spirv => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
@ -601,6 +618,7 @@ pub const File = struct {
|
||||
macho,
|
||||
c,
|
||||
wasm,
|
||||
spirv,
|
||||
};
|
||||
|
||||
pub const ErrorFlags = struct {
|
||||
@ -611,6 +629,7 @@ pub const File = struct {
|
||||
pub const Coff = @import("link/Coff.zig");
|
||||
pub const Elf = @import("link/Elf.zig");
|
||||
pub const MachO = @import("link/MachO.zig");
|
||||
pub const SpirV = @import("link/SpirV.zig");
|
||||
pub const Wasm = @import("link/Wasm.zig");
|
||||
};
|
||||
|
||||
|
||||
234
src/link/SpirV.zig
Normal file
234
src/link/SpirV.zig
Normal file
@ -0,0 +1,234 @@
|
||||
const SpirV = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Module = @import("../Module.zig");
|
||||
const Compilation = @import("../Compilation.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 spec = @import("../codegen/spirv/spec.zig");
|
||||
|
||||
//! 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, (preferrably) OpUndef instructions.
|
||||
//! All function declarations without a body (extern functions presumably).
|
||||
//! All regular functions.
|
||||
|
||||
pub const FnData = struct {
|
||||
id: ?u32 = null,
|
||||
code: std.ArrayListUnmanaged(u32) = .{},
|
||||
};
|
||||
|
||||
base: link.File,
|
||||
|
||||
// TODO: Does this file need to support multiple independent modules?
|
||||
spirv_module: codegen.SPIRVModule,
|
||||
|
||||
pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
|
||||
const spirv = try gpa.create(SpirV);
|
||||
spirv.* = .{
|
||||
.base = .{
|
||||
.tag = .spirv,
|
||||
.options = options,
|
||||
.file = null,
|
||||
.allocator = gpa,
|
||||
},
|
||||
.spirv_module = codegen.SPIRVModule.init(gpa),
|
||||
};
|
||||
|
||||
// TODO: Figure out where to put all of these
|
||||
switch (options.target.cpu.arch) {
|
||||
.spirv32, .spirv64 => {},
|
||||
else => return error.TODOArchNotSupported,
|
||||
}
|
||||
|
||||
switch (options.target.os.tag) {
|
||||
.opencl, .glsl450, .vulkan => {},
|
||||
else => return error.TODOOsNotSupported,
|
||||
}
|
||||
|
||||
if (options.target.abi != .none) {
|
||||
return error.TODOAbiNotSupported;
|
||||
}
|
||||
|
||||
return spirv;
|
||||
}
|
||||
|
||||
pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*SpirV {
|
||||
assert(options.object_format == .spirv);
|
||||
|
||||
if (options.use_llvm) return error.LLVM_BackendIsTODO_ForSpirV; // TODO: LLVM Doesn't support SpirV at all.
|
||||
if (options.use_lld) return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all.
|
||||
|
||||
// TODO: read the file and keep vaild parts instead of truncating
|
||||
const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
|
||||
errdefer file.close();
|
||||
|
||||
const spirv = try createEmpty(allocator, options);
|
||||
errdefer spirv.base.destroy();
|
||||
|
||||
spirv.base.file = file;
|
||||
return spirv;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *SpirV) void {
|
||||
self.spirv_module.deinit();
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *SpirV, module: *Module, decl: *Module.Decl) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const fn_data = &decl.fn_link.spirv;
|
||||
if (fn_data.id == null) {
|
||||
fn_data.id = self.spirv_module.allocId();
|
||||
}
|
||||
|
||||
var managed_code = fn_data.code.toManaged(self.base.allocator);
|
||||
managed_code.items.len = 0;
|
||||
|
||||
try self.spirv_module.genDecl(fn_data.id.?, &managed_code, decl);
|
||||
fn_data.code = managed_code.toUnmanaged();
|
||||
|
||||
// Free excess allocated memory for this Decl.
|
||||
fn_data.code.shrinkAndFree(self.base.allocator, fn_data.code.items.len);
|
||||
}
|
||||
|
||||
pub fn updateDeclExports(
|
||||
self: *SpirV,
|
||||
module: *Module,
|
||||
decl: *const Module.Decl,
|
||||
exports: []const *Module.Export,
|
||||
) !void {}
|
||||
|
||||
pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void {
|
||||
var fn_data = decl.fn_link.spirv;
|
||||
fn_data.code.deinit(self.base.allocator);
|
||||
if (fn_data.id) |id| self.spirv_module.freeId(id);
|
||||
decl.fn_link.spirv = undefined;
|
||||
}
|
||||
|
||||
pub fn flush(self: *SpirV, comp: *Compilation) !void {
|
||||
if (build_options.have_llvm and self.base.options.use_lld) {
|
||||
return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all.
|
||||
} else {
|
||||
return self.flushModule(comp);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const module = self.base.options.module.?;
|
||||
const target = comp.getTarget();
|
||||
|
||||
var binary = std.ArrayList(u32).init(self.base.allocator);
|
||||
defer binary.deinit();
|
||||
|
||||
// Note: The order of adding sections to the final binary
|
||||
// follows the SPIR-V logical module format!
|
||||
|
||||
try binary.appendSlice(&[_]u32{
|
||||
spec.magic_number,
|
||||
(spec.version.major << 16) | (spec.version.minor << 8),
|
||||
0, // TODO: Register Zig compiler magic number.
|
||||
self.spirv_module.idBound(),
|
||||
0, // Schema (currently reserved for future use in the SPIR-V spec).
|
||||
});
|
||||
|
||||
try writeCapabilities(&binary, target);
|
||||
try writeMemoryModel(&binary, target);
|
||||
|
||||
// Collect list of buffers to write.
|
||||
// SPIR-V files support both little and big endian words. The actual format is
|
||||
// disambiguated by the magic number, and so theoretically we don't need to worry
|
||||
// about endian-ness when writing the final binary.
|
||||
var all_buffers = std.ArrayList(std.os.iovec_const).init(self.base.allocator);
|
||||
defer all_buffers.deinit();
|
||||
|
||||
// Pre-allocate enough for the binary info + all functions
|
||||
try all_buffers.ensureCapacity(module.decl_table.count() + 1);
|
||||
|
||||
all_buffers.appendAssumeCapacity(wordsToIovConst(binary.items));
|
||||
|
||||
for (module.decl_table.items()) |entry| {
|
||||
const decl = entry.value;
|
||||
switch (decl.typed_value) {
|
||||
.most_recent => |tvm| {
|
||||
const fn_data = &decl.fn_link.spirv;
|
||||
all_buffers.appendAssumeCapacity(wordsToIovConst(fn_data.code.items));
|
||||
},
|
||||
.never_succeeded => continue,
|
||||
}
|
||||
}
|
||||
|
||||
var file_size: u64 = 0;
|
||||
for (all_buffers.items) |iov| {
|
||||
file_size += iov.iov_len;
|
||||
}
|
||||
|
||||
const file = self.base.file.?;
|
||||
try file.seekTo(0);
|
||||
try file.setEndPos(file_size);
|
||||
try file.pwritevAll(all_buffers.items, 0);
|
||||
}
|
||||
|
||||
fn writeCapabilities(binary: *std.ArrayList(u32), target: std.Target) !void {
|
||||
// TODO: Integrate with a hypothetical feature system
|
||||
const cap: spec.Capability = switch (target.os.tag) {
|
||||
.opencl => .Kernel,
|
||||
.glsl450 => .Shader,
|
||||
.vulkan => .VulkanMemoryModel,
|
||||
else => unreachable, // TODO
|
||||
};
|
||||
|
||||
try codegen.writeInstruction(binary, .OpCapability, &[_]u32{ @enumToInt(cap) });
|
||||
}
|
||||
|
||||
fn writeMemoryModel(binary: *std.ArrayList(u32), target: std.Target) !void {
|
||||
const addressing_model = switch (target.os.tag) {
|
||||
.opencl => switch (target.cpu.arch) {
|
||||
.spirv32 => spec.AddressingModel.Physical32,
|
||||
.spirv64 => spec.AddressingModel.Physical64,
|
||||
else => unreachable, // TODO
|
||||
},
|
||||
.glsl450, .vulkan => spec.AddressingModel.Logical,
|
||||
else => unreachable, // TODO
|
||||
};
|
||||
|
||||
const memory_model: spec.MemoryModel = switch (target.os.tag) {
|
||||
.opencl => .OpenCL,
|
||||
.glsl450 => .GLSL450,
|
||||
.vulkan => .Vulkan,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
try codegen.writeInstruction(binary, .OpMemoryModel, &[_]u32{
|
||||
@enumToInt(addressing_model), @enumToInt(memory_model)
|
||||
});
|
||||
}
|
||||
|
||||
fn wordsToIovConst(words: []const u32) std.os.iovec_const {
|
||||
const bytes = std.mem.sliceAsBytes(words);
|
||||
return .{
|
||||
.iov_base = bytes.ptr,
|
||||
.iov_len = bytes.len,
|
||||
};
|
||||
}
|
||||
@ -312,6 +312,7 @@ const usage_build_generic =
|
||||
\\ pe Portable Executable (Windows)
|
||||
\\ coff Common Object File Format (Windows)
|
||||
\\ macho macOS relocatables
|
||||
\\ spirv Standard, Portable Intermediate Representation V (SPIR-V)
|
||||
\\ hex (planned) Intel IHEX
|
||||
\\ raw (planned) Dump machine code directly
|
||||
\\ -dirafter [dir] Add directory to AFTER include search path
|
||||
@ -1588,6 +1589,8 @@ fn buildOutputType(
|
||||
break :blk .hex;
|
||||
} else if (mem.eql(u8, ofmt, "raw")) {
|
||||
break :blk .raw;
|
||||
} else if (mem.eql(u8, ofmt, "spirv")) {
|
||||
break :blk .spirv;
|
||||
} else {
|
||||
fatal("unsupported object format: {s}", .{ofmt});
|
||||
}
|
||||
|
||||
@ -189,7 +189,7 @@ pub fn supportsStackProbing(target: std.Target) bool {
|
||||
|
||||
pub fn osToLLVM(os_tag: std.Target.Os.Tag) llvm.OSType {
|
||||
return switch (os_tag) {
|
||||
.freestanding, .other => .UnknownOS,
|
||||
.freestanding, .other, .opencl, .glsl450, .vulkan => .UnknownOS,
|
||||
.windows, .uefi => .Win32,
|
||||
.ananas => .Ananas,
|
||||
.cloudabi => .CloudABI,
|
||||
@ -280,7 +280,7 @@ pub fn archToLLVM(arch_tag: std.Target.Cpu.Arch) llvm.ArchType {
|
||||
.renderscript32 => .renderscript32,
|
||||
.renderscript64 => .renderscript64,
|
||||
.ve => .ve,
|
||||
.spu_2 => .UnknownArch,
|
||||
.spu_2, .spirv32, .spirv64 => .UnknownArch,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3597,6 +3597,9 @@ pub const CType = enum {
|
||||
.amdpal,
|
||||
.hermit,
|
||||
.hurd,
|
||||
.opencl,
|
||||
.glsl450,
|
||||
.vulkan,
|
||||
=> @panic("TODO specify the C integer and float type sizes for this OS"),
|
||||
}
|
||||
}
|
||||
|
||||
245
tools/gen_spirv_spec.zig
Normal file
245
tools/gen_spirv_spec.zig
Normal file
@ -0,0 +1,245 @@
|
||||
const std = @import("std");
|
||||
const Writer = std.ArrayList(u8).Writer;
|
||||
|
||||
//! See https://www.khronos.org/registry/spir-v/specs/unified1/MachineReadableGrammar.html
|
||||
//! and the files in https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/unified1/
|
||||
//! Note: Non-canonical casing in these structs used to match SPIR-V spec json.
|
||||
const Registry = union(enum) {
|
||||
core: CoreRegistry,
|
||||
extension: ExtensionRegistry,
|
||||
};
|
||||
|
||||
const CoreRegistry = struct {
|
||||
copyright: [][]const u8,
|
||||
/// Hexadecimal representation of the magic number
|
||||
magic_number: []const u8,
|
||||
major_version: u32,
|
||||
minor_version: u32,
|
||||
revision: u32,
|
||||
instruction_printing_class: []InstructionPrintingClass,
|
||||
instructions: []Instruction,
|
||||
operand_kinds: []OperandKind,
|
||||
};
|
||||
|
||||
const ExtensionRegistry = struct {
|
||||
copyright: [][]const u8,
|
||||
version: u32,
|
||||
revision: u32,
|
||||
instructions: []Instruction,
|
||||
operand_kinds: []OperandKind = &[_]OperandKind{},
|
||||
};
|
||||
|
||||
const InstructionPrintingClass = struct {
|
||||
tag: []const u8,
|
||||
heading: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
const Instruction = struct {
|
||||
opname: []const u8,
|
||||
class: ?[]const u8 = null, // Note: Only available in the core registry.
|
||||
opcode: u32,
|
||||
operands: []Operand = &[_]Operand{},
|
||||
capabilities: [][]const u8 = &[_][]const u8{},
|
||||
extensions: [][]const u8 = &[_][]const u8{},
|
||||
version: ?[]const u8 = null,
|
||||
|
||||
lastVersion: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
const Operand = struct {
|
||||
kind: []const u8,
|
||||
/// If this field is 'null', the operand is only expected once.
|
||||
quantifier: ?Quantifier = null,
|
||||
name: []const u8 = "",
|
||||
};
|
||||
|
||||
const Quantifier = enum {
|
||||
/// zero or once
|
||||
@"?",
|
||||
/// zero or more
|
||||
@"*",
|
||||
};
|
||||
|
||||
const OperandCategory = enum {
|
||||
BitEnum,
|
||||
ValueEnum,
|
||||
Id,
|
||||
Literal,
|
||||
Composite,
|
||||
};
|
||||
|
||||
const OperandKind = struct {
|
||||
category: OperandCategory,
|
||||
/// The name
|
||||
kind: []const u8,
|
||||
doc: ?[]const u8 = null,
|
||||
enumerants: ?[]Enumerant = null,
|
||||
bases: ?[]const []const u8 = null,
|
||||
};
|
||||
|
||||
const Enumerant = struct {
|
||||
enumerant: []const u8,
|
||||
value: union(enum) {
|
||||
bitflag: []const u8, // Hexadecimal representation of the value
|
||||
int: u31,
|
||||
},
|
||||
capabilities: [][]const u8 = &[_][]const u8{},
|
||||
/// Valid for .ValueEnum and .BitEnum
|
||||
extensions: [][]const u8 = &[_][]const u8{},
|
||||
/// `quantifier` will always be `null`.
|
||||
parameters: []Operand = &[_]Operand{},
|
||||
version: ?[]const u8 = null,
|
||||
lastVersion: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub fn main() !void {
|
||||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||
defer arena.deinit();
|
||||
const allocator = &arena.allocator;
|
||||
|
||||
const args = try std.process.argsAlloc(allocator);
|
||||
if (args.len != 2) {
|
||||
usageAndExit(std.io.getStdErr(), args[0], 1);
|
||||
}
|
||||
|
||||
const spec_path = args[1];
|
||||
const spec = try std.fs.cwd().readFileAlloc(allocator, spec_path, std.math.maxInt(usize));
|
||||
|
||||
var tokens = std.json.TokenStream.init(spec);
|
||||
var registry = try std.json.parse(Registry, &tokens, .{.allocator = allocator});
|
||||
|
||||
var buf = std.ArrayList(u8).init(allocator);
|
||||
defer buf.deinit();
|
||||
|
||||
try render(buf.writer(), registry);
|
||||
|
||||
const tree = try std.zig.parse(allocator, buf.items);
|
||||
_ = try std.zig.render(allocator, std.io.getStdOut().writer(), tree);
|
||||
}
|
||||
|
||||
fn render(writer: Writer, registry: Registry) !void {
|
||||
switch (registry) {
|
||||
.core => |core_reg| {
|
||||
try renderCopyRight(writer, core_reg.copyright);
|
||||
try writer.print(
|
||||
\\const Version = @import("builtin").Version;
|
||||
\\pub const version = Version{{.major = {}, .minor = {}, .patch = {}}};
|
||||
\\pub const magic_number: u32 = {s};
|
||||
\\
|
||||
, .{ core_reg.major_version, core_reg.minor_version, core_reg.revision, core_reg.magic_number },
|
||||
);
|
||||
try renderOpcodes(writer, core_reg.instructions);
|
||||
try renderOperandKinds(writer, core_reg.operand_kinds);
|
||||
},
|
||||
.extension => |ext_reg| {
|
||||
try renderCopyRight(writer, ext_reg.copyright);
|
||||
try writer.print(
|
||||
\\const Version = @import("builtin").Version;
|
||||
\\pub const version = Version{{.major = {}, .minor = 0, .patch = {}}};
|
||||
\\
|
||||
, .{ ext_reg.version, ext_reg.revision },
|
||||
);
|
||||
try renderOpcodes(writer, ext_reg.instructions);
|
||||
try renderOperandKinds(writer, ext_reg.operand_kinds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn renderCopyRight(writer: Writer, copyright: []const []const u8) !void {
|
||||
for (copyright) |line| {
|
||||
try writer.print("// {s}\n", .{ line });
|
||||
}
|
||||
}
|
||||
|
||||
fn renderOpcodes(writer: Writer, instructions: []const Instruction) !void {
|
||||
try writer.writeAll("pub const Opcode = extern enum(u16) {\n");
|
||||
for (instructions) |instr| {
|
||||
try writer.print("{} = {},\n", .{ std.zig.fmtId(instr.opname), instr.opcode });
|
||||
}
|
||||
try writer.writeAll("_,\n};\n");
|
||||
}
|
||||
|
||||
fn renderOperandKinds(writer: Writer, kinds: []const OperandKind) !void {
|
||||
for (kinds) |kind| {
|
||||
switch (kind.category) {
|
||||
.ValueEnum => try renderValueEnum(writer, kind),
|
||||
.BitEnum => try renderBitEnum(writer, kind),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn renderValueEnum(writer: Writer, enumeration: OperandKind) !void {
|
||||
try writer.print("pub const {s} = extern enum(u32) {{\n", .{ enumeration.kind });
|
||||
|
||||
const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;
|
||||
for (enumerants) |enumerant| {
|
||||
if (enumerant.value != .int) return error.InvalidRegistry;
|
||||
|
||||
try writer.print("{} = {},\n", .{ std.zig.fmtId(enumerant.enumerant), enumerant.value.int });
|
||||
}
|
||||
|
||||
try writer.writeAll("_,\n};\n");
|
||||
}
|
||||
|
||||
fn renderBitEnum(writer: Writer, enumeration: OperandKind) !void {
|
||||
try writer.print("pub const {s} = packed struct {{\n", .{ enumeration.kind });
|
||||
|
||||
var flags_by_bitpos = [_]?[]const u8{null} ** 32;
|
||||
const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;
|
||||
for (enumerants) |enumerant| {
|
||||
if (enumerant.value != .bitflag) return error.InvalidRegistry;
|
||||
const value = try parseHexInt(enumerant.value.bitflag);
|
||||
if (@popCount(u32, value) != 1) {
|
||||
continue; // Skip combinations and 'none' items
|
||||
}
|
||||
|
||||
var bitpos = std.math.log2_int(u32, value);
|
||||
if (flags_by_bitpos[bitpos]) |*existing|{
|
||||
// Keep the shortest
|
||||
if (enumerant.enumerant.len < existing.len)
|
||||
existing.* = enumerant.enumerant;
|
||||
} else {
|
||||
flags_by_bitpos[bitpos] = enumerant.enumerant;
|
||||
}
|
||||
}
|
||||
|
||||
for (flags_by_bitpos) |maybe_flag_name, bitpos| {
|
||||
if (maybe_flag_name) |flag_name| {
|
||||
try writer.writeAll(flag_name);
|
||||
} else {
|
||||
try writer.print("_reserved_bit_{}", .{bitpos});
|
||||
}
|
||||
|
||||
try writer.writeAll(": bool ");
|
||||
if (bitpos == 0) { // Force alignment to integer boundaries
|
||||
try writer.writeAll("align(@alignOf(u32)) ");
|
||||
}
|
||||
try writer.writeAll("= false, ");
|
||||
}
|
||||
|
||||
try writer.writeAll("};\n");
|
||||
}
|
||||
|
||||
fn parseHexInt(text: []const u8) !u31 {
|
||||
const prefix = "0x";
|
||||
if (!std.mem.startsWith(u8, text, prefix))
|
||||
return error.InvalidHexInt;
|
||||
return try std.fmt.parseInt(u31, text[prefix.len ..], 16);
|
||||
}
|
||||
|
||||
fn usageAndExit(file: std.fs.File, arg0: []const u8, code: u8) noreturn {
|
||||
file.writer().print(
|
||||
\\Usage: {s} <spirv json spec>
|
||||
\\
|
||||
\\Generates Zig bindings for a SPIR-V specification .json (either core or
|
||||
\\extinst versions). The result, printed to stdout, should be used to update
|
||||
\\files in src/codegen/spirv.
|
||||
\\
|
||||
\\The relevant specifications can be obtained from the SPIR-V registry:
|
||||
\\https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/unified1/
|
||||
\\
|
||||
, .{arg0}
|
||||
) catch std.process.exit(1);
|
||||
std.process.exit(code);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user