diff --git a/compiler/nifbackend.nim b/compiler/nifbackend.nim index b08e3492e6..7c17a43615 100644 --- a/compiler/nifbackend.nim +++ b/compiler/nifbackend.nim @@ -157,6 +157,14 @@ proc ownsRuntimeRoutine(s: PSym; modPos: int): bool = ## - generic instances (`sfFromGeneric`): emitted by demand, deduped by merge; ## - `importc`/`compileTime`/`error`/forward sentinels and meta signatures: ## not real codegen targets. + ## - method DISPATCHERS (`sfDispatcher`): their bodies are (re)synthesized into + ## the main TU by `emitMethodDispatchers`/`generateIfMethodDispatchers`, never + ## per module. A dispatcher is a `copySym` clone of the method that shares the + ## method's body sub-tree (incl. its closure iterator); transforming it here + ## would lambda-lift that SHARED iterator a SECOND time under a different owner + ## identity, baking a conflicting `up` field → "up references do not agree" + ## (the divergence is impossible in non-IC, where the dispatcher body is empty + ## at lift time). So a dispatcher is never an owned runtime routine. ## A `{.closure.}` iterator IS a standalone runtime routine (unlike an inline ## iterator, which is expanded at each call site) and must be emitted by its ## owner — else a cross-module `for` over it links to nothing. @@ -166,6 +174,7 @@ proc ownsRuntimeRoutine(s: PSym; modPos: int): bool = s.skipGenericOwner != nil and s.skipGenericOwner.kind == skModule and s.magic == mNone and sfFromGeneric notin s.flags and + sfDispatcher notin s.flags and {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 diff --git a/koch.nim b/koch.nim index 58850e03dd..f1eac10910 100644 --- a/koch.nim +++ b/koch.nim @@ -619,7 +619,7 @@ proc runIcTestFile(inp: string) = const icSuite = ["thallo", "tconverter", "timp", "tmiscs", "tparseutils", "tcompiletimeglobal", "tsighashstable", "tpureenum", "tgenericoffer", "tconverterreexport", "ttypeoffer", "ttransitiveoffer", - "tmodsymref"] + "tmodsymref", "tmethupref"] proc icTest(args: string) = temp("") diff --git a/tests/ic/tmethupref.nim b/tests/ic/tmethupref.nim new file mode 100644 index 0000000000..333138c7f1 --- /dev/null +++ b/tests/ic/tmethupref.nim @@ -0,0 +1,15 @@ +discard """ +output: '''42''' +""" + +# Regression test: a `{.base.}` method whose body holds a closure iterator with a +# nested capturing closure must not crash the IC backend. The method's dispatcher +# (a `copySym` clone sharing the iterator) must NOT be lambda-lifted per module; +# otherwise the shared iterator's `up` field is baked twice under divergent owner +# identities -> "up references do not agree" / "could not determine closure type" +# (the real-world symptom: ~all libp2p async `{.base.}` methods failed under +# `nim ic`). Fixed by excluding `sfDispatcher` from nifbackend.ownsRuntimeRoutine. + +import mmethupref +let b = Base(val: 40) +echo b.compute()