authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-04-10 10:44:35-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-04-10 10:44:35-04:00
logaeae71f462d5a7c6a84e46c6635839c483c6acb5
tree94a827002a029579e558a3f868566f94300a17af
parentdfe34405406cf9b8f15fb0fc079a343fea4267a6
parentd1484bf4b96177135183916b95198de25dc63356
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #15068 from Snektron/spirv-test-runner-support

spirv: test runner support

19 files changed, 2664 insertions(+), 636 deletions(-)

lib/std/builtin.zig+1-2
......@@ -160,8 +160,7 @@ pub const CallingConvention = enum {
160160 AAPCSVFP,
161161 SysV,
162162 Win64,
163 PtxKernel,
164 AmdgpuKernel,
163 Kernel,
165164};
166165
167166/// This data structure is used by the Zig language code generation and
lib/std/start.zig+10-1
......@@ -24,7 +24,9 @@ pub const simplified_logic =
2424 builtin.zig_backend == .stage2_aarch64 or
2525 builtin.zig_backend == .stage2_arm or
2626 builtin.zig_backend == .stage2_riscv64 or
27 builtin.zig_backend == .stage2_sparc64;
27 builtin.zig_backend == .stage2_sparc64 or
28 builtin.cpu.arch == .spirv32 or
29 builtin.cpu.arch == .spirv64;
2830
2931comptime {
3032 // No matter what, we import the root file, so that any export, test, comptime
......@@ -43,6 +45,9 @@ comptime {
4345 }
4446 } else if (builtin.os.tag == .wasi and @hasDecl(root, "main")) {
4547 @export(wasiMain2, .{ .name = "_start" });
48 } else if (builtin.os.tag == .opencl) {
49 if (@hasDecl(root, "main"))
50 @export(spirvMain2, .{ .name = "main" });
4651 } else {
4752 if (!@hasDecl(root, "_start")) {
4853 @export(_start2, .{ .name = "_start" });
......@@ -127,6 +132,10 @@ fn wasiMain2() callconv(.C) noreturn {
127132 }
128133}
129134
135fn spirvMain2() callconv(.Kernel) void {
136 root.main();
137}
138
130139fn wWinMainCRTStartup2() callconv(.C) noreturn {
131140 root.main();
132141 exit2(0);
lib/std/target.zig+8-2
......@@ -944,7 +944,7 @@ pub const Target = struct {
944944 };
945945 }
946946
947 pub fn isSPIRV(arch: Arch) bool {
947 pub fn isSpirV(arch: Arch) bool {
948948 return switch (arch) {
949949 .spirv32, .spirv64 => true,
950950 else => false,
......@@ -1276,7 +1276,7 @@ pub const Target = struct {
12761276 .x86, .x86_64 => "x86",
12771277 .nvptx, .nvptx64 => "nvptx",
12781278 .wasm32, .wasm64 => "wasm",
1279 .spirv32, .spirv64 => "spir-v",
1279 .spirv32, .spirv64 => "spirv",
12801280 else => @tagName(arch),
12811281 };
12821282 }
......@@ -1329,6 +1329,7 @@ pub const Target = struct {
13291329 .amdgcn => comptime allCpusFromDecls(amdgpu.cpu),
13301330 .riscv32, .riscv64 => comptime allCpusFromDecls(riscv.cpu),
13311331 .sparc, .sparc64, .sparcel => comptime allCpusFromDecls(sparc.cpu),
1332 .spirv32, .spirv64 => comptime allCpusFromDecls(spirv.cpu),
13321333 .s390x => comptime allCpusFromDecls(s390x.cpu),
13331334 .x86, .x86_64 => comptime allCpusFromDecls(x86.cpu),
13341335 .xtensa => comptime allCpusFromDecls(xtensa.cpu),
......@@ -1392,6 +1393,7 @@ pub const Target = struct {
13921393 .amdgcn => &amdgpu.cpu.generic,
13931394 .riscv32 => &riscv.cpu.generic_rv32,
13941395 .riscv64 => &riscv.cpu.generic_rv64,
1396 .spirv32, .spirv64 => &spirv.cpu.generic,
13951397 .sparc, .sparcel => &sparc.cpu.generic,
13961398 .sparc64 => &sparc.cpu.v9, // 64-bit SPARC needs v9 as the baseline
13971399 .s390x => &s390x.cpu.generic,
......@@ -1532,6 +1534,10 @@ pub const Target = struct {
15321534 return !self.cpu.arch.isWasm();
15331535 }
15341536
1537 pub fn isSpirV(self: Target) bool {
1538 return self.cpu.arch.isSpirV();
1539 }
1540
15351541 pub const FloatAbi = enum {
15361542 hard,
15371543 soft,
lib/std/target/spirv.zig+8
......@@ -2081,3 +2081,11 @@ pub const all_features = blk: {
20812081 }
20822082 break :blk result;
20832083};
2084
2085pub const cpu = struct {
2086 pub const generic = CpuModel{
2087 .name = "generic",
2088 .llvm_name = "generic",
2089 .features = featureSet(&[_]Feature{}),
2090 };
2091};
src/Compilation.zig+7-5
......@@ -722,10 +722,11 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
722722 // Once they are capable this condition could be removed. When removing this condition,
723723 // also test the use case of `build-obj -fcompiler-rt` with the native backends
724724 // and make sure the compiler-rt symbols are emitted.
725 const capable_of_building_compiler_rt = build_options.have_llvm and options.target.os.tag != .plan9;
726
727 const capable_of_building_zig_libc = build_options.have_llvm and options.target.os.tag != .plan9;
728 const capable_of_building_ssp = build_options.have_llvm and options.target.os.tag != .plan9;
725 const is_p9 = options.target.os.tag == .plan9;
726 const is_spv = options.target.cpu.arch.isSpirV();
727 const capable_of_building_compiler_rt = build_options.have_llvm and !is_p9 and !is_spv;
728 const capable_of_building_zig_libc = build_options.have_llvm and !is_p9 and !is_spv;
729 const capable_of_building_ssp = build_options.have_llvm and !is_p9 and !is_spv;
729730
730731 const comp: *Compilation = comp: {
731732 // 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
19481949 .sub_path = std.fs.path.basename(sub_path),
19491950 };
19501951 }
1951 comp.bin_file.destroy();
1952 var old_bin_file = comp.bin_file;
19521953 comp.bin_file = try link.File.openPath(comp.gpa, options);
1954 old_bin_file.destroy();
19531955 }
19541956
19551957 // For compiling C objects, we rely on the cache hash system to avoid duplicating work.
src/Sema.zig+15-17
......@@ -8883,7 +8883,7 @@ fn funcCommon(
88838883 };
88848884 return sema.failWithOwnedErrorMsg(msg);
88858885 }
8886 if (!ret_poison and !Type.fnCallingConventionAllowsZigTypes(cc_resolved) and !try sema.validateExternType(return_type, .ret_ty)) {
8886 if (!ret_poison and !Type.fnCallingConventionAllowsZigTypes(target, cc_resolved) and !try sema.validateExternType(return_type, .ret_ty)) {
88878887 const msg = msg: {
88888888 const msg = try sema.errMsg(block, ret_ty_src, "return type '{}' not allowed in function with calling convention '{s}'", .{
88898889 return_type.fmt(sema.mod), @tagName(cc_resolved),
......@@ -8961,13 +8961,9 @@ fn funcCommon(
89618961 .x86_64 => null,
89628962 else => @as([]const u8, "x86_64"),
89638963 },
8964 .PtxKernel => switch (arch) {
8965 .nvptx, .nvptx64 => null,
8966 else => @as([]const u8, "nvptx and nvptx64"),
8967 },
8968 .AmdgpuKernel => switch (arch) {
8969 .amdgcn => null,
8970 else => @as([]const u8, "amdgcn"),
8964 .Kernel => switch (arch) {
8965 .nvptx, .nvptx64, .amdgcn, .spirv32, .spirv64 => null,
8966 else => @as([]const u8, "nvptx, amdgcn and SPIR-V"),
89718967 },
89728968 }) |allowed_platform| {
89738969 return sema.fail(block, cc_src, "callconv '{s}' is only available on {s}, not {s}", .{
......@@ -9093,10 +9089,11 @@ fn analyzeParameter(
90939089 comptime_params[i] = param.is_comptime or requires_comptime;
90949090 const this_generic = param.ty.tag() == .generic_poison;
90959091 is_generic.* = is_generic.* or this_generic;
9096 if (param.is_comptime and !Type.fnCallingConventionAllowsZigTypes(cc)) {
9092 const target = sema.mod.getTarget();
9093 if (param.is_comptime and !Type.fnCallingConventionAllowsZigTypes(target, cc)) {
90979094 return sema.fail(block, param_src, "comptime parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)});
90989095 }
9099 if (this_generic and !sema.no_partial_func_ty and !Type.fnCallingConventionAllowsZigTypes(cc)) {
9096 if (this_generic and !sema.no_partial_func_ty and !Type.fnCallingConventionAllowsZigTypes(target, cc)) {
91009097 return sema.fail(block, param_src, "generic parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)});
91019098 }
91029099 if (!param.ty.isValidParamType()) {
......@@ -9112,7 +9109,7 @@ fn analyzeParameter(
91129109 };
91139110 return sema.failWithOwnedErrorMsg(msg);
91149111 }
9115 if (!this_generic and !Type.fnCallingConventionAllowsZigTypes(cc) and !try sema.validateExternType(param.ty, .param_ty)) {
9112 if (!this_generic and !Type.fnCallingConventionAllowsZigTypes(target, cc) and !try sema.validateExternType(param.ty, .param_ty)) {
91169113 const msg = msg: {
91179114 const msg = try sema.errMsg(block, param_src, "parameter of type '{}' not allowed in function with calling convention '{s}'", .{
91189115 param.ty.fmt(sema.mod), @tagName(cc),
......@@ -22786,12 +22783,13 @@ fn validateExternType(
2278622783 },
2278722784 .Fn => {
2278822785 if (position != .other) return false;
22789 return switch (ty.fnCallingConvention()) {
22790 // For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI.
22791 // The goal is to experiment with more integrated CPU/GPU code.
22792 .PtxKernel => true,
22793 else => !Type.fnCallingConventionAllowsZigTypes(ty.fnCallingConvention()),
22794 };
22786 const target = sema.mod.getTarget();
22787 // For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI.
22788 // The goal is to experiment with more integrated CPU/GPU code.
22789 if (ty.fnCallingConvention() == .Kernel and (target.cpu.arch == .nvptx or target.cpu.arch == .nvptx64)) {
22790 return true;
22791 }
22792 return !Type.fnCallingConventionAllowsZigTypes(target, ty.fnCallingConvention());
2279522793 },
2279622794 .Enum => {
2279722795 var buf: Type.Payload.Bits = undefined;
src/codegen/llvm.zig+1-4
......@@ -10350,11 +10350,8 @@ fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) llvm.Ca
1035010350 .Signal => .AVR_SIGNAL,
1035110351 .SysV => .X86_64_SysV,
1035210352 .Win64 => .Win64,
10353 .PtxKernel => return switch (target.cpu.arch) {
10353 .Kernel => return switch (target.cpu.arch) {
1035410354 .nvptx, .nvptx64 => .PTX_Kernel,
10355 else => unreachable,
10356 },
10357 .AmdgpuKernel => return switch (target.cpu.arch) {
1035810355 .amdgcn => .AMDGPU_KERNEL,
1035910356 else => unreachable,
1036010357 },
src/codegen/spirv.zig+1814-237
......@@ -19,6 +19,7 @@ const Word = spec.Word;
1919const IdRef = spec.IdRef;
2020const IdResult = spec.IdResult;
2121const IdResultType = spec.IdResultType;
22const StorageClass = spec.StorageClass;
2223
2324const SpvModule = @import("spirv/Module.zig");
2425const SpvSection = @import("spirv/Section.zig");
......@@ -32,11 +33,14 @@ const IncomingBlock = struct {
3233 break_value_id: IdRef,
3334};
3435
35pub const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
36const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
3637 label_id: IdRef,
3738 incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),
3839});
3940
41/// Maps Zig decl indices to linking SPIR-V linking information.
42pub const DeclLinkMap = std.AutoHashMap(Module.Decl.Index, SpvModule.Decl.Index);
43
4044/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
4145pub const DeclGen = struct {
4246 /// A general-purpose allocator that can be used for any allocations for this DeclGen.
......@@ -59,7 +63,8 @@ pub const DeclGen = struct {
5963 /// Note: If the declaration is not a function, this value will be undefined!
6064 liveness: Liveness,
6165
62 ids: *const std.AutoHashMap(Decl.Index, IdResult),
66 /// Maps Zig Decl indices to SPIR-V globals.
67 decl_link: *DeclLinkMap,
6368
6469 /// An array of function argument result-ids. Each index corresponds with the
6570 /// function argument of the same index.
......@@ -133,13 +138,23 @@ pub const DeclGen = struct {
133138 class: Class,
134139 };
135140
141 /// Data can be lowered into in two basic representations: indirect, which is when
142 /// a type is stored in memory, and direct, which is how a type is stored when its
143 /// a direct SPIR-V value.
144 const Repr = enum {
145 /// A SPIR-V value as it would be used in operations.
146 direct,
147 /// A SPIR-V value as it is stored in memory.
148 indirect,
149 };
150
136151 /// Initialize the common resources of a DeclGen. Some fields are left uninitialized,
137152 /// only set when `gen` is called.
138153 pub fn init(
139154 allocator: Allocator,
140155 module: *Module,
141156 spv: *SpvModule,
142 ids: *const std.AutoHashMap(Decl.Index, IdResult),
157 decl_link: *DeclLinkMap,
143158 ) DeclGen {
144159 return .{
145160 .gpa = allocator,
......@@ -148,7 +163,7 @@ pub const DeclGen = struct {
148163 .decl_index = undefined,
149164 .air = undefined,
150165 .liveness = undefined,
151 .ids = ids,
166 .decl_link = decl_link,
152167 .next_arg_index = undefined,
153168 .current_block_label_id = undefined,
154169 .error_msg = undefined,
......@@ -215,19 +230,50 @@ pub const DeclGen = struct {
215230 /// Fetch the result-id for a previously generated instruction or constant.
216231 fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !IdRef {
217232 if (self.air.value(inst)) |val| {
218 return self.genConstant(self.air.typeOf(inst), val);
233 const ty = self.air.typeOf(inst);
234 if (ty.zigTypeTag() == .Fn) {
235 const fn_decl_index = switch (val.tag()) {
236 .extern_fn => val.castTag(.extern_fn).?.data.owner_decl,
237 .function => val.castTag(.function).?.data.owner_decl,
238 else => unreachable,
239 };
240 const spv_decl_index = try self.resolveDecl(fn_decl_index);
241 return self.spv.declPtr(spv_decl_index).result_id;
242 }
243
244 return try self.constant(ty, val);
219245 }
220246 const index = Air.refToIndex(inst).?;
221247 return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.
222248 }
223249
250 /// Fetch or allocate a result id for decl index. This function also marks the decl as alive.
251 /// Note: Function does not actually generate the decl.
252 fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !SpvModule.Decl.Index {
253 const decl = self.module.declPtr(decl_index);
254 self.module.markDeclAlive(decl);
255
256 const entry = try self.decl_link.getOrPut(decl_index);
257 if (!entry.found_existing) {
258 // TODO: Extern fn?
259 const kind: SpvModule.DeclKind = if (decl.val.tag() == .function)
260 .func
261 else
262 .global;
263
264 entry.value_ptr.* = try self.spv.allocDecl(kind);
265 }
266
267 return entry.value_ptr.*;
268 }
269
224270 /// Start a new SPIR-V block, Emits the label of the new block, and stores which
225271 /// block we are currently generating.
226272 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to
227273 /// keep track of the previous block.
228274 fn beginSpvBlock(self: *DeclGen, label_id: IdResult) !void {
229275 try self.func.body.emit(self.spv.gpa, .OpLabel, .{ .id_result = label_id });
230 self.current_block_label_id = label_id.toRef();
276 self.current_block_label_id = label_id;
231277 }
232278
233279 /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need
......@@ -325,150 +371,752 @@ pub const DeclGen = struct {
325371 // As of yet, there is no vector support in the self-hosted compiler.
326372 .Vector => self.todo("implement arithmeticTypeInfo for Vector", .{}),
327373 // TODO: For which types is this the case?
328 else => self.todo("implement arithmeticTypeInfo for {}", .{ty.fmtDebug()}),
374 else => self.todo("implement arithmeticTypeInfo for {}", .{ty.fmt(self.module)}),
329375 };
330376 }
331377
332 /// Generate a constant representing `val`.
333 /// TODO: Deduplication?
334 fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!IdRef {
335 if (ty.zigTypeTag() == .Fn) {
336 const fn_decl_index = switch (val.tag()) {
337 .extern_fn => val.castTag(.extern_fn).?.data.owner_decl,
338 .function => val.castTag(.function).?.data.owner_decl,
378 fn genConstInt(self: *DeclGen, ty_ref: SpvType.Ref, result_id: IdRef, value: anytype) !void {
379 const ty = self.spv.typeRefType(ty_ref);
380 const ty_id = self.typeId(ty_ref);
381
382 const Lit = spec.LiteralContextDependentNumber;
383 const literal = switch (ty.intSignedness()) {
384 .signed => switch (ty.intFloatBits()) {
385 1...32 => Lit{ .int32 = @intCast(i32, value) },
386 33...64 => Lit{ .int64 = @intCast(i64, value) },
387 else => unreachable, // TODO: composite integer literals
388 },
389 .unsigned => switch (ty.intFloatBits()) {
390 1...32 => Lit{ .uint32 = @intCast(u32, value) },
391 33...64 => Lit{ .uint64 = @intCast(u64, value) },
339392 else => unreachable,
393 },
394 };
395
396 try self.spv.emitConstant(ty_id, result_id, literal);
397 }
398
399 fn constInt(self: *DeclGen, ty_ref: SpvType.Ref, value: anytype) !IdRef {
400 const result_id = self.spv.allocId();
401 try self.genConstInt(ty_ref, result_id, value);
402 return result_id;
403 }
404
405 fn genUndef(self: *DeclGen, ty_ref: SpvType.Ref) Error!IdRef {
406 const result_id = self.spv.allocId();
407 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = self.typeId(ty_ref), .id_result = result_id });
408 return result_id;
409 }
410
411 const IndirectConstantLowering = struct {
412 const undef = 0xAA;
413
414 dg: *DeclGen,
415 /// Cached reference of the u32 type.
416 u32_ty_ref: SpvType.Ref,
417 /// Cached type id of the u32 type.
418 u32_ty_id: IdRef,
419 /// The members of the resulting structure type
420 members: std.ArrayList(SpvType.Payload.Struct.Member),
421 /// The initializers of each of the members.
422 initializers: std.ArrayList(IdRef),
423 /// The current size of the structure. Includes
424 /// the bytes in partial_word.
425 size: u32 = 0,
426 /// The partially filled last constant.
427 /// If full, its flushed.
428 partial_word: std.BoundedArray(u8, @sizeOf(Word)) = .{},
429 /// The declaration dependencies of the constant we are lowering.
430 decl_deps: std.ArrayList(SpvModule.Decl.Index),
431
432 /// Utility function to get the section that instructions should be lowered to.
433 fn section(self: *@This()) *SpvSection {
434 return &self.dg.spv.globals.section;
435 }
436
437 /// Flush the partial_word to the members. If the partial_word is not
438 /// filled, this adds padding bytes (which are undefined).
439 fn flush(self: *@This()) !void {
440 if (self.partial_word.len == 0) {
441 // No need to add it there.
442 return;
443 }
444
445 for (self.partial_word.unusedCapacitySlice()) |*unused| {
446 // TODO: Perhaps we should generate OpUndef for these bytes?
447 unused.* = undef;
448 }
449
450 const word = @bitCast(Word, self.partial_word.buffer);
451 const result_id = self.dg.spv.allocId();
452 // TODO: Integrate with caching mechanism
453 try self.dg.spv.emitConstant(self.u32_ty_id, result_id, .{ .uint32 = word });
454 try self.members.append(.{ .ty = self.u32_ty_ref });
455 try self.initializers.append(result_id);
456
457 self.partial_word.len = 0;
458 self.size = std.mem.alignForwardGeneric(u32, self.size, @sizeOf(Word));
459 }
460
461 /// Fill the buffer with undefined values until the size is aligned to `align`.
462 fn fillToAlign(self: *@This(), alignment: u32) !void {
463 const target_size = std.mem.alignForwardGeneric(u32, self.size, alignment);
464 try self.addUndef(target_size - self.size);
465 }
466
467 fn addUndef(self: *@This(), amt: u64) !void {
468 for (0..@intCast(usize, amt)) |_| {
469 try self.addByte(undef);
470 }
471 }
472
473 /// Add a single byte of data to the constant.
474 fn addByte(self: *@This(), data: u8) !void {
475 self.partial_word.append(data) catch {
476 try self.flush();
477 self.partial_word.append(data) catch unreachable;
340478 };
341 const decl = self.module.declPtr(fn_decl_index);
342 self.module.markDeclAlive(decl);
343 return self.ids.get(fn_decl_index).?.toRef();
479 self.size += 1;
480 }
481
482 /// Add many bytes of data to the constnat.
483 fn addBytes(self: *@This(), data: []const u8) !void {
484 // TODO: Improve performance by adding in bulk, or something?
485 for (data) |byte| {
486 try self.addByte(byte);
487 }
344488 }
345489
490 fn addPtr(self: *@This(), ptr_ty_ref: SpvType.Ref, ptr_id: IdRef) !void {
491 // TODO: Double check pointer sizes here.
492 // shared pointers might be u32...
493 const target = self.dg.getTarget();
494 const width = @divExact(target.cpu.arch.ptrBitWidth(), 8);
495 if (self.size % width != 0) {
496 return self.dg.todo("misaligned pointer constants", .{});
497 }
498 try self.members.append(.{ .ty = ptr_ty_ref });
499 try self.initializers.append(ptr_id);
500 self.size += width;
501 }
502
503 fn addNullPtr(self: *@This(), ptr_ty_ref: SpvType.Ref) !void {
504 const result_id = self.dg.spv.allocId();
505 try self.dg.spv.sections.types_globals_constants.emit(self.dg.spv.gpa, .OpConstantNull, .{
506 .id_result_type = self.dg.typeId(ptr_ty_ref),
507 .id_result = result_id,
508 });
509 try self.addPtr(ptr_ty_ref, result_id);
510 }
511
512 fn addConstInt(self: *@This(), comptime T: type, value: T) !void {
513 if (@bitSizeOf(T) % 8 != 0) {
514 @compileError("todo: non byte aligned int constants");
515 }
516
517 // TODO: Swap endianness if the compiler is big endian.
518 try self.addBytes(std.mem.asBytes(&value));
519 }
520
521 fn addConstBool(self: *@This(), value: bool) !void {
522 try self.addByte(@boolToInt(value)); // TODO: Keep in sync with something?
523 }
524
525 fn addInt(self: *@This(), ty: Type, val: Value) !void {
526 const target = self.dg.getTarget();
527 const int_info = ty.intInfo(target);
528 const int_bits = switch (int_info.signedness) {
529 .signed => @bitCast(u64, val.toSignedInt(target)),
530 .unsigned => val.toUnsignedInt(target),
531 };
532
533 // TODO: Swap endianess if the compiler is big endian.
534 const len = ty.abiSize(target);
535 try self.addBytes(std.mem.asBytes(&int_bits)[0..@intCast(usize, len)]);
536 }
537
538 fn addDeclRef(self: *@This(), ty: Type, decl_index: Decl.Index) !void {
539 const dg = self.dg;
540
541 const ty_ref = try self.dg.resolveType(ty, .indirect);
542 const ty_id = dg.typeId(ty_ref);
543
544 const decl = dg.module.declPtr(decl_index);
545 const spv_decl_index = try dg.resolveDecl(decl_index);
546
547 switch (decl.val.tag()) {
548 .function => {
549 // TODO: Properly lower function pointers. For now we are going to hack around it and
550 // just generate an empty pointer. Function pointers are represented by usize for now,
551 // though.
552 try self.addInt(Type.usize, Value.initTag(.zero));
553 return;
554 },
555 .extern_fn => unreachable, // TODO
556 else => {
557 const result_id = dg.spv.allocId();
558 log.debug("addDeclRef {s} = {}", .{ decl.name, result_id.id });
559
560 try self.decl_deps.append(spv_decl_index);
561
562 const decl_id = dg.spv.declPtr(spv_decl_index).result_id;
563 // TODO: Do we need a storage class cast here?
564 // TODO: We can probably eliminate these casts
565 try dg.spv.globals.section.emitSpecConstantOp(dg.spv.gpa, .OpBitcast, .{
566 .id_result_type = ty_id,
567 .id_result = result_id,
568 .operand = decl_id,
569 });
570
571 try self.addPtr(ty_ref, result_id);
572 },
573 }
574 }
575
576 fn lower(self: *@This(), ty: Type, val: Value) !void {
577 const target = self.dg.getTarget();
578 const dg = self.dg;
579
580 if (val.isUndef()) {
581 const size = ty.abiSize(target);
582 return try self.addUndef(size);
583 }
584
585 switch (ty.zigTypeTag()) {
586 .Int => try self.addInt(ty, val),
587 .Bool => try self.addConstBool(val.toBool()),
588 .Array => switch (val.tag()) {
589 .aggregate => {
590 const elem_vals = val.castTag(.aggregate).?.data;
591 const elem_ty = ty.elemType();
592 const len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way.
593 for (elem_vals[0..len]) |elem_val| {
594 try self.lower(elem_ty, elem_val);
595 }
596 },
597 .repeated => {
598 const elem_val = val.castTag(.repeated).?.data;
599 const elem_ty = ty.elemType();
600 const len = @intCast(u32, ty.arrayLen());
601 for (0..len) |_| {
602 try self.lower(elem_ty, elem_val);
603 }
604 if (ty.sentinel()) |sentinel| {
605 try self.lower(elem_ty, sentinel);
606 }
607 },
608 .str_lit => {
609 const str_lit = val.castTag(.str_lit).?.data;
610 const bytes = dg.module.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
611 try self.addBytes(bytes);
612 if (ty.sentinel()) |sentinel| {
613 try self.addByte(@intCast(u8, sentinel.toUnsignedInt(target)));
614 }
615 },
616 .bytes => {
617 const bytes = val.castTag(.bytes).?.data;
618 try self.addBytes(bytes);
619 },
620 else => |tag| return dg.todo("indirect array constant with tag {s}", .{@tagName(tag)}),
621 },
622 .Pointer => switch (val.tag()) {
623 .decl_ref_mut => {
624 const decl_index = val.castTag(.decl_ref_mut).?.data.decl_index;
625 try self.addDeclRef(ty, decl_index);
626 },
627 .decl_ref => {
628 const decl_index = val.castTag(.decl_ref).?.data;
629 try self.addDeclRef(ty, decl_index);
630 },
631 .slice => {
632 const slice = val.castTag(.slice).?.data;
633
634 var buf: Type.SlicePtrFieldTypeBuffer = undefined;
635 const ptr_ty = ty.slicePtrFieldType(&buf);
636
637 try self.lower(ptr_ty, slice.ptr);
638 try self.addInt(Type.usize, slice.len);
639 },
640 else => |tag| return dg.todo("pointer value of type {s}", .{@tagName(tag)}),
641 },
642 .Struct => {
643 if (ty.isSimpleTupleOrAnonStruct()) {
644 unreachable; // TODO
645 } else {
646 const struct_ty = ty.castTag(.@"struct").?.data;
647
648 if (struct_ty.layout == .Packed) {
649 return dg.todo("packed struct constants", .{});
650 }
651
652 const struct_begin = self.size;
653 const field_vals = val.castTag(.aggregate).?.data;
654 for (struct_ty.fields.values(), 0..) |field, i| {
655 if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
656 try self.lower(field.ty, field_vals[i]);
657
658 // Add padding if required.
659 // TODO: Add to type generation as well?
660 const unpadded_field_end = self.size - struct_begin;
661 const padded_field_end = ty.structFieldOffset(i + 1, target);
662 const padding = padded_field_end - unpadded_field_end;
663 try self.addUndef(padding);
664 }
665 }
666 },
667 .Optional => {
668 var opt_buf: Type.Payload.ElemType = undefined;
669 const payload_ty = ty.optionalChild(&opt_buf);
670 const has_payload = !val.isNull();
671 const abi_size = ty.abiSize(target);
672
673 if (!payload_ty.hasRuntimeBits()) {
674 try self.addConstBool(has_payload);
675 return;
676 } else if (ty.optionalReprIsPayload()) {
677 // Optional representation is a nullable pointer.
678 if (val.castTag(.opt_payload)) |payload| {
679 try self.lower(payload_ty, payload.data);
680 } else if (has_payload) {
681 try self.lower(payload_ty, val);
682 } else {
683 const ptr_ty_ref = try dg.resolveType(ty, .indirect);
684 try self.addNullPtr(ptr_ty_ref);
685 }
686 return;
687 }
688
689 // Optional representation is a structure.
690 // { Payload, Bool }
691
692 // Subtract 1 for @sizeOf(bool).
693 // TODO: Make this not hardcoded.
694 const payload_size = payload_ty.abiSize(target);
695 const padding = abi_size - payload_size - 1;
696
697 if (val.castTag(.opt_payload)) |payload| {
698 try self.lower(payload_ty, payload.data);
699 } else {
700 try self.addUndef(payload_size);
701 }
702 try self.addConstBool(has_payload);
703 try self.addUndef(padding);
704 },
705 .Enum => {
706 var int_val_buffer: Value.Payload.U64 = undefined;
707 const int_val = val.enumToInt(ty, &int_val_buffer);
708
709 var int_ty_buffer: Type.Payload.Bits = undefined;
710 const int_ty = ty.intTagType(&int_ty_buffer);
711
712 try self.lower(int_ty, int_val);
713 },
714 .Union => {
715 const tag_and_val = val.castTag(.@"union").?.data;
716 const layout = ty.unionGetLayout(target);
717
718 if (layout.payload_size == 0) {
719 return try self.lower(ty.unionTagTypeSafety().?, tag_and_val.tag);
720 }
721
722 const union_ty = ty.cast(Type.Payload.Union).?.data;
723 if (union_ty.layout == .Packed) {
724 return dg.todo("packed union constants", .{});
725 }
726
727 const active_field = ty.unionTagFieldIndex(tag_and_val.tag, dg.module).?;
728 const active_field_ty = union_ty.fields.values()[active_field].ty;
729
730 const has_tag = layout.tag_size != 0;
731 const tag_first = layout.tag_align >= layout.payload_align;
732
733 if (has_tag and tag_first) {
734 try self.lower(ty.unionTagTypeSafety().?, tag_and_val.tag);
735 }
736
737 const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: {
738 try self.lower(active_field_ty, tag_and_val.val);
739 break :blk active_field_ty.abiSize(target);
740 } else 0;
741
742 const payload_padding_len = layout.payload_size - active_field_size;
743 try self.addUndef(payload_padding_len);
744
745 if (has_tag and !tag_first) {
746 try self.lower(ty.unionTagTypeSafety().?, tag_and_val.tag);
747 }
748
749 try self.addUndef(layout.padding);
750 },
751 .ErrorSet => switch (val.tag()) {
752 .@"error" => {
753 const err_name = val.castTag(.@"error").?.data.name;
754 const kv = try dg.module.getErrorValue(err_name);
755 try self.addConstInt(u16, @intCast(u16, kv.value));
756 },
757 .zero => {
758 // Unactivated error set.
759 try self.addConstInt(u16, 0);
760 },
761 else => unreachable,
762 },
763 .ErrorUnion => {
764 const payload_ty = ty.errorUnionPayload();
765 const is_pl = val.errorUnionIsPayload();
766 const error_val = if (!is_pl) val else Value.initTag(.zero);
767
768 if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
769 return try self.lower(Type.anyerror, error_val);
770 }
771
772 const payload_align = payload_ty.abiAlignment(target);
773 const error_align = Type.anyerror.abiAlignment(target);
774
775 const payload_size = payload_ty.abiSize(target);
776 const error_size = Type.anyerror.abiAlignment(target);
777 const ty_size = ty.abiSize(target);
778 const padding = ty_size - payload_size - error_size;
779
780 const payload_val = if (val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef);
781
782 if (error_align > payload_align) {
783 try self.lower(Type.anyerror, error_val);
784 try self.lower(payload_ty, payload_val);
785 } else {
786 try self.lower(payload_ty, payload_val);
787 try self.lower(Type.anyerror, error_val);
788 }
789
790 try self.addUndef(padding);
791 },
792 else => |tag| return dg.todo("indirect constant of type {s}", .{@tagName(tag)}),
793 }
794 }
795 };
796
797 /// Returns a pointer to `val`. The value is placed directly
798 /// into the storage class `storage_class`, and this is also where the resulting
799 /// pointer points to. Note: result is not necessarily an OpVariable instruction!
800 fn lowerIndirectConstant(
801 self: *DeclGen,
802 spv_decl_index: SpvModule.Decl.Index,
803 ty: Type,
804 val: Value,
805 storage_class: StorageClass,
806 cast_to_generic: bool,
807 alignment: u32,
808 ) Error!void {
809 // To simplify constant generation, we're going to generate constants as a word-array, and
810 // pointer cast the result to the right type.
811 // This means that the final constant will be generated as follows:
812 // %T = OpTypeStruct %members...
813 // %P = OpTypePointer %T
814 // %U = OpTypePointer %ty
815 // %1 = OpConstantComposite %T %initializers...
816 // %2 = OpVariable %P %1
817 // %result_id = OpSpecConstantOp OpBitcast %U %2
818 //
819 // The members consist of two options:
820 // - Literal values: ints, strings, etc. These are generated as u32 words.
821 // - Relocations, such as pointers: These are generated by embedding the pointer into the
822 // to-be-generated structure. There are two options here, depending on the alignment of the
823 // pointer value itself (not the alignment of the pointee).
824 // - Natively or over-aligned values. These can just be generated directly.
825 // - Underaligned pointers. These need to be packed into the word array by using a mixture of
826 // OpSpecConstantOp instructions such as OpConvertPtrToU, OpBitcast, OpShift, etc.
827
828 // TODO: Implement alignment here.
829 // This is hoing to require some hacks because there is no real way to
830 // set an OpVariable's alignment.
831 _ = alignment;
832
833 assert(storage_class != .Generic and storage_class != .Function);
834
835 log.debug("lowerIndirectConstant: ty = {}, val = {}", .{ ty.fmt(self.module), val.fmtDebug() });
836
837 const section = &self.spv.globals.section;
838
839 const ty_ref = try self.resolveType(ty, .indirect);
840 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, 0);
841
842 // const target = self.getTarget();
843
844 // TODO: Fix the resulting global linking for these paths.
845 // if (val.isUndef()) {
846 // // Special case: the entire value is undefined. In this case, we can just
847 // // generate an OpVariable with no initializer.
848 // return try section.emit(self.spv.gpa, .OpVariable, .{
849 // .id_result_type = self.typeId(ptr_ty_ref),
850 // .id_result = result_id,
851 // .storage_class = storage_class,
852 // });
853 // } else if (ty.abiSize(target) == 0) {
854 // // Special case: if the type has no size, then return an undefined pointer.
855 // return try section.emit(self.spv.gpa, .OpUndef, .{
856 // .id_result_type = self.typeId(ptr_ty_ref),
857 // .id_result = result_id,
858 // });
859 // }
860
861 // TODO: Capture the above stuff in here as well...
862 const begin_inst = self.spv.beginGlobal();
863
864 const u32_ty_ref = try self.intType(.unsigned, 32);
865 var icl = IndirectConstantLowering{
866 .dg = self,
867 .u32_ty_ref = u32_ty_ref,
868 .u32_ty_id = self.typeId(u32_ty_ref),
869 .members = std.ArrayList(SpvType.Payload.Struct.Member).init(self.gpa),
870 .initializers = std.ArrayList(IdRef).init(self.gpa),
871 .decl_deps = std.ArrayList(SpvModule.Decl.Index).init(self.gpa),
872 };
873
874 defer icl.members.deinit();
875 defer icl.initializers.deinit();
876 defer icl.decl_deps.deinit();
877
878 try icl.lower(ty, val);
879 try icl.flush();
880
881 const constant_struct_ty_ref = try self.spv.simpleStructType(icl.members.items);
882 const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class, 0);
883
884 const constant_struct_id = self.spv.allocId();
885 try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{
886 .id_result_type = self.typeId(constant_struct_ty_ref),
887 .id_result = constant_struct_id,
888 .constituents = icl.initializers.items,
889 });
890
891 const var_id = self.spv.allocId();
892 self.spv.globalPtr(spv_decl_index).?.result_id = var_id;
893 try section.emit(self.spv.gpa, .OpVariable, .{
894 .id_result_type = self.typeId(ptr_constant_struct_ty_ref),
895 .id_result = var_id,
896 .storage_class = storage_class,
897 .initializer = constant_struct_id,
898 });
899 // TODO: Set alignment of OpVariable.
900 // TODO: We may be able to eliminate these casts.
901
902 const const_ptr_id = try self.makePointerConstant(section, ptr_constant_struct_ty_ref, var_id);
903 const result_id = self.spv.declPtr(spv_decl_index).result_id;
904
905 const bitcast_result_id = if (cast_to_generic)
906 self.spv.allocId()
907 else
908 result_id;
909
910 try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{
911 .id_result_type = self.typeId(ptr_ty_ref),
912 .id_result = bitcast_result_id,
913 .operand = const_ptr_id,
914 });
915
916 if (cast_to_generic) {
917 const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0);
918 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
919 .id_result_type = self.typeId(generic_ptr_ty_ref),
920 .id_result = result_id,
921 .pointer = bitcast_result_id,
922 });
923 }
924
925 try self.spv.declareDeclDeps(spv_decl_index, icl.decl_deps.items);
926 self.spv.endGlobal(spv_decl_index, begin_inst);
927 }
928
929 /// This function generates a load for a constant in direct (ie, non-memory) representation.
930 /// When the constant is simple, it can be generated directly using OpConstant instructions. When
931 /// the constant is more complicated however, it needs to be lowered to an indirect constant, which
932 /// is then loaded using OpLoad. Such values are loaded into the UniformConstant storage class by default.
933 /// This function should only be called during function code generation.
934 fn constant(self: *DeclGen, ty: Type, val: Value) !IdRef {
346935 const target = self.getTarget();
347936 const section = &self.spv.sections.types_globals_constants;
937 const result_ty_ref = try self.resolveType(ty, .direct);
938 const result_ty_id = self.typeId(result_ty_ref);
348939 const result_id = self.spv.allocId();
349 const result_type_id = try self.resolveTypeId(ty);
350940
351941 if (val.isUndef()) {
352 try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_type_id, .id_result = result_id });
353 return result_id.toRef();
942 try section.emit(self.spv.gpa, .OpUndef, .{
943 .id_result_type = result_ty_id,
944 .id_result = result_id,
945 });
946 return result_id;
354947 }
355948
356949 switch (ty.zigTypeTag()) {
357950 .Int => {
358 const int_info = ty.intInfo(target);
359 const backing_bits = self.backingIntBits(int_info.bits) orelse {
360 // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits.
361 return self.todo("implement composite int constants for {}", .{ty.fmtDebug()});
362 };
363
364 // We can just use toSignedInt/toUnsignedInt here as it returns u64 - a type large enough to hold any
365 // SPIR-V native type (up to i/u64 with Int64). If SPIR-V ever supports native ints of a larger size, this
366 // might need to be updated.
367 assert(self.largestSupportedIntBits() <= @bitSizeOf(u64));
368
369 // Note, value is required to be sign-extended, so we don't need to mask off the upper bits.
370 // See https://www.khronos.org/registry/SPIR-V/specs/unified1/SPIRV.html#Literal
371 var int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt(target)) else val.toUnsignedInt(target);
372
373 const value: spec.LiteralContextDependentNumber = switch (backing_bits) {
374 1...32 => .{ .uint32 = @truncate(u32, int_bits) },
375 33...64 => .{ .uint64 = int_bits },
376 else => unreachable,
377 };
378
379 try section.emit(self.spv.gpa, .OpConstant, .{
380 .id_result_type = result_type_id,
381 .id_result = result_id,
382 .value = value,
383 });
951 const int_bits = if (ty.isSignedInt())
952 @bitCast(u64, val.toSignedInt(target))
953 else
954 val.toUnsignedInt(target);
955 try self.genConstInt(result_ty_ref, result_id, int_bits);
384956 },
385957 .Bool => {
386 const operands = .{ .id_result_type = result_type_id, .id_result = result_id };
958 const operands = .{ .id_result_type = result_ty_id, .id_result = result_id };
387959 if (val.toBool()) {
388960 try section.emit(self.spv.gpa, .OpConstantTrue, operands);
389961 } else {
390962 try section.emit(self.spv.gpa, .OpConstantFalse, operands);
391963 }
392964 },
393 .Float => {
394 // At this point we are guaranteed that the target floating point type is supported, otherwise the function
395 // would have exited at resolveTypeId(ty).
396
397 const value: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) {
398 // Prevent upcasting to f32 by bitcasting and writing as a uint32.
399 16 => .{ .uint32 = @bitCast(u16, val.toFloat(f16)) },
400 32 => .{ .float32 = val.toFloat(f32) },
401 64 => .{ .float64 = val.toFloat(f64) },
402 128 => unreachable, // Filtered out in the call to resolveTypeId.
403 // TODO: Insert case for long double when the layout for that is determined?
404 else => unreachable,
405 };
406
407 try section.emit(self.spv.gpa, .OpConstant, .{
408 .id_result_type = result_type_id,
965 // TODO: We can handle most pointers here (decl refs etc), because now they emit an extra
966 // OpVariable that is not really required.
967 else => {
968 // The value cannot be generated directly, so generate it as an indirect constant,
969 // and then perform an OpLoad.
970 const alignment = ty.abiAlignment(target);
971 const spv_decl_index = try self.spv.allocDecl(.global);
972
973 try self.lowerIndirectConstant(
974 spv_decl_index,
975 ty,
976 val,
977 .UniformConstant,
978 false,
979 alignment,
980 );
981 try self.func.decl_deps.append(self.spv.gpa, spv_decl_index);
982
983 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
984 .id_result_type = result_ty_id,
409985 .id_result = result_id,
410 .value = value,
986 .pointer = self.spv.declPtr(spv_decl_index).result_id,
411987 });
988 // TODO: Convert bools? This logic should hook into `load`. It should be a dead
989 // path though considering .Bool is handled above.
412990 },
413 .Vector => switch (val.tag()) {
414 .aggregate => {
415 const elem_vals = val.castTag(.aggregate).?.data;
416 const vector_len = @intCast(usize, ty.vectorLen());
417 const elem_ty = ty.elemType();
418
419 const elem_refs = try self.gpa.alloc(IdRef, vector_len);
420 defer self.gpa.free(elem_refs);
421 for (elem_refs, 0..) |*elem, i| {
422 elem.* = try self.genConstant(elem_ty, elem_vals[i]);
423 }
424 try section.emit(self.spv.gpa, .OpConstantComposite, .{
425 .id_result_type = result_type_id,
426 .id_result = result_id,
427 .constituents = elem_refs,
428 });
429 },
430 else => unreachable, // TODO
431 },
432 .Void => unreachable,
433 .Fn => unreachable,
434 else => return self.todo("constant generation of type {}", .{ty.fmtDebug()}),
435991 }
436992
437 return result_id.toRef();
993 return result_id;
438994 }
439995
440996 /// Turn a Zig type into a SPIR-V Type, and return its type result-id.
441997 fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType {
442 const type_ref = try self.resolveType(ty);
443 return self.spv.typeResultId(type_ref);
998 const type_ref = try self.resolveType(ty, .direct);
999 return self.typeId(type_ref);
1000 }
1001
1002 fn typeId(self: *DeclGen, ty_ref: SpvType.Ref) IdRef {
1003 return self.spv.typeId(ty_ref);
1004 }
1005
1006 /// Create an integer type suitable for storing at least 'bits' bits.
1007 fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvType.Ref {
1008 const backing_bits = self.backingIntBits(bits) orelse {
1009 // TODO: Integers too big for any native type are represented as "composite integers":
1010 // An array of largestSupportedIntBits.
1011 return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });
1012 };
1013
1014 return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits));
1015 }
1016
1017 /// Create an integer type that represents 'usize'.
1018 fn sizeType(self: *DeclGen) !SpvType.Ref {
1019 return try self.intType(.unsigned, self.getTarget().cpu.arch.ptrBitWidth());
1020 }
1021
1022 /// Generate a union type, optionally with a known field. If the tag alignment is greater
1023 /// than that of the payload, a regular union (non-packed, with both tag and payload), will
1024 /// be generated as follows:
1025 /// If the active field is known:
1026 /// struct {
1027 /// tag: TagType,
1028 /// payload: ActivePayloadType,
1029 /// payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8,
1030 /// padding: [padding_size]u8,
1031 /// }
1032 /// If the payload alignment is greater than that of the tag:
1033 /// struct {
1034 /// payload: ActivePayloadType,
1035 /// payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8,
1036 /// tag: TagType,
1037 /// padding: [padding_size]u8,
1038 /// }
1039 /// If the active payload is unknown, it will default back to the most aligned field. This is
1040 /// to make sure that the overal struct has the correct alignment in spir-v.
1041 /// If any of the fields' size is 0, it will be omitted.
1042 /// NOTE: When the active field is set to something other than the most aligned field, the
1043 /// resulting struct will be *underaligned*.
1044 fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvType.Ref {
1045 const target = self.getTarget();
1046 const layout = ty.unionGetLayout(target);
1047 const union_ty = ty.cast(Type.Payload.Union).?.data;
1048
1049 if (union_ty.layout == .Packed) {
1050 return self.todo("packed union types", .{});
1051 }
1052
1053 const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect);
1054 if (layout.payload_size == 0) {
1055 // No payload, so represent this as just the tag type.
1056 return tag_ty_ref;
1057 }
1058
1059 var members = std.BoundedArray(SpvType.Payload.Struct.Member, 4){};
1060
1061 const has_tag = layout.tag_size != 0;
1062 const tag_first = layout.tag_align >= layout.payload_align;
1063 const tag_member = .{ .name = "tag", .ty = tag_ty_ref };
1064 const u8_ty_ref = try self.intType(.unsigned, 8); // TODO: What if Int8Type is not enabled?
1065
1066 if (has_tag and tag_first) {
1067 members.appendAssumeCapacity(tag_member);
1068 }
1069
1070 const active_field = maybe_active_field orelse layout.most_aligned_field;
1071 const active_field_ty = union_ty.fields.values()[active_field].ty;
1072
1073 const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: {
1074 const active_payload_ty_ref = try self.resolveType(active_field_ty, .indirect);
1075 members.appendAssumeCapacity(.{ .name = "payload", .ty = active_payload_ty_ref });
1076 break :blk active_field_ty.abiSize(target);
1077 } else 0;
1078
1079 const payload_padding_len = layout.payload_size - active_field_size;
1080 if (payload_padding_len != 0) {
1081 const payload_padding_ty_ref = try self.spv.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref);
1082 members.appendAssumeCapacity(.{ .name = "padding_payload", .ty = payload_padding_ty_ref });
1083 }
1084
1085 if (has_tag and !tag_first) {
1086 members.appendAssumeCapacity(tag_member);
1087 }
1088
1089 if (layout.padding != 0) {
1090 const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref);
1091 members.appendAssumeCapacity(.{ .name = "padding", .ty = padding_ty_ref });
1092 }
1093
1094 return try self.spv.simpleStructType(members.slice());
4441095 }
4451096
4461097 /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
447 fn resolveType(self: *DeclGen, ty: Type) Error!SpvType.Ref {
1098 fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!SpvType.Ref {
1099 log.debug("resolveType: ty = {}", .{ty.fmt(self.module)});
4481100 const target = self.getTarget();
449 return switch (ty.zigTypeTag()) {
450 .Void => try self.spv.resolveType(SpvType.initTag(.void)),
451 .Bool => blk: {
452 // TODO: SPIR-V booleans are opaque. For local variables this is fine, but for structs
453 // members we want to use integer types instead.
454 break :blk try self.spv.resolveType(SpvType.initTag(.bool));
1101 switch (ty.zigTypeTag()) {
1102 .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)),
1103 .Bool => switch (repr) {
1104 .direct => return try self.spv.resolveType(SpvType.initTag(.bool)),
1105 // SPIR-V booleans are opaque, which is fine for operations, but they cant be stored.
1106 // This function returns the *stored* type, for values directly we convert this into a bool when
1107 // it is loaded, and convert it back to this type when stored.
1108 .indirect => return try self.intType(.unsigned, 1),
4551109 },
456 .Int => blk: {
1110 .Int => {
4571111 const int_info = ty.intInfo(target);
458 const backing_bits = self.backingIntBits(int_info.bits) orelse {
459 // TODO: Integers too big for any native type are represented as "composite integers":
460 // An array of largestSupportedIntBits.
461 return self.todo("Implement composite int type {}", .{ty.fmtDebug()});
462 };
463
464 const payload = try self.spv.arena.create(SpvType.Payload.Int);
465 payload.* = .{
466 .width = backing_bits,
467 .signedness = int_info.signedness,
468 };
469 break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base));
1112 return try self.intType(int_info.signedness, int_info.bits);
1113 },
1114 .Enum => {
1115 var buffer: Type.Payload.Bits = undefined;
1116 const tag_ty = ty.intTagType(&buffer);
1117 return self.resolveType(tag_ty, repr);
4701118 },
471 .Float => blk: {
1119 .Float => {
4721120 // We can (and want) not really emulate floating points with other floating point types like with the integer types,
4731121 // so if the float is not supported, just return an error.
4741122 const bits = ty.floatBits(target);
......@@ -484,43 +1132,58 @@ pub const DeclGen = struct {
4841132 return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
4851133 }
4861134
487 const payload = try self.spv.arena.create(SpvType.Payload.Float);
488 payload.* = .{
489 .width = bits,
1135 return try self.spv.resolveType(SpvType.float(bits));
1136 },
1137 .Array => {
1138 const elem_ty = ty.childType();
1139 const elem_ty_ref = try self.resolveType(elem_ty, .indirect);
1140 const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse {
1141 return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()});
4901142 };
491 break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base));
1143 return try self.spv.arrayType(total_len, elem_ty_ref);
4921144 },
493 .Fn => blk: {
494 // We only support C-calling-convention functions for now, no varargs.
495 if (ty.fnCallingConvention() != .C)
496 return self.fail("Unsupported calling convention for SPIR-V", .{});
497 if (ty.fnIsVarArgs())
498 return self.fail("VarArgs functions are unsupported for SPIR-V", .{});
499
500 const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen());
501 for (param_types, 0..) |*param, i| {
502 param.* = try self.resolveType(ty.fnParamType(i));
503 }
1145 .Fn => switch (repr) {
1146 .direct => {
1147 // TODO: Put this somewhere in Sema.zig
1148 if (ty.fnIsVarArgs())
1149 return self.fail("VarArgs functions are unsupported for SPIR-V", .{});
1150
1151 // TODO: Parameter passing convention etc.
1152
1153 const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen());
1154 for (param_types, 0..) |*param, i| {
1155 param.* = try self.resolveType(ty.fnParamType(i), .direct);
1156 }
5041157
505 const return_type = try self.resolveType(ty.fnReturnType());
1158 const return_type = try self.resolveType(ty.fnReturnType(), .direct);
5061159
507 const payload = try self.spv.arena.create(SpvType.Payload.Function);
508 payload.* = .{ .return_type = return_type, .parameters = param_types };
509 break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base));
1160 const payload = try self.spv.arena.create(SpvType.Payload.Function);
1161 payload.* = .{ .return_type = return_type, .parameters = param_types };
1162 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
1163 },
1164 .indirect => {
1165 // TODO: Represent function pointers properly.
1166 // For now, just use an usize type.
1167 return try self.sizeType();
1168 },
5101169 },
511 .Pointer => blk: {
512 const payload = try self.spv.arena.create(SpvType.Payload.Pointer);
513 payload.* = .{
514 .storage_class = spirvStorageClass(ty.ptrAddressSpace()),
515 .child_type = try self.resolveType(ty.elemType()),
516 .array_stride = 0,
517 // Note: only available in Kernels!
518 .alignment = null,
519 .max_byte_offset = null,
520 };
521 break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base));
1170 .Pointer => {
1171 const ptr_info = ty.ptrInfo().data;
1172
1173 const storage_class = spvStorageClass(ptr_info.@"addrspace");
1174 const child_ty_ref = try self.resolveType(ptr_info.pointee_type, .indirect);
1175 const ptr_ty_ref = try self.spv.ptrType(child_ty_ref, storage_class, 0);
1176
1177 if (ptr_info.size != .Slice) {
1178 return ptr_ty_ref;
1179 }
1180
1181 return try self.spv.simpleStructType(&.{
1182 .{ .ty = ptr_ty_ref, .name = "ptr" },
1183 .{ .ty = try self.sizeType(), .name = "len" },
1184 });
5221185 },
523 .Vector => blk: {
1186 .Vector => {
5241187 // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
5251188 // which work on them), so simply use those.
5261189 // Note: SPIR-V vectors only support bools, ints and floats, so pointer vectors need to be supported another way.
......@@ -532,10 +1195,112 @@ pub const DeclGen = struct {
5321195
5331196 const payload = try self.spv.arena.create(SpvType.Payload.Vector);
5341197 payload.* = .{
535 .component_type = try self.resolveType(ty.elemType()),
1198 .component_type = try self.resolveType(ty.elemType(), repr),
5361199 .component_count = @intCast(u32, ty.vectorLen()),
5371200 };
538 break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base));
1201 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
1202 },
1203 .Struct => {
1204 if (ty.isSimpleTupleOrAnonStruct()) {
1205 const tuple = ty.tupleFields();
1206 const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, tuple.types.len);
1207 var member_index: u32 = 0;
1208 for (tuple.types, 0..) |field_ty, i| {
1209 const field_val = tuple.values[i];
1210 if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBitsIgnoreComptime()) continue;
1211 members[member_index] = .{
1212 .ty = try self.resolveType(field_ty, .indirect),
1213 };
1214 member_index += 1;
1215 }
1216 const payload = try self.spv.arena.create(SpvType.Payload.Struct);
1217 payload.* = .{
1218 .members = members[0..member_index],
1219 };
1220 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
1221 }
1222
1223 const struct_ty = ty.castTag(.@"struct").?.data;
1224
1225 if (struct_ty.layout == .Packed) {
1226 return try self.resolveType(struct_ty.backing_int_ty, .indirect);
1227 }
1228
1229 const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count());
1230 var member_index: usize = 0;
1231 for (struct_ty.fields.values(), 0..) |field, i| {
1232 if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
1233
1234 members[member_index] = .{
1235 .ty = try self.resolveType(field.ty, .indirect),
1236 .name = struct_ty.fields.keys()[i],
1237 };
1238 member_index += 1;
1239 }
1240
1241 const name = try struct_ty.getFullyQualifiedName(self.module);
1242 defer self.module.gpa.free(name);
1243
1244 const payload = try self.spv.arena.create(SpvType.Payload.Struct);
1245 payload.* = .{
1246 .members = members[0..member_index],
1247 .name = try self.spv.arena.dupe(u8, name),
1248 };
1249 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
1250 },
1251 .Optional => {
1252 var buf: Type.Payload.ElemType = undefined;
1253 const payload_ty = ty.optionalChild(&buf);
1254 if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
1255 // Just use a bool.
1256 // Note: Always generate the bool with indirect format, to save on some sanity
1257 // Perform the converison to a direct bool when the field is extracted.
1258 return try self.resolveType(Type.bool, .indirect);
1259 }
1260
1261 const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
1262 if (ty.optionalReprIsPayload()) {
1263 // Optional is actually a pointer.
1264 return payload_ty_ref;
1265 }
1266
1267 const bool_ty_ref = try self.resolveType(Type.bool, .indirect);
1268
1269 // its an actual optional
1270 return try self.spv.simpleStructType(&.{
1271 .{ .ty = payload_ty_ref, .name = "payload" },
1272 .{ .ty = bool_ty_ref, .name = "valid" },
1273 });
1274 },
1275 .Union => return try self.resolveUnionType(ty, null),
1276 .ErrorSet => return try self.intType(.unsigned, 16),
1277 .ErrorUnion => {
1278 const payload_ty = ty.errorUnionPayload();
1279 const error_ty_ref = try self.resolveType(Type.anyerror, .indirect);
1280 if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
1281 return error_ty_ref;
1282 }
1283
1284 const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
1285
1286 const payload_align = payload_ty.abiAlignment(target);
1287 const error_align = Type.anyerror.abiAlignment(target);
1288
1289 var members = std.BoundedArray(SpvType.Payload.Struct.Member, 2){};
1290 // Similar to unions, we're going to put the most aligned member first.
1291 if (error_align > payload_align) {
1292 // Put the error first
1293 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
1294 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
1295 // TODO: ABI padding?
1296 } else {
1297 // Put the payload first.
1298 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
1299 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
1300 // TODO: ABI padding?
1301 }
1302
1303 return try self.spv.simpleStructType(members.slice());
5391304 },
5401305
5411306 .Null,
......@@ -547,31 +1312,120 @@ pub const DeclGen = struct {
5471312 => unreachable, // Must be comptime.
5481313
5491314 else => |tag| return self.todo("Implement zig type '{}'", .{tag}),
550 };
1315 }
5511316 }
5521317
553 fn spirvStorageClass(as: std.builtin.AddressSpace) spec.StorageClass {
1318 fn spvStorageClass(as: std.builtin.AddressSpace) StorageClass {
5541319 return switch (as) {
555 .generic => .Generic, // TODO: Disallow?
556 .gs, .fs, .ss => unreachable,
1320 .generic => .Generic,
5571321 .shared => .Workgroup,
5581322 .local => .Private,
559 .global, .param, .constant, .flash, .flash1, .flash2, .flash3, .flash4, .flash5 => unreachable,
1323 .global => .CrossWorkgroup,
1324 .constant => .UniformConstant,
1325 .gs,
1326 .fs,
1327 .ss,
1328 .param,
1329 .flash,
1330 .flash1,
1331 .flash2,
1332 .flash3,
1333 .flash4,
1334 .flash5,
1335 => unreachable,
5601336 };
5611337 }
5621338
1339 /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure.
1340 /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry-
1341 /// points. The test executor will then be able to invoke these to run the tests.
1342 /// Note that tests are lowered according to std.builtin.TestFn, which is `fn () anyerror!void`.
1343 /// (anyerror!void has the same layout as anyerror).
1344 /// Each test declaration generates a function like.
1345 /// %anyerror = OpTypeInt 0 16
1346 /// %p_anyerror = OpTypePointer CrossWorkgroup %anyerror
1347 /// %K = OpTypeFunction %void %p_anyerror
1348 ///
1349 /// %test = OpFunction %void %K
1350 /// %p_err = OpFunctionParameter %p_anyerror
1351 /// %lbl = OpLabel
1352 /// %result = OpFunctionCall %anyerror %func
1353 /// OpStore %p_err %result
1354 /// OpFunctionEnd
1355 /// TODO is to also write out the error as a function call parameter, and to somehow fetch
1356 /// the name of an error in the text executor.
1357 fn generateTestEntryPoint(self: *DeclGen, name: []const u8, spv_test_decl_index: SpvModule.Decl.Index) !void {
1358 const anyerror_ty_ref = try self.resolveType(Type.anyerror, .direct);
1359 const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup, 0);
1360 const void_ty_ref = try self.resolveType(Type.void, .direct);
1361
1362 const kernel_proto_ty_ref = blk: {
1363 const proto_payload = try self.spv.arena.create(SpvType.Payload.Function);
1364 proto_payload.* = .{
1365 .return_type = void_ty_ref,
1366 .parameters = try self.spv.arena.dupe(SpvType.Ref, &.{ptr_anyerror_ty_ref}),
1367 };
1368 break :blk try self.spv.resolveType(SpvType.initPayload(&proto_payload.base));
1369 };
1370
1371 const test_id = self.spv.declPtr(spv_test_decl_index).result_id;
1372
1373 const spv_decl_index = try self.spv.allocDecl(.func);
1374 const kernel_id = self.spv.declPtr(spv_decl_index).result_id;
1375
1376 const error_id = self.spv.allocId();
1377 const p_error_id = self.spv.allocId();
1378
1379 const section = &self.spv.sections.functions;
1380 try section.emit(self.spv.gpa, .OpFunction, .{
1381 .id_result_type = self.typeId(void_ty_ref),
1382 .id_result = kernel_id,
1383 .function_control = .{},
1384 .function_type = self.typeId(kernel_proto_ty_ref),
1385 });
1386 try section.emit(self.spv.gpa, .OpFunctionParameter, .{
1387 .id_result_type = self.typeId(ptr_anyerror_ty_ref),
1388 .id_result = p_error_id,
1389 });
1390 try section.emit(self.spv.gpa, .OpLabel, .{
1391 .id_result = self.spv.allocId(),
1392 });
1393 try section.emit(self.spv.gpa, .OpFunctionCall, .{
1394 .id_result_type = self.typeId(anyerror_ty_ref),
1395 .id_result = error_id,
1396 .function = test_id,
1397 });
1398 try section.emit(self.spv.gpa, .OpStore, .{
1399 .pointer = p_error_id,
1400 .object = error_id,
1401 });
1402 try section.emit(self.spv.gpa, .OpReturn, {});
1403 try section.emit(self.spv.gpa, .OpFunctionEnd, {});
1404
1405 try self.spv.declareDeclDeps(spv_decl_index, &.{spv_test_decl_index});
1406
1407 // Just generate a quick other name because the intel runtime crashes when the entry-
1408 // point name is the same as a different OpName.
1409 const test_name = try std.fmt.allocPrint(self.gpa, "test {s}", .{name});
1410 defer self.gpa.free(test_name);
1411 try self.spv.declareEntryPoint(spv_decl_index, test_name);
1412 }
1413
5631414 fn genDecl(self: *DeclGen) !void {
564 const result_id = self.ids.get(self.decl_index).?;
5651415 const decl = self.module.declPtr(self.decl_index);
1416 const spv_decl_index = try self.resolveDecl(self.decl_index);
1417
1418 const decl_id = self.spv.declPtr(spv_decl_index).result_id;
1419 log.debug("genDecl {s} = {}", .{ decl.name, decl_id });
5661420
5671421 if (decl.val.castTag(.function)) |_| {
5681422 assert(decl.ty.zigTypeTag() == .Fn);
5691423 const prototype_id = try self.resolveTypeId(decl.ty);
5701424 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{
5711425 .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()),
572 .id_result = result_id,
1426 .id_result = decl_id,
5731427 .function_control = .{}, // TODO: We can set inline here if the type requires it.
574 .function_type = prototype_id.toRef(),
1428 .function_type = prototype_id,
5751429 });
5761430
5771431 const params = decl.ty.fnParamLen();
......@@ -585,7 +1439,7 @@ pub const DeclGen = struct {
5851439 .id_result_type = param_type_id,
5861440 .id_result = arg_result_id,
5871441 });
588 self.args.appendAssumeCapacity(arg_result_id.toRef());
1442 self.args.appendAssumeCapacity(arg_result_id);
5891443 }
5901444
5911445 // TODO: This could probably be done in a better way...
......@@ -596,17 +1450,53 @@ pub const DeclGen = struct {
5961450 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
5971451 .id_result = root_block_id,
5981452 });
599 self.current_block_label_id = root_block_id.toRef();
1453 self.current_block_label_id = root_block_id;
6001454
6011455 const main_body = self.air.getMainBody();
6021456 try self.genBody(main_body);
6031457
6041458 // Append the actual code into the functions section.
6051459 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
606 try self.spv.addFunction(self.func);
1460 try self.spv.addFunction(spv_decl_index, self.func);
1461
1462 const fqn = try decl.getFullyQualifiedName(self.module);
1463 defer self.module.gpa.free(fqn);
1464
1465 try self.spv.sections.debug_names.emit(self.gpa, .OpName, .{
1466 .target = decl_id,
1467 .name = fqn,
1468 });
1469
1470 // Temporarily generate a test kernel declaration if this is a test function.
1471 if (self.module.test_functions.contains(self.decl_index)) {
1472 try self.generateTestEntryPoint(fqn, spv_decl_index);
1473 }
6071474 } else {
608 // TODO
609 // return self.todo("generate decl type {}", .{decl.ty.zigTypeTag()});
1475 const init_val = if (decl.val.castTag(.variable)) |payload|
1476 payload.data.init
1477 else
1478 decl.val;
1479
1480 if (init_val.tag() == .unreachable_value) {
1481 return self.todo("importing extern variables", .{});
1482 }
1483
1484 // TODO: integrate with variable().
1485
1486 const final_storage_class = spvStorageClass(decl.@"addrspace");
1487 const actual_storage_class = switch (final_storage_class) {
1488 .Generic => .CrossWorkgroup,
1489 else => final_storage_class,
1490 };
1491
1492 try self.lowerIndirectConstant(
1493 spv_decl_index,
1494 decl.ty,
1495 init_val,
1496 actual_storage_class,
1497 final_storage_class == .Generic,
1498 decl.@"align",
1499 );
6101500 }
6111501 }
6121502
......@@ -618,11 +1508,25 @@ pub const DeclGen = struct {
6181508
6191509 fn genInst(self: *DeclGen, inst: Air.Inst.Index) !void {
6201510 const air_tags = self.air.instructions.items(.tag);
621 const result_id = switch (air_tags[inst]) {
1511 const maybe_result_id: ?IdRef = switch (air_tags[inst]) {
6221512 // zig fmt: off
623 .add, .addwrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd),
624 .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub),
625 .mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul),
1513 .add, .addwrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd, true),
1514 .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub, true),
1515 .mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul, true),
1516
1517 .div_float,
1518 .div_float_optimized,
1519 // TODO: Check that this is the right operation.
1520 .div_trunc,
1521 .div_trunc_optimized,
1522 => try self.airArithOp(inst, .OpFDiv, .OpSDiv, .OpUDiv, false),
1523 // TODO: Check if this is the right operation
1524 // TODO: Make airArithOp for rem not emit a mask for the LHS.
1525 .rem,
1526 .rem_optimized,
1527 => try self.airArithOp(inst, .OpFRem, .OpSRem, .OpSRem, false),
1528
1529 .add_with_overflow => try self.airOverflowArithOp(inst),
6261530
6271531 .shuffle => try self.airShuffle(inst),
6281532
......@@ -632,7 +1536,24 @@ pub const DeclGen = struct {
6321536 .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd),
6331537 .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr),
6341538
635 .not => try self.airNot(inst),
1539 .shl => try self.airShift(inst, .OpShiftLeftLogical),
1540
1541 .bitcast => try self.airBitcast(inst),
1542 .intcast => try self.airIntcast(inst),
1543 .not => try self.airNot(inst),
1544
1545 .slice_ptr => try self.airSliceField(inst, 0),
1546 .slice_len => try self.airSliceField(inst, 1),
1547 .slice_elem_ptr => try self.airSliceElemPtr(inst),
1548 .slice_elem_val => try self.airSliceElemVal(inst),
1549 .ptr_elem_ptr => try self.airPtrElemPtr(inst),
1550
1551 .struct_field_val => try self.airStructFieldVal(inst),
1552
1553 .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
1554 .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
1555 .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
1556 .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
6361557
6371558 .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual),
6381559 .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual),
......@@ -641,21 +1562,33 @@ pub const DeclGen = struct {
6411562 .cmp_lt => try self.airCmp(inst, .OpFOrdLessThan, .OpSLessThan, .OpULessThan),
6421563 .cmp_lte => try self.airCmp(inst, .OpFOrdLessThanEqual, .OpSLessThanEqual, .OpULessThanEqual),
6431564
644 .arg => self.airArg(),
645 .alloc => try self.airAlloc(inst),
646 .block => (try self.airBlock(inst)) orelse return,
647 .load => try self.airLoad(inst),
1565 .arg => self.airArg(),
1566 .alloc => try self.airAlloc(inst),
1567 // TODO: We probably need to have a special implementation of this for the C abi.
1568 .ret_ptr => try self.airAlloc(inst),
1569 .block => try self.airBlock(inst),
1570
1571 .load => try self.airLoad(inst),
1572 .store => return self.airStore(inst),
6481573
6491574 .br => return self.airBr(inst),
6501575 .breakpoint => return,
6511576 .cond_br => return self.airCondBr(inst),
6521577 .constant => unreachable,
1578 .const_ty => unreachable,
6531579 .dbg_stmt => return self.airDbgStmt(inst),
6541580 .loop => return self.airLoop(inst),
6551581 .ret => return self.airRet(inst),
656 .store => return self.airStore(inst),
1582 .ret_load => return self.airRetLoad(inst),
1583 .switch_br => return self.airSwitchBr(inst),
6571584 .unreach => return self.airUnreach(),
658 .assembly => (try self.airAssembly(inst)) orelse return,
1585
1586 .assembly => try self.airAssembly(inst),
1587
1588 .call => try self.airCall(inst, .auto),
1589 .call_always_tail => try self.airCall(inst, .always_tail),
1590 .call_never_tail => try self.airCall(inst, .never_tail),
1591 .call_never_inline => try self.airCall(inst, .never_inline),
6591592
6601593 .dbg_var_ptr => return,
6611594 .dbg_var_val => return,
......@@ -666,22 +1599,62 @@ pub const DeclGen = struct {
6661599 else => |tag| return self.todo("implement AIR tag {s}", .{@tagName(tag)}),
6671600 };
6681601
1602 const result_id = maybe_result_id orelse return;
6691603 try self.inst_results.putNoClobber(self.gpa, inst, result_id);
6701604 }
6711605
672 fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !IdRef {
1606 fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef {
1607 if (self.liveness.isUnused(inst)) return null;
1608 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1609 const lhs_id = try self.resolve(bin_op.lhs);
1610 const rhs_id = try self.resolve(bin_op.rhs);
1611 const result_id = self.spv.allocId();
1612 const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst));
1613 try self.func.body.emit(self.spv.gpa, opcode, .{
1614 .id_result_type = result_type_id,
1615 .id_result = result_id,
1616 .operand_1 = lhs_id,
1617 .operand_2 = rhs_id,
1618 });
1619 return result_id;
1620 }
1621
1622 fn airShift(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef {
1623 if (self.liveness.isUnused(inst)) return null;
6731624 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
6741625 const lhs_id = try self.resolve(bin_op.lhs);
6751626 const rhs_id = try self.resolve(bin_op.rhs);
676 const result_id = self.spv.allocId();
6771627 const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst));
1628
1629 // the shift and the base must be the same type in SPIR-V, but in Zig the shift is a smaller int.
1630 const shift_id = self.spv.allocId();
1631 try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
1632 .id_result_type = result_type_id,
1633 .id_result = shift_id,
1634 .unsigned_value = rhs_id,
1635 });
1636
1637 const result_id = self.spv.allocId();
6781638 try self.func.body.emit(self.spv.gpa, opcode, .{
6791639 .id_result_type = result_type_id,
6801640 .id_result = result_id,
681 .operand_1 = lhs_id,
682 .operand_2 = rhs_id,
1641 .base = lhs_id,
1642 .shift = shift_id,
1643 });
1644 return result_id;
1645 }
1646
1647 fn maskStrangeInt(self: *DeclGen, ty_ref: SpvType.Ref, value_id: IdRef, bits: u16) !IdRef {
1648 const mask_value = if (bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @intCast(u6, bits)) - 1;
1649 const result_id = self.spv.allocId();
1650 const mask_id = try self.constInt(ty_ref, mask_value);
1651 try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{
1652 .id_result_type = self.typeId(ty_ref),
1653 .id_result = result_id,
1654 .operand_1 = value_id,
1655 .operand_2 = mask_id,
6831656 });
684 return result_id.toRef();
1657 return result_id;
6851658 }
6861659
6871660 fn airArithOp(
......@@ -690,16 +1663,18 @@ pub const DeclGen = struct {
6901663 comptime fop: Opcode,
6911664 comptime sop: Opcode,
6921665 comptime uop: Opcode,
693 ) !IdRef {
1666 /// true if this operation holds under modular arithmetic.
1667 comptime modular: bool,
1668 ) !?IdRef {
1669 if (self.liveness.isUnused(inst)) return null;
6941670 // LHS and RHS are guaranteed to have the same type, and AIR guarantees
6951671 // the result to be the same as the LHS and RHS, which matches SPIR-V.
6961672 const ty = self.air.typeOfIndex(inst);
6971673 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
698 const lhs_id = try self.resolve(bin_op.lhs);
699 const rhs_id = try self.resolve(bin_op.rhs);
1674 var lhs_id = try self.resolve(bin_op.lhs);
1675 var rhs_id = try self.resolve(bin_op.rhs);
7001676
701 const result_id = self.spv.allocId();
702 const result_type_id = try self.resolveTypeId(ty);
1677 const result_ty_ref = try self.resolveType(ty, .direct);
7031678
7041679 assert(self.air.typeOf(bin_op.lhs).eql(ty, self.module));
7051680 assert(self.air.typeOf(bin_op.rhs).eql(ty, self.module));
......@@ -712,19 +1687,27 @@ pub const DeclGen = struct {
7121687 .composite_integer => {
7131688 return self.todo("binary operations for composite integers", .{});
7141689 },
715 .strange_integer => {
716 return self.todo("binary operations for strange integers", .{});
1690 .strange_integer => blk: {
1691 if (!modular) {
1692 lhs_id = try self.maskStrangeInt(result_ty_ref, lhs_id, info.bits);
1693 rhs_id = try self.maskStrangeInt(result_ty_ref, rhs_id, info.bits);
1694 }
1695 break :blk switch (info.signedness) {
1696 .signed => @as(usize, 1),
1697 .unsigned => @as(usize, 2),
1698 };
7171699 },
7181700 .integer => switch (info.signedness) {
7191701 .signed => @as(usize, 1),
7201702 .unsigned => @as(usize, 2),
7211703 },
7221704 .float => 0,
723 else => unreachable,
1705 .bool => unreachable,
7241706 };
7251707
1708 const result_id = self.spv.allocId();
7261709 const operands = .{
727 .id_result_type = result_type_id,
1710 .id_result_type = self.typeId(result_ty_ref),
7281711 .id_result = result_id,
7291712 .operand_1 = lhs_id,
7301713 .operand_2 = rhs_id,
......@@ -739,10 +1722,90 @@ pub const DeclGen = struct {
7391722 // TODO: Trap on overflow? Probably going to be annoying.
7401723 // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap.
7411724
742 return result_id.toRef();
1725 return result_id;
1726 }
1727
1728 fn airOverflowArithOp(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
1729 if (self.liveness.isUnused(inst)) return null;
1730
1731 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
1732 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
1733 const lhs = try self.resolve(extra.lhs);
1734 const rhs = try self.resolve(extra.rhs);
1735
1736 const operand_ty = self.air.typeOf(extra.lhs);
1737 const result_ty = self.air.typeOfIndex(inst);
1738
1739 const info = try self.arithmeticTypeInfo(operand_ty);
1740 switch (info.class) {
1741 .composite_integer => return self.todo("overflow ops for composite integers", .{}),
1742 .strange_integer => return self.todo("overflow ops for strange integers", .{}),
1743 .integer => {},
1744 .float, .bool => unreachable,
1745 }
1746
1747 const operand_ty_id = try self.resolveTypeId(operand_ty);
1748 const result_type_id = try self.resolveTypeId(result_ty);
1749
1750 const overflow_member_ty = try self.intType(.unsigned, info.bits);
1751 const overflow_member_ty_id = self.typeId(overflow_member_ty);
1752
1753 const op_result_id = blk: {
1754 // Construct the SPIR-V result type.
1755 // It is almost the same as the zig one, except that the fields must be the same type
1756 // and they must be unsigned.
1757 const overflow_result_ty_ref = try self.spv.simpleStructType(&.{
1758 .{ .ty = overflow_member_ty, .name = "res" },
1759 .{ .ty = overflow_member_ty, .name = "ov" },
1760 });
1761 const result_id = self.spv.allocId();
1762 try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{
1763 .id_result_type = self.typeId(overflow_result_ty_ref),
1764 .id_result = result_id,
1765 .operand_1 = lhs,
1766 .operand_2 = rhs,
1767 });
1768 break :blk result_id;
1769 };
1770
1771 // Now convert the SPIR-V flavor result into a Zig-flavor result.
1772 // First, extract the two fields.
1773 const unsigned_result = try self.extractField(overflow_member_ty_id, op_result_id, 0);
1774 const overflow = try self.extractField(overflow_member_ty_id, op_result_id, 1);
1775
1776 // We need to convert the results to the types that Zig expects here.
1777 // The `result` is the same type except unsigned, so we can just bitcast that.
1778 const result = try self.bitcast(operand_ty_id, unsigned_result);
1779
1780 // The overflow needs to be converted into whatever is used to represent it in Zig.
1781 const casted_overflow = blk: {
1782 const ov_ty = result_ty.tupleFields().types[1];
1783 const ov_ty_id = try self.resolveTypeId(ov_ty);
1784 const result_id = self.spv.allocId();
1785 try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
1786 .id_result_type = ov_ty_id,
1787 .id_result = result_id,
1788 .unsigned_value = overflow,
1789 });
1790 break :blk result_id;
1791 };
1792
1793 // TODO: If copying this function for borrow, make sure to convert -1 to 1 as appropriate.
1794
1795 // Finally, construct the Zig type.
1796 // Layout is result, overflow.
1797 const result_id = self.spv.allocId();
1798 const constituents = [_]IdRef{ result, casted_overflow };
1799 try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{
1800 .id_result_type = result_type_id,
1801 .id_result = result_id,
1802 .constituents = &constituents,
1803 });
1804 return result_id;
7431805 }
7441806
745 fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !IdRef {
1807 fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
1808 if (self.liveness.isUnused(inst)) return null;
7461809 const ty = self.air.typeOfIndex(inst);
7471810 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
7481811 const extra = self.air.extraData(Air.Shuffle, ty_pl.payload).data;
......@@ -774,15 +1837,16 @@ pub const DeclGen = struct {
7741837 self.func.body.writeOperand(spec.LiteralInteger, unsigned);
7751838 }
7761839 }
777 return result_id.toRef();
1840 return result_id;
7781841 }
7791842
780 fn airCmp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !IdRef {
1843 fn airCmp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !?IdRef {
1844 if (self.liveness.isUnused(inst)) return null;
7811845 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
782 const lhs_id = try self.resolve(bin_op.lhs);
783 const rhs_id = try self.resolve(bin_op.rhs);
1846 var lhs_id = try self.resolve(bin_op.lhs);
1847 var rhs_id = try self.resolve(bin_op.rhs);
7841848 const result_id = self.spv.allocId();
785 const result_type_id = try self.resolveTypeId(Type.initTag(.bool));
1849 const result_type_id = try self.resolveTypeId(Type.bool);
7861850 const op_ty = self.air.typeOf(bin_op.lhs);
7871851 assert(op_ty.eql(self.air.typeOf(bin_op.rhs), self.module));
7881852
......@@ -794,11 +1858,17 @@ pub const DeclGen = struct {
7941858 .composite_integer => {
7951859 return self.todo("binary operations for composite integers", .{});
7961860 },
797 .strange_integer => {
798 return self.todo("comparison for strange integers", .{});
799 },
8001861 .float => 0,
8011862 .bool => 1,
1863 .strange_integer => blk: {
1864 const op_ty_ref = try self.resolveType(op_ty, .direct);
1865 lhs_id = try self.maskStrangeInt(op_ty_ref, lhs_id, info.bits);
1866 rhs_id = try self.maskStrangeInt(op_ty_ref, rhs_id, info.bits);
1867 break :blk switch (info.signedness) {
1868 .signed => @as(usize, 1),
1869 .unsigned => @as(usize, 2),
1870 };
1871 },
8021872 .integer => switch (info.signedness) {
8031873 .signed => @as(usize, 1),
8041874 .unsigned => @as(usize, 2),
......@@ -819,41 +1889,336 @@ pub const DeclGen = struct {
8191889 else => unreachable,
8201890 }
8211891
822 return result_id.toRef();
1892 return result_id;
1893 }
1894
1895 fn bitcast(self: *DeclGen, target_type_id: IdResultType, value_id: IdRef) !IdRef {
1896 const result_id = self.spv.allocId();
1897 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
1898 .id_result_type = target_type_id,
1899 .id_result = result_id,
1900 .operand = value_id,
1901 });
1902 return result_id;
1903 }
1904
1905 fn airBitcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
1906 if (self.liveness.isUnused(inst)) return null;
1907 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1908 const operand_id = try self.resolve(ty_op.operand);
1909 const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst));
1910 return try self.bitcast(result_type_id, operand_id);
1911 }
1912
1913 fn airIntcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
1914 if (self.liveness.isUnused(inst)) return null;
1915
1916 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1917 const operand_id = try self.resolve(ty_op.operand);
1918 const dest_ty = self.air.typeOfIndex(inst);
1919 const dest_info = try self.arithmeticTypeInfo(dest_ty);
1920 const dest_ty_id = try self.resolveTypeId(dest_ty);
1921
1922 const result_id = self.spv.allocId();
1923 switch (dest_info.signedness) {
1924 .signed => try self.func.body.emit(self.spv.gpa, .OpSConvert, .{
1925 .id_result_type = dest_ty_id,
1926 .id_result = result_id,
1927 .signed_value = operand_id,
1928 }),
1929 .unsigned => try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
1930 .id_result_type = dest_ty_id,
1931 .id_result = result_id,
1932 .unsigned_value = operand_id,
1933 }),
1934 }
1935 return result_id;
8231936 }
8241937
825 fn airNot(self: *DeclGen, inst: Air.Inst.Index) !IdRef {
1938 fn airNot(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
1939 if (self.liveness.isUnused(inst)) return null;
8261940 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
8271941 const operand_id = try self.resolve(ty_op.operand);
8281942 const result_id = self.spv.allocId();
829 const result_type_id = try self.resolveTypeId(Type.initTag(.bool));
1943 const result_type_id = try self.resolveTypeId(Type.bool);
8301944 try self.func.body.emit(self.spv.gpa, .OpLogicalNot, .{
8311945 .id_result_type = result_type_id,
8321946 .id_result = result_id,
8331947 .operand = operand_id,
8341948 });
835 return result_id.toRef();
1949 return result_id;
8361950 }
8371951
838 fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !IdRef {
839 const ty = self.air.typeOfIndex(inst);
840 const result_type_id = try self.resolveTypeId(ty);
1952 fn extractField(self: *DeclGen, result_ty: IdResultType, object: IdRef, field: u32) !IdRef {
8411953 const result_id = self.spv.allocId();
1954 const indexes = [_]u32{field};
1955 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
1956 .id_result_type = result_ty,
1957 .id_result = result_id,
1958 .composite = object,
1959 .indexes = &indexes,
1960 });
1961 return result_id;
1962 }
8421963
843 // Rather than generating into code here, we're just going to generate directly into the functions section so that
844 // variable declarations appear in the first block of the function.
845 const storage_class = spirvStorageClass(ty.ptrAddressSpace());
846 const section = if (storage_class == .Function)
847 &self.func.prologue
848 else
849 &self.spv.sections.types_globals_constants;
1964 fn airSliceField(self: *DeclGen, inst: Air.Inst.Index, field: u32) !?IdRef {
1965 if (self.liveness.isUnused(inst)) return null;
1966 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1967 return try self.extractField(
1968 try self.resolveTypeId(self.air.typeOfIndex(inst)),
1969 try self.resolve(ty_op.operand),
1970 field,
1971 );
1972 }
8501973
851 try section.emit(self.spv.gpa, .OpVariable, .{
1974 fn airSliceElemPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
1975 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1976 const slice_ty = self.air.typeOf(bin_op.lhs);
1977 if (!slice_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null;
1978
1979 const slice = try self.resolve(bin_op.lhs);
1980 const index = try self.resolve(bin_op.rhs);
1981
1982 const spv_ptr_ty = try self.resolveTypeId(self.air.typeOfIndex(inst));
1983
1984 const slice_ptr = blk: {
1985 const result_id = self.spv.allocId();
1986 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
1987 .id_result_type = spv_ptr_ty,
1988 .id_result = result_id,
1989 .composite = slice,
1990 .indexes = &.{0},
1991 });
1992 break :blk result_id;
1993 };
1994
1995 const result_id = self.spv.allocId();
1996 try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{
1997 .id_result_type = spv_ptr_ty,
1998 .id_result = result_id,
1999 .base = slice_ptr,
2000 .element = index,
2001 });
2002 return result_id;
2003 }
2004
2005 fn airSliceElemVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
2006 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2007 const slice_ty = self.air.typeOf(bin_op.lhs);
2008 if (!slice_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null;
2009
2010 const slice = try self.resolve(bin_op.lhs);
2011 const index = try self.resolve(bin_op.rhs);
2012
2013 var slice_buf: Type.SlicePtrFieldTypeBuffer = undefined;
2014 const ptr_ty_id = try self.resolveTypeId(slice_ty.slicePtrFieldType(&slice_buf));
2015
2016 const slice_ptr = blk: {
2017 const result_id = self.spv.allocId();
2018 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
2019 .id_result_type = ptr_ty_id,
2020 .id_result = result_id,
2021 .composite = slice,
2022 .indexes = &.{0},
2023 });
2024 break :blk result_id;
2025 };
2026
2027 const elem_ptr = blk: {
2028 const result_id = self.spv.allocId();
2029 try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{
2030 .id_result_type = ptr_ty_id,
2031 .id_result = result_id,
2032 .base = slice_ptr,
2033 .element = index,
2034 });
2035 break :blk result_id;
2036 };
2037
2038 return try self.load(slice_ty, elem_ptr);
2039 }
2040
2041 fn airPtrElemPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
2042 if (self.liveness.isUnused(inst)) return null;
2043
2044 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
2045 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
2046 const ptr_ty = self.air.typeOf(bin_op.lhs);
2047 const result_ty = self.air.typeOfIndex(inst);
2048 const elem_ty = ptr_ty.childType();
2049 // TODO: Make this return a null ptr or something
2050 if (!elem_ty.hasRuntimeBitsIgnoreComptime()) return null;
2051
2052 const result_type_id = try self.resolveTypeId(result_ty);
2053 const base_ptr = try self.resolve(bin_op.lhs);
2054 const rhs = try self.resolve(bin_op.rhs);
2055
2056 const result_id = self.spv.allocId();
2057 const indexes = [_]IdRef{rhs};
2058 try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{
8522059 .id_result_type = result_type_id,
8532060 .id_result = result_id,
854 .storage_class = storage_class,
2061 .base = base_ptr,
2062 .indexes = &indexes,
2063 });
2064 return result_id;
2065 }
2066
2067 fn airStructFieldVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
2068 if (self.liveness.isUnused(inst)) return null;
2069
2070 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
2071 const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
2072
2073 const struct_ty = self.air.typeOf(struct_field.struct_operand);
2074 const object = try self.resolve(struct_field.struct_operand);
2075 const field_index = struct_field.field_index;
2076 const field_ty = struct_ty.structFieldType(field_index);
2077 const field_ty_id = try self.resolveTypeId(field_ty);
2078
2079 if (!field_ty.hasRuntimeBitsIgnoreComptime()) return null;
2080
2081 assert(struct_ty.zigTypeTag() == .Struct); // Cannot do unions yet.
2082
2083 const result_id = self.spv.allocId();
2084 const indexes = [_]u32{field_index};
2085 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
2086 .id_result_type = field_ty_id,
2087 .id_result = result_id,
2088 .composite = object,
2089 .indexes = &indexes,
8552090 });
856 return result_id.toRef();
2091 return result_id;
2092 }
2093
2094 fn structFieldPtr(
2095 self: *DeclGen,
2096 result_ptr_ty: Type,
2097 object_ptr_ty: Type,
2098 object_ptr: IdRef,
2099 field_index: u32,
2100 ) !?IdRef {
2101 const object_ty = object_ptr_ty.childType();
2102 switch (object_ty.zigTypeTag()) {
2103 .Struct => switch (object_ty.containerLayout()) {
2104 .Packed => unreachable, // TODO
2105 else => {
2106 const u32_ty_id = self.typeId(try self.intType(.unsigned, 32));
2107 const field_index_id = self.spv.allocId();
2108 try self.spv.emitConstant(u32_ty_id, field_index_id, .{ .uint32 = field_index });
2109 const result_id = self.spv.allocId();
2110 const result_type_id = try self.resolveTypeId(result_ptr_ty);
2111 const indexes = [_]IdRef{field_index_id};
2112 try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{
2113 .id_result_type = result_type_id,
2114 .id_result = result_id,
2115 .base = object_ptr,
2116 .indexes = &indexes,
2117 });
2118 return result_id;
2119 },
2120 },
2121 else => unreachable, // TODO
2122 }
2123 }
2124
2125 fn airStructFieldPtrIndex(self: *DeclGen, inst: Air.Inst.Index, field_index: u32) !?IdRef {
2126 if (self.liveness.isUnused(inst)) return null;
2127 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2128 const struct_ptr = try self.resolve(ty_op.operand);
2129 const struct_ptr_ty = self.air.typeOf(ty_op.operand);
2130 const result_ptr_ty = self.air.typeOfIndex(inst);
2131 return try self.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index);
2132 }
2133
2134 /// We cannot use an OpVariable directly in an OpSpecConstantOp, but we can
2135 /// after we insert a dummy AccessChain...
2136 /// TODO: Get rid of this
2137 fn makePointerConstant(
2138 self: *DeclGen,
2139 section: *SpvSection,
2140 ptr_ty_ref: SpvType.Ref,
2141 ptr_id: IdRef,
2142 ) !IdRef {
2143 const result_id = self.spv.allocId();
2144 try section.emitSpecConstantOp(self.spv.gpa, .OpInBoundsAccessChain, .{
2145 .id_result_type = self.typeId(ptr_ty_ref),
2146 .id_result = result_id,
2147 .base = ptr_id,
2148 });
2149 return result_id;
2150 }
2151
2152 fn variable(
2153 self: *DeclGen,
2154 comptime context: enum { function, global },
2155 result_id: IdRef,
2156 ptr_ty_ref: SpvType.Ref,
2157 initializer: ?IdRef,
2158 ) !void {
2159 const storage_class = self.spv.typeRefType(ptr_ty_ref).payload(.pointer).storage_class;
2160 const actual_storage_class = switch (storage_class) {
2161 .Generic => switch (context) {
2162 .function => .Function,
2163 .global => .CrossWorkgroup,
2164 },
2165 else => storage_class,
2166 };
2167 const actual_ptr_ty_ref = switch (storage_class) {
2168 .Generic => try self.spv.changePtrStorageClass(ptr_ty_ref, actual_storage_class),
2169 else => ptr_ty_ref,
2170 };
2171 const alloc_result_id = switch (storage_class) {
2172 .Generic => self.spv.allocId(),
2173 else => result_id,
2174 };
2175
2176 const section = switch (actual_storage_class) {
2177 .Generic => unreachable,
2178 // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to
2179 // directly generate them into func.prologue instead of the body.
2180 .Function => &self.func.prologue,
2181 else => &self.spv.sections.types_globals_constants,
2182 };
2183 try section.emit(self.spv.gpa, .OpVariable, .{
2184 .id_result_type = self.typeId(actual_ptr_ty_ref),
2185 .id_result = alloc_result_id,
2186 .storage_class = actual_storage_class,
2187 .initializer = initializer,
2188 });
2189
2190 if (storage_class != .Generic) {
2191 return;
2192 }
2193
2194 // Now we need to convert the pointer.
2195 // If this is a function local, we need to perform the conversion at runtime. Otherwise, we can do
2196 // it ahead of time using OpSpecConstantOp.
2197 switch (actual_storage_class) {
2198 .Function => try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{
2199 .id_result_type = self.typeId(ptr_ty_ref),
2200 .id_result = result_id,
2201 .pointer = alloc_result_id,
2202 }),
2203 // TODO: Can we do without this cast or move it to runtime?
2204 else => {
2205 const const_ptr_id = try self.makePointerConstant(section, actual_ptr_ty_ref, alloc_result_id);
2206 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
2207 .id_result_type = self.typeId(ptr_ty_ref),
2208 .id_result = result_id,
2209 .pointer = const_ptr_id,
2210 });
2211 },
2212 }
2213 }
2214
2215 fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
2216 if (self.liveness.isUnused(inst)) return null;
2217 const ty = self.air.typeOfIndex(inst);
2218 const result_ty_ref = try self.resolveType(ty, .direct);
2219 const result_id = self.spv.allocId();
2220 try self.variable(.function, result_id, result_ty_ref, null);
2221 return result_id;
8572222 }
8582223
8592224 fn airArg(self: *DeclGen) IdRef {
......@@ -873,7 +2238,7 @@ pub const DeclGen = struct {
8732238 var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.gpa, 4);
8742239
8752240 try self.blocks.putNoClobber(self.gpa, inst, .{
876 .label_id = label_id.toRef(),
2241 .label_id = label_id,
8772242 .incoming_blocks = &incoming_blocks,
8782243 });
8792244 defer {
......@@ -890,7 +2255,7 @@ pub const DeclGen = struct {
8902255 try self.beginSpvBlock(label_id);
8912256
8922257 // If this block didn't produce a value, simply return here.
893 if (!ty.hasRuntimeBits())
2258 if (!ty.hasRuntimeBitsIgnoreComptime())
8942259 return null;
8952260
8962261 // Combine the result from the blocks using the Phi instruction.
......@@ -908,7 +2273,7 @@ pub const DeclGen = struct {
9082273 self.func.body.writeOperand(spec.PairIdRefIdRef, .{ incoming.break_value_id, incoming.src_label_id });
9092274 }
9102275
911 return result_id.toRef();
2276 return result_id;
9122277 }
9132278
9142279 fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void {
......@@ -941,8 +2306,8 @@ pub const DeclGen = struct {
9412306
9422307 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
9432308 .condition = condition_id,
944 .true_label = then_label_id.toRef(),
945 .false_label = else_label_id.toRef(),
2309 .true_label = then_label_id,
2310 .false_label = else_label_id,
9462311 });
9472312
9482313 try self.beginSpvBlock(then_label_id);
......@@ -961,26 +2326,80 @@ pub const DeclGen = struct {
9612326 });
9622327 }
9632328
964 fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !IdRef {
2329 fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
9652330 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
966 const operand_id = try self.resolve(ty_op.operand);
967 const ty = self.air.typeOfIndex(inst);
2331 const ptr_ty = self.air.typeOf(ty_op.operand);
2332 const operand = try self.resolve(ty_op.operand);
2333 if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null;
9682334
969 const result_type_id = try self.resolveTypeId(ty);
970 const result_id = self.spv.allocId();
2335 return try self.load(ptr_ty, operand);
2336 }
9712337
2338 fn load(self: *DeclGen, ptr_ty: Type, ptr: IdRef) !IdRef {
2339 const value_ty = ptr_ty.childType();
2340 const direct_result_ty_ref = try self.resolveType(value_ty, .direct);
2341 const indirect_result_ty_ref = try self.resolveType(value_ty, .indirect);
2342 const result_id = self.spv.allocId();
9722343 const access = spec.MemoryAccess.Extended{
973 .Volatile = ty.isVolatilePtr(),
2344 .Volatile = ptr_ty.isVolatilePtr(),
9742345 };
975
9762346 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
977 .id_result_type = result_type_id,
2347 .id_result_type = self.typeId(indirect_result_ty_ref),
9782348 .id_result = result_id,
979 .pointer = operand_id,
2349 .pointer = ptr,
9802350 .memory_access = access,
9812351 });
2352 if (value_ty.zigTypeTag() == .Bool) {
2353 // Convert indirect bool to direct bool
2354 const zero_id = try self.constInt(indirect_result_ty_ref, 0);
2355 const casted_result_id = self.spv.allocId();
2356 try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
2357 .id_result_type = self.typeId(direct_result_ty_ref),
2358 .id_result = casted_result_id,
2359 .operand_1 = result_id,
2360 .operand_2 = zero_id,
2361 });
2362 return casted_result_id;
2363 }
2364 return result_id;
2365 }
2366
2367 fn airStore(self: *DeclGen, inst: Air.Inst.Index) !void {
2368 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2369 const ptr_ty = self.air.typeOf(bin_op.lhs);
2370 const ptr = try self.resolve(bin_op.lhs);
2371 const value = try self.resolve(bin_op.rhs);
9822372
983 return result_id.toRef();
2373 try self.store(ptr_ty, ptr, value);
2374 }
2375
2376 fn store(self: *DeclGen, ptr_ty: Type, ptr: IdRef, value: IdRef) !void {
2377 const value_ty = ptr_ty.childType();
2378 const converted_value = switch (value_ty.zigTypeTag()) {
2379 .Bool => blk: {
2380 const indirect_bool_ty_ref = try self.resolveType(value_ty, .indirect);
2381 const result_id = self.spv.allocId();
2382 const zero = try self.constInt(indirect_bool_ty_ref, 0);
2383 const one = try self.constInt(indirect_bool_ty_ref, 1);
2384 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2385 .id_result_type = self.typeId(indirect_bool_ty_ref),
2386 .id_result = result_id,
2387 .condition = value,
2388 .object_1 = one,
2389 .object_2 = zero,
2390 });
2391 break :blk result_id;
2392 },
2393 else => value,
2394 };
2395 const access = spec.MemoryAccess.Extended{
2396 .Volatile = ptr_ty.isVolatilePtr(),
2397 };
2398 try self.func.body.emit(self.spv.gpa, .OpStore, .{
2399 .pointer = ptr,
2400 .object = converted_value,
2401 .memory_access = access,
2402 });
9842403 }
9852404
9862405 fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void {
......@@ -990,13 +2409,13 @@ pub const DeclGen = struct {
9902409 const loop_label_id = self.spv.allocId();
9912410
9922411 // Jump to the loop entry point
993 try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id.toRef() });
2412 try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id });
9942413
9952414 // TODO: Look into OpLoopMerge.
9962415 try self.beginSpvBlock(loop_label_id);
9972416 try self.genBody(body);
9982417
999 try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id.toRef() });
2418 try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id });
10002419 }
10012420
10022421 fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {
......@@ -1010,23 +2429,138 @@ pub const DeclGen = struct {
10102429 }
10112430 }
10122431
1013 fn airStore(self: *DeclGen, inst: Air.Inst.Index) !void {
1014 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1015 const dst_ptr_id = try self.resolve(bin_op.lhs);
1016 const src_val_id = try self.resolve(bin_op.rhs);
1017 const lhs_ty = self.air.typeOf(bin_op.lhs);
2432 fn airRetLoad(self: *DeclGen, inst: Air.Inst.Index) !void {
2433 const un_op = self.air.instructions.items(.data)[inst].un_op;
2434 const ptr_ty = self.air.typeOf(un_op);
2435 const ret_ty = ptr_ty.childType();
10182436
1019 const access = spec.MemoryAccess.Extended{
1020 .Volatile = lhs_ty.isVolatilePtr(),
1021 };
2437 if (!ret_ty.hasRuntimeBitsIgnoreComptime()) {
2438 try self.func.body.emit(self.spv.gpa, .OpReturn, {});
2439 return;
2440 }
10222441
1023 try self.func.body.emit(self.spv.gpa, .OpStore, .{
1024 .pointer = dst_ptr_id,
1025 .object = src_val_id,
1026 .memory_access = access,
2442 const ptr = try self.resolve(un_op);
2443 const value = try self.load(ptr_ty, ptr);
2444 try self.func.body.emit(self.spv.gpa, .OpReturnValue, .{
2445 .value = value,
10272446 });
10282447 }
10292448
2449 fn airSwitchBr(self: *DeclGen, inst: Air.Inst.Index) !void {
2450 const target = self.getTarget();
2451 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
2452 const cond = try self.resolve(pl_op.operand);
2453 const cond_ty = self.air.typeOf(pl_op.operand);
2454 const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload);
2455
2456 const cond_words: u32 = switch (cond_ty.zigTypeTag()) {
2457 .Int => blk: {
2458 const bits = cond_ty.intInfo(target).bits;
2459 const backing_bits = self.backingIntBits(bits) orelse {
2460 return self.todo("implement composite int switch", .{});
2461 };
2462 break :blk if (backing_bits <= 32) @as(u32, 1) else 2;
2463 },
2464 .Enum => blk: {
2465 var buffer: Type.Payload.Bits = undefined;
2466 const int_ty = cond_ty.intTagType(&buffer);
2467 const int_info = int_ty.intInfo(target);
2468 const backing_bits = self.backingIntBits(int_info.bits) orelse {
2469 return self.todo("implement composite int switch", .{});
2470 };
2471 break :blk if (backing_bits <= 32) @as(u32, 1) else 2;
2472 },
2473 else => return self.todo("implement switch for type {s}", .{@tagName(cond_ty.zigTypeTag())}), // TODO: Figure out which types apply here, and work around them as we can only do integers.
2474 };
2475
2476 const num_cases = switch_br.data.cases_len;
2477
2478 // Compute the total number of arms that we need.
2479 // Zig switches are grouped by condition, so we need to loop through all of them
2480 const num_conditions = blk: {
2481 var extra_index: usize = switch_br.end;
2482 var case_i: u32 = 0;
2483 var num_conditions: u32 = 0;
2484 while (case_i < num_cases) : (case_i += 1) {
2485 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
2486 const case_body = self.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];
2487 extra_index = case.end + case.data.items_len + case_body.len;
2488 num_conditions += case.data.items_len;
2489 }
2490 break :blk num_conditions;
2491 };
2492
2493 // First, pre-allocate the labels for the cases.
2494 const first_case_label = self.spv.allocIds(num_cases);
2495 // We always need the default case - if zig has none, we will generate unreachable there.
2496 const default = self.spv.allocId();
2497
2498 // Emit the instruction before generating the blocks.
2499 try self.func.body.emitRaw(self.spv.gpa, .OpSwitch, 2 + (cond_words + 1) * num_conditions);
2500 self.func.body.writeOperand(IdRef, cond);
2501 self.func.body.writeOperand(IdRef, default);
2502
2503 // Emit each of the cases
2504 {
2505 var extra_index: usize = switch_br.end;
2506 var case_i: u32 = 0;
2507 while (case_i < num_cases) : (case_i += 1) {
2508 // SPIR-V needs a literal here, which' width depends on the case condition.
2509 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
2510 const items = @ptrCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]);
2511 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
2512 extra_index = case.end + case.data.items_len + case_body.len;
2513
2514 const label = IdRef{ .id = first_case_label.id + case_i };
2515
2516 for (items) |item| {
2517 const value = self.air.value(item) orelse {
2518 return self.todo("switch on runtime value???", .{});
2519 };
2520 const int_val = switch (cond_ty.zigTypeTag()) {
2521 .Int => if (cond_ty.isSignedInt()) @bitCast(u64, value.toSignedInt(target)) else value.toUnsignedInt(target),
2522 .Enum => blk: {
2523 var int_buffer: Value.Payload.U64 = undefined;
2524 // TODO: figure out of cond_ty is correct (something with enum literals)
2525 break :blk value.enumToInt(cond_ty, &int_buffer).toUnsignedInt(target); // TODO: composite integer constants
2526 },
2527 else => unreachable,
2528 };
2529 const int_lit: spec.LiteralContextDependentNumber = switch (cond_words) {
2530 1 => .{ .uint32 = @intCast(u32, int_val) },
2531 2 => .{ .uint64 = int_val },
2532 else => unreachable,
2533 };
2534 self.func.body.writeOperand(spec.LiteralContextDependentNumber, int_lit);
2535 self.func.body.writeOperand(IdRef, label);
2536 }
2537 }
2538 }
2539
2540 // Now, finally, we can start emitting each of the cases.
2541 var extra_index: usize = switch_br.end;
2542 var case_i: u32 = 0;
2543 while (case_i < num_cases) : (case_i += 1) {
2544 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
2545 const items = @ptrCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]);
2546 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
2547 extra_index = case.end + case.data.items_len + case_body.len;
2548
2549 const label = IdResult{ .id = first_case_label.id + case_i };
2550
2551 try self.beginSpvBlock(label);
2552 try self.genBody(case_body);
2553 }
2554
2555 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
2556 try self.beginSpvBlock(default);
2557 if (else_body.len != 0) {
2558 try self.genBody(else_body);
2559 } else {
2560 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});
2561 }
2562 }
2563
10302564 fn airUnreach(self: *DeclGen) !void {
10312565 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});
10322566 }
......@@ -1158,4 +2692,47 @@ pub const DeclGen = struct {
11582692
11592693 return null;
11602694 }
2695
2696 fn airCall(self: *DeclGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !?IdRef {
2697 _ = modifier;
2698
2699 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
2700 const extra = self.air.extraData(Air.Call, pl_op.payload);
2701 const args = @ptrCast([]const Air.Inst.Ref, self.air.extra[extra.end..][0..extra.data.args_len]);
2702 const callee_ty = self.air.typeOf(pl_op.operand);
2703 const zig_fn_ty = switch (callee_ty.zigTypeTag()) {
2704 .Fn => callee_ty,
2705 .Pointer => return self.fail("cannot call function pointers", .{}),
2706 else => unreachable,
2707 };
2708 const fn_info = zig_fn_ty.fnInfo();
2709 const return_type = fn_info.return_type;
2710
2711 const result_type_id = try self.resolveTypeId(return_type);
2712 const result_id = self.spv.allocId();
2713 const callee_id = try self.resolve(pl_op.operand);
2714
2715 try self.func.body.emitRaw(self.spv.gpa, .OpFunctionCall, 3 + args.len);
2716 self.func.body.writeOperand(spec.IdResultType, result_type_id);
2717 self.func.body.writeOperand(spec.IdResult, result_id);
2718 self.func.body.writeOperand(spec.IdRef, callee_id);
2719
2720 for (args) |arg| {
2721 const arg_id = try self.resolve(arg);
2722 const arg_ty = self.air.typeOf(arg);
2723 if (!arg_ty.hasRuntimeBitsIgnoreComptime()) continue;
2724
2725 self.func.body.writeOperand(spec.IdRef, arg_id);
2726 }
2727
2728 if (return_type.isNoReturn()) {
2729 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});
2730 }
2731
2732 if (self.liveness.isUnused(inst) or !return_type.hasRuntimeBitsIgnoreComptime()) {
2733 return null;
2734 }
2735
2736 return result_id;
2737 }
11612738};
src/codegen/spirv/Assembler.zig+46-41
......@@ -135,7 +135,7 @@ const AsmValue = union(enum) {
135135 return switch (self) {
136136 .just_declared, .unresolved_forward_reference => unreachable,
137137 .value => |result| result,
138 .ty => |ref| spv.typeResultId(ref).toRef(),
138 .ty => |ref| spv.typeId(ref),
139139 };
140140 }
141141};
......@@ -239,12 +239,17 @@ fn todo(self: *Assembler, comptime fmt: []const u8, args: anytype) Error {
239239/// If this function returns `error.AssembleFail`, an explanatory
240240/// error message has already been emitted into `self.errors`.
241241fn processInstruction(self: *Assembler) !void {
242 const result = switch (self.inst.opcode.class()) {
243 .TypeDeclaration => try self.processTypeInstruction(),
244 else => if (try self.processGenericInstruction()) |result|
245 result
246 else
247 return,
242 const result = switch (self.inst.opcode) {
243 .OpEntryPoint => {
244 return self.fail(0, "cannot export entry points via OpEntryPoint, export the kernel using callconv(.Kernel)", .{});
245 },
246 else => switch (self.inst.opcode.class()) {
247 .TypeDeclaration => try self.processTypeInstruction(),
248 else => if (try self.processGenericInstruction()) |result|
249 result
250 else
251 return,
252 },
248253 };
249254
250255 const result_ref = self.inst.result().?;
......@@ -266,27 +271,28 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
266271 .OpTypeVoid => SpvType.initTag(.void),
267272 .OpTypeBool => SpvType.initTag(.bool),
268273 .OpTypeInt => blk: {
269 const payload = try self.spv.arena.create(SpvType.Payload.Int);
270274 const signedness: std.builtin.Signedness = switch (operands[2].literal32) {
271275 0 => .unsigned,
272276 1 => .signed,
273277 else => {
274278 // TODO: Improve source location.
275 return self.fail(0, "'{}' is not a valid signedness (expected 0 or 1)", .{operands[2].literal32});
279 return self.fail(0, "{} is not a valid signedness (expected 0 or 1)", .{operands[2].literal32});
276280 },
277281 };
278 payload.* = .{
279 .width = operands[1].literal32,
280 .signedness = signedness,
282 const width = std.math.cast(u16, operands[1].literal32) orelse {
283 return self.fail(0, "int type of {} bits is too large", .{operands[1].literal32});
281284 };
282 break :blk SpvType.initPayload(&payload.base);
285 break :blk try SpvType.int(self.spv.arena, signedness, width);
283286 },
284287 .OpTypeFloat => blk: {
285 const payload = try self.spv.arena.create(SpvType.Payload.Float);
286 payload.* = .{
287 .width = operands[1].literal32,
288 };
289 break :blk SpvType.initPayload(&payload.base);
288 const bits = operands[1].literal32;
289 switch (bits) {
290 16, 32, 64 => {},
291 else => {
292 return self.fail(0, "{} is not a valid bit count for floats (expected 16, 32 or 64)", .{bits});
293 },
294 }
295 break :blk SpvType.float(@intCast(u16, bits));
290296 },
291297 .OpTypeVector => blk: {
292298 const payload = try self.spv.arena.create(SpvType.Payload.Vector);
......@@ -382,10 +388,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
382388 payload.* = .{
383389 .storage_class = @intToEnum(spec.StorageClass, operands[1].value),
384390 .child_type = try self.resolveTypeRef(operands[2].ref_id),
385 // TODO: Fetch these values from decorations.
386 .array_stride = 0,
387 .alignment = null,
388 .max_byte_offset = null,
391 // TODO: Fetch decorations
389392 };
390393 break :blk SpvType.initPayload(&payload.base);
391394 },
......@@ -434,11 +437,16 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {
434437 .Annotation => &self.spv.sections.annotations,
435438 .TypeDeclaration => unreachable, // Handled elsewhere.
436439 else => switch (self.inst.opcode) {
437 .OpEntryPoint => &self.spv.sections.entry_points,
440 .OpEntryPoint => unreachable,
438441 .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes,
439442 .OpVariable => switch (@intToEnum(spec.StorageClass, operands[2].value)) {
440443 .Function => &self.func.prologue,
441 else => &self.spv.sections.types_globals_constants,
444 else => {
445 // This is currently disabled because global variables are required to be
446 // emitted in the proper order, and this should be honored in inline assembly
447 // as well.
448 return self.todo("global variables", .{});
449 },
442450 },
443451 // Default case - to be worked out further.
444452 else => &self.func.body,
......@@ -485,7 +493,7 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {
485493 section.instructions.items[first_word] |= @as(u32, @intCast(u16, actual_word_count)) << 16 | @enumToInt(self.inst.opcode);
486494
487495 if (maybe_result_id) |result| {
488 return AsmValue{ .value = result.toRef() };
496 return AsmValue{ .value = result };
489497 }
490498 return null;
491499}
......@@ -753,22 +761,19 @@ fn parseContextDependentNumber(self: *Assembler) !void {
753761
754762 const tok = self.currentToken();
755763 const result_type_ref = try self.resolveTypeRef(self.inst.operands.items[0].ref_id);
756 const result_type = self.spv.type_cache.keys()[result_type_ref];
757 switch (result_type.tag()) {
758 .int => {
759 const int = result_type.castTag(.int).?;
760 try self.parseContextDependentInt(int.signedness, int.width);
761 },
762 .float => {
763 const width = result_type.castTag(.float).?.width;
764 switch (width) {
765 16 => try self.parseContextDependentFloat(16),
766 32 => try self.parseContextDependentFloat(32),
767 64 => try self.parseContextDependentFloat(64),
768 else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}),
769 }
770 },
771 else => return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())}),
764 const result_type = self.spv.type_cache.keys()[@enumToInt(result_type_ref)];
765 if (result_type.isInt()) {
766 try self.parseContextDependentInt(result_type.intSignedness(), result_type.intFloatBits());
767 } else if (result_type.isFloat()) {
768 const width = result_type.intFloatBits();
769 switch (width) {
770 16 => try self.parseContextDependentFloat(16),
771 32 => try self.parseContextDependentFloat(32),
772 64 => try self.parseContextDependentFloat(64),
773 else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}),
774 }
775 } else {
776 return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())});
772777 }
773778}
774779
src/codegen/spirv/Module.zig+474-76
......@@ -39,22 +39,68 @@ pub const Fn = struct {
3939 /// This section should also contain the OpFunctionEnd instruction marking
4040 /// the end of this function definition.
4141 body: Section = .{},
42 /// The decl dependencies that this function depends on.
43 decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{},
4244
4345 /// Reset this function without deallocating resources, so that
4446 /// it may be used to emit code for another function.
4547 pub fn reset(self: *Fn) void {
4648 self.prologue.reset();
4749 self.body.reset();
50 self.decl_deps.items.len = 0;
4851 }
4952
5053 /// Free the resources owned by this function.
5154 pub fn deinit(self: *Fn, a: Allocator) void {
5255 self.prologue.deinit(a);
5356 self.body.deinit(a);
57 self.decl_deps.deinit(a);
5458 self.* = undefined;
5559 }
5660};
5761
62/// Declarations, both functions and globals, can have dependencies. These are used for 2 things:
63/// - Globals must be declared before they are used, also between globals. The compiler processes
64/// globals unordered, so we must use the dependencies here to figure out how to order the globals
65/// in the final module. The Globals structure is also used for that.
66/// - Entry points must declare the complete list of OpVariable instructions that they access.
67/// For these we use the same dependency structure.
68/// In this mechanism, globals will only depend on other globals, while functions may depend on
69/// globals or other functions.
70pub const Decl = struct {
71 /// Index to refer to a Decl by.
72 pub const Index = enum(u32) { _ };
73
74 /// The result-id to be used for this declaration. This is the final result-id
75 /// of the decl, which may be an OpFunction, OpVariable, or the result of a sequence
76 /// of OpSpecConstantOp operations.
77 result_id: IdRef,
78 /// The offset of the first dependency of this decl in the `decl_deps` array.
79 begin_dep: u32,
80 /// The past-end offset of the dependencies of this decl in the `decl_deps` array.
81 end_dep: u32,
82};
83
84/// Globals must be kept in order: operations involving globals must be ordered
85/// so that the global declaration precedes any usage.
86pub const Global = struct {
87 /// This is the result-id of the OpVariable instruction that declares the global.
88 result_id: IdRef,
89 /// The offset into `self.globals.section` of the first instruction of this global
90 /// declaration.
91 begin_inst: u32,
92 /// The past-end offset into `self.flobals.section`.
93 end_inst: u32,
94};
95
96/// This models a kernel entry point.
97pub const EntryPoint = struct {
98 /// The declaration that should be exported.
99 decl_index: Decl.Index,
100 /// The name of the kernel to be exported.
101 name: []const u8,
102};
103
58104/// A general-purpose allocator which may be used to allocate resources for this module
59105gpa: Allocator,
60106
......@@ -69,13 +115,13 @@ sections: struct {
69115 extensions: Section = .{},
70116 // OpExtInstImport instructions - skip for now.
71117 // memory model defined by target, not required here.
72 /// OpEntryPoint instructions.
73 entry_points: Section = .{},
118 /// OpEntryPoint instructions - Handled by `self.entry_points`.
74119 /// OpExecutionMode and OpExecutionModeId instructions.
75120 execution_modes: Section = .{},
76121 /// OpString, OpSourcExtension, OpSource, OpSourceContinued.
77122 debug_strings: Section = .{},
78 // OpName, OpMemberName - skip for now.
123 // OpName, OpMemberName.
124 debug_names: Section = .{},
79125 // OpModuleProcessed - skip for now.
80126 /// Annotation instructions (OpDecorate etc).
81127 annotations: Section = .{},
......@@ -101,6 +147,26 @@ source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},
101147/// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref).
102148type_cache: TypeCache = .{},
103149
150/// Set of Decls, referred to by Decl.Index.
151decls: std.ArrayListUnmanaged(Decl) = .{},
152
153/// List of dependencies, per decl. This list holds all the dependencies, sliced by the
154/// begin_dep and end_dep in `self.decls`.
155decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{},
156
157/// The list of entry points that should be exported from this module.
158entry_points: std.ArrayListUnmanaged(EntryPoint) = .{},
159
160/// The fields in this structure help to maintain the required order for global variables.
161globals: struct {
162 /// Set of globals, referred to by Decl.Index.
163 globals: std.AutoArrayHashMapUnmanaged(Decl.Index, Global) = .{},
164 /// This pseudo-section contains the initialization code for all the globals. Instructions from
165 /// here are reordered when flushing the module. Its contents should be part of the
166 /// `types_globals_constants` SPIR-V section.
167 section: Section = .{},
168} = .{},
169
104170pub fn init(gpa: Allocator, arena: Allocator) Module {
105171 return .{
106172 .gpa = gpa,
......@@ -112,9 +178,9 @@ pub fn init(gpa: Allocator, arena: Allocator) Module {
112178pub fn deinit(self: *Module) void {
113179 self.sections.capabilities.deinit(self.gpa);
114180 self.sections.extensions.deinit(self.gpa);
115 self.sections.entry_points.deinit(self.gpa);
116181 self.sections.execution_modes.deinit(self.gpa);
117182 self.sections.debug_strings.deinit(self.gpa);
183 self.sections.debug_names.deinit(self.gpa);
118184 self.sections.annotations.deinit(self.gpa);
119185 self.sections.types_globals_constants.deinit(self.gpa);
120186 self.sections.functions.deinit(self.gpa);
......@@ -122,6 +188,14 @@ pub fn deinit(self: *Module) void {
122188 self.source_file_names.deinit(self.gpa);
123189 self.type_cache.deinit(self.gpa);
124190
191 self.decls.deinit(self.gpa);
192 self.decl_deps.deinit(self.gpa);
193
194 self.entry_points.deinit(self.gpa);
195
196 self.globals.globals.deinit(self.gpa);
197 self.globals.section.deinit(self.gpa);
198
125199 self.* = undefined;
126200}
127201
......@@ -130,32 +204,138 @@ pub fn allocId(self: *Module) spec.IdResult {
130204 return .{ .id = self.next_result_id };
131205}
132206
207pub fn allocIds(self: *Module, n: u32) spec.IdResult {
208 defer self.next_result_id += n;
209 return .{ .id = self.next_result_id };
210}
211
133212pub fn idBound(self: Module) Word {
134213 return self.next_result_id;
135214}
136215
216fn orderGlobalsInto(
217 self: *Module,
218 decl_index: Decl.Index,
219 section: *Section,
220 seen: *std.DynamicBitSetUnmanaged,
221) !void {
222 const decl = self.declPtr(decl_index);
223 const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep];
224 const global = self.globalPtr(decl_index).?;
225 const insts = self.globals.section.instructions.items[global.begin_inst..global.end_inst];
226
227 seen.set(@enumToInt(decl_index));
228
229 for (deps) |dep| {
230 if (!seen.isSet(@enumToInt(dep))) {
231 try self.orderGlobalsInto(dep, section, seen);
232 }
233 }
234
235 try section.instructions.appendSlice(self.gpa, insts);
236}
237
238fn orderGlobals(self: *Module) !Section {
239 const globals = self.globals.globals.keys();
240
241 var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len);
242 defer seen.deinit(self.gpa);
243
244 var ordered_globals = Section{};
245 errdefer ordered_globals.deinit(self.gpa);
246
247 for (globals) |decl_index| {
248 if (!seen.isSet(@enumToInt(decl_index))) {
249 try self.orderGlobalsInto(decl_index, &ordered_globals, &seen);
250 }
251 }
252
253 return ordered_globals;
254}
255
256fn addEntryPointDeps(
257 self: *Module,
258 decl_index: Decl.Index,
259 seen: *std.DynamicBitSetUnmanaged,
260 interface: *std.ArrayList(IdRef),
261) !void {
262 const decl = self.declPtr(decl_index);
263 const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep];
264
265 seen.set(@enumToInt(decl_index));
266
267 if (self.globalPtr(decl_index)) |global| {
268 try interface.append(global.result_id);
269 }
270
271 for (deps) |dep| {
272 if (!seen.isSet(@enumToInt(dep))) {
273 try self.addEntryPointDeps(dep, seen, interface);
274 }
275 }
276}
277
278fn entryPoints(self: *Module) !Section {
279 var entry_points = Section{};
280 errdefer entry_points.deinit(self.gpa);
281
282 var interface = std.ArrayList(IdRef).init(self.gpa);
283 defer interface.deinit();
284
285 var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len);
286 defer seen.deinit(self.gpa);
287
288 for (self.entry_points.items) |entry_point| {
289 interface.items.len = 0;
290 seen.setRangeValue(.{ .start = 0, .end = self.decls.items.len }, false);
291
292 try self.addEntryPointDeps(entry_point.decl_index, &seen, &interface);
293
294 const entry_point_id = self.declPtr(entry_point.decl_index).result_id;
295 try entry_points.emit(self.gpa, .OpEntryPoint, .{
296 .execution_model = .Kernel,
297 .entry_point = entry_point_id,
298 .name = entry_point.name,
299 .interface = interface.items,
300 });
301 }
302
303 return entry_points;
304}
305
137306/// Emit this module as a spir-v binary.
138pub fn flush(self: Module, file: std.fs.File) !void {
307pub fn flush(self: *Module, file: std.fs.File) !void {
139308 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
140309
141310 const header = [_]Word{
142311 spec.magic_number,
143 (1 << 16) | (5 << 8),
312 // TODO: From cpu features
313 // Emit SPIR-V 1.4 for now. This is the highest version that Intel's CPU OpenCL supports.
314 (1 << 16) | (4 << 8),
144315 0, // TODO: Register Zig compiler magic number.
145316 self.idBound(),
146317 0, // Schema (currently reserved for future use)
147318 };
148319
320 // TODO: Perform topological sort on the globals.
321 var globals = try self.orderGlobals();
322 defer globals.deinit(self.gpa);
323
324 var entry_points = try self.entryPoints();
325 defer entry_points.deinit(self.gpa);
326
149327 // Note: needs to be kept in order according to section 2.3!
150328 const buffers = &[_][]const Word{
151329 &header,
152330 self.sections.capabilities.toWords(),
153331 self.sections.extensions.toWords(),
154 self.sections.entry_points.toWords(),
332 entry_points.toWords(),
155333 self.sections.execution_modes.toWords(),
156334 self.sections.debug_strings.toWords(),
335 self.sections.debug_names.toWords(),
157336 self.sections.annotations.toWords(),
158337 self.sections.types_globals_constants.toWords(),
338 globals.toWords(),
159339 self.sections.functions.toWords(),
160340 };
161341
......@@ -175,9 +355,10 @@ pub fn flush(self: Module, file: std.fs.File) !void {
175355}
176356
177357/// Merge the sections making up a function declaration into this module.
178pub fn addFunction(self: *Module, func: Fn) !void {
358pub fn addFunction(self: *Module, decl_index: Decl.Index, func: Fn) !void {
179359 try self.sections.functions.append(self.gpa, func.prologue);
180360 try self.sections.functions.append(self.gpa, func.body);
361 try self.declareDeclDeps(decl_index, func.decl_deps.items);
181362}
182363
183364/// 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 {
188369 const result = try self.source_file_names.getOrPut(self.gpa, path);
189370 if (!result.found_existing) {
190371 const file_result_id = self.allocId();
191 result.value_ptr.* = file_result_id.toRef();
372 result.value_ptr.* = file_result_id;
192373 try self.sections.debug_strings.emit(self.gpa, .OpString, .{
193374 .id_result = file_result_id,
194375 .string = path,
......@@ -197,7 +378,7 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
197378 try self.sections.debug_strings.emit(self.gpa, .OpSource, .{
198379 .source_language = .Unknown, // TODO: Register Zig source language.
199380 .version = 0, // TODO: Zig version as u32?
200 .file = file_result_id.toRef(),
381 .file = file_result_id,
201382 .source = null, // TODO: Store actual source also?
202383 });
203384 }
......@@ -216,22 +397,21 @@ pub fn resolveType(self: *Module, ty: Type) !Type.Ref {
216397 result.value_ptr.* = try self.emitType(ty);
217398 }
218399
219 return result.index;
400 return @intToEnum(Type.Ref, result.index);
220401}
221402
222pub fn resolveTypeId(self: *Module, ty: Type) !IdRef {
223 const type_ref = try self.resolveType(ty);
224 return self.typeResultId(type_ref);
403pub fn resolveTypeId(self: *Module, ty: Type) !IdResultType {
404 const ty_ref = try self.resolveType(ty);
405 return self.typeId(ty_ref);
225406}
226407
227/// Get the result-id of a particular type, by reference. Asserts type_ref is valid.
228pub fn typeResultId(self: Module, type_ref: Type.Ref) IdResultType {
229 return self.type_cache.values()[type_ref];
408pub fn typeRefType(self: Module, ty_ref: Type.Ref) Type {
409 return self.type_cache.keys()[@enumToInt(ty_ref)];
230410}
231411
232/// Get the result-id of a particular type as IdRef, by Type.Ref. Asserts type_ref is valid.
233pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef {
234 return self.type_cache.values()[type_ref].toRef();
412/// Get the result-id of a particular type, by reference. Asserts type_ref is valid.
413pub fn typeId(self: Module, ty_ref: Type.Ref) IdResultType {
414 return self.type_cache.values()[@enumToInt(ty_ref)];
235415}
236416
237417/// Unconditionally emit a spir-v type into the appropriate section.
......@@ -240,47 +420,94 @@ pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef {
240420/// Note: This function does not attempt to perform any validation on the type.
241421/// The type is emitted in a shallow fashion; any child types should already
242422/// be emitted at this point.
243pub fn emitType(self: *Module, ty: Type) !IdResultType {
423pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType {
244424 const result_id = self.allocId();
245 const ref_id = result_id.toRef();
425 const ref_id = result_id;
246426 const types = &self.sections.types_globals_constants;
247 const annotations = &self.sections.annotations;
427 const debug_names = &self.sections.debug_names;
248428 const result_id_operand = .{ .id_result = result_id };
249429
250430 switch (ty.tag()) {
251 .void => try types.emit(self.gpa, .OpTypeVoid, result_id_operand),
252 .bool => try types.emit(self.gpa, .OpTypeBool, result_id_operand),
253 .int => {
254 const signedness: spec.LiteralInteger = switch (ty.payload(.int).signedness) {
431 .void => {
432 try types.emit(self.gpa, .OpTypeVoid, result_id_operand);
433 try debug_names.emit(self.gpa, .OpName, .{
434 .target = result_id,
435 .name = "void",
436 });
437 },
438 .bool => {
439 try types.emit(self.gpa, .OpTypeBool, result_id_operand);
440 try debug_names.emit(self.gpa, .OpName, .{
441 .target = result_id,
442 .name = "bool",
443 });
444 },
445 .u8,
446 .u16,
447 .u32,
448 .u64,
449 .i8,
450 .i16,
451 .i32,
452 .i64,
453 .int,
454 => {
455 // TODO: Kernels do not support OpTypeInt that is signed. We can probably
456 // can get rid of the signedness all together, in Shaders also.
457 const bits = ty.intFloatBits();
458 const signedness: spec.LiteralInteger = switch (ty.intSignedness()) {
255459 .unsigned => 0,
256460 .signed => 1,
257461 };
258462
259463 try types.emit(self.gpa, .OpTypeInt, .{
260464 .id_result = result_id,
261 .width = ty.payload(.int).width,
465 .width = bits,
262466 .signedness = signedness,
263467 });
468
469 const ui: []const u8 = switch (signedness) {
470 0 => "u",
471 1 => "i",
472 else => unreachable,
473 };
474 const name = try std.fmt.allocPrint(self.gpa, "{s}{}", .{ ui, bits });
475 defer self.gpa.free(name);
476
477 try debug_names.emit(self.gpa, .OpName, .{
478 .target = result_id,
479 .name = name,
480 });
481 },
482 .f16, .f32, .f64 => {
483 const bits = ty.intFloatBits();
484 try types.emit(self.gpa, .OpTypeFloat, .{
485 .id_result = result_id,
486 .width = bits,
487 });
488
489 const name = try std.fmt.allocPrint(self.gpa, "f{}", .{bits});
490 defer self.gpa.free(name);
491 try debug_names.emit(self.gpa, .OpName, .{
492 .target = result_id,
493 .name = name,
494 });
264495 },
265 .float => try types.emit(self.gpa, .OpTypeFloat, .{
266 .id_result = result_id,
267 .width = ty.payload(.float).width,
268 }),
269496 .vector => try types.emit(self.gpa, .OpTypeVector, .{
270497 .id_result = result_id,
271 .component_type = self.typeResultId(ty.childType()).toRef(),
498 .component_type = self.typeId(ty.childType()),
272499 .component_count = ty.payload(.vector).component_count,
273500 }),
274501 .matrix => try types.emit(self.gpa, .OpTypeMatrix, .{
275502 .id_result = result_id,
276 .column_type = self.typeResultId(ty.childType()).toRef(),
503 .column_type = self.typeId(ty.childType()),
277504 .column_count = ty.payload(.matrix).column_count,
278505 }),
279506 .image => {
280507 const info = ty.payload(.image);
281508 try types.emit(self.gpa, .OpTypeImage, .{
282509 .id_result = result_id,
283 .sampled_type = self.typeResultId(ty.childType()).toRef(),
510 .sampled_type = self.typeId(ty.childType()),
284511 .dim = info.dim,
285512 .depth = @enumToInt(info.depth),
286513 .arrayed = @boolToInt(info.arrayed),
......@@ -293,28 +520,34 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType {
293520 .sampler => try types.emit(self.gpa, .OpTypeSampler, result_id_operand),
294521 .sampled_image => try types.emit(self.gpa, .OpTypeSampledImage, .{
295522 .id_result = result_id,
296 .image_type = self.typeResultId(ty.childType()).toRef(),
523 .image_type = self.typeId(ty.childType()),
297524 }),
298525 .array => {
299526 const info = ty.payload(.array);
300527 assert(info.length != 0);
528
529 const size_type = Type.initTag(.u32);
530 const size_type_id = try self.resolveTypeId(size_type);
531 const length_id = self.allocId();
532 try self.emitConstant(size_type_id, length_id, .{ .uint32 = info.length });
533
301534 try types.emit(self.gpa, .OpTypeArray, .{
302535 .id_result = result_id,
303 .element_type = self.typeResultId(ty.childType()).toRef(),
304 .length = .{ .id = 0 }, // TODO: info.length must be emitted as constant!
536 .element_type = self.typeId(ty.childType()),
537 .length = length_id,
305538 });
306539 if (info.array_stride != 0) {
307 try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
540 try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
308541 }
309542 },
310543 .runtime_array => {
311544 const info = ty.payload(.runtime_array);
312545 try types.emit(self.gpa, .OpTypeRuntimeArray, .{
313546 .id_result = result_id,
314 .element_type = self.typeResultId(ty.childType()).toRef(),
547 .element_type = self.typeId(ty.childType()),
315548 });
316549 if (info.array_stride != 0) {
317 try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
550 try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
318551 }
319552 },
320553 .@"struct" => {
......@@ -322,7 +555,7 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType {
322555 try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len);
323556 types.writeOperand(IdResult, result_id);
324557 for (info.members) |member| {
325 types.writeOperand(IdRef, self.typeResultId(member.ty).toRef());
558 types.writeOperand(IdRef, self.typeId(member.ty));
326559 }
327560 try self.decorateStruct(ref_id, info);
328561 },
......@@ -335,25 +568,25 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType {
335568 try types.emit(self.gpa, .OpTypePointer, .{
336569 .id_result = result_id,
337570 .storage_class = info.storage_class,
338 .type = self.typeResultId(ty.childType()).toRef(),
571 .type = self.typeId(ty.childType()),
339572 });
340573 if (info.array_stride != 0) {
341 try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
574 try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
342575 }
343 if (info.alignment) |alignment| {
344 try annotations.decorate(self.gpa, ref_id, .{ .Alignment = .{ .alignment = alignment } });
576 if (info.alignment != 0) {
577 try self.decorate(ref_id, .{ .Alignment = .{ .alignment = info.alignment } });
345578 }
346579 if (info.max_byte_offset) |max_byte_offset| {
347 try annotations.decorate(self.gpa, ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } });
580 try self.decorate(ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } });
348581 }
349582 },
350583 .function => {
351584 const info = ty.payload(.function);
352585 try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len);
353586 types.writeOperand(IdResult, result_id);
354 types.writeOperand(IdRef, self.typeResultId(info.return_type).toRef());
587 types.writeOperand(IdRef, self.typeId(info.return_type));
355588 for (info.parameters) |parameter_type| {
356 types.writeOperand(IdRef, self.typeResultId(parameter_type).toRef());
589 types.writeOperand(IdRef, self.typeId(parameter_type));
357590 }
358591 },
359592 .event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand),
......@@ -368,23 +601,30 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType {
368601 .named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand),
369602 }
370603
371 return result_id.toResultType();
604 return result_id;
372605}
373606
374607fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void {
375 const annotations = &self.sections.annotations;
608 const debug_names = &self.sections.debug_names;
609
610 if (info.name.len != 0) {
611 try debug_names.emit(self.gpa, .OpName, .{
612 .target = target,
613 .name = info.name,
614 });
615 }
376616
377617 // Decorations for the struct type itself.
378618 if (info.decorations.block)
379 try annotations.decorate(self.gpa, target, .Block);
619 try self.decorate(target, .Block);
380620 if (info.decorations.buffer_block)
381 try annotations.decorate(self.gpa, target, .BufferBlock);
621 try self.decorate(target, .BufferBlock);
382622 if (info.decorations.glsl_shared)
383 try annotations.decorate(self.gpa, target, .GLSLShared);
623 try self.decorate(target, .GLSLShared);
384624 if (info.decorations.glsl_packed)
385 try annotations.decorate(self.gpa, target, .GLSLPacked);
625 try self.decorate(target, .GLSLPacked);
386626 if (info.decorations.c_packed)
387 try annotations.decorate(self.gpa, target, .CPacked);
627 try self.decorate(target, .CPacked);
388628
389629 // Decorations for the struct members.
390630 const extra = info.member_decoration_extra;
......@@ -392,71 +632,89 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct
392632 for (info.members, 0..) |member, i| {
393633 const d = member.decorations;
394634 const index = @intCast(Word, i);
635
636 if (member.name.len != 0) {
637 try debug_names.emit(self.gpa, .OpMemberName, .{
638 .type = target,
639 .member = index,
640 .name = member.name,
641 });
642 }
643
644 switch (member.offset) {
645 .none => {},
646 else => try self.decorateMember(
647 target,
648 index,
649 .{ .Offset = .{ .byte_offset = @enumToInt(member.offset) } },
650 ),
651 }
652
395653 switch (d.matrix_layout) {
396 .row_major => try annotations.decorateMember(self.gpa, target, index, .RowMajor),
397 .col_major => try annotations.decorateMember(self.gpa, target, index, .ColMajor),
654 .row_major => try self.decorateMember(target, index, .RowMajor),
655 .col_major => try self.decorateMember(target, index, .ColMajor),
398656 .none => {},
399657 }
400658 if (d.matrix_layout != .none) {
401 try annotations.decorateMember(self.gpa, target, index, .{
659 try self.decorateMember(target, index, .{
402660 .MatrixStride = .{ .matrix_stride = extra[extra_i] },
403661 });
404662 extra_i += 1;
405663 }
406664
407665 if (d.no_perspective)
408 try annotations.decorateMember(self.gpa, target, index, .NoPerspective);
666 try self.decorateMember(target, index, .NoPerspective);
409667 if (d.flat)
410 try annotations.decorateMember(self.gpa, target, index, .Flat);
668 try self.decorateMember(target, index, .Flat);
411669 if (d.patch)
412 try annotations.decorateMember(self.gpa, target, index, .Patch);
670 try self.decorateMember(target, index, .Patch);
413671 if (d.centroid)
414 try annotations.decorateMember(self.gpa, target, index, .Centroid);
672 try self.decorateMember(target, index, .Centroid);
415673 if (d.sample)
416 try annotations.decorateMember(self.gpa, target, index, .Sample);
674 try self.decorateMember(target, index, .Sample);
417675 if (d.invariant)
418 try annotations.decorateMember(self.gpa, target, index, .Invariant);
676 try self.decorateMember(target, index, .Invariant);
419677 if (d.@"volatile")
420 try annotations.decorateMember(self.gpa, target, index, .Volatile);
678 try self.decorateMember(target, index, .Volatile);
421679 if (d.coherent)
422 try annotations.decorateMember(self.gpa, target, index, .Coherent);
680 try self.decorateMember(target, index, .Coherent);
423681 if (d.non_writable)
424 try annotations.decorateMember(self.gpa, target, index, .NonWritable);
682 try self.decorateMember(target, index, .NonWritable);
425683 if (d.non_readable)
426 try annotations.decorateMember(self.gpa, target, index, .NonReadable);
684 try self.decorateMember(target, index, .NonReadable);
427685
428686 if (d.builtin) {
429 try annotations.decorateMember(self.gpa, target, index, .{
687 try self.decorateMember(target, index, .{
430688 .BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) },
431689 });
432690 extra_i += 1;
433691 }
434692 if (d.stream) {
435 try annotations.decorateMember(self.gpa, target, index, .{
693 try self.decorateMember(target, index, .{
436694 .Stream = .{ .stream_number = extra[extra_i] },
437695 });
438696 extra_i += 1;
439697 }
440698 if (d.location) {
441 try annotations.decorateMember(self.gpa, target, index, .{
699 try self.decorateMember(target, index, .{
442700 .Location = .{ .location = extra[extra_i] },
443701 });
444702 extra_i += 1;
445703 }
446704 if (d.component) {
447 try annotations.decorateMember(self.gpa, target, index, .{
705 try self.decorateMember(target, index, .{
448706 .Component = .{ .component = extra[extra_i] },
449707 });
450708 extra_i += 1;
451709 }
452710 if (d.xfb_buffer) {
453 try annotations.decorateMember(self.gpa, target, index, .{
711 try self.decorateMember(target, index, .{
454712 .XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] },
455713 });
456714 extra_i += 1;
457715 }
458716 if (d.xfb_stride) {
459 try annotations.decorateMember(self.gpa, target, index, .{
717 try self.decorateMember(target, index, .{
460718 .XfbStride = .{ .xfb_stride = extra[extra_i] },
461719 });
462720 extra_i += 1;
......@@ -465,10 +723,150 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct
465723 const len = extra[extra_i];
466724 extra_i += 1;
467725 const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len];
468 try annotations.decorateMember(self.gpa, target, index, .{
726 try self.decorateMember(target, index, .{
469727 .UserSemantic = .{ .semantic = semantic },
470728 });
471729 extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable;
472730 }
473731 }
474732}
733
734pub fn simpleStructType(self: *Module, members: []const Type.Payload.Struct.Member) !Type.Ref {
735 const payload = try self.arena.create(Type.Payload.Struct);
736 payload.* = .{
737 .members = try self.arena.dupe(Type.Payload.Struct.Member, members),
738 .decorations = .{},
739 };
740 return try self.resolveType(Type.initPayload(&payload.base));
741}
742
743pub fn arrayType(self: *Module, len: u32, ty: Type.Ref) !Type.Ref {
744 const payload = try self.arena.create(Type.Payload.Array);
745 payload.* = .{
746 .element_type = ty,
747 .length = len,
748 };
749 return try self.resolveType(Type.initPayload(&payload.base));
750}
751
752pub fn ptrType(
753 self: *Module,
754 child: Type.Ref,
755 storage_class: spec.StorageClass,
756 alignment: u32,
757) !Type.Ref {
758 const ptr_payload = try self.arena.create(Type.Payload.Pointer);
759 ptr_payload.* = .{
760 .storage_class = storage_class,
761 .child_type = child,
762 .alignment = alignment,
763 };
764 return try self.resolveType(Type.initPayload(&ptr_payload.base));
765}
766
767pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref {
768 const payload = try self.arena.create(Type.Payload.Pointer);
769 payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*;
770 payload.storage_class = new_storage_class;
771 return try self.resolveType(Type.initPayload(&payload.base));
772}
773
774pub fn emitConstant(
775 self: *Module,
776 ty_id: IdRef,
777 result_id: IdRef,
778 value: spec.LiteralContextDependentNumber,
779) !void {
780 try self.sections.types_globals_constants.emit(self.gpa, .OpConstant, .{
781 .id_result_type = ty_id,
782 .id_result = result_id,
783 .value = value,
784 });
785}
786
787/// Decorate a result-id.
788pub fn decorate(
789 self: *Module,
790 target: IdRef,
791 decoration: spec.Decoration.Extended,
792) !void {
793 try self.sections.annotations.emit(self.gpa, .OpDecorate, .{
794 .target = target,
795 .decoration = decoration,
796 });
797}
798
799/// Decorate a result-id which is a member of some struct.
800pub fn decorateMember(
801 self: *Module,
802 structure_type: IdRef,
803 member: u32,
804 decoration: spec.Decoration.Extended,
805) !void {
806 try self.sections.annotations.emit(self.gpa, .OpMemberDecorate, .{
807 .structure_type = structure_type,
808 .member = member,
809 .decoration = decoration,
810 });
811}
812
813pub const DeclKind = enum {
814 func,
815 global,
816};
817
818pub fn allocDecl(self: *Module, kind: DeclKind) !Decl.Index {
819 try self.decls.append(self.gpa, .{
820 .result_id = self.allocId(),
821 .begin_dep = undefined,
822 .end_dep = undefined,
823 });
824 const index = @intToEnum(Decl.Index, @intCast(u32, self.decls.items.len - 1));
825 switch (kind) {
826 .func => {},
827 // If the decl represents a global, also allocate a global node.
828 .global => try self.globals.globals.putNoClobber(self.gpa, index, .{
829 .result_id = undefined,
830 .begin_inst = undefined,
831 .end_inst = undefined,
832 }),
833 }
834
835 return index;
836}
837
838pub fn declPtr(self: *Module, index: Decl.Index) *Decl {
839 return &self.decls.items[@enumToInt(index)];
840}
841
842pub fn globalPtr(self: *Module, index: Decl.Index) ?*Global {
843 return self.globals.globals.getPtr(index);
844}
845
846/// Declare ALL dependencies for a decl.
847pub fn declareDeclDeps(self: *Module, decl_index: Decl.Index, deps: []const Decl.Index) !void {
848 const begin_dep = @intCast(u32, self.decl_deps.items.len);
849 try self.decl_deps.appendSlice(self.gpa, deps);
850 const end_dep = @intCast(u32, self.decl_deps.items.len);
851
852 const decl = self.declPtr(decl_index);
853 decl.begin_dep = begin_dep;
854 decl.end_dep = end_dep;
855}
856
857pub fn beginGlobal(self: *Module) u32 {
858 return @intCast(u32, self.globals.section.instructions.items.len);
859}
860
861pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32) void {
862 const global = self.globalPtr(global_index).?;
863 global.begin_inst = begin_inst;
864 global.end_inst = @intCast(u32, self.globals.section.instructions.items.len);
865}
866
867pub fn declareEntryPoint(self: *Module, decl_index: Decl.Index, name: []const u8) !void {
868 try self.entry_points.append(self.gpa, .{
869 .decl_index = decl_index,
870 .name = try self.arena.dupe(u8, name),
871 });
872}
src/codegen/spirv/Section.zig+18-27
......@@ -65,32 +65,23 @@ pub fn emit(
6565 section.writeOperands(opcode.Operands(), operands);
6666}
6767
68/// Decorate a result-id.
69pub fn decorate(
68pub fn emitSpecConstantOp(
7069 section: *Section,
7170 allocator: Allocator,
72 target: spec.IdRef,
73 decoration: spec.Decoration.Extended,
74) !void {
75 try section.emit(allocator, .OpDecorate, .{
76 .target = target,
77 .decoration = decoration,
78 });
79}
80
81/// Decorate a result-id which is a member of some struct.
82pub fn decorateMember(
83 section: *Section,
84 allocator: Allocator,
85 structure_type: spec.IdRef,
86 member: u32,
87 decoration: spec.Decoration.Extended,
71 comptime opcode: spec.Opcode,
72 operands: opcode.Operands(),
8873) !void {
89 try section.emit(allocator, .OpMemberDecorate, .{
90 .structure_type = structure_type,
91 .member = member,
92 .decoration = decoration,
93 });
74 const word_count = operandsSize(opcode.Operands(), operands);
75 try section.emitRaw(allocator, .OpSpecConstantOp, 1 + word_count);
76 section.writeOperand(spec.IdRef, operands.id_result_type);
77 section.writeOperand(spec.IdRef, operands.id_result);
78 section.writeOperand(Opcode, opcode);
79
80 const fields = @typeInfo(opcode.Operands()).Struct.fields;
81 // First 2 fields are always id_result_type and id_result.
82 inline for (fields[2..]) |field| {
83 section.writeOperand(field.type, @field(operands, field.name));
84 }
9485}
9586
9687pub fn writeWord(section: *Section, word: Word) void {
......@@ -122,7 +113,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
122113
123114pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
124115 switch (Operand) {
125 spec.IdResultType, spec.IdResult, spec.IdRef => section.writeWord(operand.id),
116 spec.IdResult => section.writeWord(operand.id),
126117
127118 spec.LiteralInteger => section.writeWord(operand),
128119
......@@ -258,9 +249,7 @@ fn operandsSize(comptime Operands: type, operands: Operands) usize {
258249
259250fn operandSize(comptime Operand: type, operand: Operand) usize {
260251 return switch (Operand) {
261 spec.IdResultType,
262252 spec.IdResult,
263 spec.IdRef,
264253 spec.LiteralInteger,
265254 spec.LiteralExtInstInteger,
266255 => 1,
......@@ -382,7 +371,9 @@ test "SPIR-V Section emit() - string" {
382371 }, section.instructions.items);
383372}
384373
385test "SPIR-V Section emit()- extended mask" {
374test "SPIR-V Section emit() - extended mask" {
375 if (@import("builtin").zig_backend == .stage1) return error.SkipZigTest;
376
386377 var section = Section{};
387378 defer section.deinit(std.testing.allocator);
388379
src/codegen/spirv/spec.zig+2-13
......@@ -3,22 +3,11 @@
33const Version = @import("std").builtin.Version;
44
55pub const Word = u32;
6pub const IdResultType = struct {
7 id: Word,
8 pub fn toRef(self: IdResultType) IdRef {
9 return .{ .id = self.id };
10 }
11};
126pub const IdResult = struct {
137 id: Word,
14 pub fn toRef(self: IdResult) IdRef {
15 return .{ .id = self.id };
16 }
17 pub fn toResultType(self: IdResult) IdResultType {
18 return .{ .id = self.id };
19 }
208};
21pub const IdRef = struct { id: Word };
9pub const IdResultType = IdResult;
10pub const IdRef = IdResult;
2211
2312pub const IdMemorySemantics = IdRef;
2413pub const IdScope = IdRef;
src/codegen/spirv/type.zig+179-46
......@@ -3,6 +3,8 @@
33
44const std = @import("std");
55const assert = std.debug.assert;
6const Signedness = std.builtin.Signedness;
7const Allocator = std.mem.Allocator;
68
79const spec = @import("spec.zig");
810
......@@ -11,7 +13,7 @@ pub const Type = extern union {
1113 ptr_otherwise: *Payload,
1214
1315 /// A reference to another SPIR-V type.
14 pub const Ref = usize;
16 pub const Ref = enum(u32) { _ };
1517
1618 pub fn initTag(comptime small_tag: Tag) Type {
1719 comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
......@@ -23,6 +25,41 @@ pub const Type = extern union {
2325 return .{ .ptr_otherwise = pl };
2426 }
2527
28 pub fn int(arena: Allocator, signedness: Signedness, bits: u16) !Type {
29 const bits_and_signedness = switch (signedness) {
30 .signed => -@as(i32, bits),
31 .unsigned => @as(i32, bits),
32 };
33
34 return switch (bits_and_signedness) {
35 8 => initTag(.u8),
36 16 => initTag(.u16),
37 32 => initTag(.u32),
38 64 => initTag(.u64),
39 -8 => initTag(.i8),
40 -16 => initTag(.i16),
41 -32 => initTag(.i32),
42 -64 => initTag(.i64),
43 else => {
44 const int_payload = try arena.create(Payload.Int);
45 int_payload.* = .{
46 .width = bits,
47 .signedness = signedness,
48 };
49 return initPayload(&int_payload.base);
50 },
51 };
52 }
53
54 pub fn float(bits: u16) Type {
55 return switch (bits) {
56 16 => initTag(.f16),
57 32 => initTag(.f32),
58 64 => initTag(.f64),
59 else => unreachable, // Enable more types if required.
60 };
61 }
62
2663 pub fn tag(self: Type) Tag {
2764 if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) {
2865 return self.tag_if_small_enough;
......@@ -80,9 +117,19 @@ pub const Type = extern union {
80117 .queue,
81118 .pipe_storage,
82119 .named_barrier,
120 .u8,
121 .u16,
122 .u32,
123 .u64,
124 .i8,
125 .i16,
126 .i32,
127 .i64,
128 .f16,
129 .f32,
130 .f64,
83131 => return true,
84132 .int,
85 .float,
86133 .vector,
87134 .matrix,
88135 .sampled_image,
......@@ -132,6 +179,17 @@ pub const Type = extern union {
132179 .queue,
133180 .pipe_storage,
134181 .named_barrier,
182 .u8,
183 .u16,
184 .u32,
185 .u64,
186 .i8,
187 .i16,
188 .i32,
189 .i64,
190 .f16,
191 .f32,
192 .f64,
135193 => {},
136194 else => self.hashPayload(@field(Tag, field.name), &hasher),
137195 }
......@@ -185,6 +243,53 @@ pub const Type = extern union {
185243 };
186244 }
187245
246 pub fn isInt(self: Type) bool {
247 return switch (self.tag()) {
248 .u8,
249 .u16,
250 .u32,
251 .u64,
252 .i8,
253 .i16,
254 .i32,
255 .i64,
256 .int,
257 => true,
258 else => false,
259 };
260 }
261
262 pub fn isFloat(self: Type) bool {
263 return switch (self.tag()) {
264 .f16, .f32, .f64 => true,
265 else => false,
266 };
267 }
268
269 /// Returns the number of bits that make up an int or float type.
270 /// Asserts type is either int or float.
271 pub fn intFloatBits(self: Type) u16 {
272 return switch (self.tag()) {
273 .u8, .i8 => 8,
274 .u16, .i16, .f16 => 16,
275 .u32, .i32, .f32 => 32,
276 .u64, .i64, .f64 => 64,
277 .int => self.payload(.int).width,
278 else => unreachable,
279 };
280 }
281
282 /// Returns the signedness of an integer type.
283 /// Asserts that the type is an int.
284 pub fn intSignedness(self: Type) Signedness {
285 return switch (self.tag()) {
286 .u8, .u16, .u32, .u64 => .unsigned,
287 .i8, .i16, .i32, .i64 => .signed,
288 .int => self.payload(.int).signedness,
289 else => unreachable,
290 };
291 }
292
188293 pub const Tag = enum(usize) {
189294 void,
190295 bool,
......@@ -195,10 +300,20 @@ pub const Type = extern union {
195300 queue,
196301 pipe_storage,
197302 named_barrier,
303 u8,
304 u16,
305 u32,
306 u64,
307 i8,
308 i16,
309 i32,
310 i64,
311 f16,
312 f32,
313 f64,
198314
199315 // After this, the tag requires a payload.
200316 int,
201 float,
202317 vector,
203318 matrix,
204319 image,
......@@ -211,14 +326,33 @@ pub const Type = extern union {
211326 function,
212327 pipe,
213328
214 pub const last_no_payload_tag = Tag.named_barrier;
329 pub const last_no_payload_tag = Tag.f64;
215330 pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
216331
217332 pub fn Type(comptime t: Tag) type {
218333 return switch (t) {
219 .void, .bool, .sampler, .event, .device_event, .reserve_id, .queue, .pipe_storage, .named_barrier => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
334 .void,
335 .bool,
336 .sampler,
337 .event,
338 .device_event,
339 .reserve_id,
340 .queue,
341 .pipe_storage,
342 .named_barrier,
343 .u8,
344 .u16,
345 .u32,
346 .u64,
347 .i8,
348 .i16,
349 .i32,
350 .i64,
351 .f16,
352 .f32,
353 .f64,
354 => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
220355 .int => Payload.Int,
221 .float => Payload.Float,
222356 .vector => Payload.Vector,
223357 .matrix => Payload.Matrix,
224358 .image => Payload.Image,
......@@ -239,13 +373,8 @@ pub const Type = extern union {
239373
240374 pub const Int = struct {
241375 base: Payload = .{ .tag = .int },
242 width: u32,
243 signedness: std.builtin.Signedness,
244 };
245
246 pub const Float = struct {
247 base: Payload = .{ .tag = .float },
248 width: u32,
376 width: u16,
377 signedness: Signedness,
249378 };
250379
251380 pub const Vector = struct {
......@@ -292,7 +421,7 @@ pub const Type = extern union {
292421 length: u32,
293422 /// Type has the 'ArrayStride' decoration.
294423 /// If zero, no stride is present.
295 array_stride: u32,
424 array_stride: u32 = 0,
296425 };
297426
298427 pub const RuntimeArray = struct {
......@@ -300,35 +429,39 @@ pub const Type = extern union {
300429 element_type: Ref,
301430 /// Type has the 'ArrayStride' decoration.
302431 /// If zero, no stride is present.
303 array_stride: u32,
432 array_stride: u32 = 0,
304433 };
305434
306435 pub const Struct = struct {
307436 base: Payload = .{ .tag = .@"struct" },
308437 members: []Member,
309 decorations: StructDecorations,
438 name: []const u8 = "",
439 decorations: StructDecorations = .{},
310440
311441 /// Extra information for decorations, packed for efficiency. Fields are stored sequentially by
312442 /// order of the `members` slice and `MemberDecorations` struct.
313 member_decoration_extra: []u32,
443 member_decoration_extra: []u32 = &.{},
314444
315445 pub const Member = struct {
316446 ty: Ref,
317 offset: u32,
318 decorations: MemberDecorations,
447 name: []const u8 = "",
448 offset: MemberOffset = .none,
449 decorations: MemberDecorations = .{},
319450 };
320451
452 pub const MemberOffset = enum(u32) { none = 0xFFFF_FFFF, _ };
453
321454 pub const StructDecorations = packed struct {
322455 /// Type has the 'Block' decoration.
323 block: bool,
456 block: bool = false,
324457 /// Type has the 'BufferBlock' decoration.
325 buffer_block: bool,
458 buffer_block: bool = false,
326459 /// Type has the 'GLSLShared' decoration.
327 glsl_shared: bool,
460 glsl_shared: bool = false,
328461 /// Type has the 'GLSLPacked' decoration.
329 glsl_packed: bool,
462 glsl_packed: bool = false,
330463 /// Type has the 'CPacked' decoration.
331 c_packed: bool,
464 c_packed: bool = false,
332465 };
333466
334467 pub const MemberDecorations = packed struct {
......@@ -344,31 +477,31 @@ pub const Type = extern union {
344477 col_major,
345478 /// Member is not a matrix or array of matrices.
346479 none,
347 },
480 } = .none,
348481
349482 // Regular decorations, these do not imply extra fields.
350483
351484 /// Member has the 'NoPerspective' decoration.
352 no_perspective: bool,
485 no_perspective: bool = false,
353486 /// Member has the 'Flat' decoration.
354 flat: bool,
487 flat: bool = false,
355488 /// Member has the 'Patch' decoration.
356 patch: bool,
489 patch: bool = false,
357490 /// Member has the 'Centroid' decoration.
358 centroid: bool,
491 centroid: bool = false,
359492 /// Member has the 'Sample' decoration.
360 sample: bool,
493 sample: bool = false,
361494 /// Member has the 'Invariant' decoration.
362495 /// Note: requires parent struct to have 'Block'.
363 invariant: bool,
496 invariant: bool = false,
364497 /// Member has the 'Volatile' decoration.
365 @"volatile": bool,
498 @"volatile": bool = false,
366499 /// Member has the 'Coherent' decoration.
367 coherent: bool,
500 coherent: bool = false,
368501 /// Member has the 'NonWritable' decoration.
369 non_writable: bool,
502 non_writable: bool = false,
370503 /// Member has the 'NonReadable' decoration.
371 non_readable: bool,
504 non_readable: bool = false,
372505
373506 // The following decorations all imply extra field(s).
374507
......@@ -377,27 +510,27 @@ pub const Type = extern union {
377510 /// Note: If any member of a struct has the BuiltIn decoration, all members must have one.
378511 /// Note: Each builtin may only be reachable once for a particular entry point.
379512 /// Note: The member type may be constrained by a particular built-in, defined in the client API specification.
380 builtin: bool,
513 builtin: bool = false,
381514 /// Member has the 'Stream' decoration.
382515 /// This member has an extra field of type `u32`.
383 stream: bool,
516 stream: bool = false,
384517 /// Member has the 'Location' decoration.
385518 /// This member has an extra field of type `u32`.
386 location: bool,
519 location: bool = false,
387520 /// Member has the 'Component' decoration.
388521 /// This member has an extra field of type `u32`.
389 component: bool,
522 component: bool = false,
390523 /// Member has the 'XfbBuffer' decoration.
391524 /// This member has an extra field of type `u32`.
392 xfb_buffer: bool,
525 xfb_buffer: bool = false,
393526 /// Member has the 'XfbStride' decoration.
394527 /// This member has an extra field of type `u32`.
395 xfb_stride: bool,
528 xfb_stride: bool = false,
396529 /// Member has the 'UserSemantic' decoration.
397530 /// This member has an extra field of type `[]u8`, which is encoded
398531 /// by an `u32` containing the number of chars exactly, and then the string padded to
399532 /// a multiple of 4 bytes with zeroes.
400 user_semantic: bool,
533 user_semantic: bool = false,
401534 };
402535 };
403536
......@@ -413,11 +546,11 @@ pub const Type = extern union {
413546 /// Type has the 'ArrayStride' decoration.
414547 /// This is valid for pointers to elements of an array.
415548 /// If zero, no stride is present.
416 array_stride: u32,
417 /// Type has the 'Alignment' decoration.
418 alignment: ?u32,
549 array_stride: u32 = 0,
550 /// If nonzero, type has the 'Alignment' decoration.
551 alignment: u32 = 0,
419552 /// Type has the 'MaxByteOffset' decoration.
420 max_byte_offset: ?u32,
553 max_byte_offset: ?u32 = null,
421554 };
422555
423556 pub const Function = struct {
src/link/NvPtx.zig+1-1
......@@ -1,7 +1,7 @@
11//! NVidia PTX (Paralle Thread Execution)
22//! https://docs.nvidia.com/cuda/parallel-thread-execution/index.html
33//! For this we rely on the nvptx backend of LLVM
4//! Kernel functions need to be marked both as "export" and "callconv(.PtxKernel)"
4//! Kernel functions need to be marked both as "export" and "callconv(.Kernel)"
55
66const NvPtx = @This();
77
src/link/SpirV.zig+65-141
......@@ -44,34 +44,25 @@ const IdResult = spec.IdResult;
4444
4545base: link.File,
4646
47/// This linker backend does not try to incrementally link output SPIR-V code.
48/// Instead, it tracks all declarations in this table, and iterates over it
49/// in the flush function.
50decl_table: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, DeclGenContext) = .{},
51
52const DeclGenContext = struct {
53 air: Air,
54 air_arena: ArenaAllocator.State,
55 liveness: Liveness,
56
57 fn deinit(self: *DeclGenContext, gpa: Allocator) void {
58 self.air.deinit(gpa);
59 self.liveness.deinit(gpa);
60 self.air_arena.promote(gpa).deinit();
61 self.* = undefined;
62 }
63};
47spv: SpvModule,
48spv_arena: ArenaAllocator,
49decl_link: codegen.DeclLinkMap,
6450
6551pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {
66 const spirv = try gpa.create(SpirV);
67 spirv.* = .{
52 const self = try gpa.create(SpirV);
53 self.* = .{
6854 .base = .{
6955 .tag = .spirv,
7056 .options = options,
7157 .file = null,
7258 .allocator = gpa,
7359 },
60 .spv = undefined,
61 .spv_arena = ArenaAllocator.init(gpa),
62 .decl_link = codegen.DeclLinkMap.init(self.base.allocator),
7463 };
64 self.spv = SpvModule.init(gpa, self.spv_arena.allocator());
65 errdefer self.deinit();
7566
7667 // TODO: Figure out where to put all of these
7768 switch (options.target.cpu.arch) {
......@@ -88,7 +79,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {
8879 return error.TODOAbiNotSupported;
8980 }
9081
91 return spirv;
82 return self;
9283}
9384
9485pub 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
10798}
10899
109100pub fn deinit(self: *SpirV) void {
110 self.decl_table.deinit(self.base.allocator);
101 self.spv.deinit();
102 self.spv_arena.deinit();
103 self.decl_link.deinit();
111104}
112105
113106pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
114107 if (build_options.skip_non_native) {
115108 @panic("Attempted to compile for architecture that was disabled by build configuration");
116109 }
117 _ = module;
118
119 // Keep track of all decls so we can iterate over them on flush().
120 const result = try self.decl_table.getOrPut(self.base.allocator, func.owner_decl);
121 if (result.found_existing) {
122 result.value_ptr.deinit(self.base.allocator);
123 }
124110
125 var arena = ArenaAllocator.init(self.base.allocator);
126 errdefer arena.deinit();
127
128 var new_air = try cloneAir(air, self.base.allocator, arena.allocator());
129 errdefer new_air.deinit(self.base.allocator);
130
131 var new_liveness = try cloneLiveness(liveness, self.base.allocator);
132 errdefer new_liveness.deinit(self.base.allocator);
111 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link);
112 defer decl_gen.deinit();
133113
134 result.value_ptr.* = .{
135 .air = new_air,
136 .air_arena = arena.state,
137 .liveness = new_liveness,
138 };
114 if (try decl_gen.gen(func.owner_decl, air, liveness)) |msg| {
115 try module.failed_decls.put(module.gpa, func.owner_decl, msg);
116 }
139117}
140118
141119pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index) !void {
142120 if (build_options.skip_non_native) {
143121 @panic("Attempted to compile for architecture that was disabled by build configuration");
144122 }
145 _ = module;
146 // Keep track of all decls so we can iterate over them on flush().
147 _ = try self.decl_table.getOrPut(self.base.allocator, decl_index);
123
124 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link);
125 defer decl_gen.deinit();
126
127 if (try decl_gen.gen(decl_index, undefined, undefined)) |msg| {
128 try module.failed_decls.put(module.gpa, decl_index, msg);
129 }
148130}
149131
150132pub fn updateDeclExports(
......@@ -153,20 +135,26 @@ pub fn updateDeclExports(
153135 decl_index: Module.Decl.Index,
154136 exports: []const *Module.Export,
155137) !void {
156 _ = self;
157 _ = module;
158 _ = decl_index;
159 _ = exports;
160}
138 const decl = module.declPtr(decl_index);
139 if (decl.val.tag() == .function and decl.ty.fnCallingConvention() == .Kernel) {
140 // TODO: Unify with resolveDecl in spirv.zig.
141 const entry = try self.decl_link.getOrPut(decl_index);
142 if (!entry.found_existing) {
143 entry.value_ptr.* = try self.spv.allocDecl(.func);
144 }
145 const spv_decl_index = entry.value_ptr.*;
161146
162pub fn freeDecl(self: *SpirV, decl_index: Module.Decl.Index) void {
163 if (self.decl_table.getIndex(decl_index)) |index| {
164 const module = self.base.options.module.?;
165 const decl = module.declPtr(decl_index);
166 if (decl.val.tag() == .function) {
167 self.decl_table.values()[index].deinit(self.base.allocator);
147 for (exports) |exp| {
148 try self.spv.declareEntryPoint(spv_decl_index, exp.options.name);
168149 }
169150 }
151
152 // TODO: Export regular functions, variables, etc using Linkage attributes.
153}
154
155pub fn freeDecl(self: *SpirV, decl_index: Module.Decl.Index) void {
156 _ = self;
157 _ = decl_index;
170158}
171159
172160pub 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
189177 sub_prog_node.activate();
190178 defer sub_prog_node.end();
191179
192 const module = self.base.options.module.?;
193180 const target = comp.getTarget();
181 try writeCapabilities(&self.spv, target);
182 try writeMemoryModel(&self.spv, target);
194183
195 var arena = std.heap.ArenaAllocator.init(self.base.allocator);
196 defer arena.deinit();
197
198 var spv = SpvModule.init(self.base.allocator, arena.allocator());
199 defer spv.deinit();
200
201 // Allocate an ID for every declaration before generating code,
202 // so that we can access them before processing them.
203 // TODO: We're allocating an ID unconditionally now, are there
204 // declarations which don't generate a result?
205 var ids = std.AutoHashMap(Module.Decl.Index, IdResult).init(self.base.allocator);
206 defer ids.deinit();
207 try ids.ensureTotalCapacity(@intCast(u32, self.decl_table.count()));
184 // We need to export the list of error names somewhere so that we can pretty-print them in the
185 // executor. This is not really an important thing though, so we can just dump it in any old
186 // nonsemantic instruction. For now, just put it in OpSourceExtension with a special name.
208187
209 for (self.decl_table.keys()) |decl_index| {
210 const decl = module.declPtr(decl_index);
211 if (decl.has_tv) {
212 ids.putAssumeCapacityNoClobber(decl_index, spv.allocId());
213 }
214 }
215
216 // Now, actually generate the code for all declarations.
217 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &spv, &ids);
218 defer decl_gen.deinit();
219
220 var it = self.decl_table.iterator();
221 while (it.next()) |entry| {
222 const decl_index = entry.key_ptr.*;
223 const decl = module.declPtr(decl_index);
224 if (!decl.has_tv) continue;
225
226 const air = entry.value_ptr.air;
227 const liveness = entry.value_ptr.liveness;
228
229 // Note, if `decl` is not a function, air/liveness may be undefined.
230 if (try decl_gen.gen(decl_index, air, liveness)) |msg| {
231 try module.failed_decls.put(module.gpa, decl_index, msg);
232 return; // TODO: Attempt to generate more decls?
233 }
188 var error_info = std.ArrayList(u8).init(self.spv.arena);
189 try error_info.appendSlice("zig_errors");
190 const module = self.base.options.module.?;
191 for (module.error_name_list.items) |name| {
192 // Errors can contain pretty much any character - to encode them in a string we must escape
193 // them somehow. Easiest here is to use some established scheme, one which also preseves the
194 // name if it contains no strange characters is nice for debugging. URI encoding fits the bill.
195 // We're using : as separator, which is a reserved character.
196
197 const escaped_name = try std.Uri.escapeString(self.base.allocator, name);
198 defer self.base.allocator.free(escaped_name);
199 try error_info.writer().print(":{s}", .{escaped_name});
234200 }
201 try self.spv.sections.debug_strings.emit(self.spv.gpa, .OpSourceExtension, .{
202 .extension = error_info.items,
203 });
235204
236 try writeCapabilities(&spv, target);
237 try writeMemoryModel(&spv, target);
238
239 try spv.flush(self.base.file.?);
205 try self.spv.flush(self.base.file.?);
240206}
241207
242208fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
243209 // TODO: Integrate with a hypothetical feature system
244210 const caps: []const spec.Capability = switch (target.os.tag) {
245 .opencl => &.{.Kernel},
211 .opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .GenericPointer },
246212 .glsl450 => &.{.Shader},
247213 .vulkan => &.{.Shader},
248214 else => unreachable, // TODO
......@@ -279,45 +245,3 @@ fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void {
279245 .memory_model = memory_model,
280246 });
281247}
282
283fn cloneLiveness(l: Liveness, gpa: Allocator) !Liveness {
284 const tomb_bits = try gpa.dupe(usize, l.tomb_bits);
285 errdefer gpa.free(tomb_bits);
286
287 const extra = try gpa.dupe(u32, l.extra);
288 errdefer gpa.free(extra);
289
290 return Liveness{
291 .tomb_bits = tomb_bits,
292 .extra = extra,
293 .special = try l.special.clone(gpa),
294 };
295}
296
297fn cloneAir(air: Air, gpa: Allocator, air_arena: Allocator) !Air {
298 const values = try gpa.alloc(Value, air.values.len);
299 errdefer gpa.free(values);
300
301 for (values, 0..) |*value, i| {
302 value.* = try air.values[i].copy(air_arena);
303 }
304
305 var instructions = try air.instructions.toMultiArrayList().clone(gpa);
306 errdefer instructions.deinit(gpa);
307
308 const air_tags = instructions.items(.tag);
309 const air_datas = instructions.items(.data);
310
311 for (air_tags, 0..) |tag, i| {
312 switch (tag) {
313 .alloc, .ret_ptr, .const_ty => air_datas[i].ty = try air_datas[i].ty.copy(air_arena),
314 else => {},
315 }
316 }
317
318 return Air{
319 .instructions = instructions.slice(),
320 .extra = try gpa.dupe(u32, air.extra),
321 .values = values,
322 };
323}
src/target.zig+4-4
......@@ -163,7 +163,7 @@ pub fn canBuildLibC(target: std.Target) bool {
163163pub fn cannotDynamicLink(target: std.Target) bool {
164164 return switch (target.os.tag) {
165165 .freestanding, .other => true,
166 else => false,
166 else => target.isSpirV(),
167167 };
168168}
169169
......@@ -331,18 +331,18 @@ pub fn supportsStackProbing(target: std.Target) bool {
331331}
332332
333333pub fn supportsStackProtector(target: std.Target) bool {
334 _ = target;
335 return true;
334 return !target.isSpirV();
336335}
337336
338337pub fn libcProvidesStackProtector(target: std.Target) bool {
339 return !target.isMinGW() and target.os.tag != .wasi;
338 return !target.isMinGW() and target.os.tag != .wasi and !target.isSpirV();
340339}
341340
342341pub fn supportsReturnAddress(target: std.Target) bool {
343342 return switch (target.cpu.arch) {
344343 .wasm32, .wasm64 => target.os.tag == .emscripten,
345344 .bpfel, .bpfeb => false,
345 .spirv32, .spirv64 => false,
346346 else => true,
347347 };
348348}
src/type.zig+5-2
......@@ -4796,9 +4796,12 @@ pub const Type = extern union {
47964796 }
47974797
47984798 /// Asserts the type is a function.
4799 pub fn fnCallingConventionAllowsZigTypes(cc: std.builtin.CallingConvention) bool {
4799 pub fn fnCallingConventionAllowsZigTypes(target: Target, cc: std.builtin.CallingConvention) bool {
48004800 return switch (cc) {
4801 .Unspecified, .Async, .Inline, .PtxKernel => true,
4801 .Unspecified, .Async, .Inline => true,
4802 // For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI.
4803 // The goal is to experiment with more integrated CPU/GPU code.
4804 .Kernel => target.cpu.arch == .nvptx or target.cpu.arch == .nvptx64,
48024805 else => false,
48034806 };
48044807 }
test/nvptx.zig+4-4
......@@ -10,7 +10,7 @@ pub fn addCases(ctx: *Cases) !void {
1010 \\ return a + b;
1111 \\}
1212 \\
13 \\pub export fn add_and_substract(a: i32, out: *i32) callconv(.PtxKernel) void {
13 \\pub export fn add_and_substract(a: i32, out: *i32) callconv(.Kernel) void {
1414 \\ const x = add(a, 7);
1515 \\ var y = add(2, 0);
1616 \\ y -= x;
......@@ -29,7 +29,7 @@ pub fn addCases(ctx: *Cases) !void {
2929 \\ );
3030 \\}
3131 \\
32 \\pub export fn special_reg(a: []const i32, out: []i32) callconv(.PtxKernel) void {
32 \\pub export fn special_reg(a: []const i32, out: []i32) callconv(.Kernel) void {
3333 \\ const i = threadIdX();
3434 \\ out[i] = a[i] + 7;
3535 \\}
......@@ -42,7 +42,7 @@ pub fn addCases(ctx: *Cases) !void {
4242 case.addCompile(
4343 \\var x: i32 addrspace(.global) = 0;
4444 \\
45 \\pub export fn increment(out: *i32) callconv(.PtxKernel) void {
45 \\pub export fn increment(out: *i32) callconv(.Kernel) void {
4646 \\ x += 1;
4747 \\ out.* = x;
4848 \\}
......@@ -59,7 +59,7 @@ pub fn addCases(ctx: *Cases) !void {
5959 \\}
6060 \\
6161 \\ var _sdata: [1024]f32 addrspace(.shared) = undefined;
62 \\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.PtxKernel) void {
62 \\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.Kernel) void {
6363 \\ var sdata = @addrSpaceCast(.generic, &_sdata);
6464 \\ const tid: u32 = threadIdX();
6565 \\ var sum = d_x[tid];
tools/gen_spirv_spec.zig+2-13
......@@ -80,22 +80,11 @@ fn render(writer: anytype, allocator: Allocator, registry: g.CoreRegistry) !void
8080 \\const Version = @import("std").builtin.Version;
8181 \\
8282 \\pub const Word = u32;
83 \\pub const IdResultType = struct{
84 \\ id: Word,
85 \\ pub fn toRef(self: IdResultType) IdRef {
86 \\ return .{.id = self.id};
87 \\ }
88 \\};
8983 \\pub const IdResult = struct{
9084 \\ id: Word,
91 \\ pub fn toRef(self: IdResult) IdRef {
92 \\ return .{.id = self.id};
93 \\ }
94 \\ pub fn toResultType(self: IdResult) IdResultType {
95 \\ return .{.id = self.id};
96 \\ }
9785 \\};
98 \\pub const IdRef = struct{ id: Word };
86 \\pub const IdResultType = IdResult;
87 \\pub const IdRef = IdResult;
9988 \\
10089 \\pub const IdMemorySemantics = IdRef;
10190 \\pub const IdScope = IdRef;