mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-03 20:30:29 +00:00
A closure's environment type — and the `=destroy`/`=copy`/`=sink` the compiler lifts for it — is minted by the BACKEND and exists in no module's semmed NIF. Grinding a corpus of closure programs against `nim c` as the oracle turned up three ways the per-module backend gets those wrong. `tests/closure` is now green under `--ic:on`, and `tests/iter` goes from 13 failures to 9. * **The env hook is emitted by nobody.** `emitsBodyInThisModule` walks up to the outermost enclosing routine to pick the TU that emits a body. For the env `=destroy` of a generic closure iterator defined in one module and instantiated in another, that walk lands on the module of the ORIGINAL generic — a module that never sees the instance — so the routine was emitted nowhere (`undefined reference to eqdestroy__c485__…`). A backend-minted routine has no owning module NIF at all: it is written into the `.t.bif` of every module that references it, re-homed there with `@bk`. Every referencing TU emits it now and the merge stage keeps one, like any other content-addressed definition. * **The `:up` link is stored without an increment.** `env.:up = enclosingEnv` has to go through `=copy` (with the cyclic incref) or the parent's refcount is one too low, and at teardown the two envs' mutually recursive `=destroy`s each believe they hold the last reference and recurse until the stack is gone — a SIGSEGV after the program's own output has already been printed (`tests/iter/tnestedclosures`, "Test 3"). Whether it becomes a `=copy` depends on the up-field type's hooks existing at injection time. Whole-program cgen got that for free: a LATER lifting pass creates them and it runs before any routine's injection. The per-module backend injects a routine right after lifting it (`lower`), long before the module's top level is transformed at all (`cg`) — so both up-field assignment sites create the ops themselves. * **Two backend hooks collide on one C name.** A backend-minted symbol is named `_c<itemId.item>`, a PER-PROCESS counter — fine while "nifc lifts, emits and compiles them in one run", which is what `mangleProcNameExt` assumed. But `lower` mints the env hooks of nested routines and `cg` mints those of the module's top level, and both land in the same translation unit: two unrelated `=destroy`s became one C function. `mangleProcNameExt` already makes an exception for hooks whose `disamb` is content-derived (`HookDisambBit`); `toNifSymName` now mirrors it, so the content-derived value survives the round trip instead of being overwritten by the loader. `InstanceDisambBit`/`HookDisambBit` move to `astdef` — `ast2nif` names symbols by them and cannot import `modulegraphs`. Two new metamorphic tests, both of which fail on the previous compiler: `tclosure_hooks` (generic closure iterator instantiated across modules) and `tclosure_nested_iter` (closure iterator nested in a closure iterator). `icFormatVersion` 37 -> 38 for the hook naming. `koch bootic` reaches its byte-identical fixed point; `tests/closure`, `tests/destructor` (3 known `--newruntime` failures), `tests/cpp` and the classic categories are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
2.7 KiB
Nim
102 lines
2.7 KiB
Nim
discard """
|
|
description: '''IC vs `nim c`: closure environments, their hooks and their owners'''
|
|
"""
|
|
|
|
#? metamorphic
|
|
|
|
# A closure's environment type — and the `=destroy`/`=copy` the compiler lifts
|
|
# for it — is minted by the BACKEND, during the `lower` stage, and exists in no
|
|
# module's semmed NIF. The per-module backend has to decide which translation
|
|
# unit emits such a routine, and the owner walk it uses lands on the module of
|
|
# the ORIGINAL generic: for a generic closure iterator defined in one module and
|
|
# instantiated in another, that is a module which never sees the instance, so the
|
|
# env's `=destroy` was emitted by nobody (`undefined reference to
|
|
# eqdestroy__c485__…`). Every referencing TU emits it now.
|
|
#
|
|
# The steps then move the captured state around, because the env's LAYOUT is what
|
|
# decides whether those hooks are trivial: a body-only edit that adds a capture
|
|
# changes the env type of a routine whose importers do not re-sem.
|
|
|
|
#!FILE clleaf.nim
|
|
type Ev* = proc (s: string): string {.closure.}
|
|
|
|
proc leafMaker*(tag: string): Ev =
|
|
var n = 0
|
|
proc outer(s: string): string =
|
|
proc inner(t: string): string =
|
|
inc n
|
|
tag & ":" & t & ":" & $n
|
|
inner(s)
|
|
result = outer
|
|
|
|
iterator leafIter*[T](xs: seq[T]): T {.closure.} =
|
|
for x in xs: yield x
|
|
|
|
#!FILE clmid.nim
|
|
import clleaf
|
|
|
|
proc midMaker*(tag: string): Ev =
|
|
let base = leafMaker(tag & "/mid")
|
|
var calls = 0
|
|
result = proc (s: string): string =
|
|
inc calls
|
|
base(s) & "#" & $calls
|
|
|
|
proc midIter*(): seq[string] =
|
|
# instantiates `leafIter[string]` HERE, not where it is defined
|
|
result = @[]
|
|
for x in leafIter(@["p", "q"]): result.add x
|
|
|
|
#!FILE main.nim
|
|
import clleaf, clmid
|
|
|
|
let t = midMaker("top")
|
|
echo t("Alpha")
|
|
echo t("Beta")
|
|
echo midIter()
|
|
|
|
# an instance only the main module has
|
|
var fs: seq[float] = @[]
|
|
for x in leafIter(@[1.5, 2.5]): fs.add x
|
|
echo fs
|
|
#!STEP
|
|
|
|
# body-only edit that GROWS the environment: a second captured local
|
|
#!FILE clleaf.nim
|
|
type Ev* = proc (s: string): string {.closure.}
|
|
|
|
proc leafMaker*(tag: string): Ev =
|
|
var n = 0
|
|
var seen: seq[string] = @[]
|
|
proc outer(s: string): string =
|
|
proc inner(t: string): string =
|
|
inc n
|
|
seen.add t
|
|
tag & ":" & t & ":" & $n & ":" & $seen.len
|
|
inner(s)
|
|
result = outer
|
|
|
|
iterator leafIter*[T](xs: seq[T]): T {.closure.} =
|
|
var i = 0
|
|
for x in xs:
|
|
inc i
|
|
yield x
|
|
#!STEP
|
|
|
|
# and shrink it again
|
|
#!FILE clleaf.nim
|
|
type Ev* = proc (s: string): string {.closure.}
|
|
|
|
proc leafMaker*(tag: string): Ev =
|
|
var n = 0
|
|
proc outer(s: string): string =
|
|
proc inner(t: string): string =
|
|
inc n
|
|
tag & ":" & t & ":" & $n
|
|
inner(s)
|
|
result = outer
|
|
|
|
iterator leafIter*[T](xs: seq[T]): T {.closure.} =
|
|
for x in xs: yield x
|
|
#!STEP
|