authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2024-02-01 15:48:51+03:30
committergravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2024-02-05 11:55:14+03:30
logafa779335186acf10f79848775afaf55698d8d88
tree7d53299a957a1fc048e4fd6b444299b50d5705f7
parent7634a115c50ef66edbdd5644c4ba310eb31e6343

spirv: basic shader support


9 files changed, 220 insertions(+), 115 deletions(-)

lib/std/Target.zig+2-2
......@@ -1221,6 +1221,7 @@ pub const Cpu = struct {
12211221 .fs, .gs, .ss => arch == .x86_64 or arch == .x86,
12221222 .global, .constant, .local, .shared => is_gpu,
12231223 .param => is_nvptx,
1224 .input, .output, .uniform => is_spirv,
12241225 // TODO this should also check how many flash banks the cpu has
12251226 .flash, .flash1, .flash2, .flash3, .flash4, .flash5 => arch == .avr,
12261227 };
......@@ -2353,7 +2354,7 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 {
23532354 .longdouble => return 128,
23542355 },
23552356
2356 .opencl => switch (c_type) {
2357 .opencl, .vulkan => switch (c_type) {
23572358 .char => return 8,
23582359 .short, .ushort => return 16,
23592360 .int, .uint, .float => return 32,
......@@ -2386,7 +2387,6 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 {
23862387 .hermit,
23872388 .hurd,
23882389 .glsl450,
2389 .vulkan,
23902390 .driverkit,
23912391 .shadermodel,
23922392 .liteos,
lib/std/builtin.zig+6
......@@ -205,6 +205,9 @@ pub const CallingConvention = enum(u8) {
205205 Win64,
206206 /// AMD GPU, NVPTX, or SPIR-V kernel
207207 Kernel,
208 // Vulkan-only
209 Fragment,
210 Vertex,
208211};
209212
210213/// This data structure is used by the Zig language code generation and
......@@ -222,6 +225,9 @@ pub const AddressSpace = enum(u5) {
222225 param,
223226 shared,
224227 local,
228 input,
229 output,
230 uniform,
225231
226232 // AVR address spaces.
227233 flash,
src/Sema.zig+5
......@@ -9741,6 +9741,10 @@ fn finishFunc(
97419741 .nvptx, .nvptx64, .amdgcn, .spirv32, .spirv64 => null,
97429742 else => "nvptx, amdgcn and SPIR-V",
97439743 },
9744 .Fragment, .Vertex => switch (arch) {
9745 .spirv32, .spirv64 => null,
9746 else => "SPIR-V",
9747 },
97449748 })) |allowed_platform| {
97459749 return sema.fail(block, cc_src, "callconv '{s}' is only available on {s}, not {s}", .{
97469750 @tagName(cc_resolved),
......@@ -37917,6 +37921,7 @@ pub fn analyzeAddressSpace(
3791737921 .gs, .fs, .ss => (arch == .x86 or arch == .x86_64) and ctx == .pointer,
3791837922 // TODO: check that .shared and .local are left uninitialized
3791937923 .param => is_nv,
37924 .input, .output, .uniform => is_spirv,
3792037925 .global, .shared, .local => is_gpu,
3792137926 .constant => is_gpu and (ctx == .constant),
3792237927 // TODO this should also check how many flash banks the cpu has
src/codegen/llvm.zig+1
......@@ -10848,6 +10848,7 @@ fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) Builder
1084810848 .amdgcn => .amdgpu_kernel,
1084910849 else => unreachable,
1085010850 },
10851 .Vertex, .Fragment => unreachable,
1085110852 };
1085210853}
1085310854
src/codegen/spirv.zig+124-91
......@@ -451,12 +451,12 @@ const DeclGen = struct {
451451 const spv_decl_index = blk: {
452452 const entry = try self.object.anon_decl_link.getOrPut(self.object.gpa, .{ val, storage_class });
453453 if (entry.found_existing) {
454 try self.func.decl_deps.put(self.spv.gpa, entry.value_ptr.*, {});
454 try self.addFunctionDep(entry.value_ptr.*, storage_class);
455455 return self.spv.declPtr(entry.value_ptr.*).result_id;
456456 }
457457
458458 const spv_decl_index = try self.spv.allocDecl(.global);
459 try self.func.decl_deps.put(self.spv.gpa, spv_decl_index, {});
459 try self.addFunctionDep(spv_decl_index, storage_class);
460460 entry.value_ptr.* = spv_decl_index;
461461 break :blk spv_decl_index;
462462 };
......@@ -529,6 +529,37 @@ const DeclGen = struct {
529529 return var_id;
530530 }
531531
532 fn addFunctionDep(self: *DeclGen, decl_index: SpvModule.Decl.Index, storage_class: StorageClass) !void {
533 const target = self.getTarget();
534 if (target.os.tag == .vulkan) {
535 // Shader entry point dependencies must be variables with Input or Output storage class
536 switch (storage_class) {
537 .Input, .Output => {
538 try self.func.decl_deps.put(self.spv.gpa, decl_index, {});
539 },
540 else => {},
541 }
542 } else {
543 try self.func.decl_deps.put(self.spv.gpa, decl_index, {});
544 }
545 }
546
547 fn castToGeneric(self: *DeclGen, type_id: IdRef, ptr_id: IdRef) !IdRef {
548 const target = self.getTarget();
549
550 if (target.os.tag == .vulkan) {
551 return ptr_id;
552 } else {
553 const result_id = self.spv.allocId();
554 try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{
555 .id_result_type = type_id,
556 .id_result = result_id,
557 .pointer = ptr_id,
558 });
559 return result_id;
560 }
561 }
562
532563 /// Start a new SPIR-V block, Emits the label of the new block, and stores which
533564 /// block we are currently generating.
534565 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to
......@@ -1019,7 +1050,7 @@ const DeclGen = struct {
10191050
10201051 // TODO: Can we consolidate this in ptrElemPtr?
10211052 const elem_ty = parent_ptr_ty.elemType2(mod); // use elemType() so that we get T for *[N]T.
1022 const elem_ptr_ty_ref = try self.ptrType(elem_ty, spvStorageClass(parent_ptr_ty.ptrAddressSpace(mod)));
1053 const elem_ptr_ty_ref = try self.ptrType(elem_ty, self.spvStorageClass(parent_ptr_ty.ptrAddressSpace(mod)));
10231054
10241055 if (elem_ptr_ty_ref == result_ty_ref) {
10251056 return elem_ptr_id;
......@@ -1074,7 +1105,7 @@ const DeclGen = struct {
10741105 unreachable; // TODO
10751106 }
10761107
1077 const final_storage_class = spvStorageClass(ty.ptrAddressSpace(mod));
1108 const final_storage_class = self.spvStorageClass(ty.ptrAddressSpace(mod));
10781109 const actual_storage_class = switch (final_storage_class) {
10791110 .Generic => .CrossWorkgroup,
10801111 else => |other| other,
......@@ -1084,15 +1115,7 @@ const DeclGen = struct {
10841115 const decl_ptr_ty_ref = try self.ptrType(decl_ty, final_storage_class);
10851116
10861117 const ptr_id = switch (final_storage_class) {
1087 .Generic => blk: {
1088 const result_id = self.spv.allocId();
1089 try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{
1090 .id_result_type = self.typeId(decl_ptr_ty_ref),
1091 .id_result = result_id,
1092 .pointer = decl_id,
1093 });
1094 break :blk result_id;
1095 },
1118 .Generic => try self.castToGeneric(self.typeId(decl_ptr_ty_ref), decl_id),
10961119 else => decl_id,
10971120 };
10981121
......@@ -1115,6 +1138,7 @@ const DeclGen = struct {
11151138 const ty_ref = try self.resolveType(ty, .direct);
11161139 const ty_id = self.typeId(ty_ref);
11171140 const decl = mod.declPtr(decl_index);
1141
11181142 switch (mod.intern_pool.indexToKey(decl.val.ip_index)) {
11191143 .func => {
11201144 // TODO: Properly lower function pointers. For now we are going to hack around it and
......@@ -1133,23 +1157,13 @@ const DeclGen = struct {
11331157 const spv_decl_index = try self.object.resolveDecl(mod, decl_index);
11341158
11351159 const decl_id = self.spv.declPtr(spv_decl_index).result_id;
1136 try self.func.decl_deps.put(self.spv.gpa, spv_decl_index, {});
1137
1138 const final_storage_class = spvStorageClass(decl.@"addrspace");
1160 const final_storage_class = self.spvStorageClass(decl.@"addrspace");
1161 try self.addFunctionDep(spv_decl_index, final_storage_class);
11391162
11401163 const decl_ptr_ty_ref = try self.ptrType(decl.ty, final_storage_class);
11411164
11421165 const ptr_id = switch (final_storage_class) {
1143 .Generic => blk: {
1144 // Pointer should be Generic, but is actually placed in CrossWorkgroup.
1145 const result_id = self.spv.allocId();
1146 try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{
1147 .id_result_type = self.typeId(decl_ptr_ty_ref),
1148 .id_result = result_id,
1149 .pointer = decl_id,
1150 });
1151 break :blk result_id;
1152 },
1166 .Generic => try self.castToGeneric(self.typeId(decl_ptr_ty_ref), decl_id),
11531167 else => decl_id,
11541168 };
11551169
......@@ -1195,8 +1209,12 @@ const DeclGen = struct {
11951209 // An array of largestSupportedIntBits.
11961210 return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });
11971211 };
1212
11981213 // Kernel only supports unsigned ints.
1199 // TODO: Only do this with Kernels
1214 if (self.getTarget().os.tag == .vulkan) {
1215 return self.spv.intType(signedness, backing_bits);
1216 }
1217
12001218 return self.spv.intType(.unsigned, backing_bits);
12011219 }
12021220
......@@ -1453,7 +1471,7 @@ const DeclGen = struct {
14531471 // Note: Don't cache this pointer type, it would mess up the recursive pointer functionality
14541472 // in ptrType()!
14551473
1456 const storage_class = spvStorageClass(ptr_info.flags.address_space);
1474 const storage_class = self.spvStorageClass(ptr_info.flags.address_space);
14571475 const ptr_ty_ref = try self.ptrType(Type.fromInterned(ptr_info.child), storage_class);
14581476
14591477 if (ptr_info.flags.size != .Slice) {
......@@ -1634,13 +1652,20 @@ const DeclGen = struct {
16341652 }
16351653 }
16361654
1637 fn spvStorageClass(as: std.builtin.AddressSpace) StorageClass {
1655 fn spvStorageClass(self: *DeclGen, as: std.builtin.AddressSpace) StorageClass {
1656 const target = self.getTarget();
16381657 return switch (as) {
1639 .generic => .Generic,
1658 .generic => switch (target.os.tag) {
1659 .vulkan => .Private,
1660 else => .Generic,
1661 },
16401662 .shared => .Workgroup,
16411663 .local => .Private,
16421664 .global => .CrossWorkgroup,
16431665 .constant => .UniformConstant,
1666 .input => .Input,
1667 .output => .Output,
1668 .uniform => .Uniform,
16441669 .gs,
16451670 .fs,
16461671 .ss,
......@@ -1920,7 +1945,7 @@ const DeclGen = struct {
19201945 // point name is the same as a different OpName.
19211946 const test_name = try std.fmt.allocPrint(self.gpa, "test {s}", .{name});
19221947 defer self.gpa.free(test_name);
1923 try self.spv.declareEntryPoint(spv_decl_index, test_name);
1948 try self.spv.declareEntryPoint(spv_decl_index, test_name, .Kernel);
19241949 }
19251950
19261951 fn genDecl(self: *DeclGen) !void {
......@@ -1928,6 +1953,7 @@ const DeclGen = struct {
19281953 const ip = &mod.intern_pool;
19291954 const decl = mod.declPtr(self.decl_index);
19301955 const spv_decl_index = try self.object.resolveDecl(mod, self.decl_index);
1956 const target = self.getTarget();
19311957
19321958 const decl_id = self.spv.declPtr(spv_decl_index).result_id;
19331959
......@@ -1994,30 +2020,24 @@ const DeclGen = struct {
19942020 try self.generateTestEntryPoint(fqn, spv_decl_index);
19952021 }
19962022 } else {
1997 const init_val = if (decl.val.getVariable(mod)) |payload|
1998 Value.fromInterned(payload.init)
1999 else
2000 decl.val;
2001
2002 if (init_val.ip_index == .unreachable_value) {
2003 return self.todo("importing extern variables", .{});
2004 }
2005
2006 // Currently, initializers for CrossWorkgroup variables is not implemented
2007 // in Mesa. Therefore we generate an initialization kernel instead.
2008
2009 const void_ty_ref = try self.resolveType(Type.void, .direct);
2010
2011 const initializer_proto_ty_ref = try self.spv.resolve(.{ .function_type = .{
2012 .return_type = void_ty_ref,
2013 .parameters = &.{},
2014 } });
2023 const opt_init_val: ?Value = blk: {
2024 if (decl.val.getVariable(mod)) |payload| {
2025 if (payload.is_extern) break :blk null;
2026 break :blk Value.fromInterned(payload.init);
2027 }
2028 break :blk decl.val;
2029 };
20152030
20162031 // Generate the actual variable for the global...
2017 const final_storage_class = spvStorageClass(decl.@"addrspace");
2018 const actual_storage_class = switch (final_storage_class) {
2019 .Generic => .CrossWorkgroup,
2020 else => final_storage_class,
2032 const final_storage_class = self.spvStorageClass(decl.@"addrspace");
2033 const actual_storage_class = blk: {
2034 if (target.os.tag != .vulkan) {
2035 break :blk switch (final_storage_class) {
2036 .Generic => .CrossWorkgroup,
2037 else => final_storage_class,
2038 };
2039 }
2040 break :blk final_storage_class;
20212041 };
20222042
20232043 const ptr_ty_ref = try self.ptrType(decl.ty, actual_storage_class);
......@@ -2028,37 +2048,51 @@ const DeclGen = struct {
20282048 .id_result = decl_id,
20292049 .storage_class = actual_storage_class,
20302050 });
2051 const fqn = ip.stringToSlice(try decl.getFullyQualifiedName(self.module));
2052 try self.spv.debugName(decl_id, fqn);
20312053
2032 // Now emit the instructions that initialize the variable.
2033 const initializer_id = self.spv.allocId();
2034 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{
2035 .id_result_type = self.typeId(void_ty_ref),
2036 .id_result = initializer_id,
2037 .function_control = .{},
2038 .function_type = self.typeId(initializer_proto_ty_ref),
2039 });
2040 const root_block_id = self.spv.allocId();
2041 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
2042 .id_result = root_block_id,
2043 });
2044 self.current_block_label = root_block_id;
2054 if (opt_init_val) |init_val| {
2055 // Currently, initializers for CrossWorkgroup variables is not implemented
2056 // in Mesa. Therefore we generate an initialization kernel instead.
2057 const void_ty_ref = try self.resolveType(Type.void, .direct);
20452058
2046 const val_id = try self.constant(decl.ty, init_val, .indirect);
2047 try self.func.body.emit(self.spv.gpa, .OpStore, .{
2048 .pointer = decl_id,
2049 .object = val_id,
2050 });
2059 const initializer_proto_ty_ref = try self.spv.resolve(.{ .function_type = .{
2060 .return_type = void_ty_ref,
2061 .parameters = &.{},
2062 } });
20512063
2052 // TODO: We should be able to get rid of this by now...
2053 self.spv.endGlobal(spv_decl_index, begin, decl_id, initializer_id);
2064 // Now emit the instructions that initialize the variable.
2065 const initializer_id = self.spv.allocId();
2066 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{
2067 .id_result_type = self.typeId(void_ty_ref),
2068 .id_result = initializer_id,
2069 .function_control = .{},
2070 .function_type = self.typeId(initializer_proto_ty_ref),
2071 });
2072 const root_block_id = self.spv.allocId();
2073 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
2074 .id_result = root_block_id,
2075 });
2076 self.current_block_label = root_block_id;
20542077
2055 try self.func.body.emit(self.spv.gpa, .OpReturn, {});
2056 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
2057 try self.spv.addFunction(spv_decl_index, self.func);
2078 const val_id = try self.constant(decl.ty, init_val, .indirect);
2079 try self.func.body.emit(self.spv.gpa, .OpStore, .{
2080 .pointer = decl_id,
2081 .object = val_id,
2082 });
20582083
2059 const fqn = ip.stringToSlice(try decl.getFullyQualifiedName(self.module));
2060 try self.spv.debugName(decl_id, fqn);
2061 try self.spv.debugNameFmt(initializer_id, "initializer of {s}", .{fqn});
2084 // TODO: We should be able to get rid of this by now...
2085 self.spv.endGlobal(spv_decl_index, begin, decl_id, initializer_id);
2086
2087 try self.func.body.emit(self.spv.gpa, .OpReturn, {});
2088 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
2089 try self.spv.addFunction(spv_decl_index, self.func);
2090
2091 try self.spv.debugNameFmt(initializer_id, "initializer of {s}", .{fqn});
2092 } else {
2093 self.spv.endGlobal(spv_decl_index, begin, decl_id, null);
2094 try self.spv.declareDeclDeps(spv_decl_index, &.{});
2095 }
20622096 }
20632097 }
20642098
......@@ -3761,7 +3795,7 @@ const DeclGen = struct {
37613795 const mod = self.module;
37623796 // Construct new pointer type for the resulting pointer
37633797 const elem_ty = ptr_ty.elemType2(mod); // use elemType() so that we get T for *[N]T.
3764 const elem_ptr_ty_ref = try self.ptrType(elem_ty, spvStorageClass(ptr_ty.ptrAddressSpace(mod)));
3798 const elem_ptr_ty_ref = try self.ptrType(elem_ty, self.spvStorageClass(ptr_ty.ptrAddressSpace(mod)));
37653799 if (ptr_ty.isSinglePointer(mod)) {
37663800 // Pointer-to-array. In this case, the resulting pointer is not of the same type
37673801 // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain.
......@@ -3835,7 +3869,7 @@ const DeclGen = struct {
38353869 const vector_ty = vector_ptr_ty.childType(mod);
38363870 const scalar_ty = vector_ty.scalarType(mod);
38373871
3838 const storage_class = spvStorageClass(vector_ptr_ty.ptrAddressSpace(mod));
3872 const storage_class = self.spvStorageClass(vector_ptr_ty.ptrAddressSpace(mod));
38393873 const scalar_ptr_ty_ref = try self.ptrType(scalar_ty, storage_class);
38403874
38413875 const vector_ptr = try self.resolve(data.vector_ptr);
......@@ -3858,7 +3892,7 @@ const DeclGen = struct {
38583892 if (layout.tag_size == 0) return;
38593893
38603894 const tag_ty = un_ty.unionTagTypeSafety(mod).?;
3861 const tag_ptr_ty_ref = try self.ptrType(tag_ty, spvStorageClass(un_ptr_ty.ptrAddressSpace(mod)));
3895 const tag_ptr_ty_ref = try self.ptrType(tag_ty, self.spvStorageClass(un_ptr_ty.ptrAddressSpace(mod)));
38623896
38633897 const union_ptr_id = try self.resolve(bin_op.lhs);
38643898 const new_tag_id = try self.resolve(bin_op.rhs);
......@@ -4079,7 +4113,7 @@ const DeclGen = struct {
40794113 return try self.spv.constUndef(result_ty_ref);
40804114 }
40814115
4082 const storage_class = spvStorageClass(object_ptr_ty.ptrAddressSpace(mod));
4116 const storage_class = self.spvStorageClass(object_ptr_ty.ptrAddressSpace(mod));
40834117 const pl_ptr_ty_ref = try self.ptrType(layout.payload_ty, storage_class);
40844118 const pl_ptr_id = try self.accessChain(pl_ptr_ty_ref, object_ptr, &.{layout.payload_index});
40854119
......@@ -4134,17 +4168,16 @@ const DeclGen = struct {
41344168 .initializer = options.initializer,
41354169 });
41364170
4171 const target = self.getTarget();
4172 if (target.os.tag == .vulkan) {
4173 return var_id;
4174 }
4175
41374176 switch (options.storage_class) {
41384177 .Generic => {
41394178 const ptr_gn_ty_ref = try self.ptrType(ty, .Generic);
41404179 // Convert to a generic pointer
4141 const result_id = self.spv.allocId();
4142 try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{
4143 .id_result_type = self.typeId(ptr_gn_ty_ref),
4144 .id_result = result_id,
4145 .pointer = var_id,
4146 });
4147 return result_id;
4180 return self.castToGeneric(self.typeId(ptr_gn_ty_ref), var_id);
41484181 },
41494182 .Function => return var_id,
41504183 else => unreachable,
......@@ -4880,7 +4913,7 @@ const DeclGen = struct {
48804913 const is_non_null_id = blk: {
48814914 if (is_pointer) {
48824915 if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
4883 const storage_class = spvStorageClass(operand_ty.ptrAddressSpace(mod));
4916 const storage_class = self.spvStorageClass(operand_ty.ptrAddressSpace(mod));
48844917 const bool_ptr_ty = try self.ptrType(Type.bool, storage_class);
48854918 const tag_ptr_id = try self.accessChain(bool_ptr_ty, operand_id, &.{1});
48864919 break :blk try self.load(Type.bool, tag_ptr_id, .{});
src/codegen/spirv/Module.zig+40-13
......@@ -92,7 +92,7 @@ pub const Global = struct {
9292 /// The past-end offset into `self.flobals.section`.
9393 end_inst: u32,
9494 /// The result-id of the function that initializes this value.
95 initializer_id: IdRef,
95 initializer_id: ?IdRef,
9696};
9797
9898/// This models a kernel entry point.
......@@ -101,6 +101,8 @@ pub const EntryPoint = struct {
101101 decl_index: Decl.Index,
102102 /// The name of the kernel to be exported.
103103 name: CacheString,
104 /// Calling Convention
105 execution_model: spec.ExecutionModel,
104106};
105107
106108/// A general-purpose allocator which may be used to allocate resources for this module
......@@ -313,7 +315,7 @@ fn entryPoints(self: *Module) !Section {
313315
314316 const entry_point_id = self.declPtr(entry_point.decl_index).result_id;
315317 try entry_points.emit(self.gpa, .OpEntryPoint, .{
316 .execution_model = .Kernel,
318 .execution_model = entry_point.execution_model,
317319 .entry_point = entry_point_id,
318320 .name = self.cache.getString(entry_point.name).?,
319321 .interface = interface.items,
......@@ -362,11 +364,13 @@ fn initializer(self: *Module, entry_points: *Section) !Section {
362364
363365 for (self.globals.globals.keys(), self.globals.globals.values()) |decl_index, global| {
364366 try self.addEntryPointDeps(decl_index, &seen, &interface);
365 try section.emit(self.gpa, .OpFunctionCall, .{
366 .id_result_type = void_ty_id,
367 .id_result = self.allocId(),
368 .function = global.initializer_id,
369 });
367 if (global.initializer_id) |initializer_id| {
368 try section.emit(self.gpa, .OpFunctionCall, .{
369 .id_result_type = void_ty_id,
370 .id_result = self.allocId(),
371 .function = initializer_id,
372 });
373 }
370374 }
371375
372376 try section.emit(self.gpa, .OpReturn, {});
......@@ -390,7 +394,7 @@ fn initializer(self: *Module, entry_points: *Section) !Section {
390394}
391395
392396/// Emit this module as a spir-v binary.
393pub fn flush(self: *Module, file: std.fs.File) !void {
397pub fn flush(self: *Module, file: std.fs.File, target: std.Target) !void {
394398 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
395399
396400 // TODO: Perform topological sort on the globals.
......@@ -403,14 +407,25 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
403407 var types_constants = try self.cache.materialize(self);
404408 defer types_constants.deinit(self.gpa);
405409
406 var init_func = try self.initializer(&entry_points);
410 // TODO: Vulkan doesn't support initializer kernel
411 var init_func = if (target.os.tag != .vulkan)
412 try self.initializer(&entry_points)
413 else
414 Section{};
407415 defer init_func.deinit(self.gpa);
408416
409417 const header = [_]Word{
410418 spec.magic_number,
411419 // TODO: From cpu features
412 // Emit SPIR-V 1.4 for now. This is the highest version that Intel's CPU OpenCL supports.
413 (1 << 16) | (4 << 8),
420 spec.Version.toWord(.{
421 .major = 1,
422 .minor = switch (target.os.tag) {
423 // Emit SPIR-V 1.3 for now. This is the highest version that Vulkan 1.1 supports.
424 .vulkan => 3,
425 // Emit SPIR-V 1.4 for now. This is the highest version that Intel's CPU OpenCL supports.
426 else => 4,
427 },
428 }),
414429 0, // TODO: Register Zig compiler magic number.
415430 self.idBound(),
416431 0, // Schema (currently reserved for future use)
......@@ -617,7 +632,13 @@ pub fn beginGlobal(self: *Module) u32 {
617632 return @as(u32, @intCast(self.globals.section.instructions.items.len));
618633}
619634
620pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32, result_id: IdRef, initializer_id: IdRef) void {
635pub fn endGlobal(
636 self: *Module,
637 global_index: Decl.Index,
638 begin_inst: u32,
639 result_id: IdRef,
640 initializer_id: ?IdRef,
641) void {
621642 const global = self.globalPtr(global_index).?;
622643 global.* = .{
623644 .result_id = result_id,
......@@ -627,10 +648,16 @@ pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32, resul
627648 };
628649}
629650
630pub fn declareEntryPoint(self: *Module, decl_index: Decl.Index, name: []const u8) !void {
651pub fn declareEntryPoint(
652 self: *Module,
653 decl_index: Decl.Index,
654 name: []const u8,
655 execution_model: spec.ExecutionModel,
656) !void {
631657 try self.entry_points.append(self.gpa, .{
632658 .decl_index = decl_index,
633659 .name = try self.resolveString(name),
660 .execution_model = execution_model,
634661 });
635662}
636663
src/codegen/spirv/spec.zig+10-1
......@@ -1,6 +1,15 @@
11//! This file is auto-generated by tools/gen_spirv_spec.zig.
22
3const Version = @import("std").SemanticVersion;
3pub const Version = packed struct(Word) {
4 padding: u8 = 0,
5 minor: u8,
6 major: u8,
7 padding0: u8 = 0,
8
9 pub fn toWord(self: @This()) Word {
10 return @bitCast(self);
11 }
12};
413
514pub const Word = u32;
615pub const IdResult = struct {
src/link/SpirV.zig+22-7
......@@ -86,8 +86,6 @@ pub fn createEmpty(
8686 else => unreachable, // Caught by Compilation.Config.resolve.
8787 }
8888
89 assert(target.abi != .none); // Caught by Compilation.Config.resolve.
90
9189 return self;
9290}
9391
......@@ -158,10 +156,27 @@ pub fn updateExports(
158156 },
159157 };
160158 const decl = mod.declPtr(decl_index);
161 if (decl.val.isFuncBody(mod) and decl.ty.fnCallingConvention(mod) == .Kernel) {
159 if (decl.val.isFuncBody(mod)) {
160 const target = mod.getTarget();
162161 const spv_decl_index = try self.object.resolveDecl(mod, decl_index);
163 for (exports) |exp| {
164 try self.object.spv.declareEntryPoint(spv_decl_index, mod.intern_pool.stringToSlice(exp.opts.name));
162 const execution_model = switch (decl.ty.fnCallingConvention(mod)) {
163 .Vertex => spec.ExecutionModel.Vertex,
164 .Fragment => spec.ExecutionModel.Fragment,
165 .Kernel => spec.ExecutionModel.Kernel,
166 else => unreachable,
167 };
168 const is_vulkan = target.os.tag == .vulkan;
169
170 if ((!is_vulkan and execution_model == .Kernel) or
171 (is_vulkan and (execution_model == .Fragment or execution_model == .Vertex)))
172 {
173 for (exports) |exp| {
174 try self.object.spv.declareEntryPoint(
175 spv_decl_index,
176 mod.intern_pool.stringToSlice(exp.opts.name),
177 execution_model,
178 );
179 }
165180 }
166181 }
167182
......@@ -224,7 +239,7 @@ pub fn flushModule(self: *SpirV, arena: Allocator, prog_node: *std.Progress.Node
224239 .extension = error_info.items,
225240 });
226241
227 try spv.flush(self.base.file.?);
242 try spv.flush(self.base.file.?, target);
228243}
229244
230245fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
......@@ -233,7 +248,7 @@ fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
233248 const caps: []const spec.Capability = switch (target.os.tag) {
234249 .opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .Float64, .Float16, .GenericPointer },
235250 .glsl450 => &.{.Shader},
236 .vulkan => &.{.Shader},
251 .vulkan => &.{ .Shader, .VariablePointersStorageBuffer, .Int8, .Int16, .Int64, .Float64, .Float16 },
237252 else => unreachable, // TODO
238253 };
239254
tools/gen_spirv_spec.zig+10-1
......@@ -77,7 +77,16 @@ fn render(writer: anytype, allocator: Allocator, registry: g.CoreRegistry) !void
7777 try writer.writeAll(
7878 \\//! This file is auto-generated by tools/gen_spirv_spec.zig.
7979 \\
80 \\const Version = @import("std").SemanticVersion;
80 \\pub const Version = packed struct(Word) {
81 \\ padding: u8 = 0,
82 \\ minor: u8,
83 \\ major: u8,
84 \\ padding0: u8 = 0,
85 \\
86 \\ pub fn toWord(self: @This()) Word {
87 \\ return @bitCast(self);
88 \\ }
89 \\};
8190 \\
8291 \\pub const Word = u32;
8392 \\pub const IdResult = struct{