authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-06-08 14:55:38+03:30
committergravatar for alichraghi@noreply.codeberg.orgAli Cheraghi <alichraghi@noreply.codeberg.org> 2026-06-14 09:11:59+02:00
loge2d11ff76bad62b094f50a5d36332c70a5c87ea1
tree005eaea6adf63132e42e92149e71ad26bbdd83af
parent3deb86bafdb2f622b9099c405e0e861f70c36a45

spirv: set execution mode via cc info

Execution modes (e.g. `LocalSize`, `OriginUpperLeft`) were previously set via `gpu.executionMode()`, which used inline assembly to emit `OpExecutionMode`. The SPIR-V assembler now rejects this instruction and retrieves execution mode information from function cc, deleting `gpu.executionMode()` entirely. Two new `spirv_task` and `spirv_mesh` calling conventions are also added and `PackedCallingConvention.unpack()` now takes a trailing data slice.

14 files changed, 300 insertions(+), 148 deletions(-)

lib/std/Target.zig+2
...@@ -1984,6 +1984,8 @@ pub const Cpu = struct {...@@ -1984,6 +1984,8 @@ pub const Cpu = struct {
1984 .spirv_kernel,1984 .spirv_kernel,
1985 .spirv_fragment,1985 .spirv_fragment,
1986 .spirv_vertex,1986 .spirv_vertex,
1987 .spirv_task,
1988 .spirv_mesh,
1987 => &.{ .spirv32, .spirv64 },1989 => &.{ .spirv32, .spirv64 },
19881990
1989 .ez80_cet,1991 .ez80_cet,
lib/std/gpu.zig-83
...@@ -19,86 +19,3 @@ pub extern const local_invocation_id: @Vector(3, u32) addrspace(.input);...@@ -19,86 +19,3 @@ pub extern const local_invocation_id: @Vector(3, u32) addrspace(.input);
19pub extern const global_invocation_id: @Vector(3, u32) addrspace(.input);19pub extern const global_invocation_id: @Vector(3, u32) addrspace(.input);
20pub extern const vertex_index: u32 addrspace(.input);20pub extern const vertex_index: u32 addrspace(.input);
21pub extern const instance_index: u32 addrspace(.input);21pub extern const instance_index: u32 addrspace(.input);
22
23pub const ExecutionMode = union(Tag) {
24 /// Sets origin of the framebuffer to the upper-left corner
25 origin_upper_left,
26 /// Sets origin of the framebuffer to the lower-left corner
27 origin_lower_left,
28 /// Indicates that the fragment shader writes to `frag_depth`,
29 /// replacing the fixed-function depth value.
30 depth_replacing,
31 /// Indicates that per-fragment tests may assume that
32 /// any `frag_depth` built in-decorated value written by the shader is
33 /// greater-than-or-equal to the fragment’s interpolated depth value
34 depth_greater,
35 /// Indicates that per-fragment tests may assume that
36 /// any `frag_depth` built in-decorated value written by the shader is
37 /// less-than-or-equal to the fragment’s interpolated depth value
38 depth_less,
39 /// Indicates that per-fragment tests may assume that
40 /// any `frag_depth` built in-decorated value written by the shader is
41 /// the same as the fragment’s interpolated depth value
42 depth_unchanged,
43 /// Indicates the workgroup size in the x, y, and z dimensions.
44 local_size: LocalSize,
45
46 pub const Tag = enum(u32) {
47 origin_upper_left = 7,
48 origin_lower_left = 8,
49 depth_replacing = 12,
50 depth_greater = 14,
51 depth_less = 15,
52 depth_unchanged = 16,
53 local_size = 17,
54 };
55
56 pub const LocalSize = struct { x: u32, y: u32, z: u32 };
57};
58
59/// Declare the mode entry point executes in.
60pub fn executionMode(comptime entry_point: anytype, comptime mode: ExecutionMode) void {
61 const cc = @typeInfo(@TypeOf(entry_point)).@"fn".attrs.@"callconv";
62 switch (mode) {
63 .origin_upper_left,
64 .origin_lower_left,
65 .depth_replacing,
66 .depth_greater,
67 .depth_less,
68 .depth_unchanged,
69 => {
70 if (cc != .spirv_fragment) {
71 @compileError(
72 \\invalid execution mode '
73 ++ @tagName(mode) ++
74 \\' for function with '
75 ++ @tagName(cc) ++
76 \\' calling convention
77 );
78 }
79 asm volatile (
80 \\OpExecutionMode %entry_point $mode
81 :
82 : [entry_point] "" (entry_point),
83 [mode] "c" (@intFromEnum(mode)),
84 );
85 },
86 .local_size => |size| {
87 if (cc != .spirv_kernel) {
88 @compileError(
89 \\invalid execution mode 'local_size' for function with '
90 ++ @tagName(cc) ++
91 \\' calling convention
92 );
93 }
94 asm volatile (
95 \\OpExecutionMode %entry_point LocalSize $x $y $z
96 :
97 : [entry_point] "" (entry_point),
98 [x] "c" (size.x),
99 [y] "c" (size.y),
100 [z] "c" (size.z),
101 );
102 },
103 }
104}
lib/std/lang.zig+36-4
...@@ -140,7 +140,7 @@ pub const CallingConvention = union(enum(u8)) {...@@ -140,7 +140,7 @@ pub const CallingConvention = union(enum(u8)) {
140 pub const kernel: CallingConvention = switch (builtin.target.cpu.arch) {140 pub const kernel: CallingConvention = switch (builtin.target.cpu.arch) {
141 .amdgcn => .amdgcn_kernel,141 .amdgcn => .amdgcn_kernel,
142 .nvptx, .nvptx64 => .nvptx_kernel,142 .nvptx, .nvptx64 => .nvptx_kernel,
143 .spirv32, .spirv64 => .spirv_kernel,143 .spirv32, .spirv64 => .{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } },
144 else => unreachable,144 else => unreachable,
145 };145 };
146146
...@@ -337,11 +337,13 @@ pub const CallingConvention = union(enum(u8)) {...@@ -337,11 +337,13 @@ pub const CallingConvention = union(enum(u8)) {
337 nvptx_device,337 nvptx_device,
338 nvptx_kernel,338 nvptx_kernel,
339339
340 // Calling conventions for kernels and shaders on the `spirv`, `spirv32`, and `spirv64` architectures.340 // Calling conventions for kernels and shaders on the `spirv32` and `spirv64` architectures.
341 spirv_device,341 spirv_device,
342 spirv_kernel,
343 spirv_fragment,
344 spirv_vertex,342 spirv_vertex,
343 spirv_kernel: SpirvKernelOptions,
344 spirv_fragment: SpirvFragmentOptions,
345 spirv_task: SpirvKernelOptions,
346 spirv_mesh: SpirvMeshOptions,
345347
346 // Calling conventions for the `ez80` architecture.348 // Calling conventions for the `ez80` architecture.
347 ez80_cet,349 ez80_cet,
...@@ -473,6 +475,36 @@ pub const CallingConvention = union(enum(u8)) {...@@ -473,6 +475,36 @@ pub const CallingConvention = union(enum(u8)) {
473 };475 };
474 };476 };
475477
478 pub const SpirvKernelOptions = struct {
479 x: u32,
480 y: u32,
481 z: u32,
482 };
483
484 pub const SpirvFragmentOptions = struct {
485 pub const DepthAssumption = enum(u2) {
486 none = 0,
487 greater = 1,
488 less = 2,
489 unchanged = 3,
490 };
491
492 pixel_centered_integer: bool = false,
493 depth_assumption: DepthAssumption = .none,
494 };
495
496 pub const SpirvMeshOptions = struct {
497 pub const StageOutput = enum(u2) {
498 output_points = 0,
499 output_lines = 1,
500 output_triangles = 2,
501 };
502
503 stage_output: StageOutput = .output_triangles,
504 max_primitives: u32 = 1,
505 max_vertices: u32 = 3,
506 };
507
476 /// Returns the array of `std.Target.Cpu.Arch` to which this `CallingConvention` applies.508 /// Returns the array of `std.Target.Cpu.Arch` to which this `CallingConvention` applies.
477 /// Asserts that `cc` is not `.auto`, `.@"async"`, `.naked`, or `.@"inline"`.509 /// Asserts that `cc` is not `.auto`, `.@"async"`, `.naked`, or `.@"inline"`.
478 pub fn archs(cc: CallingConvention) []const std.Target.Cpu.Arch {510 pub fn archs(cc: CallingConvention) []const std.Target.Cpu.Arch {
src/InternPool.zig+65-4
...@@ -4202,12 +4202,14 @@ pub const Index = enum(u32) {...@@ -4202,12 +4202,14 @@ pub const Index = enum(u32) {
4202 type_function: struct {4202 type_function: struct {
4203 const @"data.flags.has_comptime_bits" = opaque {};4203 const @"data.flags.has_comptime_bits" = opaque {};
4204 const @"data.flags.has_noalias_bits" = opaque {};4204 const @"data.flags.has_noalias_bits" = opaque {};
4205 const @"data.flags.cc.extraLen()" = opaque {};
4205 const @"data.params_len" = opaque {};4206 const @"data.params_len" = opaque {};
4206 data: *Tag.TypeFunction,4207 data: *Tag.TypeFunction,
4207 @"trailing.comptime_bits.len": *@"data.flags.has_comptime_bits",4208 @"trailing.comptime_bits.len": *@"data.flags.has_comptime_bits",
4208 @"trailing.noalias_bits.len": *@"data.flags.has_noalias_bits",4209 @"trailing.noalias_bits.len": *@"data.flags.has_noalias_bits",
4210 @"trailing.cc_bits.len": *@"data.flags.cc.extraLen()",
4209 @"trailing.param_types.len": *@"data.params_len",4211 @"trailing.param_types.len": *@"data.params_len",
4210 trailing: struct { comptime_bits: []u32, noalias_bits: []u32, param_types: []Index },4212 trailing: struct { comptime_bits: []u32, noalias_bits: []u32, cc_bits: []u32, param_types: []Index },
4211 },4213 },
4212 type_tuple: struct {4214 type_tuple: struct {
4213 const @"data.fields_len" = opaque {};4215 const @"data.fields_len" = opaque {};
...@@ -5165,6 +5167,7 @@ pub const Tag = enum(u8) {...@@ -5165,6 +5167,7 @@ pub const Tag = enum(u8) {
5165 .trailing = struct {5167 .trailing = struct {
5166 param_comptime_bits: ?[]u32,5168 param_comptime_bits: ?[]u32,
5167 param_noalias_bits: ?[]u32,5169 param_noalias_bits: ?[]u32,
5170 param_cc_bits: ?[]u32,
5168 param_type: []Index,5171 param_type: []Index,
5169 },5172 },
5170 .config = .{5173 .config = .{
...@@ -5172,6 +5175,8 @@ pub const Tag = enum(u8) {...@@ -5172,6 +5175,8 @@ pub const Tag = enum(u8) {
5172 .@"trailing.param_comptime_bits.?.len" = .@"(payload.params_len + 31) / 32",5175 .@"trailing.param_comptime_bits.?.len" = .@"(payload.params_len + 31) / 32",
5173 .@"trailing.param_noalias_bits.?" = .@"payload.flags.has_noalias_bits",5176 .@"trailing.param_noalias_bits.?" = .@"payload.flags.has_noalias_bits",
5174 .@"trailing.param_noalias_bits.?.len" = .@"(payload.params_len + 31) / 32",5177 .@"trailing.param_noalias_bits.?.len" = .@"(payload.params_len + 31) / 32",
5178 .@"trailing.param_cc_bits.?" = .@"payload.flags.cc.extraLen() != 0",
5179 .@"trailing.param_cc_bits.?.len" = .@"payload.flags.cc.extraLen()",
5175 .@"trailing.param_type.len" = .@"payload.params_len",5180 .@"trailing.param_type.len" = .@"payload.params_len",
5176 },5181 },
5177 },5182 },
...@@ -6983,6 +6988,9 @@ fn extraFuncType(tid: Zcu.PerThread.Id, extra: Local.Extra, extra_index: u32) Ke...@@ -6983,6 +6988,9 @@ fn extraFuncType(tid: Zcu.PerThread.Id, extra: Local.Extra, extra_index: u32) Ke
6983 trail_index += 1;6988 trail_index += 1;
6984 break :b x;6989 break :b x;
6985 };6990 };
6991 const cc_extra_len = type_function.data.flags.cc.extraLen();
6992 const cc = type_function.data.flags.cc.unpack(extra.view().items(.@"0")[trail_index..][0..cc_extra_len]);
6993 trail_index += cc_extra_len;
6986 return .{6994 return .{
6987 .param_types = .{6995 .param_types = .{
6988 .tid = tid,6996 .tid = tid,
...@@ -6992,7 +7000,7 @@ fn extraFuncType(tid: Zcu.PerThread.Id, extra: Local.Extra, extra_index: u32) Ke...@@ -6992,7 +7000,7 @@ fn extraFuncType(tid: Zcu.PerThread.Id, extra: Local.Extra, extra_index: u32) Ke
6992 .return_type = type_function.data.return_type,7000 .return_type = type_function.data.return_type,
6993 .comptime_bits = comptime_bits,7001 .comptime_bits = comptime_bits,
6994 .noalias_bits = noalias_bits,7002 .noalias_bits = noalias_bits,
6995 .cc = type_function.data.flags.cc.unpack(),7003 .cc = cc,
6996 .is_var_args = type_function.data.flags.is_var_args,7004 .is_var_args = type_function.data.flags.is_var_args,
6997 .is_noinline = type_function.data.flags.is_noinline,7005 .is_noinline = type_function.data.flags.is_noinline,
6998 };7006 };
...@@ -9091,18 +9099,21 @@ pub fn getFuncType(...@@ -9091,18 +9099,21 @@ pub fn getFuncType(
9091 // ask if it already exists, and if so, revert the lengths of the mutated9099 // ask if it already exists, and if so, revert the lengths of the mutated
9092 // arrays. This is similar to what `getOrPutTrailingString` does.9100 // arrays. This is similar to what `getOrPutTrailingString` does.
9093 const prev_extra_len = extra.mutate.len;9101 const prev_extra_len = extra.mutate.len;
9102 const packed_cc: PackedCallingConvention = .pack(key.cc orelse .auto);
9103 const cc_extra_len = packed_cc.extraLen();
9094 const params_len: u32 = @intCast(key.param_types.len);9104 const params_len: u32 = @intCast(key.param_types.len);
90959105
9096 try extra.ensureUnusedCapacity(@typeInfo(Tag.TypeFunction).@"struct".field_names.len +9106 try extra.ensureUnusedCapacity(@typeInfo(Tag.TypeFunction).@"struct".field_names.len +
9097 @intFromBool(key.comptime_bits != 0) +9107 @intFromBool(key.comptime_bits != 0) +
9098 @intFromBool(key.noalias_bits != 0) +9108 @intFromBool(key.noalias_bits != 0) +
9109 cc_extra_len +
9099 params_len);9110 params_len);
91009111
9101 const func_type_extra_index = addExtraAssumeCapacity(extra, Tag.TypeFunction{9112 const func_type_extra_index = addExtraAssumeCapacity(extra, Tag.TypeFunction{
9102 .params_len = params_len,9113 .params_len = params_len,
9103 .return_type = key.return_type,9114 .return_type = key.return_type,
9104 .flags = .{9115 .flags = .{
9105 .cc = .pack(key.cc orelse .auto),9116 .cc = packed_cc,
9106 .is_var_args = key.is_var_args,9117 .is_var_args = key.is_var_args,
9107 .has_comptime_bits = key.comptime_bits != 0,9118 .has_comptime_bits = key.comptime_bits != 0,
9108 .has_noalias_bits = key.noalias_bits != 0,9119 .has_noalias_bits = key.noalias_bits != 0,
...@@ -9112,6 +9123,18 @@ pub fn getFuncType(...@@ -9112,6 +9123,18 @@ pub fn getFuncType(
91129123
9113 if (key.comptime_bits != 0) extra.appendAssumeCapacity(.{key.comptime_bits});9124 if (key.comptime_bits != 0) extra.appendAssumeCapacity(.{key.comptime_bits});
9114 if (key.noalias_bits != 0) extra.appendAssumeCapacity(.{key.noalias_bits});9125 if (key.noalias_bits != 0) extra.appendAssumeCapacity(.{key.noalias_bits});
9126 if (key.cc) |cc| switch (cc) {
9127 .spirv_kernel, .spirv_task => |kernel| extra.appendSliceAssumeCapacity(.{&.{
9128 kernel.x,
9129 kernel.y,
9130 kernel.z,
9131 }}),
9132 .spirv_mesh => |mesh| extra.appendSliceAssumeCapacity(.{&.{
9133 mesh.max_primitives,
9134 mesh.max_vertices,
9135 }}),
9136 else => {},
9137 };
9115 extra.appendSliceAssumeCapacity(.{@ptrCast(key.param_types)});9138 extra.appendSliceAssumeCapacity(.{@ptrCast(key.param_types)});
9116 errdefer extra.mutate.len = prev_extra_len;9139 errdefer extra.mutate.len = prev_extra_len;
91179140
...@@ -10704,6 +10727,7 @@ fn dumpStatsFallible(ip: *const InternPool, w: *Io.Writer, arena: Allocator) !vo...@@ -10704,6 +10727,7 @@ fn dumpStatsFallible(ip: *const InternPool, w: *Io.Writer, arena: Allocator) !vo
10704 const info = extraData(extra_list, Tag.TypeFunction, data);10727 const info = extraData(extra_list, Tag.TypeFunction, data);
10705 break :b @sizeOf(Tag.TypeFunction) +10728 break :b @sizeOf(Tag.TypeFunction) +
10706 (@sizeOf(Index) * info.params_len) +10729 (@sizeOf(Index) * info.params_len) +
10730 (@as(u32, 4) * info.flags.cc.extraLen()) +
10707 (@as(u32, 4) * @intFromBool(info.flags.has_comptime_bits)) +10731 (@as(u32, 4) * @intFromBool(info.flags.has_comptime_bits)) +
10708 (@as(u32, 4) * @intFromBool(info.flags.has_noalias_bits));10732 (@as(u32, 4) * @intFromBool(info.flags.has_noalias_bits));
10709 },10733 },
...@@ -12573,12 +12597,35 @@ const PackedCallingConvention = packed struct(u18) {...@@ -12573,12 +12597,35 @@ const PackedCallingConvention = packed struct(u18) {
12573 .incoming_stack_alignment = .fromByteUnits(pl.incoming_stack_alignment orelse 0),12597 .incoming_stack_alignment = .fromByteUnits(pl.incoming_stack_alignment orelse 0),
12574 .extra = @intFromEnum(pl.save),12598 .extra = @intFromEnum(pl.save),
12575 },12599 },
12600 std.lang.CallingConvention.SpirvKernelOptions => .{
12601 .tag = tag,
12602 .incoming_stack_alignment = .none,
12603 .extra = 0,
12604 },
12605 std.lang.CallingConvention.SpirvFragmentOptions => .{
12606 .tag = tag,
12607 .incoming_stack_alignment = .none,
12608 .extra = @as(u4, @intFromEnum(pl.depth_assumption)) << 1 | @intFromBool(pl.pixel_centered_integer),
12609 },
12610 std.lang.CallingConvention.SpirvMeshOptions => .{
12611 .tag = tag,
12612 .incoming_stack_alignment = .none,
12613 .extra = @intFromEnum(pl.stage_output),
12614 },
12576 else => comptime unreachable,12615 else => comptime unreachable,
12577 },12616 },
12578 };12617 };
12579 }12618 }
1258012619
12581 fn unpack(cc: PackedCallingConvention) std.lang.CallingConvention {12620 fn extraLen(cc: PackedCallingConvention) u2 {
12621 return switch (cc.tag) {
12622 .spirv_kernel, .spirv_task => 3,
12623 .spirv_mesh => 2,
12624 else => 0,
12625 };
12626 }
12627
12628 fn unpack(cc: PackedCallingConvention, trailing: []const u32) std.lang.CallingConvention {
12582 return switch (cc.tag) {12629 return switch (cc.tag) {
12583 inline else => |tag| @unionInit(12630 inline else => |tag| @unionInit(
12584 std.lang.CallingConvention,12631 std.lang.CallingConvention,
...@@ -12616,6 +12663,20 @@ const PackedCallingConvention = packed struct(u18) {...@@ -12616,6 +12663,20 @@ const PackedCallingConvention = packed struct(u18) {
12616 .incoming_stack_alignment = cc.incoming_stack_alignment.toByteUnits(),12663 .incoming_stack_alignment = cc.incoming_stack_alignment.toByteUnits(),
12617 .save = @enumFromInt(cc.extra),12664 .save = @enumFromInt(cc.extra),
12618 },12665 },
12666 std.lang.CallingConvention.SpirvKernelOptions => .{
12667 .x = trailing[0],
12668 .y = trailing[1],
12669 .z = trailing[2],
12670 },
12671 std.lang.CallingConvention.SpirvFragmentOptions => .{
12672 .pixel_centered_integer = @bitCast(@as(u1, @truncate(cc.extra))),
12673 .depth_assumption = @enumFromInt(@as(u2, @truncate(cc.extra >> 1))),
12674 },
12675 std.lang.CallingConvention.SpirvMeshOptions => .{
12676 .stage_output = @enumFromInt(cc.extra),
12677 .max_primitives = trailing[0],
12678 .max_vertices = trailing[1],
12679 },
12619 else => comptime unreachable,12680 else => comptime unreachable,
12620 },12681 },
12621 ),12682 ),
src/Sema.zig+23-1
...@@ -8599,6 +8599,7 @@ fn checkReturnTypeAndCallConv(...@@ -8599,6 +8599,7 @@ fn checkReturnTypeAndCallConv(
8599) CompileError!void {8599) CompileError!void {
8600 const pt = sema.pt;8600 const pt = sema.pt;
8601 const zcu = pt.zcu;8601 const zcu = pt.zcu;
8602 const target = zcu.getTarget();
8602 if (opt_varargs_src) |varargs_src| {8603 if (opt_varargs_src) |varargs_src| {
8603 try sema.checkCallConvSupportsVarArgs(block, varargs_src, @"callconv");8604 try sema.checkCallConvSupportsVarArgs(block, varargs_src, @"callconv");
8604 }8605 }
...@@ -8660,6 +8661,21 @@ fn checkReturnTypeAndCallConv(...@@ -8660,6 +8661,21 @@ fn checkReturnTypeAndCallConv(
8660 .@"inline" => if (is_noinline) {8661 .@"inline" => if (is_noinline) {
8661 return sema.fail(block, callconv_src, "'noinline' function cannot have calling convention 'inline'", .{});8662 return sema.fail(block, callconv_src, "'noinline' function cannot have calling convention 'inline'", .{});
8662 },8663 },
8664 .spirv_fragment => |fragment| {
8665 if (fragment.pixel_centered_integer and target.os.tag != .opengl) {
8666 return sema.fail(block, callconv_src, "'pixel_centered_integer' is not supported on this target", .{});
8667 }
8668 },
8669 .spirv_kernel, .spirv_task => |kernel| {
8670 if (kernel.x == 0 or kernel.y == 0 or kernel.z == 0) {
8671 return sema.fail(block, callconv_src, "kernel workgroup dimensions must be at least 1", .{});
8672 }
8673 },
8674 .spirv_mesh => |mesh| {
8675 if (mesh.max_vertices == 0 or mesh.max_primitives == 0) {
8676 return sema.fail(block, callconv_src, "mesh shader 'max_vertices' and 'max_primitives' must be at least 1", .{});
8677 }
8678 },
8663 else => {},8679 else => {},
8664 }8680 }
8665 switch (zcu.callconvSupported(@"callconv")) {8681 switch (zcu.callconvSupported(@"callconv")) {
...@@ -8770,6 +8786,8 @@ fn callConvIsCallable(cc: std.lang.CallingConvention.Tag) bool {...@@ -8770,6 +8786,8 @@ fn callConvIsCallable(cc: std.lang.CallingConvention.Tag) bool {
8770 .spirv_kernel,8786 .spirv_kernel,
8771 .spirv_fragment,8787 .spirv_fragment,
8772 .spirv_vertex,8788 .spirv_vertex,
8789 .spirv_task,
8790 .spirv_mesh,
8773 => false,8791 => false,
87748792
8775 else => true,8793 else => true,
...@@ -29127,7 +29145,7 @@ fn callconvCoerceAllowed(...@@ -29127,7 +29145,7 @@ fn callconvCoerceAllowed(
29127 switch (src_cc) {29145 switch (src_cc) {
29128 inline else => |src_data, tag| {29146 inline else => |src_data, tag| {
29129 const dest_data = @field(dest_cc, @tagName(tag));29147 const dest_data = @field(dest_cc, @tagName(tag));
29130 if (@TypeOf(src_data) != void) {29148 if (@TypeOf(src_data) != void and @hasField(@TypeOf(src_data), "incoming_stack_alignment")) {
29131 const default_stack_align = target.stackAlignment();29149 const default_stack_align = target.stackAlignment();
29132 const src_stack_align = src_data.incoming_stack_alignment orelse default_stack_align;29150 const src_stack_align = src_data.incoming_stack_alignment orelse default_stack_align;
29133 const dest_stack_align = dest_data.incoming_stack_alignment orelse default_stack_align;29151 const dest_stack_align = dest_data.incoming_stack_alignment orelse default_stack_align;
...@@ -29156,6 +29174,10 @@ fn callconvCoerceAllowed(...@@ -29156,6 +29174,10 @@ fn callconvCoerceAllowed(
29156 std.lang.CallingConvention.ShInterruptOptions => {29174 std.lang.CallingConvention.ShInterruptOptions => {
29157 if (src_data.save != dest_data.save) return false;29175 if (src_data.save != dest_data.save) return false;
29158 },29176 },
29177 std.lang.CallingConvention.SpirvKernelOptions,
29178 std.lang.CallingConvention.SpirvFragmentOptions,
29179 std.lang.CallingConvention.SpirvMeshOptions,
29180 => {},
29159 else => comptime unreachable,29181 else => comptime unreachable,
29160 }29182 }
29161 },29183 },
src/Zcu.zig+1
...@@ -4699,6 +4699,7 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.lang.CallingConvention) union(enum)...@@ -4699,6 +4699,7 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.lang.CallingConvention) union(enum)
4699 .stage2_spirv => switch (cc) {4699 .stage2_spirv => switch (cc) {
4700 .spirv_device, .spirv_kernel => true,4700 .spirv_device, .spirv_kernel => true,
4701 .spirv_fragment, .spirv_vertex => target.os.tag == .vulkan or target.os.tag == .opengl,4701 .spirv_fragment, .spirv_vertex => target.os.tag == .vulkan or target.os.tag == .opengl,
4702 .spirv_task, .spirv_mesh => target.os.tag == .vulkan,
4702 else => false,4703 else => false,
4703 },4704 },
4704 };4705 };
src/codegen/llvm.zig+6
...@@ -4445,6 +4445,10 @@ pub fn toLlvmCallConv(cc: std.lang.CallingConvention, target: *const std.Target)...@@ -4445,6 +4445,10 @@ pub fn toLlvmCallConv(cc: std.lang.CallingConvention, target: *const std.Target)
4445 std.lang.CallingConvention.CommonOptions,4445 std.lang.CallingConvention.CommonOptions,
4446 => .{ pl.incoming_stack_alignment, 0, 0 },4446 => .{ pl.incoming_stack_alignment, 0, 0 },
4447 std.lang.CallingConvention.X86RegparmOptions => .{ pl.incoming_stack_alignment, pl.register_params, 0 },4447 std.lang.CallingConvention.X86RegparmOptions => .{ pl.incoming_stack_alignment, pl.register_params, 0 },
4448 std.lang.CallingConvention.SpirvKernelOptions,
4449 std.lang.CallingConvention.SpirvFragmentOptions,
4450 std.lang.CallingConvention.SpirvMeshOptions,
4451 => .{ null, 0, 0 },
4448 else => @compileError("TODO: toLlvmCallConv" ++ @tagName(pl)),4452 else => @compileError("TODO: toLlvmCallConv" ++ @tagName(pl)),
4449 },4453 },
4450 };4454 };
...@@ -4588,6 +4592,8 @@ pub fn toLlvmCallConvTag(cc_tag: std.lang.CallingConvention.Tag, target: *const...@@ -4588,6 +4592,8 @@ pub fn toLlvmCallConvTag(cc_tag: std.lang.CallingConvention.Tag, target: *const
4588 .spirv_kernel,4592 .spirv_kernel,
4589 .spirv_fragment,4593 .spirv_fragment,
4590 .spirv_vertex,4594 .spirv_vertex,
4595 .spirv_task,
4596 .spirv_mesh,
4591 => null,4597 => null,
4592 };4598 };
4593}4599}
src/codegen/spirv/CodeGen.zig+3-16
...@@ -1442,6 +1442,8 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {...@@ -1442,6 +1442,8 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
1442 .spirv_fragment,1442 .spirv_fragment,
1443 .spirv_vertex,1443 .spirv_vertex,
1444 .spirv_device,1444 .spirv_device,
1445 .spirv_task,
1446 .spirv_mesh,
1445 => {},1447 => {},
1446 else => unreachable,1448 else => unreachable,
1447 }1449 }
...@@ -2542,15 +2544,6 @@ fn generateTestEntryPoint(...@@ -2542,15 +2544,6 @@ fn generateTestEntryPoint(
2542 cg.module.error_buffer = spv_err_decl_index;2544 cg.module.error_buffer = spv_err_decl_index;
2543 }2545 }
25442546
2545 try cg.module.sections.execution_modes.emit(gpa, .OpExecutionMode, .{
2546 .entry_point = kernel_id,
2547 .mode = .{ .local_size = .{
2548 .x_size = 1,
2549 .y_size = 1,
2550 .z_size = 1,
2551 } },
2552 });
2553
2554 const void_ty_id = try cg.resolveType(.void, .direct);2547 const void_ty_id = try cg.resolveType(.void, .direct);
2555 const kernel_proto_ty_id = try cg.module.functionType(void_ty_id, &.{});2548 const kernel_proto_ty_id = try cg.module.functionType(void_ty_id, &.{});
2556 try section.emit(gpa, .OpFunction, .{2549 try section.emit(gpa, .OpFunction, .{
...@@ -2599,13 +2592,7 @@ fn generateTestEntryPoint(...@@ -2599,13 +2592,7 @@ fn generateTestEntryPoint(
2599 // point name is the same as a different OpName.2592 // point name is the same as a different OpName.
2600 const test_name = try std.fmt.allocPrint(cg.module.arena, "test {s}", .{name});2593 const test_name = try std.fmt.allocPrint(cg.module.arena, "test {s}", .{name});
26012594
2602 const execution_mode: spec.ExecutionModel = switch (target.os.tag) {2595 try cg.module.declareEntryPoint(spv_decl_index, test_name, .{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } });
2603 .vulkan, .opengl => .gl_compute,
2604 .opencl, .amdhsa => .kernel,
2605 else => unreachable,
2606 };
2607
2608 try cg.module.declareEntryPoint(spv_decl_index, test_name, execution_mode, null);
2609}2596}
26102597
2611fn intFromBool(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary {2598fn intFromBool(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary {
src/codegen/spirv/Module.zig+76-20
...@@ -132,15 +132,10 @@ pub const Decl = struct {...@@ -132,15 +132,10 @@ pub const Decl = struct {
132 end_dep: usize = 0,132 end_dep: usize = 0,
133};133};
134134
135/// This models a kernel entry point.
136pub const EntryPoint = struct {135pub const EntryPoint = struct {
137 /// The declaration that should be exported.
138 decl_index: Decl.Index,136 decl_index: Decl.Index,
139 /// The name of the kernel to be exported.
140 name: []const u8,137 name: []const u8,
141 /// Calling Convention138 cc: std.builtin.CallingConvention,
142 exec_model: spec.ExecutionModel,
143 exec_mode: ?spec.ExecutionMode = null,
144};139};
145140
146const StructType = struct {141const StructType = struct {
...@@ -320,25 +315,89 @@ fn entryPoints(module: *Module) !Section {...@@ -320,25 +315,89 @@ fn entryPoints(module: *Module) !Section {
320 interface.items.len = 0;315 interface.items.len = 0;
321 seen.setRangeValue(.{ .start = 0, .end = module.decls.items.len }, false);316 seen.setRangeValue(.{ .start = 0, .end = module.decls.items.len }, false);
322317
318 const exec_model: spec.ExecutionModel = switch (target.os.tag) {
319 .vulkan, .opengl => switch (entry_point.cc) {
320 .spirv_vertex => .vertex,
321 .spirv_fragment => .fragment,
322 .spirv_kernel => .gl_compute,
323 .spirv_task => .task_ext,
324 .spirv_mesh => .mesh_ext,
325 // TODO: We should integrate with the Linkage capability and export this function
326 .spirv_device => continue,
327 else => unreachable,
328 },
329 .opencl => switch (entry_point.cc) {
330 .spirv_kernel => .kernel,
331 // TODO: We should integrate with the Linkage capability and export this function
332 .spirv_device => continue,
333 else => unreachable,
334 },
335 else => unreachable,
336 };
323 try module.addEntryPointDeps(entry_point.decl_index, &seen, &interface);337 try module.addEntryPointDeps(entry_point.decl_index, &seen, &interface);
324 try entry_points.emit(module.gpa, .OpEntryPoint, .{338 try entry_points.emit(module.gpa, .OpEntryPoint, .{
325 .execution_model = entry_point.exec_model,339 .execution_model = exec_model,
326 .entry_point = entry_point_id,340 .entry_point = entry_point_id,
327 .name = entry_point.name,341 .name = entry_point.name,
328 .interface = interface.items,342 .interface = interface.items,
329 });343 });
330344
331 if (entry_point.exec_mode == null and entry_point.exec_model == .fragment) {345 switch (entry_point.cc) {
332 switch (target.os.tag) {346 .spirv_kernel, .spirv_task => |kernel| {
333 .vulkan, .opengl => |tag| {347 try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
348 .entry_point = entry_point_id,
349 .mode = .{ .local_size = .{
350 .x_size = kernel.x,
351 .y_size = kernel.y,
352 .z_size = kernel.z,
353 } },
354 });
355 },
356 .spirv_fragment => |fragment| {
357 try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
358 .entry_point = entry_point_id,
359 .mode = if (target.os.tag == .vulkan) .origin_upper_left else .origin_lower_left,
360 });
361 if (fragment.pixel_centered_integer) {
334 try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{362 try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
335 .entry_point = entry_point_id,363 .entry_point = entry_point_id,
336 .mode = if (tag == .vulkan) .origin_upper_left else .origin_lower_left,364 .mode = .pixel_center_integer,
337 });365 });
338 },366 }
339 .opencl => {},367
340 else => unreachable,368 const exec_mode: ?spec.ExecutionMode.Extended = switch (fragment.depth_assumption) {
341 }369 .none => null,
370 .greater => .depth_greater,
371 .less => .depth_less,
372 .unchanged => .depth_unchanged,
373 };
374 if (exec_mode) |mode| {
375 try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
376 .entry_point = entry_point_id,
377 .mode = mode,
378 });
379 }
380 },
381 .spirv_mesh => |mesh| {
382 try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
383 .entry_point = entry_point_id,
384 .mode = .{ .output_vertices = .{ .vertex_count = mesh.max_vertices } },
385 });
386 try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
387 .entry_point = entry_point_id,
388 .mode = .{ .output_primitives_ext = .{ .primitive_count = mesh.max_primitives } },
389 });
390
391 try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
392 .entry_point = entry_point_id,
393 .mode = switch (mesh.stage_output) {
394 .output_points => .output_points,
395 .output_lines => .output_lines_ext,
396 .output_triangles => .output_triangles_ext,
397 },
398 });
399 },
400 else => {}, // TODO: should this be unreachable?
342 }401 }
343 }402 }
344403
...@@ -925,15 +984,12 @@ pub fn declareEntryPoint(...@@ -925,15 +984,12 @@ pub fn declareEntryPoint(
925 module: *Module,984 module: *Module,
926 decl_index: Decl.Index,985 decl_index: Decl.Index,
927 name: []const u8,986 name: []const u8,
928 exec_model: spec.ExecutionModel,987 cc: std.builtin.CallingConvention,
929 exec_mode: ?spec.ExecutionMode,
930) !void {988) !void {
931 const gop = try module.entry_points.getOrPut(module.gpa, module.declPtr(decl_index).result_id);989 const gop = try module.entry_points.getOrPut(module.gpa, module.declPtr(decl_index).result_id);
932 gop.value_ptr.decl_index = decl_index;990 gop.value_ptr.decl_index = decl_index;
933 gop.value_ptr.name = name;991 gop.value_ptr.name = name;
934 gop.value_ptr.exec_model = exec_model;992 gop.value_ptr.cc = cc;
935 // Might've been set by assembler
936 if (!gop.found_existing) gop.value_ptr.exec_mode = exec_mode;
937}993}
938994
939pub fn debugName(module: *Module, target: Id, name: []const u8) !void {995pub fn debugName(module: *Module, target: Id, name: []const u8) !void {
src/link/SpirV.zig+2-20
...@@ -179,35 +179,17 @@ pub fn updateExports(...@@ -179,35 +179,17 @@ pub fn updateExports(
179 },179 },
180 };180 };
181 const nav_ty = ip.getNav(nav_index).resolved.?.type;181 const nav_ty = ip.getNav(nav_index).resolved.?.type;
182 const target = zcu.getTarget();
183 if (ip.isFunctionType(nav_ty)) {182 if (ip.isFunctionType(nav_ty)) {
184 const spv_decl_index = try linker.module.resolveNav(ip, nav_index);183 const spv_decl_index = try linker.module.resolveNav(ip, nav_index);
185 const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);184 const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
186 const exec_model: spec.ExecutionModel = switch (target.os.tag) {185 if (cc == .spirv_device) return;
187 .vulkan, .opengl => switch (cc) {
188 .spirv_vertex => .vertex,
189 .spirv_fragment => .fragment,
190 .spirv_kernel => .gl_compute,
191 // TODO: We should integrate with the Linkage capability and export this function
192 .spirv_device => return,
193 else => unreachable,
194 },
195 .opencl => switch (cc) {
196 .spirv_kernel => .kernel,
197 // TODO: We should integrate with the Linkage capability and export this function
198 .spirv_device => return,
199 else => unreachable,
200 },
201 else => unreachable,
202 };
203186
204 for (export_indices) |export_idx| {187 for (export_indices) |export_idx| {
205 const exp = export_idx.ptr(zcu);188 const exp = export_idx.ptr(zcu);
206 try linker.module.declareEntryPoint(189 try linker.module.declareEntryPoint(
207 spv_decl_index,190 spv_decl_index,
208 exp.opts.name.toSlice(ip),191 exp.opts.name.toSlice(ip),
209 exec_model,192 cc,
210 null,
211 );193 );
212 }194 }
213 }195 }
test/cases/callconv_spirv.zig created+11
...@@ -0,0 +1,11 @@
1export fn vert() callconv(.spirv_vertex) void {}
2export fn frag() callconv(.{ .spirv_fragment = .{ .depth_assumption = .greater } }) void {}
3export fn comp() callconv(.{ .spirv_kernel = .{ .x = 8, .y = 8, .z = 1 } }) void {}
4export fn task() callconv(.{ .spirv_task = .{ .x = 1, .y = 1, .z = 1 } }) void {}
5export fn mesh() callconv(.{ .spirv_mesh = .{ .stage_output = .output_lines, .max_primitives = 1, .max_vertices = 2 } }) void {}
6
7// compile
8// output_mode=Obj
9// backend=selfhosted
10// target=spirv64-vulkan
11// emit_bin=false
test/cases/compile_errors/callconv_spirv_invalid_options.zig created+29
...@@ -0,0 +1,29 @@
1const F1 = fn () callconv(.{ .spirv_kernel = .{ .x = 0, .y = 1, .z = 1 } }) void;
2const F2 = fn () callconv(.{ .spirv_task = .{ .x = 1, .y = 0, .z = 1 } }) void;
3const F3 = fn () callconv(.{ .spirv_mesh = .{ .max_vertices = 0 } }) void;
4const F4 = fn () callconv(.{ .spirv_fragment = .{ .pixel_centered_integer = true } }) void;
5export fn entry1() void {
6 const a: F1 = undefined;
7 _ = a;
8}
9export fn entry2() void {
10 const a: F2 = undefined;
11 _ = a;
12}
13export fn entry3() void {
14 const a: F3 = undefined;
15 _ = a;
16}
17export fn entry4() void {
18 const a: F4 = undefined;
19 _ = a;
20}
21
22// error
23// backend=selfhosted
24// target=spirv64-vulkan
25//
26// :1:28: error: kernel workgroup dimensions must be at least 1
27// :2:28: error: kernel workgroup dimensions must be at least 1
28// :3:28: error: mesh shader 'max_vertices' and 'max_primitives' must be at least 1
29// :4:28: error: 'pixel_centered_integer' is not supported on this target
test/cases/compile_errors/callconv_spirv_mesh_task_require_vulkan.zig created+17
...@@ -0,0 +1,17 @@
1const F1 = fn () callconv(.{ .spirv_task = .{ .x = 1, .y = 1, .z = 1 } }) void;
2const F2 = fn () callconv(.{ .spirv_mesh = .{} }) void;
3export fn entry1() void {
4 const a: F1 = undefined;
5 _ = a;
6}
7export fn entry2() void {
8 const a: F2 = undefined;
9 _ = a;
10}
11
12// error
13// backend=selfhosted
14// target=spirv64-opengl
15//
16// :1:28: error: calling convention 'spirv_task' not supported by compiler backend 'stage2_spirv'
17// :2:28: error: calling convention 'spirv_mesh' not supported by compiler backend 'stage2_spirv'
test/cases/compile_errors/callconv_spirv_on_unsupported_platform.zig created+29
...@@ -0,0 +1,29 @@
1const F1 = fn () callconv(.{ .spirv_fragment = .{} }) void;
2const F2 = fn () callconv(.spirv_vertex) void;
3const F3 = fn () callconv(.{ .spirv_task = .{ .x = 1, .y = 1, .z = 1 } }) void;
4const F4 = fn () callconv(.{ .spirv_mesh = .{} }) void;
5export fn entry1() void {
6 const a: F1 = undefined;
7 _ = a;
8}
9export fn entry2() void {
10 const a: F2 = undefined;
11 _ = a;
12}
13export fn entry3() void {
14 const a: F3 = undefined;
15 _ = a;
16}
17export fn entry4() void {
18 const a: F4 = undefined;
19 _ = a;
20}
21
22// error
23// backend=selfhosted
24// target=spirv64-opencl
25//
26// :1:28: error: calling convention 'spirv_fragment' not supported by compiler backend 'stage2_spirv'
27// :2:28: error: calling convention 'spirv_vertex' not supported by compiler backend 'stage2_spirv'
28// :3:28: error: calling convention 'spirv_task' not supported by compiler backend 'stage2_spirv'
29// :4:28: error: calling convention 'spirv_mesh' not supported by compiler backend 'stage2_spirv'