authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-05 13:30:52-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-02-05 13:30:52-08:00
log39ec3d311673716e145957d6d81f9d4ec7848471
tree0d276a37fcf3491c7699be123b74fc0a7438d150
parent0266017b597e8fc74f41ec4eb78b1466751021c9
parentbe32ae0534e62ff9f4c285d5301d869924223e1c
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #18822 from alichraghi/shader

spirv: basic shader support

12 files changed, 454 insertions(+), 124 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,
lib/std/gpu.zig created+166
......@@ -0,0 +1,166 @@
1const std = @import("std.zig");
2const comptimePrint = std.fmt.comptimePrint;
3
4/// Will make `ptr` contain the location of the current invocation within the
5/// global workgroup. Each component is equal to the index of the local workgroup
6/// multiplied by the size of the local workgroup plus `localInvocationId`.
7/// `ptr` must be a reference to variable or struct field.
8pub fn globalInvocationId(comptime ptr: *addrspace(.input) @Vector(3, u32)) void {
9 asm volatile (
10 \\OpDecorate %ptr BuiltIn GlobalInvocationId
11 :
12 : [ptr] "" (ptr),
13 );
14}
15
16/// Will make that variable contain the location of the current cluster
17/// culling, task, mesh, or compute shader invocation within the local
18/// workgroup. Each component ranges from zero through to the size of the
19/// workgroup in that dimension minus one.
20/// `ptr` must be a reference to variable or struct field.
21pub fn localInvocationId(comptime ptr: *addrspace(.input) @Vector(3, u32)) void {
22 asm volatile (
23 \\OpDecorate %ptr BuiltIn LocalInvocationId
24 :
25 : [ptr] "" (ptr),
26 );
27}
28
29/// Output vertex position from a `Vertex` entrypoint
30/// `ptr` must be a reference to variable or struct field.
31pub fn position(comptime ptr: *addrspace(.output) @Vector(4, f32)) void {
32 asm volatile (
33 \\OpDecorate %ptr BuiltIn Position
34 :
35 : [ptr] "" (ptr),
36 );
37}
38
39/// Will make `ptr` contain the index of the vertex that is
40/// being processed by the current vertex shader invocation.
41/// `ptr` must be a reference to variable or struct field.
42pub fn vertexIndex(comptime ptr: *addrspace(.input) u32) void {
43 asm volatile (
44 \\OpDecorate %ptr BuiltIn VertexIndex
45 :
46 : [ptr] "" (ptr),
47 );
48}
49
50/// Output fragment depth from a `Fragment` entrypoint
51/// `ptr` must be a reference to variable or struct field.
52pub fn fragmentCoord(comptime ptr: *addrspace(.input) @Vector(4, f32)) void {
53 asm volatile (
54 \\OpDecorate %ptr BuiltIn FragCoord
55 :
56 : [ptr] "" (ptr),
57 );
58}
59
60/// Output fragment depth from a `Fragment` entrypoint
61/// `ptr` must be a reference to variable or struct field.
62pub fn fragmentDepth(comptime ptr: *addrspace(.output) f32) void {
63 asm volatile (
64 \\OpDecorate %ptr BuiltIn FragDepth
65 :
66 : [ptr] "" (ptr),
67 );
68}
69
70/// Forms the main linkage for `input` and `output` address spaces.
71/// `ptr` must be a reference to variable or struct field.
72pub fn location(comptime ptr: anytype, comptime loc: u32) void {
73 const code = comptimePrint("OpDecorate %ptr Location {}", .{loc});
74 asm volatile (code
75 :
76 : [ptr] "" (ptr),
77 );
78}
79
80/// Forms the main linkage for `input` and `output` address spaces.
81/// `ptr` must be a reference to variable or struct field.
82pub fn binding(comptime ptr: anytype, comptime group: u32, comptime bind: u32) void {
83 const code = comptimePrint(
84 \\OpDecorate %ptr DescriptorSet {}
85 \\OpDecorate %ptr Binding {}
86 , .{ group, bind });
87 asm volatile (code
88 :
89 : [ptr] "" (ptr),
90 );
91}
92
93pub const Origin = enum(u32) {
94 /// Increase toward the right and downward
95 upper_left = 7,
96 /// Increase toward the right and upward
97 lower_left = 8,
98};
99
100/// The coordinates appear to originate in the specified `origin`.
101/// Only valid with the `Fragment` calling convention.
102pub fn fragmentOrigin(comptime entry_point: anytype, comptime origin: Origin) void {
103 const origin_enum = switch (origin) {
104 .upper_left => .OriginUpperLeft,
105 .lower_left => .OriginLowerLeft,
106 };
107 asm volatile ("OpExecutionMode %entry_point " ++ @tagName(origin_enum)
108 :
109 : [entry_point] "" (entry_point),
110 );
111}
112
113pub const DepthMode = enum(u32) {
114 /// Declares that this entry point dynamically writes the
115 /// `fragmentDepth` built in-decorated variable.
116 replacing = 12,
117 /// Indicates that per-fragment tests may assume that
118 /// any `fragmentDepth` built in-decorated value written by the shader is
119 /// greater-than-or-equal to the fragment’s interpolated depth value
120 greater = 14,
121 /// Indicates that per-fragment tests may assume that
122 /// any `fragmentDepth` built in-decorated value written by the shader is
123 /// less-than-or-equal to the fragment’s interpolated depth value
124 less = 15,
125 /// Indicates that per-fragment tests may assume that
126 /// any `fragmentDepth` built in-decorated value written by the shader is
127 /// the same as the fragment’s interpolated depth value
128 unchanged = 16,
129};
130
131/// Only valid with the `Fragment` calling convention.
132pub fn depthMode(comptime entry_point: anytype, comptime mode: DepthMode) void {
133 const code = comptimePrint("OpExecutionMode %entry_point {}", .{@intFromEnum(mode)});
134 asm volatile (code
135 :
136 : [entry_point] "" (entry_point),
137 );
138}
139
140/// Indicates the workgroup size in the `x`, `y`, and `z` dimensions.
141/// Only valid with the `GLCompute` or `Kernel` calling conventions.
142pub fn workgroupSize(comptime entry_point: anytype, comptime size: @Vector(3, u32)) void {
143 const code = comptimePrint("OpExecutionMode %entry_point LocalSize {} {} {}", .{
144 size[0],
145 size[1],
146 size[2],
147 });
148 asm volatile (code
149 :
150 : [entry_point] "" (entry_point),
151 );
152}
153
154/// A hint to the client, which indicates the workgroup size in the `x`, `y`, and `z` dimensions.
155/// Only valid with the `GLCompute` or `Kernel` calling conventions.
156pub fn workgroupSizeHint(comptime entry_point: anytype, comptime size: @Vector(3, u32)) void {
157 const code = comptimePrint("OpExecutionMode %entry_point LocalSizeHint {} {} {}", .{
158 size[0],
159 size[1],
160 size[2],
161 });
162 asm volatile (code
163 :
164 : [entry_point] "" (entry_point),
165 );
166}
lib/std/std.zig+3
......@@ -104,6 +104,9 @@ pub const fmt = @import("fmt.zig");
104104/// File system-related functionality.
105105pub const fs = @import("fs.zig");
106106
107/// GPU programming helpers.
108pub const gpu = @import("gpu.zig");
109
107110/// Fast hashing functions (i.e. not cryptographically secure).
108111pub const hash = @import("hash.zig");
109112pub const hash_map = @import("hash_map.zig");
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+176-98
......@@ -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
......@@ -713,6 +744,30 @@ const DeclGen = struct {
713744 return try self.load(ty, ptr_composite_id, .{});
714745 }
715746
747 /// Construct a vector at runtime.
748 /// ty must be an vector type.
749 /// Constituents should be in `indirect` representation (as the elements of an vector should be).
750 /// Result is in `direct` representation.
751 fn constructVector(self: *DeclGen, ty: Type, constituents: []const IdRef) !IdRef {
752 // The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which'
753 // operands are not constant.
754 // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349
755 // For now, just initialize the struct by setting the fields manually...
756 // TODO: Make this OpCompositeConstruct when we can
757 const mod = self.module;
758 const ptr_composite_id = try self.alloc(ty, .{ .storage_class = .Function });
759 const ptr_elem_ty_ref = try self.ptrType(ty.elemType2(mod), .Function);
760 for (constituents, 0..) |constitent_id, index| {
761 const ptr_id = try self.accessChain(ptr_elem_ty_ref, ptr_composite_id, &.{@as(u32, @intCast(index))});
762 try self.func.body.emit(self.spv.gpa, .OpStore, .{
763 .pointer = ptr_id,
764 .object = constitent_id,
765 });
766 }
767
768 return try self.load(ty, ptr_composite_id, .{});
769 }
770
716771 /// Construct an array at runtime.
717772 /// ty must be an array type.
718773 /// Constituents should be in `indirect` representation (as the elements of an array should be).
......@@ -932,13 +987,16 @@ const DeclGen = struct {
932987 }
933988
934989 switch (tag) {
935 inline .array_type => if (array_type.sentinel != .none) {
936 constituents[constituents.len - 1] = try self.constant(elem_ty, Value.fromInterned(array_type.sentinel), .indirect);
990 inline .array_type => {
991 if (array_type.sentinel != .none) {
992 const sentinel = Value.fromInterned(array_type.sentinel);
993 constituents[constituents.len - 1] = try self.constant(elem_ty, sentinel, .indirect);
994 }
995 return self.constructArray(ty, constituents);
937996 },
938 else => {},
997 inline .vector_type => return self.constructVector(ty, constituents),
998 else => unreachable,
939999 }
940
941 return try self.constructArray(ty, constituents);
9421000 },
9431001 .struct_type => {
9441002 const struct_type = mod.typeToStruct(ty).?;
......@@ -1019,7 +1077,7 @@ const DeclGen = struct {
10191077
10201078 // TODO: Can we consolidate this in ptrElemPtr?
10211079 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)));
1080 const elem_ptr_ty_ref = try self.ptrType(elem_ty, self.spvStorageClass(parent_ptr_ty.ptrAddressSpace(mod)));
10231081
10241082 if (elem_ptr_ty_ref == result_ty_ref) {
10251083 return elem_ptr_id;
......@@ -1074,7 +1132,7 @@ const DeclGen = struct {
10741132 unreachable; // TODO
10751133 }
10761134
1077 const final_storage_class = spvStorageClass(ty.ptrAddressSpace(mod));
1135 const final_storage_class = self.spvStorageClass(ty.ptrAddressSpace(mod));
10781136 const actual_storage_class = switch (final_storage_class) {
10791137 .Generic => .CrossWorkgroup,
10801138 else => |other| other,
......@@ -1084,15 +1142,7 @@ const DeclGen = struct {
10841142 const decl_ptr_ty_ref = try self.ptrType(decl_ty, final_storage_class);
10851143
10861144 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 },
1145 .Generic => try self.castToGeneric(self.typeId(decl_ptr_ty_ref), decl_id),
10961146 else => decl_id,
10971147 };
10981148
......@@ -1115,6 +1165,7 @@ const DeclGen = struct {
11151165 const ty_ref = try self.resolveType(ty, .direct);
11161166 const ty_id = self.typeId(ty_ref);
11171167 const decl = mod.declPtr(decl_index);
1168
11181169 switch (mod.intern_pool.indexToKey(decl.val.ip_index)) {
11191170 .func => {
11201171 // TODO: Properly lower function pointers. For now we are going to hack around it and
......@@ -1133,23 +1184,13 @@ const DeclGen = struct {
11331184 const spv_decl_index = try self.object.resolveDecl(mod, decl_index);
11341185
11351186 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");
1187 const final_storage_class = self.spvStorageClass(decl.@"addrspace");
1188 try self.addFunctionDep(spv_decl_index, final_storage_class);
11391189
11401190 const decl_ptr_ty_ref = try self.ptrType(decl.ty, final_storage_class);
11411191
11421192 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 },
1193 .Generic => try self.castToGeneric(self.typeId(decl_ptr_ty_ref), decl_id),
11531194 else => decl_id,
11541195 };
11551196
......@@ -1195,8 +1236,12 @@ const DeclGen = struct {
11951236 // An array of largestSupportedIntBits.
11961237 return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });
11971238 };
1239
11981240 // Kernel only supports unsigned ints.
1199 // TODO: Only do this with Kernels
1241 if (self.getTarget().os.tag == .vulkan) {
1242 return self.spv.intType(signedness, backing_bits);
1243 }
1244
12001245 return self.spv.intType(.unsigned, backing_bits);
12011246 }
12021247
......@@ -1453,7 +1498,7 @@ const DeclGen = struct {
14531498 // Note: Don't cache this pointer type, it would mess up the recursive pointer functionality
14541499 // in ptrType()!
14551500
1456 const storage_class = spvStorageClass(ptr_info.flags.address_space);
1501 const storage_class = self.spvStorageClass(ptr_info.flags.address_space);
14571502 const ptr_ty_ref = try self.ptrType(Type.fromInterned(ptr_info.child), storage_class);
14581503
14591504 if (ptr_info.flags.size != .Slice) {
......@@ -1474,8 +1519,14 @@ const DeclGen = struct {
14741519
14751520 const elem_ty = ty.childType(mod);
14761521 const elem_ty_ref = try self.resolveType(elem_ty, .indirect);
1522 const len = ty.vectorLen(mod);
1523 const is_scalar = elem_ty.isNumeric(mod) or elem_ty.toIntern() == .bool_type;
1524
1525 const ty_ref = if (is_scalar and len > 1 and len <= 4)
1526 try self.spv.vectorType(ty.vectorLen(mod), elem_ty_ref)
1527 else
1528 try self.spv.arrayType(ty.vectorLen(mod), elem_ty_ref);
14771529
1478 const ty_ref = try self.spv.arrayType(ty.vectorLen(mod), elem_ty_ref);
14791530 try self.type_map.put(self.gpa, ty.toIntern(), .{ .ty_ref = ty_ref });
14801531 return ty_ref;
14811532 },
......@@ -1634,13 +1685,20 @@ const DeclGen = struct {
16341685 }
16351686 }
16361687
1637 fn spvStorageClass(as: std.builtin.AddressSpace) StorageClass {
1688 fn spvStorageClass(self: *DeclGen, as: std.builtin.AddressSpace) StorageClass {
1689 const target = self.getTarget();
16381690 return switch (as) {
1639 .generic => .Generic,
1691 .generic => switch (target.os.tag) {
1692 .vulkan => .Private,
1693 else => .Generic,
1694 },
16401695 .shared => .Workgroup,
16411696 .local => .Private,
16421697 .global => .CrossWorkgroup,
16431698 .constant => .UniformConstant,
1699 .input => .Input,
1700 .output => .Output,
1701 .uniform => .Uniform,
16441702 .gs,
16451703 .fs,
16461704 .ss,
......@@ -1920,7 +1978,7 @@ const DeclGen = struct {
19201978 // point name is the same as a different OpName.
19211979 const test_name = try std.fmt.allocPrint(self.gpa, "test {s}", .{name});
19221980 defer self.gpa.free(test_name);
1923 try self.spv.declareEntryPoint(spv_decl_index, test_name);
1981 try self.spv.declareEntryPoint(spv_decl_index, test_name, .Kernel);
19241982 }
19251983
19261984 fn genDecl(self: *DeclGen) !void {
......@@ -1928,6 +1986,7 @@ const DeclGen = struct {
19281986 const ip = &mod.intern_pool;
19291987 const decl = mod.declPtr(self.decl_index);
19301988 const spv_decl_index = try self.object.resolveDecl(mod, self.decl_index);
1989 const target = self.getTarget();
19311990
19321991 const decl_id = self.spv.declPtr(spv_decl_index).result_id;
19331992
......@@ -1994,30 +2053,24 @@ const DeclGen = struct {
19942053 try self.generateTestEntryPoint(fqn, spv_decl_index);
19952054 }
19962055 } 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 } });
2056 const opt_init_val: ?Value = blk: {
2057 if (decl.val.getVariable(mod)) |payload| {
2058 if (payload.is_extern) break :blk null;
2059 break :blk Value.fromInterned(payload.init);
2060 }
2061 break :blk decl.val;
2062 };
20152063
20162064 // 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,
2065 const final_storage_class = self.spvStorageClass(decl.@"addrspace");
2066 const actual_storage_class = blk: {
2067 if (target.os.tag != .vulkan) {
2068 break :blk switch (final_storage_class) {
2069 .Generic => .CrossWorkgroup,
2070 else => final_storage_class,
2071 };
2072 }
2073 break :blk final_storage_class;
20212074 };
20222075
20232076 const ptr_ty_ref = try self.ptrType(decl.ty, actual_storage_class);
......@@ -2028,37 +2081,51 @@ const DeclGen = struct {
20282081 .id_result = decl_id,
20292082 .storage_class = actual_storage_class,
20302083 });
2084 const fqn = ip.stringToSlice(try decl.getFullyQualifiedName(self.module));
2085 try self.spv.debugName(decl_id, fqn);
20312086
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;
2087 if (opt_init_val) |init_val| {
2088 // Currently, initializers for CrossWorkgroup variables is not implemented
2089 // in Mesa. Therefore we generate an initialization kernel instead.
2090 const void_ty_ref = try self.resolveType(Type.void, .direct);
20452091
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 });
2092 const initializer_proto_ty_ref = try self.spv.resolve(.{ .function_type = .{
2093 .return_type = void_ty_ref,
2094 .parameters = &.{},
2095 } });
2096
2097 // Now emit the instructions that initialize the variable.
2098 const initializer_id = self.spv.allocId();
2099 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{
2100 .id_result_type = self.typeId(void_ty_ref),
2101 .id_result = initializer_id,
2102 .function_control = .{},
2103 .function_type = self.typeId(initializer_proto_ty_ref),
2104 });
2105 const root_block_id = self.spv.allocId();
2106 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
2107 .id_result = root_block_id,
2108 });
2109 self.current_block_label = root_block_id;
20512110
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);
2111 const val_id = try self.constant(decl.ty, init_val, .indirect);
2112 try self.func.body.emit(self.spv.gpa, .OpStore, .{
2113 .pointer = decl_id,
2114 .object = val_id,
2115 });
20542116
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);
2117 // TODO: We should be able to get rid of this by now...
2118 self.spv.endGlobal(spv_decl_index, begin, decl_id, initializer_id);
20582119
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});
2120 try self.func.body.emit(self.spv.gpa, .OpReturn, {});
2121 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
2122 try self.spv.addFunction(spv_decl_index, self.func);
2123
2124 try self.spv.debugNameFmt(initializer_id, "initializer of {s}", .{fqn});
2125 } else {
2126 self.spv.endGlobal(spv_decl_index, begin, decl_id, null);
2127 try self.spv.declareDeclDeps(spv_decl_index, &.{});
2128 }
20622129 }
20632130 }
20642131
......@@ -3654,7 +3721,19 @@ const DeclGen = struct {
36543721 constituents[0..index],
36553722 );
36563723 },
3657 .Vector, .Array => {
3724 .Vector => {
3725 const n_elems = result_ty.vectorLen(mod);
3726 const elem_ids = try self.gpa.alloc(IdRef, n_elems);
3727 defer self.gpa.free(elem_ids);
3728
3729 for (elements, 0..) |element, i| {
3730 const id = try self.resolve(element);
3731 elem_ids[i] = try self.convertToIndirect(result_ty.childType(mod), id);
3732 }
3733
3734 return try self.constructVector(result_ty, elem_ids);
3735 },
3736 .Array => {
36583737 const array_info = result_ty.arrayInfo(mod);
36593738 const n_elems: usize = @intCast(result_ty.arrayLenIncludingSentinel(mod));
36603739 const elem_ids = try self.gpa.alloc(IdRef, n_elems);
......@@ -3761,7 +3840,7 @@ const DeclGen = struct {
37613840 const mod = self.module;
37623841 // Construct new pointer type for the resulting pointer
37633842 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)));
3843 const elem_ptr_ty_ref = try self.ptrType(elem_ty, self.spvStorageClass(ptr_ty.ptrAddressSpace(mod)));
37653844 if (ptr_ty.isSinglePointer(mod)) {
37663845 // Pointer-to-array. In this case, the resulting pointer is not of the same type
37673846 // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain.
......@@ -3835,7 +3914,7 @@ const DeclGen = struct {
38353914 const vector_ty = vector_ptr_ty.childType(mod);
38363915 const scalar_ty = vector_ty.scalarType(mod);
38373916
3838 const storage_class = spvStorageClass(vector_ptr_ty.ptrAddressSpace(mod));
3917 const storage_class = self.spvStorageClass(vector_ptr_ty.ptrAddressSpace(mod));
38393918 const scalar_ptr_ty_ref = try self.ptrType(scalar_ty, storage_class);
38403919
38413920 const vector_ptr = try self.resolve(data.vector_ptr);
......@@ -3858,7 +3937,7 @@ const DeclGen = struct {
38583937 if (layout.tag_size == 0) return;
38593938
38603939 const tag_ty = un_ty.unionTagTypeSafety(mod).?;
3861 const tag_ptr_ty_ref = try self.ptrType(tag_ty, spvStorageClass(un_ptr_ty.ptrAddressSpace(mod)));
3940 const tag_ptr_ty_ref = try self.ptrType(tag_ty, self.spvStorageClass(un_ptr_ty.ptrAddressSpace(mod)));
38623941
38633942 const union_ptr_id = try self.resolve(bin_op.lhs);
38643943 const new_tag_id = try self.resolve(bin_op.rhs);
......@@ -4079,7 +4158,7 @@ const DeclGen = struct {
40794158 return try self.spv.constUndef(result_ty_ref);
40804159 }
40814160
4082 const storage_class = spvStorageClass(object_ptr_ty.ptrAddressSpace(mod));
4161 const storage_class = self.spvStorageClass(object_ptr_ty.ptrAddressSpace(mod));
40834162 const pl_ptr_ty_ref = try self.ptrType(layout.payload_ty, storage_class);
40844163 const pl_ptr_id = try self.accessChain(pl_ptr_ty_ref, object_ptr, &.{layout.payload_index});
40854164
......@@ -4134,17 +4213,16 @@ const DeclGen = struct {
41344213 .initializer = options.initializer,
41354214 });
41364215
4216 const target = self.getTarget();
4217 if (target.os.tag == .vulkan) {
4218 return var_id;
4219 }
4220
41374221 switch (options.storage_class) {
41384222 .Generic => {
41394223 const ptr_gn_ty_ref = try self.ptrType(ty, .Generic);
41404224 // 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;
4225 return self.castToGeneric(self.typeId(ptr_gn_ty_ref), var_id);
41484226 },
41494227 .Function => return var_id,
41504228 else => unreachable,
......@@ -4880,7 +4958,7 @@ const DeclGen = struct {
48804958 const is_non_null_id = blk: {
48814959 if (is_pointer) {
48824960 if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
4883 const storage_class = spvStorageClass(operand_ty.ptrAddressSpace(mod));
4961 const storage_class = self.spvStorageClass(operand_ty.ptrAddressSpace(mod));
48844962 const bool_ptr_ty = try self.ptrType(Type.bool, storage_class);
48854963 const tag_ptr_id = try self.accessChain(bool_ptr_ty, operand_id, &.{1});
48864964 break :blk try self.load(Type.bool, tag_ptr_id, .{});
src/codegen/spirv/Assembler.zig+6-2
......@@ -591,9 +591,13 @@ fn parseValueEnum(self: *Assembler, kind: spec.OperandKind) !void {
591591 try self.expectToken(.value);
592592
593593 const text = self.tokenText(tok);
594 const int_value = std.fmt.parseInt(u32, text, 0) catch null;
594595 const enumerant = for (kind.enumerants()) |enumerant| {
595 if (std.mem.eql(u8, enumerant.name, text))
596 break enumerant;
596 if (int_value) |v| {
597 if (v == enumerant.value) break enumerant;
598 } else {
599 if (std.mem.eql(u8, enumerant.name, text)) break enumerant;
600 }
597601 } else {
598602 return self.fail(tok.start, "'{s}' is not a valid value for enumeration {s}", .{ text, @tagName(kind) });
599603 };
src/codegen/spirv/Module.zig+47-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)
......@@ -493,6 +508,13 @@ pub fn intType(self: *Module, signedness: std.builtin.Signedness, bits: u16) !Ca
493508 } });
494509}
495510
511pub fn vectorType(self: *Module, len: u32, elem_ty_ref: CacheRef) !CacheRef {
512 return try self.resolve(.{ .vector_type = .{
513 .component_type = elem_ty_ref,
514 .component_count = len,
515 } });
516}
517
496518pub fn arrayType(self: *Module, len: u32, elem_ty_ref: CacheRef) !CacheRef {
497519 const len_ty_ref = try self.resolve(.{ .int_type = .{
498520 .signedness = .unsigned,
......@@ -617,7 +639,13 @@ pub fn beginGlobal(self: *Module) u32 {
617639 return @as(u32, @intCast(self.globals.section.instructions.items.len));
618640}
619641
620pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32, result_id: IdRef, initializer_id: IdRef) void {
642pub fn endGlobal(
643 self: *Module,
644 global_index: Decl.Index,
645 begin_inst: u32,
646 result_id: IdRef,
647 initializer_id: ?IdRef,
648) void {
621649 const global = self.globalPtr(global_index).?;
622650 global.* = .{
623651 .result_id = result_id,
......@@ -627,10 +655,16 @@ pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32, resul
627655 };
628656}
629657
630pub fn declareEntryPoint(self: *Module, decl_index: Decl.Index, name: []const u8) !void {
658pub fn declareEntryPoint(
659 self: *Module,
660 decl_index: Decl.Index,
661 name: []const u8,
662 execution_model: spec.ExecutionModel,
663) !void {
631664 try self.entry_points.append(self.gpa, .{
632665 .decl_index = decl_index,
633666 .name = try self.resolveString(name),
667 .execution_model = execution_model,
634668 });
635669}
636670
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{