IC: bugfix

This commit is contained in:
Araq
2026-06-30 23:15:18 +02:00
parent 32989998e6
commit be0ce3f65c
4 changed files with 49 additions and 2 deletions

View File

@@ -198,8 +198,16 @@ proc ownsRuntimeRoutine(s: PSym; modPos: int): bool =
{sfForward, sfImportc, sfCompileTime, sfError} * s.flags == {} and
s.typ != nil and not signatureHasMetaType(s.typ) and
s.ast != nil and s.ast.safeLen > bodyPos and
s.ast[genericParamsPos].kind == nkEmpty and
s.ast[bodyPos].kind != nkEmpty
s.ast[genericParamsPos].kind == nkEmpty
# NOTE: an `nkEmpty` body is NOT a disqualifier. A concrete, owned, non-
# forward/-importc/-magic routine whose body folds to nothing is still a real
# definition the owner must emit (`void f(void){}`), exactly as whole-program
# cgen does — else a cross-module caller links to nothing. This bites e.g.
# Nimbus' `extras.incInternalErrors`, a plain `proc` whose sole statement is a
# metrics-counter `.inc()` that the `metrics` library expands to a no-op when
# the importing tool (ncli) builds with `-u:metrics`; the body is then a bare
# `nkEmpty`, but `state_transition_epoch` still calls it. Forward declarations
# (the other empty-body case) carry `sfForward` and are excluded above.
proc generateCodeForModule(g: ModuleGraph; precomp: PrecompiledModule) =
## Generate C code for a single module.

View File

@@ -0,0 +1,9 @@
# Calls `emptyOwned` from a DIFFERENT module than the one that owns it, so the
# reference at link time must resolve to a definition the owning module emits.
import memptyowned
proc callEmpty*(cond: bool) =
if cond:
emptyOwned()
echo "called ", cond

12
tests/ic/memptyowned.nim Normal file
View File

@@ -0,0 +1,12 @@
# Helper for `temptyowned`: exports a concrete proc whose body folds to
# nothing (an `nkEmpty` body, like Nimbus' `extras.incInternalErrors` when the
# metrics counter's `.inc()` expands to a no-op under `-u:metrics`). The proc is
# NOT called within its own module — only `memptycaller` references it — so the
# per-module backend's owned-routine seeding is the ONLY thing that can emit it.
template maybe*(x: untyped) =
when false:
x
proc emptyOwned*() =
maybe(echo "unreachable")

18
tests/ic/temptyowned.nim Normal file
View File

@@ -0,0 +1,18 @@
discard """
output: '''called false
done'''
"""
# Regression test for the per-module IC backend dropping an owned routine whose
# body folds to `nkEmpty`. `memptyowned.emptyOwned` is a real, concrete, owned
# proc with an empty body; `memptycaller.callEmpty` references it across a module
# boundary. The owning module's `ownsRuntimeRoutine` seeding used to reject any
# routine with an `nkEmpty` body, so nobody emitted `emptyOwned` -> undefined
# reference at link (mirrors Nimbus `ncli` linking `extras.incInternalErrors`).
# Whole-program cgen always emits it as `void emptyOwned(void){}`; the per-module
# backend must too.
import memptycaller
callEmpty(false)
echo "done"