authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-06-09 21:14:36+03:30
committergravatar for alichraghi@noreply.codeberg.orgAli Cheraghi <alichraghi@noreply.codeberg.org> 2026-06-14 09:11:59+02:00
log88d2961df475806a73af0f2eb4e4076159dc7f61
tree1e1d474a5c1c2f2c0a75cda00b9cd22a49e96bb5
parent281991328ed25c049c1b86b21fd72815cb0959d8

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`.

3 files changed, 157 insertions(+), 45 deletions(-)

lib/std/start.zig+2-4
......@@ -19,9 +19,7 @@ comptime {
1919 // decls there get run.
2020 _ = root;
2121
22 if (builtin.zig_backend == .stage2_spirv) {
23 // Do nothing
24 } else if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) {
22 if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) {
2523 const dll_main_crt_startup = if (builtin.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup";
2624 if (native_os == .windows and !builtin.link_libc and !@hasDecl(root, dll_main_crt_startup)) {
2725 @export(&DllMainCRTStartup, .{ .name = dll_main_crt_startup });
......@@ -72,7 +70,7 @@ comptime {
7270 // case it's not required to provide an entrypoint such as main.
7371 if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(&wasm_freestanding_start, .{ .name = start_sym_name });
7472 } else switch (native_os) {
75 .other, .freestanding, .@"3ds", .psp, .vita => {},
73 .other, .freestanding, .@"3ds", .psp, .vita, .vulkan, .opengl, .opencl => {},
7674 else => if (!@hasDecl(root, start_sym_name)) @export(&_start, .{ .name = start_sym_name }),
7775 }
7876 }
src/codegen/spirv/CodeGen.zig+119-41
......@@ -152,6 +152,7 @@ base_line: u32,
152152block_label: Id = .none,
153153next_arg_index: u32 = 0,
154154args: std.ArrayList(Id) = .empty,
155virtual_allocas: std.AutoHashMapUnmanaged(Id, ?Id) = .empty,
155156inst_results: std.AutoHashMapUnmanaged(Air.Inst.Index, Id) = .empty,
156157id_scratch: std.ArrayList(Id) = .empty,
157158prologue: Section = .{},
......@@ -161,6 +162,7 @@ pub fn deinit(cg: *CodeGen) void {
161162 const gpa = cg.module.gpa;
162163 cg.control_flow.deinit(gpa);
163164 cg.args.deinit(gpa);
165 cg.virtual_allocas.deinit(gpa);
164166 cg.inst_results.deinit(gpa);
165167 cg.id_scratch.deinit(gpa);
166168 cg.prologue.deinit(gpa);
......@@ -269,23 +271,19 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {
269271
270272 switch (target.os.tag) {
271273 .vulkan, .opengl => {
272 if (ty.zigTypeTag(zcu) == .@"struct") {
273 switch (storage_class) {
274 .uniform,
275 .push_constant,
276 .storage_buffer,
277 => {
274 switch (storage_class) {
275 .uniform, .push_constant, .storage_buffer, .physical_storage_buffer => {
276 if (ty.zigTypeTag(zcu) == .@"struct" and storage_class != .physical_storage_buffer) {
278277 try cg.module.decorate(ty_id, .block);
279 try cg.decorateBlockOffsets(ty, ty_id);
280 },
281 else => {},
282 }
278 }
279 try cg.module.decorate(ptr_ty_id, .{
280 .array_stride = .{ .array_stride = @intCast(ty.abiSize(zcu)) },
281 });
282 try cg.decorateLayout(ty, ty_id);
283 },
284 else => {},
283285 }
284286
285 try cg.module.decorate(ptr_ty_id, .{
286 .array_stride = .{ .array_stride = @intCast(ty.abiSize(zcu)) },
287 });
288
289287 if (key.decoration) |decoration| switch (decoration) {
290288 .location => |location| {
291289 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 {
378376 cg.module.declPtr(spv_decl_index).end_dep = cg.module.decl_deps.items.len;
379377}
380378
381fn decorateBlockOffsets(cg: *CodeGen, ty: Type, ty_id: spec.Id) !void {
379fn decorateLayout(cg: *CodeGen, ty: Type, ty_id: spec.Id) Error!void {
382380 const zcu = cg.module.zcu;
383381 const ip = &zcu.intern_pool;
384 const struct_type = ip.loadStructType(ty.toIntern());
385 var it = struct_type.iterateRuntimeOrder(ip);
386 var member: u32 = 0;
387 while (it.next()) |field_index| {
388 const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
389 if (!field_ty.hasRuntimeBits(zcu)) continue;
390 const offset: u32 = @intCast(ty.structFieldOffset(field_index, zcu));
391 try cg.module.decorateMember(ty_id, member, .{ .offset = .{ .byte_offset = offset } });
392 member += 1;
382 switch (ty.zigTypeTag(zcu)) {
383 .array => {
384 const elem_ty = ty.childType(zcu);
385 if (!elem_ty.hasRuntimeBits(zcu)) return;
386 try cg.module.decorate(ty_id, .{
387 .array_stride = .{ .array_stride = @intCast(elem_ty.abiSize(zcu)) },
388 });
389 try cg.decorateLayout(elem_ty, try cg.resolveType(elem_ty, .indirect));
390 },
391 .vector => {
392 const elem_ty = ty.childType(zcu);
393 try cg.decorateLayout(elem_ty, try cg.resolveType(elem_ty, .indirect));
394 if (cg.isSpvVector(ty)) return;
395 try cg.module.decorate(ty_id, .{
396 .array_stride = .{ .array_stride = @intCast(elem_ty.abiSize(zcu)) },
397 });
398 },
399 .@"struct" => switch (ip.indexToKey(ty.toIntern())) {
400 .struct_type => {
401 const struct_type = ip.loadStructType(ty.toIntern());
402 if (struct_type.layout == .@"packed") return;
403 var it = struct_type.iterateRuntimeOrder(ip);
404 var member: u32 = 0;
405 while (it.next()) |field_index| {
406 const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
407 if (!field_ty.hasRuntimeBits(zcu)) continue;
408 const offset: u32 = @intCast(ty.structFieldOffset(field_index, zcu));
409 try cg.module.decorateMember(ty_id, member, .{ .offset = .{ .byte_offset = offset } });
410 try cg.decorateLayout(field_ty, try cg.resolveType(field_ty, .indirect));
411 member += 1;
412 }
413 },
414 .tuple_type => |tuple| {
415 for (tuple.types.get(ip), tuple.values.get(ip)) |field_ty, field_val| {
416 if (field_val != .none) continue;
417 const ft: Type = .fromInterned(field_ty);
418 if (ft.hasRuntimeBits(zcu)) try cg.decorateLayout(ft, try cg.resolveType(ft, .indirect));
419 }
420 },
421 else => {},
422 },
423 .@"union" => {
424 const union_obj = zcu.typeToUnion(ty).?;
425 if (union_obj.layout == .@"packed") return;
426 const layout = cg.unionLayout(ty);
427 if (layout.tag_size != 0) {
428 const tag_ty: Type = .fromInterned(union_obj.enum_tag_type);
429 try cg.decorateLayout(tag_ty, try cg.resolveType(tag_ty, .indirect));
430 }
431 if (layout.has_payload) {
432 try cg.decorateLayout(layout.payload_ty, try cg.resolveType(layout.payload_ty, .indirect));
433 }
434 const u8_id = try cg.resolveType(.u8, .direct);
435 if (layout.payload_padding_size != 0) {
436 const len_id = try cg.constInt(.u32, layout.payload_padding_size);
437 const arr_id = try cg.module.arrayType(len_id, u8_id);
438 try cg.module.decorate(arr_id, .{ .array_stride = .{ .array_stride = 1 } });
439 }
440 if (layout.padding_size != 0) {
441 const len_id = try cg.constInt(.u32, layout.padding_size);
442 const arr_id = try cg.module.arrayType(len_id, u8_id);
443 try cg.module.decorate(arr_id, .{ .array_stride = .{ .array_stride = 1 } });
444 }
445 },
446 .optional => {
447 const payload_ty = ty.optionalChild(zcu);
448 if (payload_ty.hasRuntimeBits(zcu)) try cg.decorateLayout(payload_ty, try cg.resolveType(payload_ty, .indirect));
449 },
450 .error_union => {
451 const payload_ty = ty.errorUnionPayload(zcu);
452 if (payload_ty.hasRuntimeBits(zcu)) try cg.decorateLayout(payload_ty, try cg.resolveType(payload_ty, .indirect));
453 },
454 else => {},
393455 }
394456}
395457
......@@ -1408,18 +1470,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
14081470 return try cg.module.arrayType(len_id, elem_ty_id);
14091471 } else {
14101472 const total_len_id = try cg.constInt(.u32, total_len);
1411 const result_id = try cg.module.arrayType(total_len_id, elem_ty_id);
1412 switch (target.os.tag) {
1413 .vulkan, .opengl => {
1414 try cg.module.decorate(result_id, .{
1415 .array_stride = .{
1416 .array_stride = @intCast(elem_ty.abiSize(zcu)),
1417 },
1418 });
1419 },
1420 else => {},
1421 }
1422 return result_id;
1473 return try cg.module.arrayType(total_len_id, elem_ty_id);
14231474 }
14241475 },
14251476 .vector => {
......@@ -2518,11 +2569,12 @@ fn generateTestEntryPoint(
25182569 const spv_err_decl_index = try cg.module.allocDecl(.global);
25192570 const err_buf_result_id = cg.module.declPtr(spv_err_decl_index).result_id;
25202571
2521 const buffer_struct_ty_id = try cg.module.structType(
2522 &.{anyerror_ty_id},
2523 &.{"error_out"},
2524 .none,
2525 );
2572 const buffer_struct_ty_id = cg.module.allocId();
2573 try cg.module.sections.globals.emit(gpa, .OpTypeStruct, .{
2574 .id_result = buffer_struct_ty_id,
2575 .id_ref = &.{anyerror_ty_id},
2576 });
2577 try cg.module.memberDebugName(buffer_struct_ty_id, 0, "error_out");
25262578 try cg.module.decorate(buffer_struct_ty_id, .block);
25272579 try cg.module.decorateMember(buffer_struct_ty_id, 0, .{ .offset = .{ .byte_offset = 0 } });
25282580
......@@ -2794,6 +2846,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) Error!void {
27942846
27952847 .slice_ptr => try cg.airSliceField(inst, 0),
27962848 .slice_len => try cg.airSliceField(inst, 1),
2849 .ptr_slice_ptr_ptr => try cg.airStructFieldPtrIndex(inst, 0),
2850 .ptr_slice_len_ptr => try cg.airStructFieldPtrIndex(inst, 1),
27972851 .spirv_runtime_array_len => try cg.airSpirvRuntimeArrayLen(inst),
27982852 .slice_elem_ptr => try cg.airSliceElemPtr(inst),
27992853 .slice_elem_val => try cg.airSliceElemVal(inst),
......@@ -4052,6 +4106,7 @@ fn airBitCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
40524106 return try result.materialize(cg);
40534107 }
40544108 const operand_id = try cg.resolve(ty_op.operand);
4109 if (cg.virtual_allocas.contains(operand_id)) return operand_id;
40554110 return try cg.bitCast(result_ty, operand_ty, operand_id);
40564111}
40574112
......@@ -4881,6 +4936,21 @@ fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
48814936 const target = zcu.getTarget();
48824937 const ptr_ty = cg.typeOfIndex(inst);
48834938 const child_ty = ptr_ty.childType(zcu);
4939
4940 switch (target.os.tag) {
4941 .vulkan, .opengl => {
4942 if (child_ty.zigTypeTag(zcu) == .pointer and !child_ty.isSlice(zcu)) {
4943 const as = child_ty.ptrAddressSpace(zcu);
4944 if (cg.module.storageClass(as) == .function) {
4945 const result_id = cg.module.allocId();
4946 try cg.virtual_allocas.put(cg.module.gpa, result_id, null);
4947 return result_id;
4948 }
4949 }
4950 },
4951 else => {},
4952 }
4953
48844954 const child_ty_id = try cg.resolveType(child_ty, .indirect);
48854955 const ptr_align = ptr_ty.ptrAlignment(zcu);
48864956 const result_id = try cg.alloc(child_ty_id, null);
......@@ -5355,6 +5425,8 @@ fn airLoad(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
53555425 const operand = try cg.resolve(ty_op.operand);
53565426 if (!ptr_ty.isVolatilePtr(zcu) and cg.liveness.isUnused(inst)) return null;
53575427
5428 if (cg.virtual_allocas.get(operand)) |stored| return stored.?;
5429
53585430 return try cg.load(elem_ty, operand, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) });
53595431}
53605432
......@@ -5366,6 +5438,11 @@ fn airStore(cg: *CodeGen, inst: Air.Inst.Index) !void {
53665438 const ptr = try cg.resolve(bin_op.lhs);
53675439 const value = try cg.resolve(bin_op.rhs);
53685440
5441 if (cg.virtual_allocas.getPtr(ptr)) |slot| {
5442 slot.* = value;
5443 return;
5444 }
5445
53695446 try cg.store(elem_ty, ptr, value, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) });
53705447}
53715448
......@@ -5933,6 +6010,7 @@ fn airDbgInlineBlock(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
59336010fn airDbgVar(cg: *CodeGen, inst: Air.Inst.Index) !void {
59346011 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
59356012 const target_id = try cg.resolve(pl_op.operand);
6013 if (cg.virtual_allocas.contains(target_id)) return;
59366014 const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload);
59376015 try cg.module.debugName(target_id, name.toSlice(cg.air));
59386016}
src/link/SpirV/lower_invocation_globals.zig+36
......@@ -46,6 +46,11 @@ const ModuleInfo = struct {
4646 callee_store: []const ResultId,
4747 /// Maps each invocation global result-id to a type-id.
4848 invocation_globals: std.array_hash_map.Auto(ResultId, InvocationGlobal),
49 /// Subset of `invocation_globals` reachable from any entry point.
50 live_invocation_globals: std.array_hash_map.Auto(ResultId, void),
51 /// Initializer functions of unreachable invocation globals. Their
52 /// OpFunction...OpFunctionEnd ranges are skipped during rewriteFunctions.
53 dead_initializers: std.array_hash_map.Auto(ResultId, void),
4954
5055 /// Fetch the list of callees per function. Guaranteed to contain only unique IDs.
5156 fn callees(self: ModuleInfo, fn_id: ResultId) []const ResultId {
......@@ -196,6 +201,8 @@ const ModuleInfo = struct {
196201 .entry_points = entry_points,
197202 .callee_store = callee_store.items,
198203 .invocation_globals = invocation_globals,
204 .live_invocation_globals = .empty,
205 .dead_initializers = .empty,
199206 };
200207 }
201208
......@@ -203,6 +210,25 @@ const ModuleInfo = struct {
203210 fn resolve(self: *ModuleInfo, arena: Allocator) !void {
204211 try self.resolveInvocationGlobalUsage(arena);
205212 try self.resolveInvocationGlobalDependencies(arena);
213 try self.resolveLiveSet(arena);
214 }
215
216 fn resolveLiveSet(self: *ModuleInfo, arena: Allocator) !void {
217 for (self.entry_points.keys()) |ep_id| {
218 const ep_info = self.functions.get(ep_id) orelse continue;
219 for (ep_info.invocation_globals.keys()) |g| {
220 try self.live_invocation_globals.put(arena, g, {});
221 const g_info = self.invocation_globals.get(g).?;
222 for (g_info.dependencies.keys()) |dep| {
223 try self.live_invocation_globals.put(arena, dep, {});
224 }
225 }
226 }
227 for (self.invocation_globals.keys(), self.invocation_globals.values()) |g, info| {
228 if (info.initializer == .none) continue;
229 if (self.live_invocation_globals.contains(g)) continue;
230 try self.dead_initializers.put(arena, info.initializer, {});
231 }
206232 }
207233
208234 /// For each function, extend the list of `invocation_globals` with the
......@@ -385,6 +411,7 @@ const ModuleBuilder = struct {
385411 .OpName => {
386412 const id: ResultId = @enumFromInt(inst.operands[0]);
387413 if (info.invocation_globals.contains(id)) continue;
414 if (info.dead_initializers.contains(id)) continue;
388415 },
389416 .OpExtInstImport => {
390417 const set_id: ResultId = @enumFromInt(inst.operands[0]);
......@@ -502,9 +529,14 @@ const ModuleBuilder = struct {
502529 var operands = std.array_list.Managed(u32).init(self.arena);
503530
504531 var maybe_current_function: ?ResultId = null;
532 var skip_until_end: bool = false;
505533 var it = binary.iterateInstructionsFrom(binary.sections.functions);
506534 self.new_functions_section = self.section.instructions.items.len;
507535 while (it.next()) |inst| {
536 if (skip_until_end) {
537 if (inst.opcode == .OpFunctionEnd) skip_until_end = false;
538 continue;
539 }
508540 result_id_offsets.items.len = 0;
509541 try parser.parseInstructionResultIds(binary, inst, &result_id_offsets);
510542
......@@ -527,6 +559,10 @@ const ModuleBuilder = struct {
527559 .OpFunction => {
528560 // Re-declare the function with the new parameters.
529561 const func: ResultId = @enumFromInt(operands.items[1]);
562 if (info.dead_initializers.contains(func)) {
563 skip_until_end = true;
564 continue;
565 }
530566 const fn_info = info.functions.get(func).?;
531567 const new_info = self.function_new_info.get(func).?;
532568