mirror of
https://github.com/ziglang/zig.git
synced 2026-02-20 00:08:56 +00:00
Merge pull request #15068 from Snektron/spirv-test-runner-support
spirv: test runner support
This commit is contained in:
commit
aeae71f462
@ -160,8 +160,7 @@ pub const CallingConvention = enum {
|
||||
AAPCSVFP,
|
||||
SysV,
|
||||
Win64,
|
||||
PtxKernel,
|
||||
AmdgpuKernel,
|
||||
Kernel,
|
||||
};
|
||||
|
||||
/// This data structure is used by the Zig language code generation and
|
||||
|
||||
@ -24,7 +24,9 @@ pub const simplified_logic =
|
||||
builtin.zig_backend == .stage2_aarch64 or
|
||||
builtin.zig_backend == .stage2_arm or
|
||||
builtin.zig_backend == .stage2_riscv64 or
|
||||
builtin.zig_backend == .stage2_sparc64;
|
||||
builtin.zig_backend == .stage2_sparc64 or
|
||||
builtin.cpu.arch == .spirv32 or
|
||||
builtin.cpu.arch == .spirv64;
|
||||
|
||||
comptime {
|
||||
// No matter what, we import the root file, so that any export, test, comptime
|
||||
@ -43,6 +45,9 @@ comptime {
|
||||
}
|
||||
} else if (builtin.os.tag == .wasi and @hasDecl(root, "main")) {
|
||||
@export(wasiMain2, .{ .name = "_start" });
|
||||
} else if (builtin.os.tag == .opencl) {
|
||||
if (@hasDecl(root, "main"))
|
||||
@export(spirvMain2, .{ .name = "main" });
|
||||
} else {
|
||||
if (!@hasDecl(root, "_start")) {
|
||||
@export(_start2, .{ .name = "_start" });
|
||||
@ -127,6 +132,10 @@ fn wasiMain2() callconv(.C) noreturn {
|
||||
}
|
||||
}
|
||||
|
||||
fn spirvMain2() callconv(.Kernel) void {
|
||||
root.main();
|
||||
}
|
||||
|
||||
fn wWinMainCRTStartup2() callconv(.C) noreturn {
|
||||
root.main();
|
||||
exit2(0);
|
||||
|
||||
@ -944,7 +944,7 @@ pub const Target = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isSPIRV(arch: Arch) bool {
|
||||
pub fn isSpirV(arch: Arch) bool {
|
||||
return switch (arch) {
|
||||
.spirv32, .spirv64 => true,
|
||||
else => false,
|
||||
@ -1276,7 +1276,7 @@ pub const Target = struct {
|
||||
.x86, .x86_64 => "x86",
|
||||
.nvptx, .nvptx64 => "nvptx",
|
||||
.wasm32, .wasm64 => "wasm",
|
||||
.spirv32, .spirv64 => "spir-v",
|
||||
.spirv32, .spirv64 => "spirv",
|
||||
else => @tagName(arch),
|
||||
};
|
||||
}
|
||||
@ -1329,6 +1329,7 @@ pub const Target = struct {
|
||||
.amdgcn => comptime allCpusFromDecls(amdgpu.cpu),
|
||||
.riscv32, .riscv64 => comptime allCpusFromDecls(riscv.cpu),
|
||||
.sparc, .sparc64, .sparcel => comptime allCpusFromDecls(sparc.cpu),
|
||||
.spirv32, .spirv64 => comptime allCpusFromDecls(spirv.cpu),
|
||||
.s390x => comptime allCpusFromDecls(s390x.cpu),
|
||||
.x86, .x86_64 => comptime allCpusFromDecls(x86.cpu),
|
||||
.xtensa => comptime allCpusFromDecls(xtensa.cpu),
|
||||
@ -1392,6 +1393,7 @@ pub const Target = struct {
|
||||
.amdgcn => &amdgpu.cpu.generic,
|
||||
.riscv32 => &riscv.cpu.generic_rv32,
|
||||
.riscv64 => &riscv.cpu.generic_rv64,
|
||||
.spirv32, .spirv64 => &spirv.cpu.generic,
|
||||
.sparc, .sparcel => &sparc.cpu.generic,
|
||||
.sparc64 => &sparc.cpu.v9, // 64-bit SPARC needs v9 as the baseline
|
||||
.s390x => &s390x.cpu.generic,
|
||||
@ -1532,6 +1534,10 @@ pub const Target = struct {
|
||||
return !self.cpu.arch.isWasm();
|
||||
}
|
||||
|
||||
pub fn isSpirV(self: Target) bool {
|
||||
return self.cpu.arch.isSpirV();
|
||||
}
|
||||
|
||||
pub const FloatAbi = enum {
|
||||
hard,
|
||||
soft,
|
||||
|
||||
@ -2081,3 +2081,11 @@ pub const all_features = blk: {
|
||||
}
|
||||
break :blk result;
|
||||
};
|
||||
|
||||
pub const cpu = struct {
|
||||
pub const generic = CpuModel{
|
||||
.name = "generic",
|
||||
.llvm_name = "generic",
|
||||
.features = featureSet(&[_]Feature{}),
|
||||
};
|
||||
};
|
||||
|
||||
@ -722,10 +722,11 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
// Once they are capable this condition could be removed. When removing this condition,
|
||||
// also test the use case of `build-obj -fcompiler-rt` with the native backends
|
||||
// and make sure the compiler-rt symbols are emitted.
|
||||
const capable_of_building_compiler_rt = build_options.have_llvm and options.target.os.tag != .plan9;
|
||||
|
||||
const capable_of_building_zig_libc = build_options.have_llvm and options.target.os.tag != .plan9;
|
||||
const capable_of_building_ssp = build_options.have_llvm and options.target.os.tag != .plan9;
|
||||
const is_p9 = options.target.os.tag == .plan9;
|
||||
const is_spv = options.target.cpu.arch.isSpirV();
|
||||
const capable_of_building_compiler_rt = build_options.have_llvm and !is_p9 and !is_spv;
|
||||
const capable_of_building_zig_libc = build_options.have_llvm and !is_p9 and !is_spv;
|
||||
const capable_of_building_ssp = build_options.have_llvm and !is_p9 and !is_spv;
|
||||
|
||||
const comp: *Compilation = comp: {
|
||||
// For allocations that have the same lifetime as Compilation. This arena is used only during this
|
||||
@ -1948,8 +1949,9 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
|
||||
.sub_path = std.fs.path.basename(sub_path),
|
||||
};
|
||||
}
|
||||
comp.bin_file.destroy();
|
||||
var old_bin_file = comp.bin_file;
|
||||
comp.bin_file = try link.File.openPath(comp.gpa, options);
|
||||
old_bin_file.destroy();
|
||||
}
|
||||
|
||||
// For compiling C objects, we rely on the cache hash system to avoid duplicating work.
|
||||
|
||||
32
src/Sema.zig
32
src/Sema.zig
@ -8883,7 +8883,7 @@ fn funcCommon(
|
||||
};
|
||||
return sema.failWithOwnedErrorMsg(msg);
|
||||
}
|
||||
if (!ret_poison and !Type.fnCallingConventionAllowsZigTypes(cc_resolved) and !try sema.validateExternType(return_type, .ret_ty)) {
|
||||
if (!ret_poison and !Type.fnCallingConventionAllowsZigTypes(target, cc_resolved) and !try sema.validateExternType(return_type, .ret_ty)) {
|
||||
const msg = msg: {
|
||||
const msg = try sema.errMsg(block, ret_ty_src, "return type '{}' not allowed in function with calling convention '{s}'", .{
|
||||
return_type.fmt(sema.mod), @tagName(cc_resolved),
|
||||
@ -8961,13 +8961,9 @@ fn funcCommon(
|
||||
.x86_64 => null,
|
||||
else => @as([]const u8, "x86_64"),
|
||||
},
|
||||
.PtxKernel => switch (arch) {
|
||||
.nvptx, .nvptx64 => null,
|
||||
else => @as([]const u8, "nvptx and nvptx64"),
|
||||
},
|
||||
.AmdgpuKernel => switch (arch) {
|
||||
.amdgcn => null,
|
||||
else => @as([]const u8, "amdgcn"),
|
||||
.Kernel => switch (arch) {
|
||||
.nvptx, .nvptx64, .amdgcn, .spirv32, .spirv64 => null,
|
||||
else => @as([]const u8, "nvptx, amdgcn and SPIR-V"),
|
||||
},
|
||||
}) |allowed_platform| {
|
||||
return sema.fail(block, cc_src, "callconv '{s}' is only available on {s}, not {s}", .{
|
||||
@ -9093,10 +9089,11 @@ fn analyzeParameter(
|
||||
comptime_params[i] = param.is_comptime or requires_comptime;
|
||||
const this_generic = param.ty.tag() == .generic_poison;
|
||||
is_generic.* = is_generic.* or this_generic;
|
||||
if (param.is_comptime and !Type.fnCallingConventionAllowsZigTypes(cc)) {
|
||||
const target = sema.mod.getTarget();
|
||||
if (param.is_comptime and !Type.fnCallingConventionAllowsZigTypes(target, cc)) {
|
||||
return sema.fail(block, param_src, "comptime parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)});
|
||||
}
|
||||
if (this_generic and !sema.no_partial_func_ty and !Type.fnCallingConventionAllowsZigTypes(cc)) {
|
||||
if (this_generic and !sema.no_partial_func_ty and !Type.fnCallingConventionAllowsZigTypes(target, cc)) {
|
||||
return sema.fail(block, param_src, "generic parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)});
|
||||
}
|
||||
if (!param.ty.isValidParamType()) {
|
||||
@ -9112,7 +9109,7 @@ fn analyzeParameter(
|
||||
};
|
||||
return sema.failWithOwnedErrorMsg(msg);
|
||||
}
|
||||
if (!this_generic and !Type.fnCallingConventionAllowsZigTypes(cc) and !try sema.validateExternType(param.ty, .param_ty)) {
|
||||
if (!this_generic and !Type.fnCallingConventionAllowsZigTypes(target, cc) and !try sema.validateExternType(param.ty, .param_ty)) {
|
||||
const msg = msg: {
|
||||
const msg = try sema.errMsg(block, param_src, "parameter of type '{}' not allowed in function with calling convention '{s}'", .{
|
||||
param.ty.fmt(sema.mod), @tagName(cc),
|
||||
@ -22786,12 +22783,13 @@ fn validateExternType(
|
||||
},
|
||||
.Fn => {
|
||||
if (position != .other) return false;
|
||||
return switch (ty.fnCallingConvention()) {
|
||||
// For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI.
|
||||
// The goal is to experiment with more integrated CPU/GPU code.
|
||||
.PtxKernel => true,
|
||||
else => !Type.fnCallingConventionAllowsZigTypes(ty.fnCallingConvention()),
|
||||
};
|
||||
const target = sema.mod.getTarget();
|
||||
// For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI.
|
||||
// The goal is to experiment with more integrated CPU/GPU code.
|
||||
if (ty.fnCallingConvention() == .Kernel and (target.cpu.arch == .nvptx or target.cpu.arch == .nvptx64)) {
|
||||
return true;
|
||||
}
|
||||
return !Type.fnCallingConventionAllowsZigTypes(target, ty.fnCallingConvention());
|
||||
},
|
||||
.Enum => {
|
||||
var buf: Type.Payload.Bits = undefined;
|
||||
|
||||
@ -10350,11 +10350,8 @@ fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) llvm.Ca
|
||||
.Signal => .AVR_SIGNAL,
|
||||
.SysV => .X86_64_SysV,
|
||||
.Win64 => .Win64,
|
||||
.PtxKernel => return switch (target.cpu.arch) {
|
||||
.Kernel => return switch (target.cpu.arch) {
|
||||
.nvptx, .nvptx64 => .PTX_Kernel,
|
||||
else => unreachable,
|
||||
},
|
||||
.AmdgpuKernel => return switch (target.cpu.arch) {
|
||||
.amdgcn => .AMDGPU_KERNEL,
|
||||
else => unreachable,
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -135,7 +135,7 @@ const AsmValue = union(enum) {
|
||||
return switch (self) {
|
||||
.just_declared, .unresolved_forward_reference => unreachable,
|
||||
.value => |result| result,
|
||||
.ty => |ref| spv.typeResultId(ref).toRef(),
|
||||
.ty => |ref| spv.typeId(ref),
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -239,12 +239,17 @@ fn todo(self: *Assembler, comptime fmt: []const u8, args: anytype) Error {
|
||||
/// If this function returns `error.AssembleFail`, an explanatory
|
||||
/// error message has already been emitted into `self.errors`.
|
||||
fn processInstruction(self: *Assembler) !void {
|
||||
const result = switch (self.inst.opcode.class()) {
|
||||
.TypeDeclaration => try self.processTypeInstruction(),
|
||||
else => if (try self.processGenericInstruction()) |result|
|
||||
result
|
||||
else
|
||||
return,
|
||||
const result = switch (self.inst.opcode) {
|
||||
.OpEntryPoint => {
|
||||
return self.fail(0, "cannot export entry points via OpEntryPoint, export the kernel using callconv(.Kernel)", .{});
|
||||
},
|
||||
else => switch (self.inst.opcode.class()) {
|
||||
.TypeDeclaration => try self.processTypeInstruction(),
|
||||
else => if (try self.processGenericInstruction()) |result|
|
||||
result
|
||||
else
|
||||
return,
|
||||
},
|
||||
};
|
||||
|
||||
const result_ref = self.inst.result().?;
|
||||
@ -266,27 +271,28 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
|
||||
.OpTypeVoid => SpvType.initTag(.void),
|
||||
.OpTypeBool => SpvType.initTag(.bool),
|
||||
.OpTypeInt => blk: {
|
||||
const payload = try self.spv.arena.create(SpvType.Payload.Int);
|
||||
const signedness: std.builtin.Signedness = switch (operands[2].literal32) {
|
||||
0 => .unsigned,
|
||||
1 => .signed,
|
||||
else => {
|
||||
// TODO: Improve source location.
|
||||
return self.fail(0, "'{}' is not a valid signedness (expected 0 or 1)", .{operands[2].literal32});
|
||||
return self.fail(0, "{} is not a valid signedness (expected 0 or 1)", .{operands[2].literal32});
|
||||
},
|
||||
};
|
||||
payload.* = .{
|
||||
.width = operands[1].literal32,
|
||||
.signedness = signedness,
|
||||
const width = std.math.cast(u16, operands[1].literal32) orelse {
|
||||
return self.fail(0, "int type of {} bits is too large", .{operands[1].literal32});
|
||||
};
|
||||
break :blk SpvType.initPayload(&payload.base);
|
||||
break :blk try SpvType.int(self.spv.arena, signedness, width);
|
||||
},
|
||||
.OpTypeFloat => blk: {
|
||||
const payload = try self.spv.arena.create(SpvType.Payload.Float);
|
||||
payload.* = .{
|
||||
.width = operands[1].literal32,
|
||||
};
|
||||
break :blk SpvType.initPayload(&payload.base);
|
||||
const bits = operands[1].literal32;
|
||||
switch (bits) {
|
||||
16, 32, 64 => {},
|
||||
else => {
|
||||
return self.fail(0, "{} is not a valid bit count for floats (expected 16, 32 or 64)", .{bits});
|
||||
},
|
||||
}
|
||||
break :blk SpvType.float(@intCast(u16, bits));
|
||||
},
|
||||
.OpTypeVector => blk: {
|
||||
const payload = try self.spv.arena.create(SpvType.Payload.Vector);
|
||||
@ -382,10 +388,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
|
||||
payload.* = .{
|
||||
.storage_class = @intToEnum(spec.StorageClass, operands[1].value),
|
||||
.child_type = try self.resolveTypeRef(operands[2].ref_id),
|
||||
// TODO: Fetch these values from decorations.
|
||||
.array_stride = 0,
|
||||
.alignment = null,
|
||||
.max_byte_offset = null,
|
||||
// TODO: Fetch decorations
|
||||
};
|
||||
break :blk SpvType.initPayload(&payload.base);
|
||||
},
|
||||
@ -434,11 +437,16 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {
|
||||
.Annotation => &self.spv.sections.annotations,
|
||||
.TypeDeclaration => unreachable, // Handled elsewhere.
|
||||
else => switch (self.inst.opcode) {
|
||||
.OpEntryPoint => &self.spv.sections.entry_points,
|
||||
.OpEntryPoint => unreachable,
|
||||
.OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes,
|
||||
.OpVariable => switch (@intToEnum(spec.StorageClass, operands[2].value)) {
|
||||
.Function => &self.func.prologue,
|
||||
else => &self.spv.sections.types_globals_constants,
|
||||
else => {
|
||||
// This is currently disabled because global variables are required to be
|
||||
// emitted in the proper order, and this should be honored in inline assembly
|
||||
// as well.
|
||||
return self.todo("global variables", .{});
|
||||
},
|
||||
},
|
||||
// Default case - to be worked out further.
|
||||
else => &self.func.body,
|
||||
@ -485,7 +493,7 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {
|
||||
section.instructions.items[first_word] |= @as(u32, @intCast(u16, actual_word_count)) << 16 | @enumToInt(self.inst.opcode);
|
||||
|
||||
if (maybe_result_id) |result| {
|
||||
return AsmValue{ .value = result.toRef() };
|
||||
return AsmValue{ .value = result };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -753,22 +761,19 @@ fn parseContextDependentNumber(self: *Assembler) !void {
|
||||
|
||||
const tok = self.currentToken();
|
||||
const result_type_ref = try self.resolveTypeRef(self.inst.operands.items[0].ref_id);
|
||||
const result_type = self.spv.type_cache.keys()[result_type_ref];
|
||||
switch (result_type.tag()) {
|
||||
.int => {
|
||||
const int = result_type.castTag(.int).?;
|
||||
try self.parseContextDependentInt(int.signedness, int.width);
|
||||
},
|
||||
.float => {
|
||||
const width = result_type.castTag(.float).?.width;
|
||||
switch (width) {
|
||||
16 => try self.parseContextDependentFloat(16),
|
||||
32 => try self.parseContextDependentFloat(32),
|
||||
64 => try self.parseContextDependentFloat(64),
|
||||
else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}),
|
||||
}
|
||||
},
|
||||
else => return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())}),
|
||||
const result_type = self.spv.type_cache.keys()[@enumToInt(result_type_ref)];
|
||||
if (result_type.isInt()) {
|
||||
try self.parseContextDependentInt(result_type.intSignedness(), result_type.intFloatBits());
|
||||
} else if (result_type.isFloat()) {
|
||||
const width = result_type.intFloatBits();
|
||||
switch (width) {
|
||||
16 => try self.parseContextDependentFloat(16),
|
||||
32 => try self.parseContextDependentFloat(32),
|
||||
64 => try self.parseContextDependentFloat(64),
|
||||
else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}),
|
||||
}
|
||||
} else {
|
||||
return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -39,22 +39,68 @@ pub const Fn = struct {
|
||||
/// This section should also contain the OpFunctionEnd instruction marking
|
||||
/// the end of this function definition.
|
||||
body: Section = .{},
|
||||
/// The decl dependencies that this function depends on.
|
||||
decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{},
|
||||
|
||||
/// Reset this function without deallocating resources, so that
|
||||
/// it may be used to emit code for another function.
|
||||
pub fn reset(self: *Fn) void {
|
||||
self.prologue.reset();
|
||||
self.body.reset();
|
||||
self.decl_deps.items.len = 0;
|
||||
}
|
||||
|
||||
/// Free the resources owned by this function.
|
||||
pub fn deinit(self: *Fn, a: Allocator) void {
|
||||
self.prologue.deinit(a);
|
||||
self.body.deinit(a);
|
||||
self.decl_deps.deinit(a);
|
||||
self.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/// Declarations, both functions and globals, can have dependencies. These are used for 2 things:
|
||||
/// - Globals must be declared before they are used, also between globals. The compiler processes
|
||||
/// globals unordered, so we must use the dependencies here to figure out how to order the globals
|
||||
/// in the final module. The Globals structure is also used for that.
|
||||
/// - Entry points must declare the complete list of OpVariable instructions that they access.
|
||||
/// For these we use the same dependency structure.
|
||||
/// In this mechanism, globals will only depend on other globals, while functions may depend on
|
||||
/// globals or other functions.
|
||||
pub const Decl = struct {
|
||||
/// Index to refer to a Decl by.
|
||||
pub const Index = enum(u32) { _ };
|
||||
|
||||
/// The result-id to be used for this declaration. This is the final result-id
|
||||
/// of the decl, which may be an OpFunction, OpVariable, or the result of a sequence
|
||||
/// of OpSpecConstantOp operations.
|
||||
result_id: IdRef,
|
||||
/// The offset of the first dependency of this decl in the `decl_deps` array.
|
||||
begin_dep: u32,
|
||||
/// The past-end offset of the dependencies of this decl in the `decl_deps` array.
|
||||
end_dep: u32,
|
||||
};
|
||||
|
||||
/// Globals must be kept in order: operations involving globals must be ordered
|
||||
/// so that the global declaration precedes any usage.
|
||||
pub const Global = struct {
|
||||
/// This is the result-id of the OpVariable instruction that declares the global.
|
||||
result_id: IdRef,
|
||||
/// The offset into `self.globals.section` of the first instruction of this global
|
||||
/// declaration.
|
||||
begin_inst: u32,
|
||||
/// The past-end offset into `self.flobals.section`.
|
||||
end_inst: u32,
|
||||
};
|
||||
|
||||
/// This models a kernel entry point.
|
||||
pub const EntryPoint = struct {
|
||||
/// The declaration that should be exported.
|
||||
decl_index: Decl.Index,
|
||||
/// The name of the kernel to be exported.
|
||||
name: []const u8,
|
||||
};
|
||||
|
||||
/// A general-purpose allocator which may be used to allocate resources for this module
|
||||
gpa: Allocator,
|
||||
|
||||
@ -69,13 +115,13 @@ sections: struct {
|
||||
extensions: Section = .{},
|
||||
// OpExtInstImport instructions - skip for now.
|
||||
// memory model defined by target, not required here.
|
||||
/// OpEntryPoint instructions.
|
||||
entry_points: Section = .{},
|
||||
/// OpEntryPoint instructions - Handled by `self.entry_points`.
|
||||
/// OpExecutionMode and OpExecutionModeId instructions.
|
||||
execution_modes: Section = .{},
|
||||
/// OpString, OpSourcExtension, OpSource, OpSourceContinued.
|
||||
debug_strings: Section = .{},
|
||||
// OpName, OpMemberName - skip for now.
|
||||
// OpName, OpMemberName.
|
||||
debug_names: Section = .{},
|
||||
// OpModuleProcessed - skip for now.
|
||||
/// Annotation instructions (OpDecorate etc).
|
||||
annotations: Section = .{},
|
||||
@ -101,6 +147,26 @@ source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},
|
||||
/// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref).
|
||||
type_cache: TypeCache = .{},
|
||||
|
||||
/// Set of Decls, referred to by Decl.Index.
|
||||
decls: std.ArrayListUnmanaged(Decl) = .{},
|
||||
|
||||
/// List of dependencies, per decl. This list holds all the dependencies, sliced by the
|
||||
/// begin_dep and end_dep in `self.decls`.
|
||||
decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{},
|
||||
|
||||
/// The list of entry points that should be exported from this module.
|
||||
entry_points: std.ArrayListUnmanaged(EntryPoint) = .{},
|
||||
|
||||
/// The fields in this structure help to maintain the required order for global variables.
|
||||
globals: struct {
|
||||
/// Set of globals, referred to by Decl.Index.
|
||||
globals: std.AutoArrayHashMapUnmanaged(Decl.Index, Global) = .{},
|
||||
/// This pseudo-section contains the initialization code for all the globals. Instructions from
|
||||
/// here are reordered when flushing the module. Its contents should be part of the
|
||||
/// `types_globals_constants` SPIR-V section.
|
||||
section: Section = .{},
|
||||
} = .{},
|
||||
|
||||
pub fn init(gpa: Allocator, arena: Allocator) Module {
|
||||
return .{
|
||||
.gpa = gpa,
|
||||
@ -112,9 +178,9 @@ pub fn init(gpa: Allocator, arena: Allocator) Module {
|
||||
pub fn deinit(self: *Module) void {
|
||||
self.sections.capabilities.deinit(self.gpa);
|
||||
self.sections.extensions.deinit(self.gpa);
|
||||
self.sections.entry_points.deinit(self.gpa);
|
||||
self.sections.execution_modes.deinit(self.gpa);
|
||||
self.sections.debug_strings.deinit(self.gpa);
|
||||
self.sections.debug_names.deinit(self.gpa);
|
||||
self.sections.annotations.deinit(self.gpa);
|
||||
self.sections.types_globals_constants.deinit(self.gpa);
|
||||
self.sections.functions.deinit(self.gpa);
|
||||
@ -122,6 +188,14 @@ pub fn deinit(self: *Module) void {
|
||||
self.source_file_names.deinit(self.gpa);
|
||||
self.type_cache.deinit(self.gpa);
|
||||
|
||||
self.decls.deinit(self.gpa);
|
||||
self.decl_deps.deinit(self.gpa);
|
||||
|
||||
self.entry_points.deinit(self.gpa);
|
||||
|
||||
self.globals.globals.deinit(self.gpa);
|
||||
self.globals.section.deinit(self.gpa);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
@ -130,32 +204,138 @@ pub fn allocId(self: *Module) spec.IdResult {
|
||||
return .{ .id = self.next_result_id };
|
||||
}
|
||||
|
||||
pub fn allocIds(self: *Module, n: u32) spec.IdResult {
|
||||
defer self.next_result_id += n;
|
||||
return .{ .id = self.next_result_id };
|
||||
}
|
||||
|
||||
pub fn idBound(self: Module) Word {
|
||||
return self.next_result_id;
|
||||
}
|
||||
|
||||
fn orderGlobalsInto(
|
||||
self: *Module,
|
||||
decl_index: Decl.Index,
|
||||
section: *Section,
|
||||
seen: *std.DynamicBitSetUnmanaged,
|
||||
) !void {
|
||||
const decl = self.declPtr(decl_index);
|
||||
const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep];
|
||||
const global = self.globalPtr(decl_index).?;
|
||||
const insts = self.globals.section.instructions.items[global.begin_inst..global.end_inst];
|
||||
|
||||
seen.set(@enumToInt(decl_index));
|
||||
|
||||
for (deps) |dep| {
|
||||
if (!seen.isSet(@enumToInt(dep))) {
|
||||
try self.orderGlobalsInto(dep, section, seen);
|
||||
}
|
||||
}
|
||||
|
||||
try section.instructions.appendSlice(self.gpa, insts);
|
||||
}
|
||||
|
||||
fn orderGlobals(self: *Module) !Section {
|
||||
const globals = self.globals.globals.keys();
|
||||
|
||||
var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len);
|
||||
defer seen.deinit(self.gpa);
|
||||
|
||||
var ordered_globals = Section{};
|
||||
errdefer ordered_globals.deinit(self.gpa);
|
||||
|
||||
for (globals) |decl_index| {
|
||||
if (!seen.isSet(@enumToInt(decl_index))) {
|
||||
try self.orderGlobalsInto(decl_index, &ordered_globals, &seen);
|
||||
}
|
||||
}
|
||||
|
||||
return ordered_globals;
|
||||
}
|
||||
|
||||
fn addEntryPointDeps(
|
||||
self: *Module,
|
||||
decl_index: Decl.Index,
|
||||
seen: *std.DynamicBitSetUnmanaged,
|
||||
interface: *std.ArrayList(IdRef),
|
||||
) !void {
|
||||
const decl = self.declPtr(decl_index);
|
||||
const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep];
|
||||
|
||||
seen.set(@enumToInt(decl_index));
|
||||
|
||||
if (self.globalPtr(decl_index)) |global| {
|
||||
try interface.append(global.result_id);
|
||||
}
|
||||
|
||||
for (deps) |dep| {
|
||||
if (!seen.isSet(@enumToInt(dep))) {
|
||||
try self.addEntryPointDeps(dep, seen, interface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn entryPoints(self: *Module) !Section {
|
||||
var entry_points = Section{};
|
||||
errdefer entry_points.deinit(self.gpa);
|
||||
|
||||
var interface = std.ArrayList(IdRef).init(self.gpa);
|
||||
defer interface.deinit();
|
||||
|
||||
var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len);
|
||||
defer seen.deinit(self.gpa);
|
||||
|
||||
for (self.entry_points.items) |entry_point| {
|
||||
interface.items.len = 0;
|
||||
seen.setRangeValue(.{ .start = 0, .end = self.decls.items.len }, false);
|
||||
|
||||
try self.addEntryPointDeps(entry_point.decl_index, &seen, &interface);
|
||||
|
||||
const entry_point_id = self.declPtr(entry_point.decl_index).result_id;
|
||||
try entry_points.emit(self.gpa, .OpEntryPoint, .{
|
||||
.execution_model = .Kernel,
|
||||
.entry_point = entry_point_id,
|
||||
.name = entry_point.name,
|
||||
.interface = interface.items,
|
||||
});
|
||||
}
|
||||
|
||||
return entry_points;
|
||||
}
|
||||
|
||||
/// Emit this module as a spir-v binary.
|
||||
pub fn flush(self: Module, file: std.fs.File) !void {
|
||||
pub fn flush(self: *Module, file: std.fs.File) !void {
|
||||
// See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
|
||||
|
||||
const header = [_]Word{
|
||||
spec.magic_number,
|
||||
(1 << 16) | (5 << 8),
|
||||
// TODO: From cpu features
|
||||
// Emit SPIR-V 1.4 for now. This is the highest version that Intel's CPU OpenCL supports.
|
||||
(1 << 16) | (4 << 8),
|
||||
0, // TODO: Register Zig compiler magic number.
|
||||
self.idBound(),
|
||||
0, // Schema (currently reserved for future use)
|
||||
};
|
||||
|
||||
// TODO: Perform topological sort on the globals.
|
||||
var globals = try self.orderGlobals();
|
||||
defer globals.deinit(self.gpa);
|
||||
|
||||
var entry_points = try self.entryPoints();
|
||||
defer entry_points.deinit(self.gpa);
|
||||
|
||||
// Note: needs to be kept in order according to section 2.3!
|
||||
const buffers = &[_][]const Word{
|
||||
&header,
|
||||
self.sections.capabilities.toWords(),
|
||||
self.sections.extensions.toWords(),
|
||||
self.sections.entry_points.toWords(),
|
||||
entry_points.toWords(),
|
||||
self.sections.execution_modes.toWords(),
|
||||
self.sections.debug_strings.toWords(),
|
||||
self.sections.debug_names.toWords(),
|
||||
self.sections.annotations.toWords(),
|
||||
self.sections.types_globals_constants.toWords(),
|
||||
globals.toWords(),
|
||||
self.sections.functions.toWords(),
|
||||
};
|
||||
|
||||
@ -175,9 +355,10 @@ pub fn flush(self: Module, file: std.fs.File) !void {
|
||||
}
|
||||
|
||||
/// Merge the sections making up a function declaration into this module.
|
||||
pub fn addFunction(self: *Module, func: Fn) !void {
|
||||
pub fn addFunction(self: *Module, decl_index: Decl.Index, func: Fn) !void {
|
||||
try self.sections.functions.append(self.gpa, func.prologue);
|
||||
try self.sections.functions.append(self.gpa, func.body);
|
||||
try self.declareDeclDeps(decl_index, func.decl_deps.items);
|
||||
}
|
||||
|
||||
/// Fetch the result-id of an OpString instruction that encodes the path of the source
|
||||
@ -188,7 +369,7 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
|
||||
const result = try self.source_file_names.getOrPut(self.gpa, path);
|
||||
if (!result.found_existing) {
|
||||
const file_result_id = self.allocId();
|
||||
result.value_ptr.* = file_result_id.toRef();
|
||||
result.value_ptr.* = file_result_id;
|
||||
try self.sections.debug_strings.emit(self.gpa, .OpString, .{
|
||||
.id_result = file_result_id,
|
||||
.string = path,
|
||||
@ -197,7 +378,7 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
|
||||
try self.sections.debug_strings.emit(self.gpa, .OpSource, .{
|
||||
.source_language = .Unknown, // TODO: Register Zig source language.
|
||||
.version = 0, // TODO: Zig version as u32?
|
||||
.file = file_result_id.toRef(),
|
||||
.file = file_result_id,
|
||||
.source = null, // TODO: Store actual source also?
|
||||
});
|
||||
}
|
||||
@ -216,22 +397,21 @@ pub fn resolveType(self: *Module, ty: Type) !Type.Ref {
|
||||
result.value_ptr.* = try self.emitType(ty);
|
||||
}
|
||||
|
||||
return result.index;
|
||||
return @intToEnum(Type.Ref, result.index);
|
||||
}
|
||||
|
||||
pub fn resolveTypeId(self: *Module, ty: Type) !IdRef {
|
||||
const type_ref = try self.resolveType(ty);
|
||||
return self.typeResultId(type_ref);
|
||||
pub fn resolveTypeId(self: *Module, ty: Type) !IdResultType {
|
||||
const ty_ref = try self.resolveType(ty);
|
||||
return self.typeId(ty_ref);
|
||||
}
|
||||
|
||||
pub fn typeRefType(self: Module, ty_ref: Type.Ref) Type {
|
||||
return self.type_cache.keys()[@enumToInt(ty_ref)];
|
||||
}
|
||||
|
||||
/// Get the result-id of a particular type, by reference. Asserts type_ref is valid.
|
||||
pub fn typeResultId(self: Module, type_ref: Type.Ref) IdResultType {
|
||||
return self.type_cache.values()[type_ref];
|
||||
}
|
||||
|
||||
/// Get the result-id of a particular type as IdRef, by Type.Ref. Asserts type_ref is valid.
|
||||
pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef {
|
||||
return self.type_cache.values()[type_ref].toRef();
|
||||
pub fn typeId(self: Module, ty_ref: Type.Ref) IdResultType {
|
||||
return self.type_cache.values()[@enumToInt(ty_ref)];
|
||||
}
|
||||
|
||||
/// Unconditionally emit a spir-v type into the appropriate section.
|
||||
@ -240,47 +420,94 @@ pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef {
|
||||
/// Note: This function does not attempt to perform any validation on the type.
|
||||
/// The type is emitted in a shallow fashion; any child types should already
|
||||
/// be emitted at this point.
|
||||
pub fn emitType(self: *Module, ty: Type) !IdResultType {
|
||||
pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType {
|
||||
const result_id = self.allocId();
|
||||
const ref_id = result_id.toRef();
|
||||
const ref_id = result_id;
|
||||
const types = &self.sections.types_globals_constants;
|
||||
const annotations = &self.sections.annotations;
|
||||
const debug_names = &self.sections.debug_names;
|
||||
const result_id_operand = .{ .id_result = result_id };
|
||||
|
||||
switch (ty.tag()) {
|
||||
.void => try types.emit(self.gpa, .OpTypeVoid, result_id_operand),
|
||||
.bool => try types.emit(self.gpa, .OpTypeBool, result_id_operand),
|
||||
.int => {
|
||||
const signedness: spec.LiteralInteger = switch (ty.payload(.int).signedness) {
|
||||
.void => {
|
||||
try types.emit(self.gpa, .OpTypeVoid, result_id_operand);
|
||||
try debug_names.emit(self.gpa, .OpName, .{
|
||||
.target = result_id,
|
||||
.name = "void",
|
||||
});
|
||||
},
|
||||
.bool => {
|
||||
try types.emit(self.gpa, .OpTypeBool, result_id_operand);
|
||||
try debug_names.emit(self.gpa, .OpName, .{
|
||||
.target = result_id,
|
||||
.name = "bool",
|
||||
});
|
||||
},
|
||||
.u8,
|
||||
.u16,
|
||||
.u32,
|
||||
.u64,
|
||||
.i8,
|
||||
.i16,
|
||||
.i32,
|
||||
.i64,
|
||||
.int,
|
||||
=> {
|
||||
// TODO: Kernels do not support OpTypeInt that is signed. We can probably
|
||||
// can get rid of the signedness all together, in Shaders also.
|
||||
const bits = ty.intFloatBits();
|
||||
const signedness: spec.LiteralInteger = switch (ty.intSignedness()) {
|
||||
.unsigned => 0,
|
||||
.signed => 1,
|
||||
};
|
||||
|
||||
try types.emit(self.gpa, .OpTypeInt, .{
|
||||
.id_result = result_id,
|
||||
.width = ty.payload(.int).width,
|
||||
.width = bits,
|
||||
.signedness = signedness,
|
||||
});
|
||||
|
||||
const ui: []const u8 = switch (signedness) {
|
||||
0 => "u",
|
||||
1 => "i",
|
||||
else => unreachable,
|
||||
};
|
||||
const name = try std.fmt.allocPrint(self.gpa, "{s}{}", .{ ui, bits });
|
||||
defer self.gpa.free(name);
|
||||
|
||||
try debug_names.emit(self.gpa, .OpName, .{
|
||||
.target = result_id,
|
||||
.name = name,
|
||||
});
|
||||
},
|
||||
.f16, .f32, .f64 => {
|
||||
const bits = ty.intFloatBits();
|
||||
try types.emit(self.gpa, .OpTypeFloat, .{
|
||||
.id_result = result_id,
|
||||
.width = bits,
|
||||
});
|
||||
|
||||
const name = try std.fmt.allocPrint(self.gpa, "f{}", .{bits});
|
||||
defer self.gpa.free(name);
|
||||
try debug_names.emit(self.gpa, .OpName, .{
|
||||
.target = result_id,
|
||||
.name = name,
|
||||
});
|
||||
},
|
||||
.float => try types.emit(self.gpa, .OpTypeFloat, .{
|
||||
.id_result = result_id,
|
||||
.width = ty.payload(.float).width,
|
||||
}),
|
||||
.vector => try types.emit(self.gpa, .OpTypeVector, .{
|
||||
.id_result = result_id,
|
||||
.component_type = self.typeResultId(ty.childType()).toRef(),
|
||||
.component_type = self.typeId(ty.childType()),
|
||||
.component_count = ty.payload(.vector).component_count,
|
||||
}),
|
||||
.matrix => try types.emit(self.gpa, .OpTypeMatrix, .{
|
||||
.id_result = result_id,
|
||||
.column_type = self.typeResultId(ty.childType()).toRef(),
|
||||
.column_type = self.typeId(ty.childType()),
|
||||
.column_count = ty.payload(.matrix).column_count,
|
||||
}),
|
||||
.image => {
|
||||
const info = ty.payload(.image);
|
||||
try types.emit(self.gpa, .OpTypeImage, .{
|
||||
.id_result = result_id,
|
||||
.sampled_type = self.typeResultId(ty.childType()).toRef(),
|
||||
.sampled_type = self.typeId(ty.childType()),
|
||||
.dim = info.dim,
|
||||
.depth = @enumToInt(info.depth),
|
||||
.arrayed = @boolToInt(info.arrayed),
|
||||
@ -293,28 +520,34 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType {
|
||||
.sampler => try types.emit(self.gpa, .OpTypeSampler, result_id_operand),
|
||||
.sampled_image => try types.emit(self.gpa, .OpTypeSampledImage, .{
|
||||
.id_result = result_id,
|
||||
.image_type = self.typeResultId(ty.childType()).toRef(),
|
||||
.image_type = self.typeId(ty.childType()),
|
||||
}),
|
||||
.array => {
|
||||
const info = ty.payload(.array);
|
||||
assert(info.length != 0);
|
||||
|
||||
const size_type = Type.initTag(.u32);
|
||||
const size_type_id = try self.resolveTypeId(size_type);
|
||||
const length_id = self.allocId();
|
||||
try self.emitConstant(size_type_id, length_id, .{ .uint32 = info.length });
|
||||
|
||||
try types.emit(self.gpa, .OpTypeArray, .{
|
||||
.id_result = result_id,
|
||||
.element_type = self.typeResultId(ty.childType()).toRef(),
|
||||
.length = .{ .id = 0 }, // TODO: info.length must be emitted as constant!
|
||||
.element_type = self.typeId(ty.childType()),
|
||||
.length = length_id,
|
||||
});
|
||||
if (info.array_stride != 0) {
|
||||
try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
|
||||
try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
|
||||
}
|
||||
},
|
||||
.runtime_array => {
|
||||
const info = ty.payload(.runtime_array);
|
||||
try types.emit(self.gpa, .OpTypeRuntimeArray, .{
|
||||
.id_result = result_id,
|
||||
.element_type = self.typeResultId(ty.childType()).toRef(),
|
||||
.element_type = self.typeId(ty.childType()),
|
||||
});
|
||||
if (info.array_stride != 0) {
|
||||
try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
|
||||
try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
|
||||
}
|
||||
},
|
||||
.@"struct" => {
|
||||
@ -322,7 +555,7 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType {
|
||||
try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len);
|
||||
types.writeOperand(IdResult, result_id);
|
||||
for (info.members) |member| {
|
||||
types.writeOperand(IdRef, self.typeResultId(member.ty).toRef());
|
||||
types.writeOperand(IdRef, self.typeId(member.ty));
|
||||
}
|
||||
try self.decorateStruct(ref_id, info);
|
||||
},
|
||||
@ -335,25 +568,25 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType {
|
||||
try types.emit(self.gpa, .OpTypePointer, .{
|
||||
.id_result = result_id,
|
||||
.storage_class = info.storage_class,
|
||||
.type = self.typeResultId(ty.childType()).toRef(),
|
||||
.type = self.typeId(ty.childType()),
|
||||
});
|
||||
if (info.array_stride != 0) {
|
||||
try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
|
||||
try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
|
||||
}
|
||||
if (info.alignment) |alignment| {
|
||||
try annotations.decorate(self.gpa, ref_id, .{ .Alignment = .{ .alignment = alignment } });
|
||||
if (info.alignment != 0) {
|
||||
try self.decorate(ref_id, .{ .Alignment = .{ .alignment = info.alignment } });
|
||||
}
|
||||
if (info.max_byte_offset) |max_byte_offset| {
|
||||
try annotations.decorate(self.gpa, ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } });
|
||||
try self.decorate(ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } });
|
||||
}
|
||||
},
|
||||
.function => {
|
||||
const info = ty.payload(.function);
|
||||
try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len);
|
||||
types.writeOperand(IdResult, result_id);
|
||||
types.writeOperand(IdRef, self.typeResultId(info.return_type).toRef());
|
||||
types.writeOperand(IdRef, self.typeId(info.return_type));
|
||||
for (info.parameters) |parameter_type| {
|
||||
types.writeOperand(IdRef, self.typeResultId(parameter_type).toRef());
|
||||
types.writeOperand(IdRef, self.typeId(parameter_type));
|
||||
}
|
||||
},
|
||||
.event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand),
|
||||
@ -368,23 +601,30 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType {
|
||||
.named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand),
|
||||
}
|
||||
|
||||
return result_id.toResultType();
|
||||
return result_id;
|
||||
}
|
||||
|
||||
fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void {
|
||||
const annotations = &self.sections.annotations;
|
||||
const debug_names = &self.sections.debug_names;
|
||||
|
||||
if (info.name.len != 0) {
|
||||
try debug_names.emit(self.gpa, .OpName, .{
|
||||
.target = target,
|
||||
.name = info.name,
|
||||
});
|
||||
}
|
||||
|
||||
// Decorations for the struct type itself.
|
||||
if (info.decorations.block)
|
||||
try annotations.decorate(self.gpa, target, .Block);
|
||||
try self.decorate(target, .Block);
|
||||
if (info.decorations.buffer_block)
|
||||
try annotations.decorate(self.gpa, target, .BufferBlock);
|
||||
try self.decorate(target, .BufferBlock);
|
||||
if (info.decorations.glsl_shared)
|
||||
try annotations.decorate(self.gpa, target, .GLSLShared);
|
||||
try self.decorate(target, .GLSLShared);
|
||||
if (info.decorations.glsl_packed)
|
||||
try annotations.decorate(self.gpa, target, .GLSLPacked);
|
||||
try self.decorate(target, .GLSLPacked);
|
||||
if (info.decorations.c_packed)
|
||||
try annotations.decorate(self.gpa, target, .CPacked);
|
||||
try self.decorate(target, .CPacked);
|
||||
|
||||
// Decorations for the struct members.
|
||||
const extra = info.member_decoration_extra;
|
||||
@ -392,71 +632,89 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct
|
||||
for (info.members, 0..) |member, i| {
|
||||
const d = member.decorations;
|
||||
const index = @intCast(Word, i);
|
||||
|
||||
if (member.name.len != 0) {
|
||||
try debug_names.emit(self.gpa, .OpMemberName, .{
|
||||
.type = target,
|
||||
.member = index,
|
||||
.name = member.name,
|
||||
});
|
||||
}
|
||||
|
||||
switch (member.offset) {
|
||||
.none => {},
|
||||
else => try self.decorateMember(
|
||||
target,
|
||||
index,
|
||||
.{ .Offset = .{ .byte_offset = @enumToInt(member.offset) } },
|
||||
),
|
||||
}
|
||||
|
||||
switch (d.matrix_layout) {
|
||||
.row_major => try annotations.decorateMember(self.gpa, target, index, .RowMajor),
|
||||
.col_major => try annotations.decorateMember(self.gpa, target, index, .ColMajor),
|
||||
.row_major => try self.decorateMember(target, index, .RowMajor),
|
||||
.col_major => try self.decorateMember(target, index, .ColMajor),
|
||||
.none => {},
|
||||
}
|
||||
if (d.matrix_layout != .none) {
|
||||
try annotations.decorateMember(self.gpa, target, index, .{
|
||||
try self.decorateMember(target, index, .{
|
||||
.MatrixStride = .{ .matrix_stride = extra[extra_i] },
|
||||
});
|
||||
extra_i += 1;
|
||||
}
|
||||
|
||||
if (d.no_perspective)
|
||||
try annotations.decorateMember(self.gpa, target, index, .NoPerspective);
|
||||
try self.decorateMember(target, index, .NoPerspective);
|
||||
if (d.flat)
|
||||
try annotations.decorateMember(self.gpa, target, index, .Flat);
|
||||
try self.decorateMember(target, index, .Flat);
|
||||
if (d.patch)
|
||||
try annotations.decorateMember(self.gpa, target, index, .Patch);
|
||||
try self.decorateMember(target, index, .Patch);
|
||||
if (d.centroid)
|
||||
try annotations.decorateMember(self.gpa, target, index, .Centroid);
|
||||
try self.decorateMember(target, index, .Centroid);
|
||||
if (d.sample)
|
||||
try annotations.decorateMember(self.gpa, target, index, .Sample);
|
||||
try self.decorateMember(target, index, .Sample);
|
||||
if (d.invariant)
|
||||
try annotations.decorateMember(self.gpa, target, index, .Invariant);
|
||||
try self.decorateMember(target, index, .Invariant);
|
||||
if (d.@"volatile")
|
||||
try annotations.decorateMember(self.gpa, target, index, .Volatile);
|
||||
try self.decorateMember(target, index, .Volatile);
|
||||
if (d.coherent)
|
||||
try annotations.decorateMember(self.gpa, target, index, .Coherent);
|
||||
try self.decorateMember(target, index, .Coherent);
|
||||
if (d.non_writable)
|
||||
try annotations.decorateMember(self.gpa, target, index, .NonWritable);
|
||||
try self.decorateMember(target, index, .NonWritable);
|
||||
if (d.non_readable)
|
||||
try annotations.decorateMember(self.gpa, target, index, .NonReadable);
|
||||
try self.decorateMember(target, index, .NonReadable);
|
||||
|
||||
if (d.builtin) {
|
||||
try annotations.decorateMember(self.gpa, target, index, .{
|
||||
try self.decorateMember(target, index, .{
|
||||
.BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) },
|
||||
});
|
||||
extra_i += 1;
|
||||
}
|
||||
if (d.stream) {
|
||||
try annotations.decorateMember(self.gpa, target, index, .{
|
||||
try self.decorateMember(target, index, .{
|
||||
.Stream = .{ .stream_number = extra[extra_i] },
|
||||
});
|
||||
extra_i += 1;
|
||||
}
|
||||
if (d.location) {
|
||||
try annotations.decorateMember(self.gpa, target, index, .{
|
||||
try self.decorateMember(target, index, .{
|
||||
.Location = .{ .location = extra[extra_i] },
|
||||
});
|
||||
extra_i += 1;
|
||||
}
|
||||
if (d.component) {
|
||||
try annotations.decorateMember(self.gpa, target, index, .{
|
||||
try self.decorateMember(target, index, .{
|
||||
.Component = .{ .component = extra[extra_i] },
|
||||
});
|
||||
extra_i += 1;
|
||||
}
|
||||
if (d.xfb_buffer) {
|
||||
try annotations.decorateMember(self.gpa, target, index, .{
|
||||
try self.decorateMember(target, index, .{
|
||||
.XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] },
|
||||
});
|
||||
extra_i += 1;
|
||||
}
|
||||
if (d.xfb_stride) {
|
||||
try annotations.decorateMember(self.gpa, target, index, .{
|
||||
try self.decorateMember(target, index, .{
|
||||
.XfbStride = .{ .xfb_stride = extra[extra_i] },
|
||||
});
|
||||
extra_i += 1;
|
||||
@ -465,10 +723,150 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct
|
||||
const len = extra[extra_i];
|
||||
extra_i += 1;
|
||||
const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len];
|
||||
try annotations.decorateMember(self.gpa, target, index, .{
|
||||
try self.decorateMember(target, index, .{
|
||||
.UserSemantic = .{ .semantic = semantic },
|
||||
});
|
||||
extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simpleStructType(self: *Module, members: []const Type.Payload.Struct.Member) !Type.Ref {
|
||||
const payload = try self.arena.create(Type.Payload.Struct);
|
||||
payload.* = .{
|
||||
.members = try self.arena.dupe(Type.Payload.Struct.Member, members),
|
||||
.decorations = .{},
|
||||
};
|
||||
return try self.resolveType(Type.initPayload(&payload.base));
|
||||
}
|
||||
|
||||
pub fn arrayType(self: *Module, len: u32, ty: Type.Ref) !Type.Ref {
|
||||
const payload = try self.arena.create(Type.Payload.Array);
|
||||
payload.* = .{
|
||||
.element_type = ty,
|
||||
.length = len,
|
||||
};
|
||||
return try self.resolveType(Type.initPayload(&payload.base));
|
||||
}
|
||||
|
||||
pub fn ptrType(
|
||||
self: *Module,
|
||||
child: Type.Ref,
|
||||
storage_class: spec.StorageClass,
|
||||
alignment: u32,
|
||||
) !Type.Ref {
|
||||
const ptr_payload = try self.arena.create(Type.Payload.Pointer);
|
||||
ptr_payload.* = .{
|
||||
.storage_class = storage_class,
|
||||
.child_type = child,
|
||||
.alignment = alignment,
|
||||
};
|
||||
return try self.resolveType(Type.initPayload(&ptr_payload.base));
|
||||
}
|
||||
|
||||
pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref {
|
||||
const payload = try self.arena.create(Type.Payload.Pointer);
|
||||
payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*;
|
||||
payload.storage_class = new_storage_class;
|
||||
return try self.resolveType(Type.initPayload(&payload.base));
|
||||
}
|
||||
|
||||
pub fn emitConstant(
|
||||
self: *Module,
|
||||
ty_id: IdRef,
|
||||
result_id: IdRef,
|
||||
value: spec.LiteralContextDependentNumber,
|
||||
) !void {
|
||||
try self.sections.types_globals_constants.emit(self.gpa, .OpConstant, .{
|
||||
.id_result_type = ty_id,
|
||||
.id_result = result_id,
|
||||
.value = value,
|
||||
});
|
||||
}
|
||||
|
||||
/// Decorate a result-id.
|
||||
pub fn decorate(
|
||||
self: *Module,
|
||||
target: IdRef,
|
||||
decoration: spec.Decoration.Extended,
|
||||
) !void {
|
||||
try self.sections.annotations.emit(self.gpa, .OpDecorate, .{
|
||||
.target = target,
|
||||
.decoration = decoration,
|
||||
});
|
||||
}
|
||||
|
||||
/// Decorate a result-id which is a member of some struct.
|
||||
pub fn decorateMember(
|
||||
self: *Module,
|
||||
structure_type: IdRef,
|
||||
member: u32,
|
||||
decoration: spec.Decoration.Extended,
|
||||
) !void {
|
||||
try self.sections.annotations.emit(self.gpa, .OpMemberDecorate, .{
|
||||
.structure_type = structure_type,
|
||||
.member = member,
|
||||
.decoration = decoration,
|
||||
});
|
||||
}
|
||||
|
||||
pub const DeclKind = enum {
|
||||
func,
|
||||
global,
|
||||
};
|
||||
|
||||
pub fn allocDecl(self: *Module, kind: DeclKind) !Decl.Index {
|
||||
try self.decls.append(self.gpa, .{
|
||||
.result_id = self.allocId(),
|
||||
.begin_dep = undefined,
|
||||
.end_dep = undefined,
|
||||
});
|
||||
const index = @intToEnum(Decl.Index, @intCast(u32, self.decls.items.len - 1));
|
||||
switch (kind) {
|
||||
.func => {},
|
||||
// If the decl represents a global, also allocate a global node.
|
||||
.global => try self.globals.globals.putNoClobber(self.gpa, index, .{
|
||||
.result_id = undefined,
|
||||
.begin_inst = undefined,
|
||||
.end_inst = undefined,
|
||||
}),
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
pub fn declPtr(self: *Module, index: Decl.Index) *Decl {
|
||||
return &self.decls.items[@enumToInt(index)];
|
||||
}
|
||||
|
||||
pub fn globalPtr(self: *Module, index: Decl.Index) ?*Global {
|
||||
return self.globals.globals.getPtr(index);
|
||||
}
|
||||
|
||||
/// Declare ALL dependencies for a decl.
|
||||
pub fn declareDeclDeps(self: *Module, decl_index: Decl.Index, deps: []const Decl.Index) !void {
|
||||
const begin_dep = @intCast(u32, self.decl_deps.items.len);
|
||||
try self.decl_deps.appendSlice(self.gpa, deps);
|
||||
const end_dep = @intCast(u32, self.decl_deps.items.len);
|
||||
|
||||
const decl = self.declPtr(decl_index);
|
||||
decl.begin_dep = begin_dep;
|
||||
decl.end_dep = end_dep;
|
||||
}
|
||||
|
||||
pub fn beginGlobal(self: *Module) u32 {
|
||||
return @intCast(u32, self.globals.section.instructions.items.len);
|
||||
}
|
||||
|
||||
pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32) void {
|
||||
const global = self.globalPtr(global_index).?;
|
||||
global.begin_inst = begin_inst;
|
||||
global.end_inst = @intCast(u32, self.globals.section.instructions.items.len);
|
||||
}
|
||||
|
||||
pub fn declareEntryPoint(self: *Module, decl_index: Decl.Index, name: []const u8) !void {
|
||||
try self.entry_points.append(self.gpa, .{
|
||||
.decl_index = decl_index,
|
||||
.name = try self.arena.dupe(u8, name),
|
||||
});
|
||||
}
|
||||
|
||||
@ -65,32 +65,23 @@ pub fn emit(
|
||||
section.writeOperands(opcode.Operands(), operands);
|
||||
}
|
||||
|
||||
/// Decorate a result-id.
|
||||
pub fn decorate(
|
||||
pub fn emitSpecConstantOp(
|
||||
section: *Section,
|
||||
allocator: Allocator,
|
||||
target: spec.IdRef,
|
||||
decoration: spec.Decoration.Extended,
|
||||
comptime opcode: spec.Opcode,
|
||||
operands: opcode.Operands(),
|
||||
) !void {
|
||||
try section.emit(allocator, .OpDecorate, .{
|
||||
.target = target,
|
||||
.decoration = decoration,
|
||||
});
|
||||
}
|
||||
const word_count = operandsSize(opcode.Operands(), operands);
|
||||
try section.emitRaw(allocator, .OpSpecConstantOp, 1 + word_count);
|
||||
section.writeOperand(spec.IdRef, operands.id_result_type);
|
||||
section.writeOperand(spec.IdRef, operands.id_result);
|
||||
section.writeOperand(Opcode, opcode);
|
||||
|
||||
/// Decorate a result-id which is a member of some struct.
|
||||
pub fn decorateMember(
|
||||
section: *Section,
|
||||
allocator: Allocator,
|
||||
structure_type: spec.IdRef,
|
||||
member: u32,
|
||||
decoration: spec.Decoration.Extended,
|
||||
) !void {
|
||||
try section.emit(allocator, .OpMemberDecorate, .{
|
||||
.structure_type = structure_type,
|
||||
.member = member,
|
||||
.decoration = decoration,
|
||||
});
|
||||
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 {
|
||||
@ -122,7 +113,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
|
||||
|
||||
pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
|
||||
switch (Operand) {
|
||||
spec.IdResultType, spec.IdResult, spec.IdRef => section.writeWord(operand.id),
|
||||
spec.IdResult => section.writeWord(operand.id),
|
||||
|
||||
spec.LiteralInteger => section.writeWord(operand),
|
||||
|
||||
@ -258,9 +249,7 @@ fn operandsSize(comptime Operands: type, operands: Operands) usize {
|
||||
|
||||
fn operandSize(comptime Operand: type, operand: Operand) usize {
|
||||
return switch (Operand) {
|
||||
spec.IdResultType,
|
||||
spec.IdResult,
|
||||
spec.IdRef,
|
||||
spec.LiteralInteger,
|
||||
spec.LiteralExtInstInteger,
|
||||
=> 1,
|
||||
@ -382,7 +371,9 @@ test "SPIR-V Section emit() - string" {
|
||||
}, section.instructions.items);
|
||||
}
|
||||
|
||||
test "SPIR-V Section emit()- extended mask" {
|
||||
test "SPIR-V Section emit() - extended mask" {
|
||||
if (@import("builtin").zig_backend == .stage1) return error.SkipZigTest;
|
||||
|
||||
var section = Section{};
|
||||
defer section.deinit(std.testing.allocator);
|
||||
|
||||
|
||||
@ -3,22 +3,11 @@
|
||||
const Version = @import("std").builtin.Version;
|
||||
|
||||
pub const Word = u32;
|
||||
pub const IdResultType = struct {
|
||||
id: Word,
|
||||
pub fn toRef(self: IdResultType) IdRef {
|
||||
return .{ .id = self.id };
|
||||
}
|
||||
};
|
||||
pub const IdResult = struct {
|
||||
id: Word,
|
||||
pub fn toRef(self: IdResult) IdRef {
|
||||
return .{ .id = self.id };
|
||||
}
|
||||
pub fn toResultType(self: IdResult) IdResultType {
|
||||
return .{ .id = self.id };
|
||||
}
|
||||
};
|
||||
pub const IdRef = struct { id: Word };
|
||||
pub const IdResultType = IdResult;
|
||||
pub const IdRef = IdResult;
|
||||
|
||||
pub const IdMemorySemantics = IdRef;
|
||||
pub const IdScope = IdRef;
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Signedness = std.builtin.Signedness;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const spec = @import("spec.zig");
|
||||
|
||||
@ -11,7 +13,7 @@ pub const Type = extern union {
|
||||
ptr_otherwise: *Payload,
|
||||
|
||||
/// A reference to another SPIR-V type.
|
||||
pub const Ref = usize;
|
||||
pub const Ref = enum(u32) { _ };
|
||||
|
||||
pub fn initTag(comptime small_tag: Tag) Type {
|
||||
comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
|
||||
@ -23,6 +25,41 @@ pub const Type = extern union {
|
||||
return .{ .ptr_otherwise = pl };
|
||||
}
|
||||
|
||||
pub fn int(arena: Allocator, signedness: Signedness, bits: u16) !Type {
|
||||
const bits_and_signedness = switch (signedness) {
|
||||
.signed => -@as(i32, bits),
|
||||
.unsigned => @as(i32, bits),
|
||||
};
|
||||
|
||||
return switch (bits_and_signedness) {
|
||||
8 => initTag(.u8),
|
||||
16 => initTag(.u16),
|
||||
32 => initTag(.u32),
|
||||
64 => initTag(.u64),
|
||||
-8 => initTag(.i8),
|
||||
-16 => initTag(.i16),
|
||||
-32 => initTag(.i32),
|
||||
-64 => initTag(.i64),
|
||||
else => {
|
||||
const int_payload = try arena.create(Payload.Int);
|
||||
int_payload.* = .{
|
||||
.width = bits,
|
||||
.signedness = signedness,
|
||||
};
|
||||
return initPayload(&int_payload.base);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn float(bits: u16) Type {
|
||||
return switch (bits) {
|
||||
16 => initTag(.f16),
|
||||
32 => initTag(.f32),
|
||||
64 => initTag(.f64),
|
||||
else => unreachable, // Enable more types if required.
|
||||
};
|
||||
}
|
||||
|
||||
pub fn tag(self: Type) Tag {
|
||||
if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) {
|
||||
return self.tag_if_small_enough;
|
||||
@ -80,9 +117,19 @@ pub const Type = extern union {
|
||||
.queue,
|
||||
.pipe_storage,
|
||||
.named_barrier,
|
||||
.u8,
|
||||
.u16,
|
||||
.u32,
|
||||
.u64,
|
||||
.i8,
|
||||
.i16,
|
||||
.i32,
|
||||
.i64,
|
||||
.f16,
|
||||
.f32,
|
||||
.f64,
|
||||
=> return true,
|
||||
.int,
|
||||
.float,
|
||||
.vector,
|
||||
.matrix,
|
||||
.sampled_image,
|
||||
@ -132,6 +179,17 @@ pub const Type = extern union {
|
||||
.queue,
|
||||
.pipe_storage,
|
||||
.named_barrier,
|
||||
.u8,
|
||||
.u16,
|
||||
.u32,
|
||||
.u64,
|
||||
.i8,
|
||||
.i16,
|
||||
.i32,
|
||||
.i64,
|
||||
.f16,
|
||||
.f32,
|
||||
.f64,
|
||||
=> {},
|
||||
else => self.hashPayload(@field(Tag, field.name), &hasher),
|
||||
}
|
||||
@ -185,6 +243,53 @@ pub const Type = extern union {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isInt(self: Type) bool {
|
||||
return switch (self.tag()) {
|
||||
.u8,
|
||||
.u16,
|
||||
.u32,
|
||||
.u64,
|
||||
.i8,
|
||||
.i16,
|
||||
.i32,
|
||||
.i64,
|
||||
.int,
|
||||
=> true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isFloat(self: Type) bool {
|
||||
return switch (self.tag()) {
|
||||
.f16, .f32, .f64 => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the number of bits that make up an int or float type.
|
||||
/// Asserts type is either int or float.
|
||||
pub fn intFloatBits(self: Type) u16 {
|
||||
return switch (self.tag()) {
|
||||
.u8, .i8 => 8,
|
||||
.u16, .i16, .f16 => 16,
|
||||
.u32, .i32, .f32 => 32,
|
||||
.u64, .i64, .f64 => 64,
|
||||
.int => self.payload(.int).width,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the signedness of an integer type.
|
||||
/// Asserts that the type is an int.
|
||||
pub fn intSignedness(self: Type) Signedness {
|
||||
return switch (self.tag()) {
|
||||
.u8, .u16, .u32, .u64 => .unsigned,
|
||||
.i8, .i16, .i32, .i64 => .signed,
|
||||
.int => self.payload(.int).signedness,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub const Tag = enum(usize) {
|
||||
void,
|
||||
bool,
|
||||
@ -195,10 +300,20 @@ pub const Type = extern union {
|
||||
queue,
|
||||
pipe_storage,
|
||||
named_barrier,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
i64,
|
||||
f16,
|
||||
f32,
|
||||
f64,
|
||||
|
||||
// After this, the tag requires a payload.
|
||||
int,
|
||||
float,
|
||||
vector,
|
||||
matrix,
|
||||
image,
|
||||
@ -211,14 +326,33 @@ pub const Type = extern union {
|
||||
function,
|
||||
pipe,
|
||||
|
||||
pub const last_no_payload_tag = Tag.named_barrier;
|
||||
pub const last_no_payload_tag = Tag.f64;
|
||||
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
|
||||
|
||||
pub fn Type(comptime t: Tag) type {
|
||||
return switch (t) {
|
||||
.void, .bool, .sampler, .event, .device_event, .reserve_id, .queue, .pipe_storage, .named_barrier => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
|
||||
.void,
|
||||
.bool,
|
||||
.sampler,
|
||||
.event,
|
||||
.device_event,
|
||||
.reserve_id,
|
||||
.queue,
|
||||
.pipe_storage,
|
||||
.named_barrier,
|
||||
.u8,
|
||||
.u16,
|
||||
.u32,
|
||||
.u64,
|
||||
.i8,
|
||||
.i16,
|
||||
.i32,
|
||||
.i64,
|
||||
.f16,
|
||||
.f32,
|
||||
.f64,
|
||||
=> @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
|
||||
.int => Payload.Int,
|
||||
.float => Payload.Float,
|
||||
.vector => Payload.Vector,
|
||||
.matrix => Payload.Matrix,
|
||||
.image => Payload.Image,
|
||||
@ -239,13 +373,8 @@ pub const Type = extern union {
|
||||
|
||||
pub const Int = struct {
|
||||
base: Payload = .{ .tag = .int },
|
||||
width: u32,
|
||||
signedness: std.builtin.Signedness,
|
||||
};
|
||||
|
||||
pub const Float = struct {
|
||||
base: Payload = .{ .tag = .float },
|
||||
width: u32,
|
||||
width: u16,
|
||||
signedness: Signedness,
|
||||
};
|
||||
|
||||
pub const Vector = struct {
|
||||
@ -292,7 +421,7 @@ pub const Type = extern union {
|
||||
length: u32,
|
||||
/// Type has the 'ArrayStride' decoration.
|
||||
/// If zero, no stride is present.
|
||||
array_stride: u32,
|
||||
array_stride: u32 = 0,
|
||||
};
|
||||
|
||||
pub const RuntimeArray = struct {
|
||||
@ -300,35 +429,39 @@ pub const Type = extern union {
|
||||
element_type: Ref,
|
||||
/// Type has the 'ArrayStride' decoration.
|
||||
/// If zero, no stride is present.
|
||||
array_stride: u32,
|
||||
array_stride: u32 = 0,
|
||||
};
|
||||
|
||||
pub const Struct = struct {
|
||||
base: Payload = .{ .tag = .@"struct" },
|
||||
members: []Member,
|
||||
decorations: StructDecorations,
|
||||
name: []const u8 = "",
|
||||
decorations: StructDecorations = .{},
|
||||
|
||||
/// Extra information for decorations, packed for efficiency. Fields are stored sequentially by
|
||||
/// order of the `members` slice and `MemberDecorations` struct.
|
||||
member_decoration_extra: []u32,
|
||||
member_decoration_extra: []u32 = &.{},
|
||||
|
||||
pub const Member = struct {
|
||||
ty: Ref,
|
||||
offset: u32,
|
||||
decorations: MemberDecorations,
|
||||
name: []const u8 = "",
|
||||
offset: MemberOffset = .none,
|
||||
decorations: MemberDecorations = .{},
|
||||
};
|
||||
|
||||
pub const MemberOffset = enum(u32) { none = 0xFFFF_FFFF, _ };
|
||||
|
||||
pub const StructDecorations = packed struct {
|
||||
/// Type has the 'Block' decoration.
|
||||
block: bool,
|
||||
block: bool = false,
|
||||
/// Type has the 'BufferBlock' decoration.
|
||||
buffer_block: bool,
|
||||
buffer_block: bool = false,
|
||||
/// Type has the 'GLSLShared' decoration.
|
||||
glsl_shared: bool,
|
||||
glsl_shared: bool = false,
|
||||
/// Type has the 'GLSLPacked' decoration.
|
||||
glsl_packed: bool,
|
||||
glsl_packed: bool = false,
|
||||
/// Type has the 'CPacked' decoration.
|
||||
c_packed: bool,
|
||||
c_packed: bool = false,
|
||||
};
|
||||
|
||||
pub const MemberDecorations = packed struct {
|
||||
@ -344,31 +477,31 @@ pub const Type = extern union {
|
||||
col_major,
|
||||
/// Member is not a matrix or array of matrices.
|
||||
none,
|
||||
},
|
||||
} = .none,
|
||||
|
||||
// Regular decorations, these do not imply extra fields.
|
||||
|
||||
/// Member has the 'NoPerspective' decoration.
|
||||
no_perspective: bool,
|
||||
no_perspective: bool = false,
|
||||
/// Member has the 'Flat' decoration.
|
||||
flat: bool,
|
||||
flat: bool = false,
|
||||
/// Member has the 'Patch' decoration.
|
||||
patch: bool,
|
||||
patch: bool = false,
|
||||
/// Member has the 'Centroid' decoration.
|
||||
centroid: bool,
|
||||
centroid: bool = false,
|
||||
/// Member has the 'Sample' decoration.
|
||||
sample: bool,
|
||||
sample: bool = false,
|
||||
/// Member has the 'Invariant' decoration.
|
||||
/// Note: requires parent struct to have 'Block'.
|
||||
invariant: bool,
|
||||
invariant: bool = false,
|
||||
/// Member has the 'Volatile' decoration.
|
||||
@"volatile": bool,
|
||||
@"volatile": bool = false,
|
||||
/// Member has the 'Coherent' decoration.
|
||||
coherent: bool,
|
||||
coherent: bool = false,
|
||||
/// Member has the 'NonWritable' decoration.
|
||||
non_writable: bool,
|
||||
non_writable: bool = false,
|
||||
/// Member has the 'NonReadable' decoration.
|
||||
non_readable: bool,
|
||||
non_readable: bool = false,
|
||||
|
||||
// The following decorations all imply extra field(s).
|
||||
|
||||
@ -377,27 +510,27 @@ pub const Type = extern union {
|
||||
/// Note: If any member of a struct has the BuiltIn decoration, all members must have one.
|
||||
/// Note: Each builtin may only be reachable once for a particular entry point.
|
||||
/// Note: The member type may be constrained by a particular built-in, defined in the client API specification.
|
||||
builtin: bool,
|
||||
builtin: bool = false,
|
||||
/// Member has the 'Stream' decoration.
|
||||
/// This member has an extra field of type `u32`.
|
||||
stream: bool,
|
||||
stream: bool = false,
|
||||
/// Member has the 'Location' decoration.
|
||||
/// This member has an extra field of type `u32`.
|
||||
location: bool,
|
||||
location: bool = false,
|
||||
/// Member has the 'Component' decoration.
|
||||
/// This member has an extra field of type `u32`.
|
||||
component: bool,
|
||||
component: bool = false,
|
||||
/// Member has the 'XfbBuffer' decoration.
|
||||
/// This member has an extra field of type `u32`.
|
||||
xfb_buffer: bool,
|
||||
xfb_buffer: bool = false,
|
||||
/// Member has the 'XfbStride' decoration.
|
||||
/// This member has an extra field of type `u32`.
|
||||
xfb_stride: bool,
|
||||
xfb_stride: bool = false,
|
||||
/// Member has the 'UserSemantic' decoration.
|
||||
/// This member has an extra field of type `[]u8`, which is encoded
|
||||
/// by an `u32` containing the number of chars exactly, and then the string padded to
|
||||
/// a multiple of 4 bytes with zeroes.
|
||||
user_semantic: bool,
|
||||
user_semantic: bool = false,
|
||||
};
|
||||
};
|
||||
|
||||
@ -413,11 +546,11 @@ pub const Type = extern union {
|
||||
/// Type has the 'ArrayStride' decoration.
|
||||
/// This is valid for pointers to elements of an array.
|
||||
/// If zero, no stride is present.
|
||||
array_stride: u32,
|
||||
/// Type has the 'Alignment' decoration.
|
||||
alignment: ?u32,
|
||||
array_stride: u32 = 0,
|
||||
/// If nonzero, type has the 'Alignment' decoration.
|
||||
alignment: u32 = 0,
|
||||
/// Type has the 'MaxByteOffset' decoration.
|
||||
max_byte_offset: ?u32,
|
||||
max_byte_offset: ?u32 = null,
|
||||
};
|
||||
|
||||
pub const Function = struct {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
//! NVidia PTX (Paralle Thread Execution)
|
||||
//! https://docs.nvidia.com/cuda/parallel-thread-execution/index.html
|
||||
//! For this we rely on the nvptx backend of LLVM
|
||||
//! Kernel functions need to be marked both as "export" and "callconv(.PtxKernel)"
|
||||
//! Kernel functions need to be marked both as "export" and "callconv(.Kernel)"
|
||||
|
||||
const NvPtx = @This();
|
||||
|
||||
|
||||
@ -44,34 +44,25 @@ const IdResult = spec.IdResult;
|
||||
|
||||
base: link.File,
|
||||
|
||||
/// This linker backend does not try to incrementally link output SPIR-V code.
|
||||
/// Instead, it tracks all declarations in this table, and iterates over it
|
||||
/// in the flush function.
|
||||
decl_table: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, DeclGenContext) = .{},
|
||||
|
||||
const DeclGenContext = struct {
|
||||
air: Air,
|
||||
air_arena: ArenaAllocator.State,
|
||||
liveness: Liveness,
|
||||
|
||||
fn deinit(self: *DeclGenContext, gpa: Allocator) void {
|
||||
self.air.deinit(gpa);
|
||||
self.liveness.deinit(gpa);
|
||||
self.air_arena.promote(gpa).deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
};
|
||||
spv: SpvModule,
|
||||
spv_arena: ArenaAllocator,
|
||||
decl_link: codegen.DeclLinkMap,
|
||||
|
||||
pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {
|
||||
const spirv = try gpa.create(SpirV);
|
||||
spirv.* = .{
|
||||
const self = try gpa.create(SpirV);
|
||||
self.* = .{
|
||||
.base = .{
|
||||
.tag = .spirv,
|
||||
.options = options,
|
||||
.file = null,
|
||||
.allocator = gpa,
|
||||
},
|
||||
.spv = undefined,
|
||||
.spv_arena = ArenaAllocator.init(gpa),
|
||||
.decl_link = codegen.DeclLinkMap.init(self.base.allocator),
|
||||
};
|
||||
self.spv = SpvModule.init(gpa, self.spv_arena.allocator());
|
||||
errdefer self.deinit();
|
||||
|
||||
// TODO: Figure out where to put all of these
|
||||
switch (options.target.cpu.arch) {
|
||||
@ -88,7 +79,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {
|
||||
return error.TODOAbiNotSupported;
|
||||
}
|
||||
|
||||
return spirv;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*SpirV {
|
||||
@ -107,44 +98,35 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
|
||||
}
|
||||
|
||||
pub fn deinit(self: *SpirV) void {
|
||||
self.decl_table.deinit(self.base.allocator);
|
||||
self.spv.deinit();
|
||||
self.spv_arena.deinit();
|
||||
self.decl_link.deinit();
|
||||
}
|
||||
|
||||
pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
|
||||
if (build_options.skip_non_native) {
|
||||
@panic("Attempted to compile for architecture that was disabled by build configuration");
|
||||
}
|
||||
_ = module;
|
||||
|
||||
// Keep track of all decls so we can iterate over them on flush().
|
||||
const result = try self.decl_table.getOrPut(self.base.allocator, func.owner_decl);
|
||||
if (result.found_existing) {
|
||||
result.value_ptr.deinit(self.base.allocator);
|
||||
var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link);
|
||||
defer decl_gen.deinit();
|
||||
|
||||
if (try decl_gen.gen(func.owner_decl, air, liveness)) |msg| {
|
||||
try module.failed_decls.put(module.gpa, func.owner_decl, msg);
|
||||
}
|
||||
|
||||
var arena = ArenaAllocator.init(self.base.allocator);
|
||||
errdefer arena.deinit();
|
||||
|
||||
var new_air = try cloneAir(air, self.base.allocator, arena.allocator());
|
||||
errdefer new_air.deinit(self.base.allocator);
|
||||
|
||||
var new_liveness = try cloneLiveness(liveness, self.base.allocator);
|
||||
errdefer new_liveness.deinit(self.base.allocator);
|
||||
|
||||
result.value_ptr.* = .{
|
||||
.air = new_air,
|
||||
.air_arena = arena.state,
|
||||
.liveness = new_liveness,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index) !void {
|
||||
if (build_options.skip_non_native) {
|
||||
@panic("Attempted to compile for architecture that was disabled by build configuration");
|
||||
}
|
||||
_ = module;
|
||||
// Keep track of all decls so we can iterate over them on flush().
|
||||
_ = try self.decl_table.getOrPut(self.base.allocator, decl_index);
|
||||
|
||||
var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link);
|
||||
defer decl_gen.deinit();
|
||||
|
||||
if (try decl_gen.gen(decl_index, undefined, undefined)) |msg| {
|
||||
try module.failed_decls.put(module.gpa, decl_index, msg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn updateDeclExports(
|
||||
@ -153,20 +135,26 @@ pub fn updateDeclExports(
|
||||
decl_index: Module.Decl.Index,
|
||||
exports: []const *Module.Export,
|
||||
) !void {
|
||||
_ = self;
|
||||
_ = module;
|
||||
_ = decl_index;
|
||||
_ = exports;
|
||||
const decl = module.declPtr(decl_index);
|
||||
if (decl.val.tag() == .function and decl.ty.fnCallingConvention() == .Kernel) {
|
||||
// TODO: Unify with resolveDecl in spirv.zig.
|
||||
const entry = try self.decl_link.getOrPut(decl_index);
|
||||
if (!entry.found_existing) {
|
||||
entry.value_ptr.* = try self.spv.allocDecl(.func);
|
||||
}
|
||||
const spv_decl_index = entry.value_ptr.*;
|
||||
|
||||
for (exports) |exp| {
|
||||
try self.spv.declareEntryPoint(spv_decl_index, exp.options.name);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Export regular functions, variables, etc using Linkage attributes.
|
||||
}
|
||||
|
||||
pub fn freeDecl(self: *SpirV, decl_index: Module.Decl.Index) void {
|
||||
if (self.decl_table.getIndex(decl_index)) |index| {
|
||||
const module = self.base.options.module.?;
|
||||
const decl = module.declPtr(decl_index);
|
||||
if (decl.val.tag() == .function) {
|
||||
self.decl_table.values()[index].deinit(self.base.allocator);
|
||||
}
|
||||
}
|
||||
_ = self;
|
||||
_ = decl_index;
|
||||
}
|
||||
|
||||
pub fn flush(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
|
||||
@ -189,60 +177,38 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No
|
||||
sub_prog_node.activate();
|
||||
defer sub_prog_node.end();
|
||||
|
||||
const module = self.base.options.module.?;
|
||||
const target = comp.getTarget();
|
||||
try writeCapabilities(&self.spv, target);
|
||||
try writeMemoryModel(&self.spv, target);
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(self.base.allocator);
|
||||
defer arena.deinit();
|
||||
// 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
|
||||
// nonsemantic instruction. For now, just put it in OpSourceExtension with a special name.
|
||||
|
||||
var spv = SpvModule.init(self.base.allocator, arena.allocator());
|
||||
defer spv.deinit();
|
||||
var error_info = std.ArrayList(u8).init(self.spv.arena);
|
||||
try error_info.appendSlice("zig_errors");
|
||||
const module = self.base.options.module.?;
|
||||
for (module.error_name_list.items) |name| {
|
||||
// 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
|
||||
// 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.
|
||||
|
||||
// Allocate an ID for every declaration before generating code,
|
||||
// so that we can access them before processing them.
|
||||
// TODO: We're allocating an ID unconditionally now, are there
|
||||
// declarations which don't generate a result?
|
||||
var ids = std.AutoHashMap(Module.Decl.Index, IdResult).init(self.base.allocator);
|
||||
defer ids.deinit();
|
||||
try ids.ensureTotalCapacity(@intCast(u32, self.decl_table.count()));
|
||||
|
||||
for (self.decl_table.keys()) |decl_index| {
|
||||
const decl = module.declPtr(decl_index);
|
||||
if (decl.has_tv) {
|
||||
ids.putAssumeCapacityNoClobber(decl_index, spv.allocId());
|
||||
}
|
||||
const escaped_name = try std.Uri.escapeString(self.base.allocator, name);
|
||||
defer self.base.allocator.free(escaped_name);
|
||||
try error_info.writer().print(":{s}", .{escaped_name});
|
||||
}
|
||||
try self.spv.sections.debug_strings.emit(self.spv.gpa, .OpSourceExtension, .{
|
||||
.extension = error_info.items,
|
||||
});
|
||||
|
||||
// Now, actually generate the code for all declarations.
|
||||
var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &spv, &ids);
|
||||
defer decl_gen.deinit();
|
||||
|
||||
var it = self.decl_table.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const decl_index = entry.key_ptr.*;
|
||||
const decl = module.declPtr(decl_index);
|
||||
if (!decl.has_tv) continue;
|
||||
|
||||
const air = entry.value_ptr.air;
|
||||
const liveness = entry.value_ptr.liveness;
|
||||
|
||||
// Note, if `decl` is not a function, air/liveness may be undefined.
|
||||
if (try decl_gen.gen(decl_index, air, liveness)) |msg| {
|
||||
try module.failed_decls.put(module.gpa, decl_index, msg);
|
||||
return; // TODO: Attempt to generate more decls?
|
||||
}
|
||||
}
|
||||
|
||||
try writeCapabilities(&spv, target);
|
||||
try writeMemoryModel(&spv, target);
|
||||
|
||||
try spv.flush(self.base.file.?);
|
||||
try self.spv.flush(self.base.file.?);
|
||||
}
|
||||
|
||||
fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
|
||||
// TODO: Integrate with a hypothetical feature system
|
||||
const caps: []const spec.Capability = switch (target.os.tag) {
|
||||
.opencl => &.{.Kernel},
|
||||
.opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .GenericPointer },
|
||||
.glsl450 => &.{.Shader},
|
||||
.vulkan => &.{.Shader},
|
||||
else => unreachable, // TODO
|
||||
@ -279,45 +245,3 @@ fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void {
|
||||
.memory_model = memory_model,
|
||||
});
|
||||
}
|
||||
|
||||
fn cloneLiveness(l: Liveness, gpa: Allocator) !Liveness {
|
||||
const tomb_bits = try gpa.dupe(usize, l.tomb_bits);
|
||||
errdefer gpa.free(tomb_bits);
|
||||
|
||||
const extra = try gpa.dupe(u32, l.extra);
|
||||
errdefer gpa.free(extra);
|
||||
|
||||
return Liveness{
|
||||
.tomb_bits = tomb_bits,
|
||||
.extra = extra,
|
||||
.special = try l.special.clone(gpa),
|
||||
};
|
||||
}
|
||||
|
||||
fn cloneAir(air: Air, gpa: Allocator, air_arena: Allocator) !Air {
|
||||
const values = try gpa.alloc(Value, air.values.len);
|
||||
errdefer gpa.free(values);
|
||||
|
||||
for (values, 0..) |*value, i| {
|
||||
value.* = try air.values[i].copy(air_arena);
|
||||
}
|
||||
|
||||
var instructions = try air.instructions.toMultiArrayList().clone(gpa);
|
||||
errdefer instructions.deinit(gpa);
|
||||
|
||||
const air_tags = instructions.items(.tag);
|
||||
const air_datas = instructions.items(.data);
|
||||
|
||||
for (air_tags, 0..) |tag, i| {
|
||||
switch (tag) {
|
||||
.alloc, .ret_ptr, .const_ty => air_datas[i].ty = try air_datas[i].ty.copy(air_arena),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
return Air{
|
||||
.instructions = instructions.slice(),
|
||||
.extra = try gpa.dupe(u32, air.extra),
|
||||
.values = values,
|
||||
};
|
||||
}
|
||||
|
||||
@ -163,7 +163,7 @@ pub fn canBuildLibC(target: std.Target) bool {
|
||||
pub fn cannotDynamicLink(target: std.Target) bool {
|
||||
return switch (target.os.tag) {
|
||||
.freestanding, .other => true,
|
||||
else => false,
|
||||
else => target.isSpirV(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -331,18 +331,18 @@ pub fn supportsStackProbing(target: std.Target) bool {
|
||||
}
|
||||
|
||||
pub fn supportsStackProtector(target: std.Target) bool {
|
||||
_ = target;
|
||||
return true;
|
||||
return !target.isSpirV();
|
||||
}
|
||||
|
||||
pub fn libcProvidesStackProtector(target: std.Target) bool {
|
||||
return !target.isMinGW() and target.os.tag != .wasi;
|
||||
return !target.isMinGW() and target.os.tag != .wasi and !target.isSpirV();
|
||||
}
|
||||
|
||||
pub fn supportsReturnAddress(target: std.Target) bool {
|
||||
return switch (target.cpu.arch) {
|
||||
.wasm32, .wasm64 => target.os.tag == .emscripten,
|
||||
.bpfel, .bpfeb => false,
|
||||
.spirv32, .spirv64 => false,
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
@ -4796,9 +4796,12 @@ pub const Type = extern union {
|
||||
}
|
||||
|
||||
/// Asserts the type is a function.
|
||||
pub fn fnCallingConventionAllowsZigTypes(cc: std.builtin.CallingConvention) bool {
|
||||
pub fn fnCallingConventionAllowsZigTypes(target: Target, cc: std.builtin.CallingConvention) bool {
|
||||
return switch (cc) {
|
||||
.Unspecified, .Async, .Inline, .PtxKernel => true,
|
||||
.Unspecified, .Async, .Inline => true,
|
||||
// For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI.
|
||||
// The goal is to experiment with more integrated CPU/GPU code.
|
||||
.Kernel => target.cpu.arch == .nvptx or target.cpu.arch == .nvptx64,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ pub fn addCases(ctx: *Cases) !void {
|
||||
\\ return a + b;
|
||||
\\}
|
||||
\\
|
||||
\\pub export fn add_and_substract(a: i32, out: *i32) callconv(.PtxKernel) void {
|
||||
\\pub export fn add_and_substract(a: i32, out: *i32) callconv(.Kernel) void {
|
||||
\\ const x = add(a, 7);
|
||||
\\ var y = add(2, 0);
|
||||
\\ y -= x;
|
||||
@ -29,7 +29,7 @@ pub fn addCases(ctx: *Cases) !void {
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
\\pub export fn special_reg(a: []const i32, out: []i32) callconv(.PtxKernel) void {
|
||||
\\pub export fn special_reg(a: []const i32, out: []i32) callconv(.Kernel) void {
|
||||
\\ const i = threadIdX();
|
||||
\\ out[i] = a[i] + 7;
|
||||
\\}
|
||||
@ -42,7 +42,7 @@ pub fn addCases(ctx: *Cases) !void {
|
||||
case.addCompile(
|
||||
\\var x: i32 addrspace(.global) = 0;
|
||||
\\
|
||||
\\pub export fn increment(out: *i32) callconv(.PtxKernel) void {
|
||||
\\pub export fn increment(out: *i32) callconv(.Kernel) void {
|
||||
\\ x += 1;
|
||||
\\ out.* = x;
|
||||
\\}
|
||||
@ -59,7 +59,7 @@ pub fn addCases(ctx: *Cases) !void {
|
||||
\\}
|
||||
\\
|
||||
\\ var _sdata: [1024]f32 addrspace(.shared) = undefined;
|
||||
\\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.PtxKernel) void {
|
||||
\\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.Kernel) void {
|
||||
\\ var sdata = @addrSpaceCast(.generic, &_sdata);
|
||||
\\ const tid: u32 = threadIdX();
|
||||
\\ var sum = d_x[tid];
|
||||
|
||||
@ -80,22 +80,11 @@ fn render(writer: anytype, allocator: Allocator, registry: g.CoreRegistry) !void
|
||||
\\const Version = @import("std").builtin.Version;
|
||||
\\
|
||||
\\pub const Word = u32;
|
||||
\\pub const IdResultType = struct{
|
||||
\\ id: Word,
|
||||
\\ pub fn toRef(self: IdResultType) IdRef {
|
||||
\\ return .{.id = self.id};
|
||||
\\ }
|
||||
\\};
|
||||
\\pub const IdResult = struct{
|
||||
\\ id: Word,
|
||||
\\ pub fn toRef(self: IdResult) IdRef {
|
||||
\\ return .{.id = self.id};
|
||||
\\ }
|
||||
\\ pub fn toResultType(self: IdResult) IdResultType {
|
||||
\\ return .{.id = self.id};
|
||||
\\ }
|
||||
\\};
|
||||
\\pub const IdRef = struct{ id: Word };
|
||||
\\pub const IdResultType = IdResult;
|
||||
\\pub const IdRef = IdResult;
|
||||
\\
|
||||
\\pub const IdMemorySemantics = IdRef;
|
||||
\\pub const IdScope = IdRef;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user