zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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:
Mlib/std/Target.zig | 2++
Mlib/std/gpu.zig | 83-------------------------------------------------------------------------------
Mlib/std/lang.zig | 40++++++++++++++++++++++++++++++++++++----
Msrc/InternPool.zig | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/Sema.zig | 24+++++++++++++++++++++++-
Msrc/Zcu.zig | 1+
Msrc/codegen/llvm.zig | 6++++++
Msrc/codegen/spirv/CodeGen.zig | 19+++----------------
Msrc/codegen/spirv/Module.zig | 96++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/link/SpirV.zig | 22++--------------------
Atest/cases/callconv_spirv.zig | 11+++++++++++
Atest/cases/compile_errors/callconv_spirv_invalid_options.zig | 29+++++++++++++++++++++++++++++
Atest/cases/compile_errors/callconv_spirv_mesh_task_require_vulkan.zig | 17+++++++++++++++++
Atest/cases/compile_errors/callconv_spirv_on_unsupported_platform.zig | 29+++++++++++++++++++++++++++++
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'