diff --git a/lib/std/start.zig b/lib/std/start.zig index a70d9e609ef0..c5664cbf0fa9 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -19,8 +19,7 @@ pub const simplified_logic = builtin.zig_backend == .stage2_aarch64 or builtin.zig_backend == .stage2_arm or builtin.zig_backend == .stage2_sparc64 or - builtin.cpu.arch == .spirv32 or - builtin.cpu.arch == .spirv64; + builtin.zig_backend == .stage2_spirv64; comptime { // No matter what, we import the root file, so that any export, test, comptime @@ -37,7 +36,7 @@ comptime { if (!@hasDecl(root, "wWinMainCRTStartup") and !@hasDecl(root, "mainCRTStartup")) { @export(&wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" }); } - } else if (builtin.os.tag == .opencl) { + } else if (builtin.os.tag == .opencl or builtin.os.tag == .vulkan) { if (@hasDecl(root, "main")) @export(&spirvMain2, .{ .name = "main" }); } else { diff --git a/src/Sema.zig b/src/Sema.zig index 987fbe4c2cd8..fdf39b830544 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6253,6 +6253,9 @@ fn resolveAnalyzedBlock( for (merges.results.items, merges.src_locs.items) |merge_inst, merge_src| { try sema.validateRuntimeValue(child_block, merge_src orelse src, merge_inst); } + + try sema.checkMergeAllowed(child_block, type_src, resolved_ty); + const ty_inst = Air.internedToRef(resolved_ty.toIntern()); switch (block_tag) { .block => { @@ -9688,6 +9691,39 @@ fn checkCallConvSupportsVarArgs(sema: *Sema, block: *Block, src: LazySrcLoc, cc: } } +fn checkMergeAllowed(sema: *Sema, block: *Block, src: LazySrcLoc, peer_ty: Type) !void { + const pt = sema.pt; + const zcu = pt.zcu; + const target = zcu.getTarget(); + + if (!peer_ty.isPtrAtRuntime(zcu)) { + return; + } + + const as = peer_ty.ptrAddressSpace(zcu); + if (!target_util.arePointersLogical(target, as)) { + return; + } + + return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(src, "value with non-mergable pointer type '{}' depends on runtime control flow", .{peer_ty.fmt(pt)}); + errdefer msg.destroy(sema.gpa); + + const runtime_src = block.runtime_cond orelse block.runtime_loop.?; + try sema.errNote(runtime_src, msg, "runtime control flow here", .{}); + + const backend = target_util.zigBackend(target, zcu.comp.config.use_llvm); + try sema.errNote(src, msg, "pointers with address space '{s}' cannot be returned from a branch on target {s}-{s} by compiler backend {s}", .{ + @tagName(as), + target.cpu.arch.genericName(), + @tagName(target.os.tag), + @tagName(backend), + }); + + break :msg msg; + }); +} + const Section = union(enum) { generic, default, diff --git a/src/Zcu.zig b/src/Zcu.zig index d86fee6365bb..aa8610b7fd5f 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -3639,11 +3639,8 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.builtin.CallingConvention) union(enu else => false, }, .stage2_spirv64 => switch (cc) { - .spirv_device, - .spirv_kernel, - .spirv_fragment, - .spirv_vertex, - => true, + .spirv_device, .spirv_kernel => true, + .spirv_fragment, .spirv_vertex => target.os.tag == .vulkan, else => false, }, }; diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 279b7d50565d..46c23d7d535c 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -169,6 +169,13 @@ pub const Object = struct { /// via the usual `intern_map` mechanism. ptr_types: PtrTypeMap = .{}, + /// For test declarations for Vulkan, we have to add a push constant with a pointer to a + /// buffer that we can use. We only need to generate this once, this holds the link information + /// related to that. + error_push_constant: ?struct { + push_constant_ptr: SpvModule.Decl.Index, + } = null, + pub fn init(gpa: Allocator) Object { return .{ .gpa = gpa, @@ -1640,13 +1647,18 @@ const NavGen = struct { comptime assert(zig_call_abi_ver == 3); switch (fn_info.cc) { - .auto, .spirv_kernel, .spirv_fragment, .spirv_vertex => {}, - else => @panic("TODO"), + .auto, + .spirv_kernel, + .spirv_fragment, + .spirv_vertex, + .spirv_device, + => {}, + else => unreachable, } - // TODO: Put this somewhere in Sema.zig - if (fn_info.is_var_args) - return self.fail("VarArgs functions are unsupported for SPIR-V", .{}); + // Guaranteed by callConvSupportsVarArgs, there are nog SPIR-V CCs which support + // varargs. + assert(!fn_info.is_var_args); // Note: Logic is different from functionType(). const param_ty_ids = try self.gpa.alloc(IdRef, fn_info.param_types.len); @@ -1838,11 +1850,16 @@ const NavGen = struct { return switch (as) { .generic => switch (target.os.tag) { .vulkan => .Private, - else => .Generic, + .opencl => .Generic, + else => unreachable, }, .shared => .Workgroup, .local => .Private, - .global => .CrossWorkgroup, + .global => switch (target.os.tag) { + .opencl => .CrossWorkgroup, + .vulkan => .PhysicalStorageBuffer, + else => unreachable, + }, .constant => .UniformConstant, .input => .Input, .output => .Output, @@ -2898,30 +2915,118 @@ const NavGen = struct { .flags = .{ .address_space = .global }, }); const ptr_anyerror_ty_id = try self.resolveType(ptr_anyerror_ty, .direct); - const kernel_proto_ty_id = try self.functionType(Type.void, &.{ptr_anyerror_ty}); - - const test_id = self.spv.declPtr(spv_test_decl_index).result_id; const spv_decl_index = try self.spv.allocDecl(.func); const kernel_id = self.spv.declPtr(spv_decl_index).result_id; + // for some reason we don't need to decorate the push constant here... + try self.spv.declareDeclDeps(spv_decl_index, &.{spv_test_decl_index}); + + const section = &self.spv.sections.functions; + + const target = self.getTarget(); - const error_id = self.spv.allocId(); const p_error_id = self.spv.allocId(); + switch (target.os.tag) { + .opencl => { + const kernel_proto_ty_id = try self.functionType(Type.void, &.{ptr_anyerror_ty}); - const section = &self.spv.sections.functions; - try section.emit(self.spv.gpa, .OpFunction, .{ - .id_result_type = try self.resolveType(Type.void, .direct), - .id_result = kernel_id, - .function_control = .{}, - .function_type = kernel_proto_ty_id, - }); - try section.emit(self.spv.gpa, .OpFunctionParameter, .{ - .id_result_type = ptr_anyerror_ty_id, - .id_result = p_error_id, - }); - try section.emit(self.spv.gpa, .OpLabel, .{ - .id_result = self.spv.allocId(), - }); + try section.emit(self.spv.gpa, .OpFunction, .{ + .id_result_type = try self.resolveType(Type.void, .direct), + .id_result = kernel_id, + .function_control = .{}, + .function_type = kernel_proto_ty_id, + }); + + try section.emit(self.spv.gpa, .OpFunctionParameter, .{ + .id_result_type = ptr_anyerror_ty_id, + .id_result = p_error_id, + }); + + try section.emit(self.spv.gpa, .OpLabel, .{ + .id_result = self.spv.allocId(), + }); + }, + .vulkan => { + const ptr_ptr_anyerror_ty_id = self.spv.allocId(); + try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{ + .id_result = ptr_ptr_anyerror_ty_id, + .storage_class = .PushConstant, + .type = ptr_anyerror_ty_id, + }); + + if (self.object.error_push_constant == null) { + const spv_err_decl_index = try self.spv.allocDecl(.global); + try self.spv.declareDeclDeps(spv_err_decl_index, &.{}); + + const push_constant_struct_ty_id = try self.spv.structType( + &.{ptr_anyerror_ty_id}, + &.{"error_out_ptr"}, + ); + try self.spv.decorate(push_constant_struct_ty_id, .Block); + try self.spv.decorateMember(push_constant_struct_ty_id, 0, .{ .Offset = .{ .byte_offset = 0 } }); + + const ptr_push_constant_struct_ty_id = self.spv.allocId(); + try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{ + .id_result = ptr_push_constant_struct_ty_id, + .storage_class = .PushConstant, + .type = push_constant_struct_ty_id, + }); + + try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpVariable, .{ + .id_result_type = ptr_push_constant_struct_ty_id, + .id_result = self.spv.declPtr(spv_err_decl_index).result_id, + .storage_class = .PushConstant, + }); + + self.object.error_push_constant = .{ + .push_constant_ptr = spv_err_decl_index, + }; + } + + try self.spv.sections.execution_modes.emit(self.spv.gpa, .OpExecutionMode, .{ + .entry_point = kernel_id, + .mode = .{ .LocalSize = .{ + .x_size = 1, + .y_size = 1, + .z_size = 1, + } }, + }); + + const kernel_proto_ty_id = try self.functionType(Type.void, &.{}); + try section.emit(self.spv.gpa, .OpFunction, .{ + .id_result_type = try self.resolveType(Type.void, .direct), + .id_result = kernel_id, + .function_control = .{}, + .function_type = kernel_proto_ty_id, + }); + try section.emit(self.spv.gpa, .OpLabel, .{ + .id_result = self.spv.allocId(), + }); + + const spv_err_decl_index = self.object.error_push_constant.?.push_constant_ptr; + const push_constant_id = self.spv.declPtr(spv_err_decl_index).result_id; + + const zero_id = try self.constInt(Type.u32, 0, .direct); + // We cannot use OpInBoundsAccessChain to dereference cross-storage class, so we have to use + // a load. + const tmp = self.spv.allocId(); + try section.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ + .id_result_type = ptr_ptr_anyerror_ty_id, + .id_result = tmp, + .base = push_constant_id, + .indexes = &.{zero_id}, + }); + try section.emit(self.spv.gpa, .OpLoad, .{ + .id_result_type = ptr_anyerror_ty_id, + .id_result = p_error_id, + .pointer = tmp, + }); + }, + else => unreachable, + } + + const test_id = self.spv.declPtr(spv_test_decl_index).result_id; + const error_id = self.spv.allocId(); try section.emit(self.spv.gpa, .OpFunctionCall, .{ .id_result_type = anyerror_ty_id, .id_result = error_id, @@ -2931,17 +3036,25 @@ const NavGen = struct { try section.emit(self.spv.gpa, .OpStore, .{ .pointer = p_error_id, .object = error_id, + .memory_access = .{ + .Aligned = .{ .literal_integer = @sizeOf(u16) }, + }, }); try section.emit(self.spv.gpa, .OpReturn, {}); try section.emit(self.spv.gpa, .OpFunctionEnd, {}); - try self.spv.declareDeclDeps(spv_decl_index, &.{spv_test_decl_index}); - // Just generate a quick other name because the intel runtime crashes when the entry- // point name is the same as a different OpName. const test_name = try std.fmt.allocPrint(self.gpa, "test {s}", .{name}); defer self.gpa.free(test_name); - try self.spv.declareEntryPoint(spv_decl_index, test_name, .Kernel); + + const execution_mode: spec.ExecutionModel = switch (target.os.tag) { + .vulkan => .GLCompute, + .opencl => .Kernel, + else => unreachable, + }; + + try self.spv.declareEntryPoint(spv_decl_index, test_name, execution_mode); } fn genNav(self: *NavGen, do_codegen: bool) !void { @@ -2969,11 +3082,10 @@ const NavGen = struct { try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{ .id_result_type = return_ty_id, .id_result = result_id, - .function_control = switch (fn_info.cc) { - .@"inline" => .{ .Inline = true }, - else => .{}, - }, .function_type = prototype_ty_id, + // Note: the backend will never be asked to generate an inline function + // (this is handled in sema), so we don't need to set function_control here. + .function_control = .{}, }); comptime assert(zig_call_abi_ver == 3); diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index d428377c3b65..41e9adb2c39c 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -3845,6 +3845,7 @@ pub fn flushModule(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void { } if (global_error_set_names.len > 0) try uleb128(diw, @intFromEnum(AbbrevCode.null)); try dwarf.debug_info.section.replaceEntry(wip_nav.unit, wip_nav.entry, dwarf, wip_nav.debug_info.items); + try wip_nav.flush(.unneeded); } { diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index c6b0752aee82..045bd05d8474 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -161,28 +161,35 @@ pub fn updateExports( }, }; const nav_ty = ip.getNav(nav_index).typeOf(ip); + const target = zcu.getTarget(); if (ip.isFunctionType(nav_ty)) { - const target = zcu.getTarget(); const spv_decl_index = try self.object.resolveNav(zcu, nav_index); - const execution_model = switch (Type.fromInterned(nav_ty).fnCallingConvention(zcu)) { - .spirv_vertex => spec.ExecutionModel.Vertex, - .spirv_fragment => spec.ExecutionModel.Fragment, - .spirv_kernel => spec.ExecutionModel.Kernel, + const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu); + const execution_model: spec.ExecutionModel = switch (target.os.tag) { + .vulkan => switch (cc) { + .spirv_vertex => .Vertex, + .spirv_fragment => .Fragment, + .spirv_kernel => .GLCompute, + // 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, }; - const is_vulkan = target.os.tag == .vulkan; - - if ((!is_vulkan and execution_model == .Kernel) or - (is_vulkan and (execution_model == .Fragment or execution_model == .Vertex))) - { - for (export_indices) |export_idx| { - const exp = zcu.all_exports.items[export_idx]; - try self.object.spv.declareEntryPoint( - spv_decl_index, - exp.opts.name.toSlice(ip), - execution_model, - ); - } + + for (export_indices) |export_idx| { + const exp = zcu.all_exports.items[export_idx]; + try self.object.spv.declareEntryPoint( + spv_decl_index, + exp.opts.name.toSlice(ip), + execution_model, + ); } } @@ -258,7 +265,7 @@ pub fn flushModule(self: *SpirV, arena: Allocator, tid: Zcu.PerThread.Id, prog_n const linked_module = self.linkModule(arena, module, sub_prog_node) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => |other| { - log.err("error while linking: {s}\n", .{@errorName(other)}); + log.err("error while linking: {s}", .{@errorName(other)}); return error.FlushFailure; }, }; @@ -289,9 +296,8 @@ fn writeCapabilities(spv: *SpvModule, target: std.Target) !void { // TODO: Integrate with a hypothetical feature system const caps: []const spec.Capability = switch (target.os.tag) { .opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .Float64, .Float16, .Vector16, .GenericPointer }, - .opengl => &.{.Shader}, - .vulkan => &.{ .Shader, .VariablePointersStorageBuffer, .Int8, .Int16, .Int64, .Float64, .Float16 }, - else => unreachable, // TODO + .vulkan => &.{ .Shader, .PhysicalStorageBufferAddresses, .StoragePushConstant16, .Int8, .Int16, .Int64, .Float64, .Float16 }, + else => unreachable, }; for (caps) |cap| { @@ -299,19 +305,32 @@ fn writeCapabilities(spv: *SpvModule, target: std.Target) !void { .capability = cap, }); } + + switch (target.os.tag) { + .vulkan => { + try spv.sections.extensions.emit(gpa, .OpExtension, .{ + .name = "SPV_KHR_physical_storage_buffer", + }); + }, + else => {}, + } } fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void { const gpa = spv.gpa; - const addressing_model = switch (target.os.tag) { + const addressing_model: spec.AddressingModel = switch (target.os.tag) { .opencl => switch (target.cpu.arch) { - .spirv32 => spec.AddressingModel.Physical32, - .spirv64 => spec.AddressingModel.Physical64, - else => unreachable, // TODO + .spirv32 => .Physical32, + .spirv64 => .Physical64, + else => unreachable, }, - .opengl, .vulkan => spec.AddressingModel.Logical, - else => unreachable, // TODO + .opengl, .vulkan => switch (target.cpu.arch) { + .spirv32 => .Logical, // TODO: I don't think this will ever be implemented. + .spirv64 => .PhysicalStorageBuffer64, + else => unreachable, + }, + else => unreachable, }; const memory_model: spec.MemoryModel = switch (target.os.tag) { diff --git a/src/link/SpirV/lower_invocation_globals.zig b/src/link/SpirV/lower_invocation_globals.zig index a06a868e1877..9d91a142e4e3 100644 --- a/src/link/SpirV/lower_invocation_globals.zig +++ b/src/link/SpirV/lower_invocation_globals.zig @@ -400,6 +400,15 @@ const ModuleBuilder = struct { self.section.writeWords(inst.operands[2..]); continue; }, + .OpExecutionMode, .OpExecutionModeId => { + const original_id: ResultId = @enumFromInt(inst.operands[0]); + const new_id_index = info.entry_points.getIndex(original_id).?; + const new_id: ResultId = @enumFromInt(self.entry_point_new_id_base + new_id_index); + try self.section.emitRaw(self.arena, inst.opcode, inst.operands.len); + self.section.writeOperand(ResultId, new_id); + self.section.writeWords(inst.operands[1..]); + continue; + }, .OpTypeFunction => { // Re-emitted in `emitFunctionTypes()`. We can do this because // OpTypeFunction's may not currently be used anywhere that is not diff --git a/src/target.zig b/src/target.zig index 1f8a567f03c9..b0d2bdfe744a 100644 --- a/src/target.zig +++ b/src/target.zig @@ -398,6 +398,31 @@ pub fn addrSpaceCastIsValid( } } +/// Under SPIR-V with Vulkan, pointers are not 'real' (physical), but rather 'logical'. Effectively, +/// this means that all such pointers have to be resolvable to a location at compile time, and places +/// a number of restrictions on usage of such pointers. For example, a logical pointer may not be +/// part of a merge (result of a branch) and may not be stored in memory at all. This function returns +/// for a particular architecture and address space wether such pointers are logical. +pub fn arePointersLogical(target: std.Target, as: AddressSpace) bool { + if (target.os.tag != .vulkan) { + return false; + } + + return switch (as) { + // TODO: Vulkan doesn't support pointers in the generic address space, we + // should remove this case but this requires a change in defaultAddressSpace(). + // For now, at least disable them from being regarded as physical. + .generic => true, + // For now, all global pointers are represented using PhysicalStorageBuffer, so these are real + // pointers. + .global => false, + // TODO: Allowed with VK_KHR_variable_pointers. + .shared => true, + .constant, .local, .input, .output, .uniform => true, + else => unreachable, + }; +} + pub fn llvmMachineAbi(target: std.Target) ?[:0]const u8 { // LLD does not support ELFv1. Rather than having LLVM produce ELFv1 code and then linking it // into a broken ELFv2 binary, just force LLVM to use ELFv2 as well. This will break when glibc diff --git a/test/cases/compile_errors/explicit_error_set_cast_known_at_comptime_violates_error_sets.zig b/test/cases/compile_errors/explicit_error_set_cast_known_at_comptime_violates_error_sets.zig index a1beac65f2f2..0418d327c951 100644 --- a/test/cases/compile_errors/explicit_error_set_cast_known_at_comptime_violates_error_sets.zig +++ b/test/cases/compile_errors/explicit_error_set_cast_known_at_comptime_violates_error_sets.zig @@ -10,4 +10,4 @@ comptime { // backend=stage2 // target=native // -// :5:21: error: 'error.B' not a member of error set 'error{C,A}' +// :5:21: error: 'error.B' not a member of error set 'error{A,C}' diff --git a/test/cases/compile_errors/implicit_cast_of_error_set_not_a_subset.zig b/test/cases/compile_errors/implicit_cast_of_error_set_not_a_subset.zig index 617e6386274d..1dab9d81c98a 100644 --- a/test/cases/compile_errors/implicit_cast_of_error_set_not_a_subset.zig +++ b/test/cases/compile_errors/implicit_cast_of_error_set_not_a_subset.zig @@ -12,5 +12,5 @@ fn foo(set1: Set1) void { // backend=stage2 // target=native // -// :7:21: error: expected type 'error{C,A}', found 'error{A,B}' +// :7:21: error: expected type 'error{A,C}', found 'error{A,B}' // :7:21: note: 'error.B' not a member of destination error set diff --git a/test/cases/compile_errors/int_to_err_non_global_invalid_number.zig b/test/cases/compile_errors/int_to_err_non_global_invalid_number.zig index c9f6c1e2e4a0..829192ab8daf 100644 --- a/test/cases/compile_errors/int_to_err_non_global_invalid_number.zig +++ b/test/cases/compile_errors/int_to_err_non_global_invalid_number.zig @@ -16,4 +16,4 @@ comptime { // backend=llvm // target=native // -// :11:21: error: 'error.B' not a member of error set 'error{C,A}' +// :11:21: error: 'error.B' not a member of error set 'error{A,C}' diff --git a/test/cases/compile_errors/spirv_merge_logical_pointers.zig b/test/cases/compile_errors/spirv_merge_logical_pointers.zig new file mode 100644 index 000000000000..ea81ef903c1a --- /dev/null +++ b/test/cases/compile_errors/spirv_merge_logical_pointers.zig @@ -0,0 +1,19 @@ +export fn a() void { + var x: *i32 = undefined; + _ = &x; + var y: *i32 = undefined; + _ = &y; + var rt_cond = false; + _ = &rt_cond; + + var z = if (rt_cond) x else y; + _ = &z; +} + +// error +// backend=stage2 +// target=spirv64-vulkan +// +// :9:13: error: value with non-mergable pointer type '*i32' depends on runtime control flow +// :9:17: note: runtime control flow here +// :9:13: note: pointers with address space 'generic' cannot be returned from a branch on target spirv-vulkan by compiler backend stage2_spirv64 diff --git a/test/cases/spirv_mergable_pointers.zig b/test/cases/spirv_mergable_pointers.zig new file mode 100644 index 000000000000..240c485f9d8e --- /dev/null +++ b/test/cases/spirv_mergable_pointers.zig @@ -0,0 +1,16 @@ +export fn a() void { + var x: *addrspace(.global) i32 = undefined; + _ = &x; + var y: *addrspace(.global) i32 = undefined; + _ = &y; + var rt_cond = false; + _ = &rt_cond; + + var z = if (rt_cond) x else y; + _ = &z; +} + +// compile +// output_mode=Obj +// backend=stage2 +// target=spirv64-vulkan diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 0e88bdd133a0..9314b5677b1f 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -467,7 +467,7 @@ fn addFromDirInner( const target = resolved_target.result; for (backends) |backend| { if (backend == .stage2 and - target.cpu.arch != .wasm32 and target.cpu.arch != .x86_64) + target.cpu.arch != .wasm32 and target.cpu.arch != .x86_64 and target.cpu.arch != .spirv64) { // Other backends don't support new liveness format continue;