commit 88d2961df475806a73af0f2eb4e4076159dc7f61 (tree)
parent 281991328ed25c049c1b86b21fd72815cb0959d8
Author: Ali Cheraghi <alichraghi@proton.me>
Date: Tue, 9 Jun 2026 21:14:36 +0330
spirv: codegen and linker fixes for logical-addressing
A handful of changes to get the regressed behavior tests running again.
- Replace `decorateBlockOffsets` with a recursive function `decorateLayout`
that walks arrays, vectors, structs, unions, optionals, and error unions,
emitting `ArrayStride` and member `Offset` decorations at every
level. Previously we weren't handling nested types.
- Restrict `Block` decoration to struct types with
`uniform`, `push_constant`, `storage_buffer` storage classes.
Previously we decorated through every pointer, contaminating the cached struct
type so the same shape used as a stack local also picked up `Block`.
- Lower `ptr_slice_ptr_ptr` and `ptr_slice_len_ptr`
- No longer emit a redundant `**T` typed `OpVariable` for function parameters.
Logical addressing also forbids such variables.
- Eliminate dead code from invocation globals unreachable from any entry point.
Reverting the workaround in `lib/std/start.zig`.
Diffstat:
3 files changed, 157 insertions(+), 45 deletions(-)
diff --git a/lib/std/start.zig b/lib/std/start.zig
@@ -19,9 +19,7 @@ comptime {
// decls there get run.
_ = root;
- if (builtin.zig_backend == .stage2_spirv) {
- // Do nothing
- } else if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) {
+ if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) {
const dll_main_crt_startup = if (builtin.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup";
if (native_os == .windows and !builtin.link_libc and !@hasDecl(root, dll_main_crt_startup)) {
@export(&DllMainCRTStartup, .{ .name = dll_main_crt_startup });
@@ -72,7 +70,7 @@ comptime {
// case it's not required to provide an entrypoint such as main.
if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(&wasm_freestanding_start, .{ .name = start_sym_name });
} else switch (native_os) {
- .other, .freestanding, .@"3ds", .psp, .vita => {},
+ .other, .freestanding, .@"3ds", .psp, .vita, .vulkan, .opengl, .opencl => {},
else => if (!@hasDecl(root, start_sym_name)) @export(&_start, .{ .name = start_sym_name }),
}
}
diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig
@@ -152,6 +152,7 @@ base_line: u32,
block_label: Id = .none,
next_arg_index: u32 = 0,
args: std.ArrayList(Id) = .empty,
+virtual_allocas: std.AutoHashMapUnmanaged(Id, ?Id) = .empty,
inst_results: std.AutoHashMapUnmanaged(Air.Inst.Index, Id) = .empty,
id_scratch: std.ArrayList(Id) = .empty,
prologue: Section = .{},
@@ -161,6 +162,7 @@ pub fn deinit(cg: *CodeGen) void {
const gpa = cg.module.gpa;
cg.control_flow.deinit(gpa);
cg.args.deinit(gpa);
+ cg.virtual_allocas.deinit(gpa);
cg.inst_results.deinit(gpa);
cg.id_scratch.deinit(gpa);
cg.prologue.deinit(gpa);
@@ -269,23 +271,19 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {
switch (target.os.tag) {
.vulkan, .opengl => {
- if (ty.zigTypeTag(zcu) == .@"struct") {
- switch (storage_class) {
- .uniform,
- .push_constant,
- .storage_buffer,
- => {
+ switch (storage_class) {
+ .uniform, .push_constant, .storage_buffer, .physical_storage_buffer => {
+ if (ty.zigTypeTag(zcu) == .@"struct" and storage_class != .physical_storage_buffer) {
try cg.module.decorate(ty_id, .block);
- try cg.decorateBlockOffsets(ty, ty_id);
- },
- else => {},
- }
+ }
+ try cg.module.decorate(ptr_ty_id, .{
+ .array_stride = .{ .array_stride = @intCast(ty.abiSize(zcu)) },
+ });
+ try cg.decorateLayout(ty, ty_id);
+ },
+ else => {},
}
- try cg.module.decorate(ptr_ty_id, .{
- .array_stride = .{ .array_stride = @intCast(ty.abiSize(zcu)) },
- });
-
if (key.decoration) |decoration| switch (decoration) {
.location => |location| {
if (storage_class != .output and storage_class != .input and storage_class != .uniform_constant) {
@@ -378,18 +376,82 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {
cg.module.declPtr(spv_decl_index).end_dep = cg.module.decl_deps.items.len;
}
-fn decorateBlockOffsets(cg: *CodeGen, ty: Type, ty_id: spec.Id) !void {
+fn decorateLayout(cg: *CodeGen, ty: Type, ty_id: spec.Id) Error!void {
const zcu = cg.module.zcu;
const ip = &zcu.intern_pool;
- const struct_type = ip.loadStructType(ty.toIntern());
- var it = struct_type.iterateRuntimeOrder(ip);
- var member: u32 = 0;
- while (it.next()) |field_index| {
- const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
- if (!field_ty.hasRuntimeBits(zcu)) continue;
- const offset: u32 = @intCast(ty.structFieldOffset(field_index, zcu));
- try cg.module.decorateMember(ty_id, member, .{ .offset = .{ .byte_offset = offset } });
- member += 1;
+ switch (ty.zigTypeTag(zcu)) {
+ .array => {
+ const elem_ty = ty.childType(zcu);
+ if (!elem_ty.hasRuntimeBits(zcu)) return;
+ try cg.module.decorate(ty_id, .{
+ .array_stride = .{ .array_stride = @intCast(elem_ty.abiSize(zcu)) },
+ });
+ try cg.decorateLayout(elem_ty, try cg.resolveType(elem_ty, .indirect));
+ },
+ .vector => {
+ const elem_ty = ty.childType(zcu);
+ try cg.decorateLayout(elem_ty, try cg.resolveType(elem_ty, .indirect));
+ if (cg.isSpvVector(ty)) return;
+ try cg.module.decorate(ty_id, .{
+ .array_stride = .{ .array_stride = @intCast(elem_ty.abiSize(zcu)) },
+ });
+ },
+ .@"struct" => switch (ip.indexToKey(ty.toIntern())) {
+ .struct_type => {
+ const struct_type = ip.loadStructType(ty.toIntern());
+ if (struct_type.layout == .@"packed") return;
+ var it = struct_type.iterateRuntimeOrder(ip);
+ var member: u32 = 0;
+ while (it.next()) |field_index| {
+ const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
+ if (!field_ty.hasRuntimeBits(zcu)) continue;
+ const offset: u32 = @intCast(ty.structFieldOffset(field_index, zcu));
+ try cg.module.decorateMember(ty_id, member, .{ .offset = .{ .byte_offset = offset } });
+ try cg.decorateLayout(field_ty, try cg.resolveType(field_ty, .indirect));
+ member += 1;
+ }
+ },
+ .tuple_type => |tuple| {
+ for (tuple.types.get(ip), tuple.values.get(ip)) |field_ty, field_val| {
+ if (field_val != .none) continue;
+ const ft: Type = .fromInterned(field_ty);
+ if (ft.hasRuntimeBits(zcu)) try cg.decorateLayout(ft, try cg.resolveType(ft, .indirect));
+ }
+ },
+ else => {},
+ },
+ .@"union" => {
+ const union_obj = zcu.typeToUnion(ty).?;
+ if (union_obj.layout == .@"packed") return;
+ const layout = cg.unionLayout(ty);
+ if (layout.tag_size != 0) {
+ const tag_ty: Type = .fromInterned(union_obj.enum_tag_type);
+ try cg.decorateLayout(tag_ty, try cg.resolveType(tag_ty, .indirect));
+ }
+ if (layout.has_payload) {
+ try cg.decorateLayout(layout.payload_ty, try cg.resolveType(layout.payload_ty, .indirect));
+ }
+ const u8_id = try cg.resolveType(.u8, .direct);
+ if (layout.payload_padding_size != 0) {
+ const len_id = try cg.constInt(.u32, layout.payload_padding_size);
+ const arr_id = try cg.module.arrayType(len_id, u8_id);
+ try cg.module.decorate(arr_id, .{ .array_stride = .{ .array_stride = 1 } });
+ }
+ if (layout.padding_size != 0) {
+ const len_id = try cg.constInt(.u32, layout.padding_size);
+ const arr_id = try cg.module.arrayType(len_id, u8_id);
+ try cg.module.decorate(arr_id, .{ .array_stride = .{ .array_stride = 1 } });
+ }
+ },
+ .optional => {
+ const payload_ty = ty.optionalChild(zcu);
+ if (payload_ty.hasRuntimeBits(zcu)) try cg.decorateLayout(payload_ty, try cg.resolveType(payload_ty, .indirect));
+ },
+ .error_union => {
+ const payload_ty = ty.errorUnionPayload(zcu);
+ if (payload_ty.hasRuntimeBits(zcu)) try cg.decorateLayout(payload_ty, try cg.resolveType(payload_ty, .indirect));
+ },
+ else => {},
}
}
@@ -1408,18 +1470,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
return try cg.module.arrayType(len_id, elem_ty_id);
} else {
const total_len_id = try cg.constInt(.u32, total_len);
- const result_id = try cg.module.arrayType(total_len_id, elem_ty_id);
- switch (target.os.tag) {
- .vulkan, .opengl => {
- try cg.module.decorate(result_id, .{
- .array_stride = .{
- .array_stride = @intCast(elem_ty.abiSize(zcu)),
- },
- });
- },
- else => {},
- }
- return result_id;
+ return try cg.module.arrayType(total_len_id, elem_ty_id);
}
},
.vector => {
@@ -2518,11 +2569,12 @@ fn generateTestEntryPoint(
const spv_err_decl_index = try cg.module.allocDecl(.global);
const err_buf_result_id = cg.module.declPtr(spv_err_decl_index).result_id;
- const buffer_struct_ty_id = try cg.module.structType(
- &.{anyerror_ty_id},
- &.{"error_out"},
- .none,
- );
+ const buffer_struct_ty_id = cg.module.allocId();
+ try cg.module.sections.globals.emit(gpa, .OpTypeStruct, .{
+ .id_result = buffer_struct_ty_id,
+ .id_ref = &.{anyerror_ty_id},
+ });
+ try cg.module.memberDebugName(buffer_struct_ty_id, 0, "error_out");
try cg.module.decorate(buffer_struct_ty_id, .block);
try cg.module.decorateMember(buffer_struct_ty_id, 0, .{ .offset = .{ .byte_offset = 0 } });
@@ -2794,6 +2846,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) Error!void {
.slice_ptr => try cg.airSliceField(inst, 0),
.slice_len => try cg.airSliceField(inst, 1),
+ .ptr_slice_ptr_ptr => try cg.airStructFieldPtrIndex(inst, 0),
+ .ptr_slice_len_ptr => try cg.airStructFieldPtrIndex(inst, 1),
.spirv_runtime_array_len => try cg.airSpirvRuntimeArrayLen(inst),
.slice_elem_ptr => try cg.airSliceElemPtr(inst),
.slice_elem_val => try cg.airSliceElemVal(inst),
@@ -4052,6 +4106,7 @@ fn airBitCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
return try result.materialize(cg);
}
const operand_id = try cg.resolve(ty_op.operand);
+ if (cg.virtual_allocas.contains(operand_id)) return operand_id;
return try cg.bitCast(result_ty, operand_ty, operand_id);
}
@@ -4881,6 +4936,21 @@ fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
const target = zcu.getTarget();
const ptr_ty = cg.typeOfIndex(inst);
const child_ty = ptr_ty.childType(zcu);
+
+ switch (target.os.tag) {
+ .vulkan, .opengl => {
+ if (child_ty.zigTypeTag(zcu) == .pointer and !child_ty.isSlice(zcu)) {
+ const as = child_ty.ptrAddressSpace(zcu);
+ if (cg.module.storageClass(as) == .function) {
+ const result_id = cg.module.allocId();
+ try cg.virtual_allocas.put(cg.module.gpa, result_id, null);
+ return result_id;
+ }
+ }
+ },
+ else => {},
+ }
+
const child_ty_id = try cg.resolveType(child_ty, .indirect);
const ptr_align = ptr_ty.ptrAlignment(zcu);
const result_id = try cg.alloc(child_ty_id, null);
@@ -5355,6 +5425,8 @@ fn airLoad(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
const operand = try cg.resolve(ty_op.operand);
if (!ptr_ty.isVolatilePtr(zcu) and cg.liveness.isUnused(inst)) return null;
+ if (cg.virtual_allocas.get(operand)) |stored| return stored.?;
+
return try cg.load(elem_ty, operand, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) });
}
@@ -5366,6 +5438,11 @@ fn airStore(cg: *CodeGen, inst: Air.Inst.Index) !void {
const ptr = try cg.resolve(bin_op.lhs);
const value = try cg.resolve(bin_op.rhs);
+ if (cg.virtual_allocas.getPtr(ptr)) |slot| {
+ slot.* = value;
+ return;
+ }
+
try cg.store(elem_ty, ptr, value, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) });
}
@@ -5933,6 +6010,7 @@ fn airDbgInlineBlock(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
fn airDbgVar(cg: *CodeGen, inst: Air.Inst.Index) !void {
const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
const target_id = try cg.resolve(pl_op.operand);
+ if (cg.virtual_allocas.contains(target_id)) return;
const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload);
try cg.module.debugName(target_id, name.toSlice(cg.air));
}
diff --git a/src/link/SpirV/lower_invocation_globals.zig b/src/link/SpirV/lower_invocation_globals.zig
@@ -46,6 +46,11 @@ const ModuleInfo = struct {
callee_store: []const ResultId,
/// Maps each invocation global result-id to a type-id.
invocation_globals: std.array_hash_map.Auto(ResultId, InvocationGlobal),
+ /// Subset of `invocation_globals` reachable from any entry point.
+ live_invocation_globals: std.array_hash_map.Auto(ResultId, void),
+ /// Initializer functions of unreachable invocation globals. Their
+ /// OpFunction...OpFunctionEnd ranges are skipped during rewriteFunctions.
+ dead_initializers: std.array_hash_map.Auto(ResultId, void),
/// Fetch the list of callees per function. Guaranteed to contain only unique IDs.
fn callees(self: ModuleInfo, fn_id: ResultId) []const ResultId {
@@ -196,6 +201,8 @@ const ModuleInfo = struct {
.entry_points = entry_points,
.callee_store = callee_store.items,
.invocation_globals = invocation_globals,
+ .live_invocation_globals = .empty,
+ .dead_initializers = .empty,
};
}
@@ -203,6 +210,25 @@ const ModuleInfo = struct {
fn resolve(self: *ModuleInfo, arena: Allocator) !void {
try self.resolveInvocationGlobalUsage(arena);
try self.resolveInvocationGlobalDependencies(arena);
+ try self.resolveLiveSet(arena);
+ }
+
+ fn resolveLiveSet(self: *ModuleInfo, arena: Allocator) !void {
+ for (self.entry_points.keys()) |ep_id| {
+ const ep_info = self.functions.get(ep_id) orelse continue;
+ for (ep_info.invocation_globals.keys()) |g| {
+ try self.live_invocation_globals.put(arena, g, {});
+ const g_info = self.invocation_globals.get(g).?;
+ for (g_info.dependencies.keys()) |dep| {
+ try self.live_invocation_globals.put(arena, dep, {});
+ }
+ }
+ }
+ for (self.invocation_globals.keys(), self.invocation_globals.values()) |g, info| {
+ if (info.initializer == .none) continue;
+ if (self.live_invocation_globals.contains(g)) continue;
+ try self.dead_initializers.put(arena, info.initializer, {});
+ }
}
/// For each function, extend the list of `invocation_globals` with the
@@ -385,6 +411,7 @@ const ModuleBuilder = struct {
.OpName => {
const id: ResultId = @enumFromInt(inst.operands[0]);
if (info.invocation_globals.contains(id)) continue;
+ if (info.dead_initializers.contains(id)) continue;
},
.OpExtInstImport => {
const set_id: ResultId = @enumFromInt(inst.operands[0]);
@@ -502,9 +529,14 @@ const ModuleBuilder = struct {
var operands = std.array_list.Managed(u32).init(self.arena);
var maybe_current_function: ?ResultId = null;
+ var skip_until_end: bool = false;
var it = binary.iterateInstructionsFrom(binary.sections.functions);
self.new_functions_section = self.section.instructions.items.len;
while (it.next()) |inst| {
+ if (skip_until_end) {
+ if (inst.opcode == .OpFunctionEnd) skip_until_end = false;
+ continue;
+ }
result_id_offsets.items.len = 0;
try parser.parseInstructionResultIds(binary, inst, &result_id_offsets);
@@ -527,6 +559,10 @@ const ModuleBuilder = struct {
.OpFunction => {
// Re-declare the function with the new parameters.
const func: ResultId = @enumFromInt(operands.items[1]);
+ if (info.dead_initializers.contains(func)) {
+ skip_until_end = true;
+ continue;
+ }
const fn_info = info.functions.get(func).?;
const new_info = self.function_new_info.get(func).?;