Skip to content

Commit

Permalink
Merge pull request #21826 from Snektron/spirv-vulkan
Browse files Browse the repository at this point in the history
spirv: vulkan setup
  • Loading branch information
Snektron authored Oct 29, 2024
2 parents 7025c06 + ae57f6f commit 3450809
Show file tree
Hide file tree
Showing 14 changed files with 305 additions and 72 deletions.
5 changes: 2 additions & 3 deletions lib/std/start.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
36 changes: 36 additions & 0 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 2 additions & 5 deletions src/Zcu.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
Expand Down
176 changes: 144 additions & 32 deletions src/codegen/spirv.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/link/Dwarf.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

{
Expand Down
Loading

0 comments on commit 3450809

Please sign in to comment.