commit e2d11ff76bad62b094f50a5d36332c70a5c87ea1 (tree)
parent 3deb86bafdb2f622b9099c405e0e861f70c36a45
Author: Ali Cheraghi <alichraghi@proton.me>
Date: Mon, 8 Jun 2026 14:55:38 +0330
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.
Diffstat:
14 files changed, 300 insertions(+), 148 deletions(-)
diff --git a/lib/std/Target.zig b/lib/std/Target.zig
@@ -1984,6 +1984,8 @@ pub const Cpu = struct {
.spirv_kernel,
.spirv_fragment,
.spirv_vertex,
+ .spirv_task,
+ .spirv_mesh,
=> &.{ .spirv32, .spirv64 },
.ez80_cet,
diff --git a/lib/std/gpu.zig b/lib/std/gpu.zig
@@ -19,86 +19,3 @@ pub extern const local_invocation_id: @Vector(3, u32) addrspace(.input);
pub extern const global_invocation_id: @Vector(3, u32) addrspace(.input);
pub extern const vertex_index: u32 addrspace(.input);
pub extern const instance_index: u32 addrspace(.input);
-
-pub const ExecutionMode = union(Tag) {
- /// Sets origin of the framebuffer to the upper-left corner
- origin_upper_left,
- /// Sets origin of the framebuffer to the lower-left corner
- origin_lower_left,
- /// Indicates that the fragment shader writes to `frag_depth`,
- /// replacing the fixed-function depth value.
- depth_replacing,
- /// Indicates that per-fragment tests may assume that
- /// any `frag_depth` built in-decorated value written by the shader is
- /// greater-than-or-equal to the fragment’s interpolated depth value
- depth_greater,
- /// Indicates that per-fragment tests may assume that
- /// any `frag_depth` built in-decorated value written by the shader is
- /// less-than-or-equal to the fragment’s interpolated depth value
- depth_less,
- /// Indicates that per-fragment tests may assume that
- /// any `frag_depth` built in-decorated value written by the shader is
- /// the same as the fragment’s interpolated depth value
- depth_unchanged,
- /// Indicates the workgroup size in the x, y, and z dimensions.
- local_size: LocalSize,
-
- pub const Tag = enum(u32) {
- origin_upper_left = 7,
- origin_lower_left = 8,
- depth_replacing = 12,
- depth_greater = 14,
- depth_less = 15,
- depth_unchanged = 16,
- local_size = 17,
- };
-
- pub const LocalSize = struct { x: u32, y: u32, z: u32 };
-};
-
-/// Declare the mode entry point executes in.
-pub fn executionMode(comptime entry_point: anytype, comptime mode: ExecutionMode) void {
- const cc = @typeInfo(@TypeOf(entry_point)).@"fn".attrs.@"callconv";
- switch (mode) {
- .origin_upper_left,
- .origin_lower_left,
- .depth_replacing,
- .depth_greater,
- .depth_less,
- .depth_unchanged,
- => {
- if (cc != .spirv_fragment) {
- @compileError(
- \\invalid execution mode '
- ++ @tagName(mode) ++
- \\' for function with '
- ++ @tagName(cc) ++
- \\' calling convention
- );
- }
- asm volatile (
- \\OpExecutionMode %entry_point $mode
- :
- : [entry_point] "" (entry_point),
- [mode] "c" (@intFromEnum(mode)),
- );
- },
- .local_size => |size| {
- if (cc != .spirv_kernel) {
- @compileError(
- \\invalid execution mode 'local_size' for function with '
- ++ @tagName(cc) ++
- \\' calling convention
- );
- }
- asm volatile (
- \\OpExecutionMode %entry_point LocalSize $x $y $z
- :
- : [entry_point] "" (entry_point),
- [x] "c" (size.x),
- [y] "c" (size.y),
- [z] "c" (size.z),
- );
- },
- }
-}
diff --git a/lib/std/lang.zig b/lib/std/lang.zig
@@ -140,7 +140,7 @@ pub const CallingConvention = union(enum(u8)) {
pub const kernel: CallingConvention = switch (builtin.target.cpu.arch) {
.amdgcn => .amdgcn_kernel,
.nvptx, .nvptx64 => .nvptx_kernel,
- .spirv32, .spirv64 => .spirv_kernel,
+ .spirv32, .spirv64 => .{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } },
else => unreachable,
};
@@ -337,11 +337,13 @@ pub const CallingConvention = union(enum(u8)) {
nvptx_device,
nvptx_kernel,
- // Calling conventions for kernels and shaders on the `spirv`, `spirv32`, and `spirv64` architectures.
+ // Calling conventions for kernels and shaders on the `spirv32` and `spirv64` architectures.
spirv_device,
- spirv_kernel,
- spirv_fragment,
spirv_vertex,
+ spirv_kernel: SpirvKernelOptions,
+ spirv_fragment: SpirvFragmentOptions,
+ spirv_task: SpirvKernelOptions,
+ spirv_mesh: SpirvMeshOptions,
// Calling conventions for the `ez80` architecture.
ez80_cet,
@@ -473,6 +475,36 @@ pub const CallingConvention = union(enum(u8)) {
};
};
+ pub const SpirvKernelOptions = struct {
+ x: u32,
+ y: u32,
+ z: u32,
+ };
+
+ pub const SpirvFragmentOptions = struct {
+ pub const DepthAssumption = enum(u2) {
+ none = 0,
+ greater = 1,
+ less = 2,
+ unchanged = 3,
+ };
+
+ pixel_centered_integer: bool = false,
+ depth_assumption: DepthAssumption = .none,
+ };
+
+ pub const SpirvMeshOptions = struct {
+ pub const StageOutput = enum(u2) {
+ output_points = 0,
+ output_lines = 1,
+ output_triangles = 2,
+ };
+
+ stage_output: StageOutput = .output_triangles,
+ max_primitives: u32 = 1,
+ max_vertices: u32 = 3,
+ };
+
/// Returns the array of `std.Target.Cpu.Arch` to which this `CallingConvention` applies.
/// Asserts that `cc` is not `.auto`, `.@"async"`, `.naked`, or `.@"inline"`.
pub fn archs(cc: CallingConvention) []const std.Target.Cpu.Arch {
diff --git a/src/InternPool.zig b/src/InternPool.zig
@@ -4202,12 +4202,14 @@ pub const Index = enum(u32) {
type_function: struct {
const @"data.flags.has_comptime_bits" = opaque {};
const @"data.flags.has_noalias_bits" = opaque {};
+ const @"data.flags.cc.extraLen()" = opaque {};
const @"data.params_len" = opaque {};
data: *Tag.TypeFunction,
@"trailing.comptime_bits.len": *@"data.flags.has_comptime_bits",
@"trailing.noalias_bits.len": *@"data.flags.has_noalias_bits",
+ @"trailing.cc_bits.len": *@"data.flags.cc.extraLen()",
@"trailing.param_types.len": *@"data.params_len",
- trailing: struct { comptime_bits: []u32, noalias_bits: []u32, param_types: []Index },
+ trailing: struct { comptime_bits: []u32, noalias_bits: []u32, cc_bits: []u32, param_types: []Index },
},
type_tuple: struct {
const @"data.fields_len" = opaque {};
@@ -5165,6 +5167,7 @@ pub const Tag = enum(u8) {
.trailing = struct {
param_comptime_bits: ?[]u32,
param_noalias_bits: ?[]u32,
+ param_cc_bits: ?[]u32,
param_type: []Index,
},
.config = .{
@@ -5172,6 +5175,8 @@ pub const Tag = enum(u8) {
.@"trailing.param_comptime_bits.?.len" = .@"(payload.params_len + 31) / 32",
.@"trailing.param_noalias_bits.?" = .@"payload.flags.has_noalias_bits",
.@"trailing.param_noalias_bits.?.len" = .@"(payload.params_len + 31) / 32",
+ .@"trailing.param_cc_bits.?" = .@"payload.flags.cc.extraLen() != 0",
+ .@"trailing.param_cc_bits.?.len" = .@"payload.flags.cc.extraLen()",
.@"trailing.param_type.len" = .@"payload.params_len",
},
},
@@ -6983,6 +6988,9 @@ fn extraFuncType(tid: Zcu.PerThread.Id, extra: Local.Extra, extra_index: u32) Ke
trail_index += 1;
break :b x;
};
+ const cc_extra_len = type_function.data.flags.cc.extraLen();
+ const cc = type_function.data.flags.cc.unpack(extra.view().items(.@"0")[trail_index..][0..cc_extra_len]);
+ trail_index += cc_extra_len;
return .{
.param_types = .{
.tid = tid,
@@ -6992,7 +7000,7 @@ fn extraFuncType(tid: Zcu.PerThread.Id, extra: Local.Extra, extra_index: u32) Ke
.return_type = type_function.data.return_type,
.comptime_bits = comptime_bits,
.noalias_bits = noalias_bits,
- .cc = type_function.data.flags.cc.unpack(),
+ .cc = cc,
.is_var_args = type_function.data.flags.is_var_args,
.is_noinline = type_function.data.flags.is_noinline,
};
@@ -9091,18 +9099,21 @@ pub fn getFuncType(
// ask if it already exists, and if so, revert the lengths of the mutated
// arrays. This is similar to what `getOrPutTrailingString` does.
const prev_extra_len = extra.mutate.len;
+ const packed_cc: PackedCallingConvention = .pack(key.cc orelse .auto);
+ const cc_extra_len = packed_cc.extraLen();
const params_len: u32 = @intCast(key.param_types.len);
try extra.ensureUnusedCapacity(@typeInfo(Tag.TypeFunction).@"struct".field_names.len +
@intFromBool(key.comptime_bits != 0) +
@intFromBool(key.noalias_bits != 0) +
+ cc_extra_len +
params_len);
const func_type_extra_index = addExtraAssumeCapacity(extra, Tag.TypeFunction{
.params_len = params_len,
.return_type = key.return_type,
.flags = .{
- .cc = .pack(key.cc orelse .auto),
+ .cc = packed_cc,
.is_var_args = key.is_var_args,
.has_comptime_bits = key.comptime_bits != 0,
.has_noalias_bits = key.noalias_bits != 0,
@@ -9112,6 +9123,18 @@ pub fn getFuncType(
if (key.comptime_bits != 0) extra.appendAssumeCapacity(.{key.comptime_bits});
if (key.noalias_bits != 0) extra.appendAssumeCapacity(.{key.noalias_bits});
+ if (key.cc) |cc| switch (cc) {
+ .spirv_kernel, .spirv_task => |kernel| extra.appendSliceAssumeCapacity(.{&.{
+ kernel.x,
+ kernel.y,
+ kernel.z,
+ }}),
+ .spirv_mesh => |mesh| extra.appendSliceAssumeCapacity(.{&.{
+ mesh.max_primitives,
+ mesh.max_vertices,
+ }}),
+ else => {},
+ };
extra.appendSliceAssumeCapacity(.{@ptrCast(key.param_types)});
errdefer extra.mutate.len = prev_extra_len;
@@ -10704,6 +10727,7 @@ fn dumpStatsFallible(ip: *const InternPool, w: *Io.Writer, arena: Allocator) !vo
const info = extraData(extra_list, Tag.TypeFunction, data);
break :b @sizeOf(Tag.TypeFunction) +
(@sizeOf(Index) * info.params_len) +
+ (@as(u32, 4) * info.flags.cc.extraLen()) +
(@as(u32, 4) * @intFromBool(info.flags.has_comptime_bits)) +
(@as(u32, 4) * @intFromBool(info.flags.has_noalias_bits));
},
@@ -12573,12 +12597,35 @@ const PackedCallingConvention = packed struct(u18) {
.incoming_stack_alignment = .fromByteUnits(pl.incoming_stack_alignment orelse 0),
.extra = @intFromEnum(pl.save),
},
+ std.lang.CallingConvention.SpirvKernelOptions => .{
+ .tag = tag,
+ .incoming_stack_alignment = .none,
+ .extra = 0,
+ },
+ std.lang.CallingConvention.SpirvFragmentOptions => .{
+ .tag = tag,
+ .incoming_stack_alignment = .none,
+ .extra = @as(u4, @intFromEnum(pl.depth_assumption)) << 1 | @intFromBool(pl.pixel_centered_integer),
+ },
+ std.lang.CallingConvention.SpirvMeshOptions => .{
+ .tag = tag,
+ .incoming_stack_alignment = .none,
+ .extra = @intFromEnum(pl.stage_output),
+ },
else => comptime unreachable,
},
};
}
- fn unpack(cc: PackedCallingConvention) std.lang.CallingConvention {
+ fn extraLen(cc: PackedCallingConvention) u2 {
+ return switch (cc.tag) {
+ .spirv_kernel, .spirv_task => 3,
+ .spirv_mesh => 2,
+ else => 0,
+ };
+ }
+
+ fn unpack(cc: PackedCallingConvention, trailing: []const u32) std.lang.CallingConvention {
return switch (cc.tag) {
inline else => |tag| @unionInit(
std.lang.CallingConvention,
@@ -12616,6 +12663,20 @@ const PackedCallingConvention = packed struct(u18) {
.incoming_stack_alignment = cc.incoming_stack_alignment.toByteUnits(),
.save = @enumFromInt(cc.extra),
},
+ std.lang.CallingConvention.SpirvKernelOptions => .{
+ .x = trailing[0],
+ .y = trailing[1],
+ .z = trailing[2],
+ },
+ std.lang.CallingConvention.SpirvFragmentOptions => .{
+ .pixel_centered_integer = @bitCast(@as(u1, @truncate(cc.extra))),
+ .depth_assumption = @enumFromInt(@as(u2, @truncate(cc.extra >> 1))),
+ },
+ std.lang.CallingConvention.SpirvMeshOptions => .{
+ .stage_output = @enumFromInt(cc.extra),
+ .max_primitives = trailing[0],
+ .max_vertices = trailing[1],
+ },
else => comptime unreachable,
},
),
diff --git a/src/Sema.zig b/src/Sema.zig
@@ -8599,6 +8599,7 @@ fn checkReturnTypeAndCallConv(
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
+ const target = zcu.getTarget();
if (opt_varargs_src) |varargs_src| {
try sema.checkCallConvSupportsVarArgs(block, varargs_src, @"callconv");
}
@@ -8660,6 +8661,21 @@ fn checkReturnTypeAndCallConv(
.@"inline" => if (is_noinline) {
return sema.fail(block, callconv_src, "'noinline' function cannot have calling convention 'inline'", .{});
},
+ .spirv_fragment => |fragment| {
+ if (fragment.pixel_centered_integer and target.os.tag != .opengl) {
+ return sema.fail(block, callconv_src, "'pixel_centered_integer' is not supported on this target", .{});
+ }
+ },
+ .spirv_kernel, .spirv_task => |kernel| {
+ if (kernel.x == 0 or kernel.y == 0 or kernel.z == 0) {
+ return sema.fail(block, callconv_src, "kernel workgroup dimensions must be at least 1", .{});
+ }
+ },
+ .spirv_mesh => |mesh| {
+ if (mesh.max_vertices == 0 or mesh.max_primitives == 0) {
+ return sema.fail(block, callconv_src, "mesh shader 'max_vertices' and 'max_primitives' must be at least 1", .{});
+ }
+ },
else => {},
}
switch (zcu.callconvSupported(@"callconv")) {
@@ -8770,6 +8786,8 @@ fn callConvIsCallable(cc: std.lang.CallingConvention.Tag) bool {
.spirv_kernel,
.spirv_fragment,
.spirv_vertex,
+ .spirv_task,
+ .spirv_mesh,
=> false,
else => true,
@@ -29127,7 +29145,7 @@ fn callconvCoerceAllowed(
switch (src_cc) {
inline else => |src_data, tag| {
const dest_data = @field(dest_cc, @tagName(tag));
- if (@TypeOf(src_data) != void) {
+ if (@TypeOf(src_data) != void and @hasField(@TypeOf(src_data), "incoming_stack_alignment")) {
const default_stack_align = target.stackAlignment();
const src_stack_align = src_data.incoming_stack_alignment orelse default_stack_align;
const dest_stack_align = dest_data.incoming_stack_alignment orelse default_stack_align;
@@ -29156,6 +29174,10 @@ fn callconvCoerceAllowed(
std.lang.CallingConvention.ShInterruptOptions => {
if (src_data.save != dest_data.save) return false;
},
+ std.lang.CallingConvention.SpirvKernelOptions,
+ std.lang.CallingConvention.SpirvFragmentOptions,
+ std.lang.CallingConvention.SpirvMeshOptions,
+ => {},
else => comptime unreachable,
}
},
diff --git a/src/Zcu.zig b/src/Zcu.zig
@@ -4699,6 +4699,7 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.lang.CallingConvention) union(enum)
.stage2_spirv => switch (cc) {
.spirv_device, .spirv_kernel => true,
.spirv_fragment, .spirv_vertex => target.os.tag == .vulkan or target.os.tag == .opengl,
+ .spirv_task, .spirv_mesh => target.os.tag == .vulkan,
else => false,
},
};
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
@@ -4445,6 +4445,10 @@ pub fn toLlvmCallConv(cc: std.lang.CallingConvention, target: *const std.Target)
std.lang.CallingConvention.CommonOptions,
=> .{ pl.incoming_stack_alignment, 0, 0 },
std.lang.CallingConvention.X86RegparmOptions => .{ pl.incoming_stack_alignment, pl.register_params, 0 },
+ std.lang.CallingConvention.SpirvKernelOptions,
+ std.lang.CallingConvention.SpirvFragmentOptions,
+ std.lang.CallingConvention.SpirvMeshOptions,
+ => .{ null, 0, 0 },
else => @compileError("TODO: toLlvmCallConv" ++ @tagName(pl)),
},
};
@@ -4588,6 +4592,8 @@ pub fn toLlvmCallConvTag(cc_tag: std.lang.CallingConvention.Tag, target: *const
.spirv_kernel,
.spirv_fragment,
.spirv_vertex,
+ .spirv_task,
+ .spirv_mesh,
=> null,
};
}
diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig
@@ -1442,6 +1442,8 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
.spirv_fragment,
.spirv_vertex,
.spirv_device,
+ .spirv_task,
+ .spirv_mesh,
=> {},
else => unreachable,
}
@@ -2542,15 +2544,6 @@ fn generateTestEntryPoint(
cg.module.error_buffer = spv_err_decl_index;
}
- try cg.module.sections.execution_modes.emit(gpa, .OpExecutionMode, .{
- .entry_point = kernel_id,
- .mode = .{ .local_size = .{
- .x_size = 1,
- .y_size = 1,
- .z_size = 1,
- } },
- });
-
const void_ty_id = try cg.resolveType(.void, .direct);
const kernel_proto_ty_id = try cg.module.functionType(void_ty_id, &.{});
try section.emit(gpa, .OpFunction, .{
@@ -2599,13 +2592,7 @@ fn generateTestEntryPoint(
// point name is the same as a different OpName.
const test_name = try std.fmt.allocPrint(cg.module.arena, "test {s}", .{name});
- const execution_mode: spec.ExecutionModel = switch (target.os.tag) {
- .vulkan, .opengl => .gl_compute,
- .opencl, .amdhsa => .kernel,
- else => unreachable,
- };
-
- try cg.module.declareEntryPoint(spv_decl_index, test_name, execution_mode, null);
+ try cg.module.declareEntryPoint(spv_decl_index, test_name, .{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } });
}
fn intFromBool(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary {
diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig
@@ -132,15 +132,10 @@ pub const Decl = struct {
end_dep: usize = 0,
};
-/// This models a kernel entry point.
pub const EntryPoint = struct {
- /// The declaration that should be exported.
decl_index: Decl.Index,
- /// The name of the kernel to be exported.
name: []const u8,
- /// Calling Convention
- exec_model: spec.ExecutionModel,
- exec_mode: ?spec.ExecutionMode = null,
+ cc: std.builtin.CallingConvention,
};
const StructType = struct {
@@ -320,25 +315,89 @@ fn entryPoints(module: *Module) !Section {
interface.items.len = 0;
seen.setRangeValue(.{ .start = 0, .end = module.decls.items.len }, false);
+ const exec_model: spec.ExecutionModel = switch (target.os.tag) {
+ .vulkan, .opengl => switch (entry_point.cc) {
+ .spirv_vertex => .vertex,
+ .spirv_fragment => .fragment,
+ .spirv_kernel => .gl_compute,
+ .spirv_task => .task_ext,
+ .spirv_mesh => .mesh_ext,
+ // TODO: We should integrate with the Linkage capability and export this function
+ .spirv_device => continue,
+ else => unreachable,
+ },
+ .opencl => switch (entry_point.cc) {
+ .spirv_kernel => .kernel,
+ // TODO: We should integrate with the Linkage capability and export this function
+ .spirv_device => continue,
+ else => unreachable,
+ },
+ else => unreachable,
+ };
try module.addEntryPointDeps(entry_point.decl_index, &seen, &interface);
try entry_points.emit(module.gpa, .OpEntryPoint, .{
- .execution_model = entry_point.exec_model,
+ .execution_model = exec_model,
.entry_point = entry_point_id,
.name = entry_point.name,
.interface = interface.items,
});
- if (entry_point.exec_mode == null and entry_point.exec_model == .fragment) {
- switch (target.os.tag) {
- .vulkan, .opengl => |tag| {
+ switch (entry_point.cc) {
+ .spirv_kernel, .spirv_task => |kernel| {
+ try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
+ .entry_point = entry_point_id,
+ .mode = .{ .local_size = .{
+ .x_size = kernel.x,
+ .y_size = kernel.y,
+ .z_size = kernel.z,
+ } },
+ });
+ },
+ .spirv_fragment => |fragment| {
+ try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
+ .entry_point = entry_point_id,
+ .mode = if (target.os.tag == .vulkan) .origin_upper_left else .origin_lower_left,
+ });
+ if (fragment.pixel_centered_integer) {
try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
.entry_point = entry_point_id,
- .mode = if (tag == .vulkan) .origin_upper_left else .origin_lower_left,
+ .mode = .pixel_center_integer,
});
- },
- .opencl => {},
- else => unreachable,
- }
+ }
+
+ const exec_mode: ?spec.ExecutionMode.Extended = switch (fragment.depth_assumption) {
+ .none => null,
+ .greater => .depth_greater,
+ .less => .depth_less,
+ .unchanged => .depth_unchanged,
+ };
+ if (exec_mode) |mode| {
+ try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
+ .entry_point = entry_point_id,
+ .mode = mode,
+ });
+ }
+ },
+ .spirv_mesh => |mesh| {
+ try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
+ .entry_point = entry_point_id,
+ .mode = .{ .output_vertices = .{ .vertex_count = mesh.max_vertices } },
+ });
+ try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
+ .entry_point = entry_point_id,
+ .mode = .{ .output_primitives_ext = .{ .primitive_count = mesh.max_primitives } },
+ });
+
+ try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
+ .entry_point = entry_point_id,
+ .mode = switch (mesh.stage_output) {
+ .output_points => .output_points,
+ .output_lines => .output_lines_ext,
+ .output_triangles => .output_triangles_ext,
+ },
+ });
+ },
+ else => {}, // TODO: should this be unreachable?
}
}
@@ -925,15 +984,12 @@ pub fn declareEntryPoint(
module: *Module,
decl_index: Decl.Index,
name: []const u8,
- exec_model: spec.ExecutionModel,
- exec_mode: ?spec.ExecutionMode,
+ cc: std.builtin.CallingConvention,
) !void {
const gop = try module.entry_points.getOrPut(module.gpa, module.declPtr(decl_index).result_id);
gop.value_ptr.decl_index = decl_index;
gop.value_ptr.name = name;
- gop.value_ptr.exec_model = exec_model;
- // Might've been set by assembler
- if (!gop.found_existing) gop.value_ptr.exec_mode = exec_mode;
+ gop.value_ptr.cc = cc;
}
pub fn debugName(module: *Module, target: Id, name: []const u8) !void {
diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig
@@ -179,35 +179,17 @@ pub fn updateExports(
},
};
const nav_ty = ip.getNav(nav_index).resolved.?.type;
- const target = zcu.getTarget();
if (ip.isFunctionType(nav_ty)) {
const spv_decl_index = try linker.module.resolveNav(ip, nav_index);
const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
- const exec_model: spec.ExecutionModel = switch (target.os.tag) {
- .vulkan, .opengl => switch (cc) {
- .spirv_vertex => .vertex,
- .spirv_fragment => .fragment,
- .spirv_kernel => .gl_compute,
- // TODO: We should integrate with the Linkage capability and export this function
- .spirv_device => return,
- else => unreachable,
- },
- .opencl => switch (cc) {
- .spirv_kernel => .kernel,
- // TODO: We should integrate with the Linkage capability and export this function
- .spirv_device => return,
- else => unreachable,
- },
- else => unreachable,
- };
+ if (cc == .spirv_device) return;
for (export_indices) |export_idx| {
const exp = export_idx.ptr(zcu);
try linker.module.declareEntryPoint(
spv_decl_index,
exp.opts.name.toSlice(ip),
- exec_model,
- null,
+ cc,
);
}
}
diff --git a/test/cases/callconv_spirv.zig b/test/cases/callconv_spirv.zig
@@ -0,0 +1,11 @@
+export fn vert() callconv(.spirv_vertex) void {}
+export fn frag() callconv(.{ .spirv_fragment = .{ .depth_assumption = .greater } }) void {}
+export fn comp() callconv(.{ .spirv_kernel = .{ .x = 8, .y = 8, .z = 1 } }) void {}
+export fn task() callconv(.{ .spirv_task = .{ .x = 1, .y = 1, .z = 1 } }) void {}
+export fn mesh() callconv(.{ .spirv_mesh = .{ .stage_output = .output_lines, .max_primitives = 1, .max_vertices = 2 } }) void {}
+
+// compile
+// output_mode=Obj
+// backend=selfhosted
+// target=spirv64-vulkan
+// emit_bin=false
diff --git a/test/cases/compile_errors/callconv_spirv_invalid_options.zig b/test/cases/compile_errors/callconv_spirv_invalid_options.zig
@@ -0,0 +1,29 @@
+const F1 = fn () callconv(.{ .spirv_kernel = .{ .x = 0, .y = 1, .z = 1 } }) void;
+const F2 = fn () callconv(.{ .spirv_task = .{ .x = 1, .y = 0, .z = 1 } }) void;
+const F3 = fn () callconv(.{ .spirv_mesh = .{ .max_vertices = 0 } }) void;
+const F4 = fn () callconv(.{ .spirv_fragment = .{ .pixel_centered_integer = true } }) void;
+export fn entry1() void {
+ const a: F1 = undefined;
+ _ = a;
+}
+export fn entry2() void {
+ const a: F2 = undefined;
+ _ = a;
+}
+export fn entry3() void {
+ const a: F3 = undefined;
+ _ = a;
+}
+export fn entry4() void {
+ const a: F4 = undefined;
+ _ = a;
+}
+
+// error
+// backend=selfhosted
+// target=spirv64-vulkan
+//
+// :1:28: error: kernel workgroup dimensions must be at least 1
+// :2:28: error: kernel workgroup dimensions must be at least 1
+// :3:28: error: mesh shader 'max_vertices' and 'max_primitives' must be at least 1
+// :4:28: error: 'pixel_centered_integer' is not supported on this target
diff --git a/test/cases/compile_errors/callconv_spirv_mesh_task_require_vulkan.zig b/test/cases/compile_errors/callconv_spirv_mesh_task_require_vulkan.zig
@@ -0,0 +1,17 @@
+const F1 = fn () callconv(.{ .spirv_task = .{ .x = 1, .y = 1, .z = 1 } }) void;
+const F2 = fn () callconv(.{ .spirv_mesh = .{} }) void;
+export fn entry1() void {
+ const a: F1 = undefined;
+ _ = a;
+}
+export fn entry2() void {
+ const a: F2 = undefined;
+ _ = a;
+}
+
+// error
+// backend=selfhosted
+// target=spirv64-opengl
+//
+// :1:28: error: calling convention 'spirv_task' not supported by compiler backend 'stage2_spirv'
+// :2:28: error: calling convention 'spirv_mesh' not supported by compiler backend 'stage2_spirv'
diff --git a/test/cases/compile_errors/callconv_spirv_on_unsupported_platform.zig b/test/cases/compile_errors/callconv_spirv_on_unsupported_platform.zig
@@ -0,0 +1,29 @@
+const F1 = fn () callconv(.{ .spirv_fragment = .{} }) void;
+const F2 = fn () callconv(.spirv_vertex) void;
+const F3 = fn () callconv(.{ .spirv_task = .{ .x = 1, .y = 1, .z = 1 } }) void;
+const F4 = fn () callconv(.{ .spirv_mesh = .{} }) void;
+export fn entry1() void {
+ const a: F1 = undefined;
+ _ = a;
+}
+export fn entry2() void {
+ const a: F2 = undefined;
+ _ = a;
+}
+export fn entry3() void {
+ const a: F3 = undefined;
+ _ = a;
+}
+export fn entry4() void {
+ const a: F4 = undefined;
+ _ = a;
+}
+
+// error
+// backend=selfhosted
+// target=spirv64-opencl
+//
+// :1:28: error: calling convention 'spirv_fragment' not supported by compiler backend 'stage2_spirv'
+// :2:28: error: calling convention 'spirv_vertex' not supported by compiler backend 'stage2_spirv'
+// :3:28: error: calling convention 'spirv_task' not supported by compiler backend 'stage2_spirv'
+// :4:28: error: calling convention 'spirv_mesh' not supported by compiler backend 'stage2_spirv'