authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-14 11:10:46+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-15 11:29:54+01:00
log4cfb58342ab40adc1a179bfde9fcb116f47bb9d7
tree07dc857d4d9656ef97f10fa1a89ae8b440f00918
parent965b2ab6c370c5bbb4e1ea2ba660125f889f572c
signaturelock-open Commit is signed but in an unrecognized format.

frontend: fix reference tracking through coerced function bodies

This bug was manifesting for user as a nasty link error because they were calling their application's main entry point as a coerced function, which essentially broke reference tracking for the entire ZCU, causing exported symbols to silently not get exported. I've been a little unsure about how coerced functions should interact with the unit graph before, but the solution is actually really obvious now: they shouldn't! `Sema` is now responsible for unwrapping possibly-coerced functions *before* queuing analysis or marking unit references. This makes the reference graph optimal (there are no redundant edges representing coerced versions of the same function) and simplifies logic elsewhere at the expense of just a few lines in Sema.

4 files changed, 64 insertions(+), 24 deletions(-)

src/Sema.zig+37-19
...@@ -4375,8 +4375,9 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com...@@ -4375,8 +4375,9 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
4375 if (zcu.intern_pool.isFuncBody(val)) {4375 if (zcu.intern_pool.isFuncBody(val)) {
4376 const ty: Type = .fromInterned(zcu.intern_pool.typeOf(val));4376 const ty: Type = .fromInterned(zcu.intern_pool.typeOf(val));
4377 if (try ty.fnHasRuntimeBitsSema(pt)) {4377 if (try ty.fnHasRuntimeBitsSema(pt)) {
4378 try sema.addReferenceEntry(block, src, AnalUnit.wrap(.{ .func = val }));4378 const orig_fn_index = zcu.intern_pool.unwrapCoercedFunc(val);
4379 try zcu.ensureFuncBodyAnalysisQueued(val);4379 try sema.addReferenceEntry(block, src, .wrap(.{ .func = orig_fn_index }));
4380 try zcu.ensureFuncBodyAnalysisQueued(orig_fn_index);
4380 }4381 }
4381 }4382 }
43824383
...@@ -5588,16 +5589,21 @@ fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void...@@ -5588,16 +5589,21 @@ fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
5588 }5589 }
55895590
5590 try sema.ensureMemoizedStateResolved(src, .panic);5591 try sema.ensureMemoizedStateResolved(src, .panic);
5591 try zcu.ensureFuncBodyAnalysisQueued(zcu.builtin_decl_values.get(.@"panic.call"));5592 const panic_fn_index = zcu.builtin_decl_values.get(.@"panic.call");
5592
5593 const panic_fn = Air.internedToRef(zcu.builtin_decl_values.get(.@"panic.call"));
5594
5595 const opt_usize_ty = try pt.optionalType(.usize_type);5593 const opt_usize_ty = try pt.optionalType(.usize_type);
5596 const null_ret_addr = Air.internedToRef((try pt.intern(.{ .opt = .{5594 const null_ret_addr = Air.internedToRef((try pt.intern(.{ .opt = .{
5597 .ty = opt_usize_ty.toIntern(),5595 .ty = opt_usize_ty.toIntern(),
5598 .val = .none,5596 .val = .none,
5599 } })));5597 } })));
5600 try sema.callBuiltin(block, src, panic_fn, .auto, &.{ coerced_msg, null_ret_addr }, .@"@panic");5598 // `callBuiltin` also calls `addReferenceEntry` to the function body for us.
5599 try sema.callBuiltin(
5600 block,
5601 src,
5602 .fromIntern(panic_fn_index),
5603 .auto,
5604 &.{ coerced_msg, null_ret_addr },
5605 .@"@panic",
5606 );
5601}5607}
56025608
5603fn zirTrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {5609fn zirTrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
...@@ -7566,8 +7572,9 @@ fn analyzeCall(...@@ -7566,8 +7572,9 @@ fn analyzeCall(
7566 ref_func: {7572 ref_func: {
7567 const runtime_func_val = try sema.resolveValue(runtime_func) orelse break :ref_func;7573 const runtime_func_val = try sema.resolveValue(runtime_func) orelse break :ref_func;
7568 if (!ip.isFuncBody(runtime_func_val.toIntern())) break :ref_func;7574 if (!ip.isFuncBody(runtime_func_val.toIntern())) break :ref_func;
7569 try sema.addReferenceEntry(block, call_src, .wrap(.{ .func = runtime_func_val.toIntern() }));7575 const orig_fn_index = ip.unwrapCoercedFunc(runtime_func_val.toIntern());
7570 try zcu.ensureFuncBodyAnalysisQueued(runtime_func_val.toIntern());7576 try sema.addReferenceEntry(block, call_src, .wrap(.{ .func = orig_fn_index }));
7577 try zcu.ensureFuncBodyAnalysisQueued(orig_fn_index);
7571 }7578 }
75727579
7573 const call_tag: Air.Inst.Tag = switch (modifier) {7580 const call_tag: Air.Inst.Tag = switch (modifier) {
...@@ -26360,23 +26367,27 @@ fn explainWhyTypeIsNotPacked(...@@ -26360,23 +26367,27 @@ fn explainWhyTypeIsNotPacked(
26360/// instructions. This function ensures the panic function will be available to26367/// instructions. This function ensures the panic function will be available to
26361/// be called during that time.26368/// be called during that time.
26362fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !void {26369fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !void {
26370 const zcu = sema.pt.zcu;
26371
26363 // If the backend doesn't support `.panic_fn`, it doesn't want us to lower the panic handlers.26372 // If the backend doesn't support `.panic_fn`, it doesn't want us to lower the panic handlers.
26364 // The backend will transform panics into traps instead.26373 // The backend will transform panics into traps instead.
26365 if (sema.pt.zcu.backendSupportsFeature(.panic_fn)) {26374 if (!zcu.backendSupportsFeature(.panic_fn)) return;
26366 _ = try sema.getPanicIdFunc(src, panic_id);26375
26367 }26376 const fn_index = try sema.getPanicIdFunc(src, panic_id);
26377 const orig_fn_index = zcu.intern_pool.unwrapCoercedFunc(fn_index);
26378 try sema.addReferenceEntry(null, src, .wrap(.{ .func = orig_fn_index }));
26379 try zcu.ensureFuncBodyAnalysisQueued(orig_fn_index);
26368}26380}
2636926381
26370fn getPanicIdFunc(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index {26382fn getPanicIdFunc(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index {
26371 const zcu = sema.pt.zcu;26383 const zcu = sema.pt.zcu;
26372 try sema.ensureMemoizedStateResolved(src, .panic);26384 try sema.ensureMemoizedStateResolved(src, .panic);
26373 const panic_func = zcu.builtin_decl_values.get(panic_id.toBuiltin());26385 const panic_fn_index = zcu.builtin_decl_values.get(panic_id.toBuiltin());
26374 try zcu.ensureFuncBodyAnalysisQueued(panic_func);
26375 switch (sema.owner.unwrap()) {26386 switch (sema.owner.unwrap()) {
26376 .@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {},26387 .@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {},
26377 .func => |owner_func| zcu.intern_pool.funcSetHasErrorTrace(owner_func, true),26388 .func => |owner_func| zcu.intern_pool.funcSetHasErrorTrace(owner_func, true),
26378 }26389 }
26379 return panic_func;26390 return panic_fn_index;
26380}26391}
2638126392
26382fn addSafetyCheck(26393fn addSafetyCheck(
...@@ -31164,6 +31175,11 @@ fn addReferenceEntry(...@@ -31164,6 +31175,11 @@ fn addReferenceEntry(
31164 referenced_unit: AnalUnit,31175 referenced_unit: AnalUnit,
31165) !void {31176) !void {
31166 const zcu = sema.pt.zcu;31177 const zcu = sema.pt.zcu;
31178 const ip = &zcu.intern_pool;
31179 switch (referenced_unit.unwrap()) {
31180 .func => |f| assert(ip.unwrapCoercedFunc(f) == f), // for `.{ .func = f }`, `f` must be uncoerced
31181 else => {},
31182 }
31167 if (!zcu.comp.incremental and zcu.comp.reference_trace == 0) return;31183 if (!zcu.comp.incremental and zcu.comp.reference_trace == 0) return;
31168 const gop = try sema.references.getOrPut(sema.gpa, referenced_unit);31184 const gop = try sema.references.getOrPut(sema.gpa, referenced_unit);
31169 if (gop.found_existing) return;31185 if (gop.found_existing) return;
...@@ -31350,8 +31366,9 @@ fn maybeQueueFuncBodyAnalysis(sema: *Sema, block: *Block, src: LazySrcLoc, nav_i...@@ -31350,8 +31366,9 @@ fn maybeQueueFuncBodyAnalysis(sema: *Sema, block: *Block, src: LazySrcLoc, nav_i
31350 const nav_val = zcu.navValue(nav_index);31366 const nav_val = zcu.navValue(nav_index);
31351 if (!ip.isFuncBody(nav_val.toIntern())) return;31367 if (!ip.isFuncBody(nav_val.toIntern())) return;
3135231368
31353 try sema.addReferenceEntry(block, src, AnalUnit.wrap(.{ .func = nav_val.toIntern() }));31369 const orig_fn_index = ip.unwrapCoercedFunc(nav_val.toIntern());
31354 try zcu.ensureFuncBodyAnalysisQueued(nav_val.toIntern());31370 try sema.addReferenceEntry(block, src, .wrap(.{ .func = orig_fn_index }));
31371 try zcu.ensureFuncBodyAnalysisQueued(orig_fn_index);
31355}31372}
3135631373
31357fn analyzeRef(31374fn analyzeRef(
...@@ -34984,8 +35001,9 @@ fn resolveInferredErrorSet(...@@ -34984,8 +35001,9 @@ fn resolveInferredErrorSet(
34984 }35001 }
34985 // In this case we are dealing with the actual InferredErrorSet object that35002 // In this case we are dealing with the actual InferredErrorSet object that
34986 // corresponds to the function, not one created to track an inline/comptime call.35003 // corresponds to the function, not one created to track an inline/comptime call.
34987 try sema.addReferenceEntry(block, src, AnalUnit.wrap(.{ .func = func_index }));35004 const orig_func_index = ip.unwrapCoercedFunc(func_index);
34988 try pt.ensureFuncBodyUpToDate(func_index);35005 try sema.addReferenceEntry(block, src, .wrap(.{ .func = orig_func_index }));
35006 try pt.ensureFuncBodyUpToDate(orig_func_index);
34989 }35007 }
3499035008
34991 // This will now have been resolved by the logic at the end of `Zcu.analyzeFnBody`35009 // This will now have been resolved by the logic at the end of `Zcu.analyzeFnBody`
src/Zcu.zig+3
...@@ -3451,8 +3451,11 @@ pub fn mapOldZirToNew(...@@ -3451,8 +3451,11 @@ pub fn mapOldZirToNew(
3451/// will be analyzed when it returns: for that, see `ensureFuncBodyAnalyzed`.3451/// will be analyzed when it returns: for that, see `ensureFuncBodyAnalyzed`.
3452pub fn ensureFuncBodyAnalysisQueued(zcu: *Zcu, func_index: InternPool.Index) !void {3452pub fn ensureFuncBodyAnalysisQueued(zcu: *Zcu, func_index: InternPool.Index) !void {
3453 const ip = &zcu.intern_pool;3453 const ip = &zcu.intern_pool;
3454
3454 const func = zcu.funcInfo(func_index);3455 const func = zcu.funcInfo(func_index);
34553456
3457 assert(func.ty == func.uncoerced_ty); // analyze the body of the original function, not a coerced one
3458
3456 if (zcu.func_body_analysis_queued.contains(func_index)) return;3459 if (zcu.func_body_analysis_queued.contains(func_index)) return;
34573460
3458 if (func.analysisUnordered(ip).is_analyzed) {3461 if (func.analysisUnordered(ip).is_analyzed) {
src/Zcu/PerThread.zig+5-5
...@@ -1571,7 +1571,7 @@ fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileEr...@@ -1571,7 +1571,7 @@ fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileEr
1571 return .{ .type_changed = true };1571 return .{ .type_changed = true };
1572}1572}
15731573
1574pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void {1574pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!void {
1575 dev.check(.sema);1575 dev.check(.sema);
15761576
1577 const tracy = trace(@src());1577 const tracy = trace(@src());
...@@ -1581,15 +1581,15 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter...@@ -1581,15 +1581,15 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
1581 const gpa = zcu.gpa;1581 const gpa = zcu.gpa;
1582 const ip = &zcu.intern_pool;1582 const ip = &zcu.intern_pool;
15831583
1584 _ = zcu.func_body_analysis_queued.swapRemove(maybe_coerced_func_index);1584 _ = zcu.func_body_analysis_queued.swapRemove(func_index);
15851585
1586 // We only care about the uncoerced function.
1587 const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index);
1588 const anal_unit: AnalUnit = .wrap(.{ .func = func_index });1586 const anal_unit: AnalUnit = .wrap(.{ .func = func_index });
15891587
1590 log.debug("ensureFuncBodyUpToDate {f}", .{zcu.fmtAnalUnit(anal_unit)});1588 log.debug("ensureFuncBodyUpToDate {f}", .{zcu.fmtAnalUnit(anal_unit)});
15911589
1592 const func = zcu.funcInfo(maybe_coerced_func_index);1590 const func = zcu.funcInfo(func_index);
1591
1592 assert(func.ty == func.uncoerced_ty); // analyze the body of the original function, not a coerced one
15931593
1594 const was_outdated = zcu.outdated.swapRemove(anal_unit) or1594 const was_outdated = zcu.outdated.swapRemove(anal_unit) or
1595 zcu.potentially_outdated.swapRemove(anal_unit);1595 zcu.potentially_outdated.swapRemove(anal_unit);
test/cases/export_from_body_of_coerced_fn.zig created+19
...@@ -0,0 +1,19 @@
1fn original() usize {
2 _ = struct {
3 export const val: u32 = 123;
4 };
5 return 0;
6}
7
8pub fn main() void {
9 const coerced: fn () u64 = original;
10 _ = coerced();
11
12 const S = struct {
13 extern const val: u32;
14 };
15 if (S.val != 123) @panic("wrong value");
16}
17
18// run
19// target=x86_64-linux