zig

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

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:
Mlib/std/start.zig | 6++----
Msrc/codegen/spirv/CodeGen.zig | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/link/SpirV/lower_invocation_globals.zig | 36++++++++++++++++++++++++++++++++++++
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).?;