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 {...@@ -1221,6 +1221,7 @@ pub const Cpu = struct {
1221 .fs, .gs, .ss => arch == .x86_64 or arch == .x86,1221 .fs, .gs, .ss => arch == .x86_64 or arch == .x86,
1222 .global, .constant, .local, .shared => is_gpu,1222 .global, .constant, .local, .shared => is_gpu,
1223 .param => is_nvptx,1223 .param => is_nvptx,
1224 .input, .output, .uniform => is_spirv,
1224 // TODO this should also check how many flash banks the cpu has1225 // TODO this should also check how many flash banks the cpu has
1225 .flash, .flash1, .flash2, .flash3, .flash4, .flash5 => arch == .avr,1226 .flash, .flash1, .flash2, .flash3, .flash4, .flash5 => arch == .avr,
1226 };1227 };
...@@ -2353,7 +2354,7 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 {...@@ -2353,7 +2354,7 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 {
2353 .longdouble => return 128,2354 .longdouble => return 128,
2354 },2355 },
23552356
2356 .opencl => switch (c_type) {2357 .opencl, .vulkan => switch (c_type) {
2357 .char => return 8,2358 .char => return 8,
2358 .short, .ushort => return 16,2359 .short, .ushort => return 16,
2359 .int, .uint, .float => return 32,2360 .int, .uint, .float => return 32,
...@@ -2386,7 +2387,6 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 {...@@ -2386,7 +2387,6 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 {
2386 .hermit,2387 .hermit,
2387 .hurd,2388 .hurd,
2388 .glsl450,2389 .glsl450,
2389 .vulkan,
2390 .driverkit,2390 .driverkit,
2391 .shadermodel,2391 .shadermodel,
2392 .liteos,2392 .liteos,
lib/std/builtin.zig+6
...@@ -205,6 +205,9 @@ pub const CallingConvention = enum(u8) {...@@ -205,6 +205,9 @@ pub const CallingConvention = enum(u8) {
205 Win64,205 Win64,
206 /// AMD GPU, NVPTX, or SPIR-V kernel206 /// AMD GPU, NVPTX, or SPIR-V kernel
207 Kernel,207 Kernel,
208 // Vulkan-only
209 Fragment,
210 Vertex,
208};211};
209212
210/// This data structure is used by the Zig language code generation and213/// This data structure is used by the Zig language code generation and
...@@ -222,6 +225,9 @@ pub const AddressSpace = enum(u5) {...@@ -222,6 +225,9 @@ pub const AddressSpace = enum(u5) {
222 param,225 param,
223 shared,226 shared,
224 local,227 local,
228 input,
229 output,
230 uniform,
225231
226 // AVR address spaces.232 // AVR address spaces.
227 flash,233 flash,
src/Sema.zig+5
...@@ -9741,6 +9741,10 @@ fn finishFunc(...@@ -9741,6 +9741,10 @@ fn finishFunc(
9741 .nvptx, .nvptx64, .amdgcn, .spirv32, .spirv64 => null,9741 .nvptx, .nvptx64, .amdgcn, .spirv32, .spirv64 => null,
9742 else => "nvptx, amdgcn and SPIR-V",9742 else => "nvptx, amdgcn and SPIR-V",
9743 },9743 },
9744 .Fragment, .Vertex => switch (arch) {
9745 .spirv32, .spirv64 => null,
9746 else => "SPIR-V",
9747 },
9744 })) |allowed_platform| {9748 })) |allowed_platform| {
9745 return sema.fail(block, cc_src, "callconv '{s}' is only available on {s}, not {s}", .{9749 return sema.fail(block, cc_src, "callconv '{s}' is only available on {s}, not {s}", .{
9746 @tagName(cc_resolved),9750 @tagName(cc_resolved),
...@@ -37917,6 +37921,7 @@ pub fn analyzeAddressSpace(...@@ -37917,6 +37921,7 @@ pub fn analyzeAddressSpace(
37917 .gs, .fs, .ss => (arch == .x86 or arch == .x86_64) and ctx == .pointer,37921 .gs, .fs, .ss => (arch == .x86 or arch == .x86_64) and ctx == .pointer,
37918 // TODO: check that .shared and .local are left uninitialized37922 // TODO: check that .shared and .local are left uninitialized
37919 .param => is_nv,37923 .param => is_nv,
37924 .input, .output, .uniform => is_spirv,
37920 .global, .shared, .local => is_gpu,37925 .global, .shared, .local => is_gpu,
37921 .constant => is_gpu and (ctx == .constant),37926 .constant => is_gpu and (ctx == .constant),
37922 // TODO this should also check how many flash banks the cpu has37927 // 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...@@ -10848,6 +10848,7 @@ fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) Builder
10848 .amdgcn => .amdgpu_kernel,10848 .amdgcn => .amdgpu_kernel,
10849 else => unreachable,10849 else => unreachable,
10850 },10850 },
10851 .Vertex, .Fragment => unreachable,
10851 };10852 };
10852}10853}
1085310854
src/codegen/spirv.zig+124-91
...@@ -451,12 +451,12 @@ const DeclGen = struct {...@@ -451,12 +451,12 @@ const DeclGen = struct {
451 const spv_decl_index = blk: {451 const spv_decl_index = blk: {
452 const entry = try self.object.anon_decl_link.getOrPut(self.object.gpa, .{ val, storage_class });452 const entry = try self.object.anon_decl_link.getOrPut(self.object.gpa, .{ val, storage_class });
453 if (entry.found_existing) {453 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);
455 return self.spv.declPtr(entry.value_ptr.*).result_id;455 return self.spv.declPtr(entry.value_ptr.*).result_id;
456 }456 }
457457
458 const spv_decl_index = try self.spv.allocDecl(.global);458 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);
460 entry.value_ptr.* = spv_decl_index;460 entry.value_ptr.* = spv_decl_index;
461 break :blk spv_decl_index;461 break :blk spv_decl_index;
462 };462 };
...@@ -529,6 +529,37 @@ const DeclGen = struct {...@@ -529,6 +529,37 @@ const DeclGen = struct {
529 return var_id;529 return var_id;
530 }530 }
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
532 /// Start a new SPIR-V block, Emits the label of the new block, and stores which563 /// Start a new SPIR-V block, Emits the label of the new block, and stores which
533 /// block we are currently generating.564 /// block we are currently generating.
534 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to565 /// 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 {...@@ -1019,7 +1050,7 @@ const DeclGen = struct {
10191050
1020 // TODO: Can we consolidate this in ptrElemPtr?1051 // TODO: Can we consolidate this in ptrElemPtr?
1021 const elem_ty = parent_ptr_ty.elemType2(mod); // use elemType() so that we get T for *[N]T.1052 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
1024 if (elem_ptr_ty_ref == result_ty_ref) {1055 if (elem_ptr_ty_ref == result_ty_ref) {
1025 return elem_ptr_id;1056 return elem_ptr_id;
...@@ -1074,7 +1105,7 @@ const DeclGen = struct {...@@ -1074,7 +1105,7 @@ const DeclGen = struct {
1074 unreachable; // TODO1105 unreachable; // TODO
1075 }1106 }
10761107
1077 const final_storage_class = spvStorageClass(ty.ptrAddressSpace(mod));1108 const final_storage_class = self.spvStorageClass(ty.ptrAddressSpace(mod));
1078 const actual_storage_class = switch (final_storage_class) {1109 const actual_storage_class = switch (final_storage_class) {
1079 .Generic => .CrossWorkgroup,1110 .Generic => .CrossWorkgroup,
1080 else => |other| other,1111 else => |other| other,
...@@ -1084,15 +1115,7 @@ const DeclGen = struct {...@@ -1084,15 +1115,7 @@ const DeclGen = struct {
1084 const decl_ptr_ty_ref = try self.ptrType(decl_ty, final_storage_class);1115 const decl_ptr_ty_ref = try self.ptrType(decl_ty, final_storage_class);
10851116
1086 const ptr_id = switch (final_storage_class) {1117 const ptr_id = switch (final_storage_class) {
1087 .Generic => blk: {1118 .Generic => try self.castToGeneric(self.typeId(decl_ptr_ty_ref), decl_id),
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 },
1096 else => decl_id,1119 else => decl_id,
1097 };1120 };
10981121
...@@ -1115,6 +1138,7 @@ const DeclGen = struct {...@@ -1115,6 +1138,7 @@ const DeclGen = struct {
1115 const ty_ref = try self.resolveType(ty, .direct);1138 const ty_ref = try self.resolveType(ty, .direct);
1116 const ty_id = self.typeId(ty_ref);1139 const ty_id = self.typeId(ty_ref);
1117 const decl = mod.declPtr(decl_index);1140 const decl = mod.declPtr(decl_index);
1141
1118 switch (mod.intern_pool.indexToKey(decl.val.ip_index)) {1142 switch (mod.intern_pool.indexToKey(decl.val.ip_index)) {
1119 .func => {1143 .func => {
1120 // TODO: Properly lower function pointers. For now we are going to hack around it and1144 // TODO: Properly lower function pointers. For now we are going to hack around it and
...@@ -1133,23 +1157,13 @@ const DeclGen = struct {...@@ -1133,23 +1157,13 @@ const DeclGen = struct {
1133 const spv_decl_index = try self.object.resolveDecl(mod, decl_index);1157 const spv_decl_index = try self.object.resolveDecl(mod, decl_index);
11341158
1135 const decl_id = self.spv.declPtr(spv_decl_index).result_id;1159 const decl_id = self.spv.declPtr(spv_decl_index).result_id;
1136 try self.func.decl_deps.put(self.spv.gpa, spv_decl_index, {});1160 const final_storage_class = self.spvStorageClass(decl.@"addrspace");
11371161 try self.addFunctionDep(spv_decl_index, final_storage_class);
1138 const final_storage_class = spvStorageClass(decl.@"addrspace");
11391162
1140 const decl_ptr_ty_ref = try self.ptrType(decl.ty, final_storage_class);1163 const decl_ptr_ty_ref = try self.ptrType(decl.ty, final_storage_class);
11411164
1142 const ptr_id = switch (final_storage_class) {1165 const ptr_id = switch (final_storage_class) {
1143 .Generic => blk: {1166 .Generic => try self.castToGeneric(self.typeId(decl_ptr_ty_ref), decl_id),
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 },
1153 else => decl_id,1167 else => decl_id,
1154 };1168 };
11551169
...@@ -1195,8 +1209,12 @@ const DeclGen = struct {...@@ -1195,8 +1209,12 @@ const DeclGen = struct {
1195 // An array of largestSupportedIntBits.1209 // An array of largestSupportedIntBits.
1196 return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });1210 return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });
1197 };1211 };
1212
1198 // Kernel only supports unsigned ints.1213 // Kernel only supports unsigned ints.
1199 // TODO: Only do this with Kernels1214 if (self.getTarget().os.tag == .vulkan) {
1215 return self.spv.intType(signedness, backing_bits);
1216 }
1217
1200 return self.spv.intType(.unsigned, backing_bits);1218 return self.spv.intType(.unsigned, backing_bits);
1201 }1219 }
12021220
...@@ -1453,7 +1471,7 @@ const DeclGen = struct {...@@ -1453,7 +1471,7 @@ const DeclGen = struct {
1453 // Note: Don't cache this pointer type, it would mess up the recursive pointer functionality1471 // Note: Don't cache this pointer type, it would mess up the recursive pointer functionality
1454 // in ptrType()!1472 // in ptrType()!
14551473
1456 const storage_class = spvStorageClass(ptr_info.flags.address_space);1474 const storage_class = self.spvStorageClass(ptr_info.flags.address_space);
1457 const ptr_ty_ref = try self.ptrType(Type.fromInterned(ptr_info.child), storage_class);1475 const ptr_ty_ref = try self.ptrType(Type.fromInterned(ptr_info.child), storage_class);
14581476
1459 if (ptr_info.flags.size != .Slice) {1477 if (ptr_info.flags.size != .Slice) {
...@@ -1634,13 +1652,20 @@ const DeclGen = struct {...@@ -1634,13 +1652,20 @@ const DeclGen = struct {
1634 }1652 }
1635 }1653 }
16361654
1637 fn spvStorageClass(as: std.builtin.AddressSpace) StorageClass {1655 fn spvStorageClass(self: *DeclGen, as: std.builtin.AddressSpace) StorageClass {
1656 const target = self.getTarget();
1638 return switch (as) {1657 return switch (as) {
1639 .generic => .Generic,1658 .generic => switch (target.os.tag) {
1659 .vulkan => .Private,
1660 else => .Generic,
1661 },
1640 .shared => .Workgroup,1662 .shared => .Workgroup,
1641 .local => .Private,1663 .local => .Private,
1642 .global => .CrossWorkgroup,1664 .global => .CrossWorkgroup,
1643 .constant => .UniformConstant,1665 .constant => .UniformConstant,
1666 .input => .Input,
1667 .output => .Output,
1668 .uniform => .Uniform,
1644 .gs,1669 .gs,
1645 .fs,1670 .fs,
1646 .ss,1671 .ss,
...@@ -1920,7 +1945,7 @@ const DeclGen = struct {...@@ -1920,7 +1945,7 @@ const DeclGen = struct {
1920 // point name is the same as a different OpName.1945 // point name is the same as a different OpName.
1921 const test_name = try std.fmt.allocPrint(self.gpa, "test {s}", .{name});1946 const test_name = try std.fmt.allocPrint(self.gpa, "test {s}", .{name});
1922 defer self.gpa.free(test_name);1947 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);
1924 }1949 }
19251950
1926 fn genDecl(self: *DeclGen) !void {1951 fn genDecl(self: *DeclGen) !void {
...@@ -1928,6 +1953,7 @@ const DeclGen = struct {...@@ -1928,6 +1953,7 @@ const DeclGen = struct {
1928 const ip = &mod.intern_pool;1953 const ip = &mod.intern_pool;
1929 const decl = mod.declPtr(self.decl_index);1954 const decl = mod.declPtr(self.decl_index);
1930 const spv_decl_index = try self.object.resolveDecl(mod, self.decl_index);1955 const spv_decl_index = try self.object.resolveDecl(mod, self.decl_index);
1956 const target = self.getTarget();
19311957
1932 const decl_id = self.spv.declPtr(spv_decl_index).result_id;1958 const decl_id = self.spv.declPtr(spv_decl_index).result_id;
19331959
...@@ -1994,30 +2020,24 @@ const DeclGen = struct {...@@ -1994,30 +2020,24 @@ const DeclGen = struct {
1994 try self.generateTestEntryPoint(fqn, spv_decl_index);2020 try self.generateTestEntryPoint(fqn, spv_decl_index);
1995 }2021 }
1996 } else {2022 } else {
1997 const init_val = if (decl.val.getVariable(mod)) |payload|2023 const opt_init_val: ?Value = blk: {
1998 Value.fromInterned(payload.init)2024 if (decl.val.getVariable(mod)) |payload| {
1999 else2025 if (payload.is_extern) break :blk null;
2000 decl.val;2026 break :blk Value.fromInterned(payload.init);
20012027 }
2002 if (init_val.ip_index == .unreachable_value) {2028 break :blk decl.val;
2003 return self.todo("importing extern variables", .{});2029 };
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 } });
20152030
2016 // Generate the actual variable for the global...2031 // Generate the actual variable for the global...
2017 const final_storage_class = spvStorageClass(decl.@"addrspace");2032 const final_storage_class = self.spvStorageClass(decl.@"addrspace");
2018 const actual_storage_class = switch (final_storage_class) {2033 const actual_storage_class = blk: {
2019 .Generic => .CrossWorkgroup,2034 if (target.os.tag != .vulkan) {
2020 else => final_storage_class,2035 break :blk switch (final_storage_class) {
2036 .Generic => .CrossWorkgroup,
2037 else => final_storage_class,
2038 };
2039 }
2040 break :blk final_storage_class;
2021 };2041 };
20222042
2023 const ptr_ty_ref = try self.ptrType(decl.ty, actual_storage_class);2043 const ptr_ty_ref = try self.ptrType(decl.ty, actual_storage_class);
...@@ -2028,37 +2048,51 @@ const DeclGen = struct {...@@ -2028,37 +2048,51 @@ const DeclGen = struct {
2028 .id_result = decl_id,2048 .id_result = decl_id,
2029 .storage_class = actual_storage_class,2049 .storage_class = actual_storage_class,
2030 });2050 });
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.2054 if (opt_init_val) |init_val| {
2033 const initializer_id = self.spv.allocId();2055 // Currently, initializers for CrossWorkgroup variables is not implemented
2034 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{2056 // in Mesa. Therefore we generate an initialization kernel instead.
2035 .id_result_type = self.typeId(void_ty_ref),2057 const void_ty_ref = try self.resolveType(Type.void, .direct);
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;
20452058
2046 const val_id = try self.constant(decl.ty, init_val, .indirect);2059 const initializer_proto_ty_ref = try self.spv.resolve(.{ .function_type = .{
2047 try self.func.body.emit(self.spv.gpa, .OpStore, .{2060 .return_type = void_ty_ref,
2048 .pointer = decl_id,2061 .parameters = &.{},
2049 .object = val_id,2062 } });
2050 });
20512063
2052 // TODO: We should be able to get rid of this by now...2064 // Now emit the instructions that initialize the variable.
2053 self.spv.endGlobal(spv_decl_index, begin, decl_id, initializer_id);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, {});2078 const val_id = try self.constant(decl.ty, init_val, .indirect);
2056 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});2079 try self.func.body.emit(self.spv.gpa, .OpStore, .{
2057 try self.spv.addFunction(spv_decl_index, self.func);2080 .pointer = decl_id,
2081 .object = val_id,
2082 });
20582083
2059 const fqn = ip.stringToSlice(try decl.getFullyQualifiedName(self.module));2084 // TODO: We should be able to get rid of this by now...
2060 try self.spv.debugName(decl_id, fqn);2085 self.spv.endGlobal(spv_decl_index, begin, decl_id, initializer_id);
2061 try self.spv.debugNameFmt(initializer_id, "initializer of {s}", .{fqn});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 }
2062 }2096 }
2063 }2097 }
20642098
...@@ -3761,7 +3795,7 @@ const DeclGen = struct {...@@ -3761,7 +3795,7 @@ const DeclGen = struct {
3761 const mod = self.module;3795 const mod = self.module;
3762 // Construct new pointer type for the resulting pointer3796 // Construct new pointer type for the resulting pointer
3763 const elem_ty = ptr_ty.elemType2(mod); // use elemType() so that we get T for *[N]T.3797 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)));
3765 if (ptr_ty.isSinglePointer(mod)) {3799 if (ptr_ty.isSinglePointer(mod)) {
3766 // Pointer-to-array. In this case, the resulting pointer is not of the same type3800 // Pointer-to-array. In this case, the resulting pointer is not of the same type
3767 // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain.3801 // 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 {...@@ -3835,7 +3869,7 @@ const DeclGen = struct {
3835 const vector_ty = vector_ptr_ty.childType(mod);3869 const vector_ty = vector_ptr_ty.childType(mod);
3836 const scalar_ty = vector_ty.scalarType(mod);3870 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));
3839 const scalar_ptr_ty_ref = try self.ptrType(scalar_ty, storage_class);3873 const scalar_ptr_ty_ref = try self.ptrType(scalar_ty, storage_class);
38403874
3841 const vector_ptr = try self.resolve(data.vector_ptr);3875 const vector_ptr = try self.resolve(data.vector_ptr);
...@@ -3858,7 +3892,7 @@ const DeclGen = struct {...@@ -3858,7 +3892,7 @@ const DeclGen = struct {
3858 if (layout.tag_size == 0) return;3892 if (layout.tag_size == 0) return;
38593893
3860 const tag_ty = un_ty.unionTagTypeSafety(mod).?;3894 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
3863 const union_ptr_id = try self.resolve(bin_op.lhs);3897 const union_ptr_id = try self.resolve(bin_op.lhs);
3864 const new_tag_id = try self.resolve(bin_op.rhs);3898 const new_tag_id = try self.resolve(bin_op.rhs);
...@@ -4079,7 +4113,7 @@ const DeclGen = struct {...@@ -4079,7 +4113,7 @@ const DeclGen = struct {
4079 return try self.spv.constUndef(result_ty_ref);4113 return try self.spv.constUndef(result_ty_ref);
4080 }4114 }
40814115
4082 const storage_class = spvStorageClass(object_ptr_ty.ptrAddressSpace(mod));4116 const storage_class = self.spvStorageClass(object_ptr_ty.ptrAddressSpace(mod));
4083 const pl_ptr_ty_ref = try self.ptrType(layout.payload_ty, storage_class);4117 const pl_ptr_ty_ref = try self.ptrType(layout.payload_ty, storage_class);
4084 const pl_ptr_id = try self.accessChain(pl_ptr_ty_ref, object_ptr, &.{layout.payload_index});4118 const pl_ptr_id = try self.accessChain(pl_ptr_ty_ref, object_ptr, &.{layout.payload_index});
40854119
...@@ -4134,17 +4168,16 @@ const DeclGen = struct {...@@ -4134,17 +4168,16 @@ const DeclGen = struct {
4134 .initializer = options.initializer,4168 .initializer = options.initializer,
4135 });4169 });
41364170
4171 const target = self.getTarget();
4172 if (target.os.tag == .vulkan) {
4173 return var_id;
4174 }
4175
4137 switch (options.storage_class) {4176 switch (options.storage_class) {
4138 .Generic => {4177 .Generic => {
4139 const ptr_gn_ty_ref = try self.ptrType(ty, .Generic);4178 const ptr_gn_ty_ref = try self.ptrType(ty, .Generic);
4140 // Convert to a generic pointer4179 // Convert to a generic pointer
4141 const result_id = self.spv.allocId();4180 return self.castToGeneric(self.typeId(ptr_gn_ty_ref), var_id);
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;
4148 },4181 },
4149 .Function => return var_id,4182 .Function => return var_id,
4150 else => unreachable,4183 else => unreachable,
...@@ -4880,7 +4913,7 @@ const DeclGen = struct {...@@ -4880,7 +4913,7 @@ const DeclGen = struct {
4880 const is_non_null_id = blk: {4913 const is_non_null_id = blk: {
4881 if (is_pointer) {4914 if (is_pointer) {
4882 if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {4915 if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
4883 const storage_class = spvStorageClass(operand_ty.ptrAddressSpace(mod));4916 const storage_class = self.spvStorageClass(operand_ty.ptrAddressSpace(mod));
4884 const bool_ptr_ty = try self.ptrType(Type.bool, storage_class);4917 const bool_ptr_ty = try self.ptrType(Type.bool, storage_class);
4885 const tag_ptr_id = try self.accessChain(bool_ptr_ty, operand_id, &.{1});4918 const tag_ptr_id = try self.accessChain(bool_ptr_ty, operand_id, &.{1});
4886 break :blk try self.load(Type.bool, tag_ptr_id, .{});4919 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 {...@@ -92,7 +92,7 @@ pub const Global = struct {
92 /// The past-end offset into `self.flobals.section`.92 /// The past-end offset into `self.flobals.section`.
93 end_inst: u32,93 end_inst: u32,
94 /// The result-id of the function that initializes this value.94 /// The result-id of the function that initializes this value.
95 initializer_id: IdRef,95 initializer_id: ?IdRef,
96};96};
9797
98/// This models a kernel entry point.98/// This models a kernel entry point.
...@@ -101,6 +101,8 @@ pub const EntryPoint = struct {...@@ -101,6 +101,8 @@ pub const EntryPoint = struct {
101 decl_index: Decl.Index,101 decl_index: Decl.Index,
102 /// The name of the kernel to be exported.102 /// The name of the kernel to be exported.
103 name: CacheString,103 name: CacheString,
104 /// Calling Convention
105 execution_model: spec.ExecutionModel,
104};106};
105107
106/// A general-purpose allocator which may be used to allocate resources for this module108/// A general-purpose allocator which may be used to allocate resources for this module
...@@ -313,7 +315,7 @@ fn entryPoints(self: *Module) !Section {...@@ -313,7 +315,7 @@ fn entryPoints(self: *Module) !Section {
313315
314 const entry_point_id = self.declPtr(entry_point.decl_index).result_id;316 const entry_point_id = self.declPtr(entry_point.decl_index).result_id;
315 try entry_points.emit(self.gpa, .OpEntryPoint, .{317 try entry_points.emit(self.gpa, .OpEntryPoint, .{
316 .execution_model = .Kernel,318 .execution_model = entry_point.execution_model,
317 .entry_point = entry_point_id,319 .entry_point = entry_point_id,
318 .name = self.cache.getString(entry_point.name).?,320 .name = self.cache.getString(entry_point.name).?,
319 .interface = interface.items,321 .interface = interface.items,
...@@ -362,11 +364,13 @@ fn initializer(self: *Module, entry_points: *Section) !Section {...@@ -362,11 +364,13 @@ fn initializer(self: *Module, entry_points: *Section) !Section {
362364
363 for (self.globals.globals.keys(), self.globals.globals.values()) |decl_index, global| {365 for (self.globals.globals.keys(), self.globals.globals.values()) |decl_index, global| {
364 try self.addEntryPointDeps(decl_index, &seen, &interface);366 try self.addEntryPointDeps(decl_index, &seen, &interface);
365 try section.emit(self.gpa, .OpFunctionCall, .{367 if (global.initializer_id) |initializer_id| {
366 .id_result_type = void_ty_id,368 try section.emit(self.gpa, .OpFunctionCall, .{
367 .id_result = self.allocId(),369 .id_result_type = void_ty_id,
368 .function = global.initializer_id,370 .id_result = self.allocId(),
369 });371 .function = initializer_id,
372 });
373 }
370 }374 }
371375
372 try section.emit(self.gpa, .OpReturn, {});376 try section.emit(self.gpa, .OpReturn, {});
...@@ -390,7 +394,7 @@ fn initializer(self: *Module, entry_points: *Section) !Section {...@@ -390,7 +394,7 @@ fn initializer(self: *Module, entry_points: *Section) !Section {
390}394}
391395
392/// Emit this module as a spir-v binary.396/// 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 {
394 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"398 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
395399
396 // TODO: Perform topological sort on the globals.400 // TODO: Perform topological sort on the globals.
...@@ -403,14 +407,25 @@ pub fn flush(self: *Module, file: std.fs.File) !void {...@@ -403,14 +407,25 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
403 var types_constants = try self.cache.materialize(self);407 var types_constants = try self.cache.materialize(self);
404 defer types_constants.deinit(self.gpa);408 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{};
407 defer init_func.deinit(self.gpa);415 defer init_func.deinit(self.gpa);
408416
409 const header = [_]Word{417 const header = [_]Word{
410 spec.magic_number,418 spec.magic_number,
411 // TODO: From cpu features419 // TODO: From cpu features
412 // Emit SPIR-V 1.4 for now. This is the highest version that Intel's CPU OpenCL supports.420 spec.Version.toWord(.{
413 (1 << 16) | (4 << 8),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 }),
414 0, // TODO: Register Zig compiler magic number.429 0, // TODO: Register Zig compiler magic number.
415 self.idBound(),430 self.idBound(),
416 0, // Schema (currently reserved for future use)431 0, // Schema (currently reserved for future use)
...@@ -617,7 +632,13 @@ pub fn beginGlobal(self: *Module) u32 {...@@ -617,7 +632,13 @@ pub fn beginGlobal(self: *Module) u32 {
617 return @as(u32, @intCast(self.globals.section.instructions.items.len));632 return @as(u32, @intCast(self.globals.section.instructions.items.len));
618}633}
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 {
621 const global = self.globalPtr(global_index).?;642 const global = self.globalPtr(global_index).?;
622 global.* = .{643 global.* = .{
623 .result_id = result_id,644 .result_id = result_id,
...@@ -627,10 +648,16 @@ pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32, resul...@@ -627,10 +648,16 @@ pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32, resul
627 };648 };
628}649}
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 {
631 try self.entry_points.append(self.gpa, .{657 try self.entry_points.append(self.gpa, .{
632 .decl_index = decl_index,658 .decl_index = decl_index,
633 .name = try self.resolveString(name),659 .name = try self.resolveString(name),
660 .execution_model = execution_model,
634 });661 });
635}662}
636663
src/codegen/spirv/spec.zig+10-1
...@@ -1,6 +1,15 @@...@@ -1,6 +1,15 @@
1//! This file is auto-generated by tools/gen_spirv_spec.zig.1//! 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
5pub const Word = u32;14pub const Word = u32;
6pub const IdResult = struct {15pub const IdResult = struct {
src/link/SpirV.zig+22-7
...@@ -86,8 +86,6 @@ pub fn createEmpty(...@@ -86,8 +86,6 @@ pub fn createEmpty(
86 else => unreachable, // Caught by Compilation.Config.resolve.86 else => unreachable, // Caught by Compilation.Config.resolve.
87 }87 }
8888
89 assert(target.abi != .none); // Caught by Compilation.Config.resolve.
90
91 return self;89 return self;
92}90}
9391
...@@ -158,10 +156,27 @@ pub fn updateExports(...@@ -158,10 +156,27 @@ pub fn updateExports(
158 },156 },
159 };157 };
160 const decl = mod.declPtr(decl_index);158 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();
162 const spv_decl_index = try self.object.resolveDecl(mod, decl_index);161 const spv_decl_index = try self.object.resolveDecl(mod, decl_index);
163 for (exports) |exp| {162 const execution_model = switch (decl.ty.fnCallingConvention(mod)) {
164 try self.object.spv.declareEntryPoint(spv_decl_index, mod.intern_pool.stringToSlice(exp.opts.name));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 }
165 }180 }
166 }181 }
167182
...@@ -224,7 +239,7 @@ pub fn flushModule(self: *SpirV, arena: Allocator, prog_node: *std.Progress.Node...@@ -224,7 +239,7 @@ pub fn flushModule(self: *SpirV, arena: Allocator, prog_node: *std.Progress.Node
224 .extension = error_info.items,239 .extension = error_info.items,
225 });240 });
226241
227 try spv.flush(self.base.file.?);242 try spv.flush(self.base.file.?, target);
228}243}
229244
230fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {245fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
...@@ -233,7 +248,7 @@ fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {...@@ -233,7 +248,7 @@ fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
233 const caps: []const spec.Capability = switch (target.os.tag) {248 const caps: []const spec.Capability = switch (target.os.tag) {
234 .opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .Float64, .Float16, .GenericPointer },249 .opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .Float64, .Float16, .GenericPointer },
235 .glsl450 => &.{.Shader},250 .glsl450 => &.{.Shader},
236 .vulkan => &.{.Shader},251 .vulkan => &.{ .Shader, .VariablePointersStorageBuffer, .Int8, .Int16, .Int64, .Float64, .Float16 },
237 else => unreachable, // TODO252 else => unreachable, // TODO
238 };253 };
239254
tools/gen_spirv_spec.zig+10-1
...@@ -77,7 +77,16 @@ fn render(writer: anytype, allocator: Allocator, registry: g.CoreRegistry) !void...@@ -77,7 +77,16 @@ fn render(writer: anytype, allocator: Allocator, registry: g.CoreRegistry) !void
77 try writer.writeAll(77 try writer.writeAll(
78 \\//! This file is auto-generated by tools/gen_spirv_spec.zig.78 \\//! This file is auto-generated by tools/gen_spirv_spec.zig.
79 \\79 \\
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 \\};
81 \\90 \\
82 \\pub const Word = u32;91 \\pub const Word = u32;
83 \\pub const IdResult = struct{92 \\pub const IdResult = struct{