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:31+01:00
log87448654256d334e1d5877736a9747de67b7eedf
tree6fc66db83d3f69a2bb69006434872955144d73ed
parent377a8b2a3b408dad4392a97254a7a6bb2d06b200

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
...@@ -4376,8 +4376,9 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com...@@ -4376,8 +4376,9 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
4376 if (zcu.intern_pool.isFuncBody(val)) {4376 if (zcu.intern_pool.isFuncBody(val)) {
4377 const ty: Type = .fromInterned(zcu.intern_pool.typeOf(val));4377 const ty: Type = .fromInterned(zcu.intern_pool.typeOf(val));
4378 if (try ty.fnHasRuntimeBitsSema(pt)) {4378 if (try ty.fnHasRuntimeBitsSema(pt)) {
4379 try sema.addReferenceEntry(block, src, AnalUnit.wrap(.{ .func = val }));4379 const orig_fn_index = zcu.intern_pool.unwrapCoercedFunc(val);
4380 try zcu.ensureFuncBodyAnalysisQueued(val);4380 try sema.addReferenceEntry(block, src, .wrap(.{ .func = orig_fn_index }));
4381 try zcu.ensureFuncBodyAnalysisQueued(orig_fn_index);
4381 }4382 }
4382 }4383 }
43834384
...@@ -5589,16 +5590,21 @@ fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void...@@ -5589,16 +5590,21 @@ fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
5589 }5590 }
55905591
5591 try sema.ensureMemoizedStateResolved(src, .panic);5592 try sema.ensureMemoizedStateResolved(src, .panic);
5592 try zcu.ensureFuncBodyAnalysisQueued(zcu.builtin_decl_values.get(.@"panic.call"));5593 const panic_fn_index = zcu.builtin_decl_values.get(.@"panic.call");
5593
5594 const panic_fn = Air.internedToRef(zcu.builtin_decl_values.get(.@"panic.call"));
5595
5596 const opt_usize_ty = try pt.optionalType(.usize_type);5594 const opt_usize_ty = try pt.optionalType(.usize_type);
5597 const null_ret_addr = Air.internedToRef((try pt.intern(.{ .opt = .{5595 const null_ret_addr = Air.internedToRef((try pt.intern(.{ .opt = .{
5598 .ty = opt_usize_ty.toIntern(),5596 .ty = opt_usize_ty.toIntern(),
5599 .val = .none,5597 .val = .none,
5600 } })));5598 } })));
5601 try sema.callBuiltin(block, src, panic_fn, .auto, &.{ coerced_msg, null_ret_addr }, .@"@panic");5599 // `callBuiltin` also calls `addReferenceEntry` to the function body for us.
5600 try sema.callBuiltin(
5601 block,
5602 src,
5603 .fromIntern(panic_fn_index),
5604 .auto,
5605 &.{ coerced_msg, null_ret_addr },
5606 .@"@panic",
5607 );
5602}5608}
56035609
5604fn zirTrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {5610fn zirTrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
...@@ -7567,8 +7573,9 @@ fn analyzeCall(...@@ -7567,8 +7573,9 @@ fn analyzeCall(
7567 ref_func: {7573 ref_func: {
7568 const runtime_func_val = try sema.resolveValue(runtime_func) orelse break :ref_func;7574 const runtime_func_val = try sema.resolveValue(runtime_func) orelse break :ref_func;
7569 if (!ip.isFuncBody(runtime_func_val.toIntern())) break :ref_func;7575 if (!ip.isFuncBody(runtime_func_val.toIntern())) break :ref_func;
7570 try sema.addReferenceEntry(block, call_src, .wrap(.{ .func = runtime_func_val.toIntern() }));7576 const orig_fn_index = ip.unwrapCoercedFunc(runtime_func_val.toIntern());
7571 try zcu.ensureFuncBodyAnalysisQueued(runtime_func_val.toIntern());7577 try sema.addReferenceEntry(block, call_src, .wrap(.{ .func = orig_fn_index }));
7578 try zcu.ensureFuncBodyAnalysisQueued(orig_fn_index);
7572 }7579 }
75737580
7574 const call_tag: Air.Inst.Tag = switch (modifier) {7581 const call_tag: Air.Inst.Tag = switch (modifier) {
...@@ -26383,23 +26390,27 @@ fn explainWhyTypeIsNotPacked(...@@ -26383,23 +26390,27 @@ fn explainWhyTypeIsNotPacked(
26383/// instructions. This function ensures the panic function will be available to26390/// instructions. This function ensures the panic function will be available to
26384/// be called during that time.26391/// be called during that time.
26385fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !void {26392fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !void {
26393 const zcu = sema.pt.zcu;
26394
26386 // If the backend doesn't support `.panic_fn`, it doesn't want us to lower the panic handlers.26395 // If the backend doesn't support `.panic_fn`, it doesn't want us to lower the panic handlers.
26387 // The backend will transform panics into traps instead.26396 // The backend will transform panics into traps instead.
26388 if (sema.pt.zcu.backendSupportsFeature(.panic_fn)) {26397 if (!zcu.backendSupportsFeature(.panic_fn)) return;
26389 _ = try sema.getPanicIdFunc(src, panic_id);26398
26390 }26399 const fn_index = try sema.getPanicIdFunc(src, panic_id);
26400 const orig_fn_index = zcu.intern_pool.unwrapCoercedFunc(fn_index);
26401 try sema.addReferenceEntry(null, src, .wrap(.{ .func = orig_fn_index }));
26402 try zcu.ensureFuncBodyAnalysisQueued(orig_fn_index);
26391}26403}
2639226404
26393fn getPanicIdFunc(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index {26405fn getPanicIdFunc(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index {
26394 const zcu = sema.pt.zcu;26406 const zcu = sema.pt.zcu;
26395 try sema.ensureMemoizedStateResolved(src, .panic);26407 try sema.ensureMemoizedStateResolved(src, .panic);
26396 const panic_func = zcu.builtin_decl_values.get(panic_id.toBuiltin());26408 const panic_fn_index = zcu.builtin_decl_values.get(panic_id.toBuiltin());
26397 try zcu.ensureFuncBodyAnalysisQueued(panic_func);
26398 switch (sema.owner.unwrap()) {26409 switch (sema.owner.unwrap()) {
26399 .@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {},26410 .@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {},
26400 .func => |owner_func| zcu.intern_pool.funcSetHasErrorTrace(owner_func, true),26411 .func => |owner_func| zcu.intern_pool.funcSetHasErrorTrace(owner_func, true),
26401 }26412 }
26402 return panic_func;26413 return panic_fn_index;
26403}26414}
2640426415
26405fn addSafetyCheck(26416fn addSafetyCheck(
...@@ -31143,6 +31154,11 @@ fn addReferenceEntry(...@@ -31143,6 +31154,11 @@ fn addReferenceEntry(
31143 referenced_unit: AnalUnit,31154 referenced_unit: AnalUnit,
31144) !void {31155) !void {
31145 const zcu = sema.pt.zcu;31156 const zcu = sema.pt.zcu;
31157 const ip = &zcu.intern_pool;
31158 switch (referenced_unit.unwrap()) {
31159 .func => |f| assert(ip.unwrapCoercedFunc(f) == f), // for `.{ .func = f }`, `f` must be uncoerced
31160 else => {},
31161 }
31146 if (!zcu.comp.incremental and zcu.comp.reference_trace == 0) return;31162 if (!zcu.comp.incremental and zcu.comp.reference_trace == 0) return;
31147 const gop = try sema.references.getOrPut(sema.gpa, referenced_unit);31163 const gop = try sema.references.getOrPut(sema.gpa, referenced_unit);
31148 if (gop.found_existing) return;31164 if (gop.found_existing) return;
...@@ -31329,8 +31345,9 @@ fn maybeQueueFuncBodyAnalysis(sema: *Sema, block: *Block, src: LazySrcLoc, nav_i...@@ -31329,8 +31345,9 @@ fn maybeQueueFuncBodyAnalysis(sema: *Sema, block: *Block, src: LazySrcLoc, nav_i
31329 const nav_val = zcu.navValue(nav_index);31345 const nav_val = zcu.navValue(nav_index);
31330 if (!ip.isFuncBody(nav_val.toIntern())) return;31346 if (!ip.isFuncBody(nav_val.toIntern())) return;
3133131347
31332 try sema.addReferenceEntry(block, src, AnalUnit.wrap(.{ .func = nav_val.toIntern() }));31348 const orig_fn_index = ip.unwrapCoercedFunc(nav_val.toIntern());
31333 try zcu.ensureFuncBodyAnalysisQueued(nav_val.toIntern());31349 try sema.addReferenceEntry(block, src, .wrap(.{ .func = orig_fn_index }));
31350 try zcu.ensureFuncBodyAnalysisQueued(orig_fn_index);
31334}31351}
3133531352
31336fn analyzeRef(31353fn analyzeRef(
...@@ -34963,8 +34980,9 @@ fn resolveInferredErrorSet(...@@ -34963,8 +34980,9 @@ fn resolveInferredErrorSet(
34963 }34980 }
34964 // In this case we are dealing with the actual InferredErrorSet object that34981 // In this case we are dealing with the actual InferredErrorSet object that
34965 // corresponds to the function, not one created to track an inline/comptime call.34982 // corresponds to the function, not one created to track an inline/comptime call.
34966 try sema.addReferenceEntry(block, src, AnalUnit.wrap(.{ .func = func_index }));34983 const orig_func_index = ip.unwrapCoercedFunc(func_index);
34967 try pt.ensureFuncBodyUpToDate(func_index);34984 try sema.addReferenceEntry(block, src, .wrap(.{ .func = orig_func_index }));
34985 try pt.ensureFuncBodyUpToDate(orig_func_index);
34968 }34986 }
3496934987
34970 // This will now have been resolved by the logic at the end of `Zcu.analyzeFnBody`34988 // 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