mirror of
https://github.com/nim-lang/Nim.git
synced 2026-07-22 00:41:28 +00:00
IC related bugfixes (#25946)
This commit is contained in:
2
tests/ic/mdefconverter.nim
Normal file
2
tests/ic/mdefconverter.nim
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
converter toBool*(x: int): bool = x != 0
|
||||
14
tests/ic/memit.nim
Normal file
14
tests/ic/memit.nim
Normal file
@@ -0,0 +1,14 @@
|
||||
# Helper for temit.nim: a NON-main module with a module-scope `{.emit.}` that
|
||||
# introduces a C macro consumed by an `{.importc, nodecl.}` const. The IC backend
|
||||
# reload used to drop top-level emit pragmas — writeToplevelNode wrote them to the
|
||||
# by-symbol-index implementation section, where a symbol-less pragma is never
|
||||
# reloaded — so the generated C lost the `#define` and failed to compile with
|
||||
# "use of undeclared identifier". Mirrors lib/pure/concurrency/cpuinfo.nim's
|
||||
# `#include <sys/sysctl.h>` + `CTL_HW`/`HW_NCPU` importc pattern.
|
||||
{.emit: """/*TYPESECTION*/
|
||||
#define NIM_IC_EMIT_ANSWER 42
|
||||
""".}
|
||||
|
||||
let icEmitAnswer {.importc: "NIM_IC_EMIT_ANSWER", nodecl.}: cint
|
||||
|
||||
proc emitAnswer*(): int = int(icEmitAnswer)
|
||||
9
tests/ic/memptycaller.nim
Normal file
9
tests/ic/memptycaller.nim
Normal 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
12
tests/ic/memptyowned.nim
Normal 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")
|
||||
29
tests/ic/minitordera.nim
Normal file
29
tests/ic/minitordera.nim
Normal file
@@ -0,0 +1,29 @@
|
||||
# Helper for tinitorder (not a test itself; no `discard`).
|
||||
#
|
||||
# Imports minitorderb and, in its OWN init, reads the state minitorderb set up.
|
||||
# With the wrong (importer-first) init order `gBState` is still 0 here. It also
|
||||
# allocates a seq in its init so that under `--mm:refc` the GC (set up by the
|
||||
# system module's init) must already be live — i.e. the system module's init has
|
||||
# to be ordered first.
|
||||
|
||||
import minitorderb
|
||||
|
||||
var
|
||||
gASawB = -1
|
||||
gAItems: seq[int]
|
||||
|
||||
proc getASawB*(): int = gASawB
|
||||
proc getACount*(): int = gAItems.len
|
||||
|
||||
proc recordA() =
|
||||
gASawB = getBState()
|
||||
gAItems = @[1, 2, 3]
|
||||
# Allocate (and drop) enough garbage to force a GC cycle DURING module init.
|
||||
# Under refc that runs a conservative stack scan, which needs the main
|
||||
# thread's stack bottom already set — i.e. `initStackBottomWith` must run
|
||||
# before the module inits, not after them.
|
||||
for i in 0 ..< 100_000:
|
||||
let s = @[i, i + 1, i + 2]
|
||||
doAssert s.len == 3
|
||||
|
||||
recordA()
|
||||
15
tests/ic/minitorderb.nim
Normal file
15
tests/ic/minitorderb.nim
Normal file
@@ -0,0 +1,15 @@
|
||||
# Helper for tinitorder (not a test itself; no `discard`).
|
||||
#
|
||||
# An imported module whose INIT sets module-level state at runtime. Under the
|
||||
# per-module backend its init must run BEFORE any importer's init (the imported
|
||||
# module is a dependency → post-order). With the buggy importer-first order this
|
||||
# module's `setupB` runs too late and importers observe `gBState == 0`.
|
||||
|
||||
var gBState: int
|
||||
|
||||
proc getBState*(): int = gBState
|
||||
|
||||
proc setupB() =
|
||||
gBState = 42
|
||||
|
||||
setupB()
|
||||
15
tests/ic/mmethanimal.nim
Normal file
15
tests/ic/mmethanimal.nim
Normal file
@@ -0,0 +1,15 @@
|
||||
# Helper for tmethitanium: the OWNER module of a `{.base.}` method. Its concrete
|
||||
# base body is emitted here; the whole-program dispatcher is synthesized into the
|
||||
# main module. Under `--debugger:native` the backend uses the Itanium mangling
|
||||
# scheme, which encodes the signature instead of the `disamb`, so the base method
|
||||
# and its same-signature dispatcher want the identical clean C name. The
|
||||
# clean-vs-unique tie-break used to depend on a per-MODULE set (`mangledPrcs`),
|
||||
# which the per-module IC backend cannot share — the base mangled clean at this
|
||||
# owner but `speak_u<n>` (an unstable `itemId.item`) at every demander, so it was
|
||||
# defined once and referenced under names nobody defined. See
|
||||
# ccgutils.makeUnique (disamb, not itemId) + ccgtypes.fillBackendName.
|
||||
|
||||
type Animal* = ref object of RootObj
|
||||
|
||||
method speak*(a: Animal): string {.base.} =
|
||||
"generic-animal-sound"
|
||||
14
tests/ic/mmethdog.nim
Normal file
14
tests/ic/mmethdog.nim
Normal file
@@ -0,0 +1,14 @@
|
||||
# Helper for tmethitanium: an override in a DIFFERENT module than the base, plus
|
||||
# a `procCall` super-reference to the base from this non-owner module. That
|
||||
# cross-module reference to the base impl is what diverged from the base's
|
||||
# definition name under the per-module Itanium mangling.
|
||||
|
||||
import mmethanimal
|
||||
|
||||
type Dog* = ref object of Animal
|
||||
|
||||
method speak*(a: Dog): string =
|
||||
"woof"
|
||||
|
||||
proc speakBoth*(a: Dog): string =
|
||||
procCall(speak(Animal(a))) & "/" & speak(a)
|
||||
7
tests/ic/temit.nim
Normal file
7
tests/ic/temit.nim
Normal file
@@ -0,0 +1,7 @@
|
||||
# Regression: a module-scope `{.emit.}` in an imported module must survive the
|
||||
# `nim ic` backend reload (see memit.nim). Before the fix the dropped `#define`
|
||||
# made the generated C fail to compile, so `nim ic` exited non-zero.
|
||||
import memit
|
||||
|
||||
doAssert emitAnswer() == 42
|
||||
echo emitAnswer()
|
||||
18
tests/ic/temptyowned.nim
Normal file
18
tests/ic/temptyowned.nim
Normal 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"
|
||||
23
tests/ic/tinitorder.nim
Normal file
23
tests/ic/tinitorder.nim
Normal file
@@ -0,0 +1,23 @@
|
||||
discard """
|
||||
output: '''42 3'''
|
||||
"""
|
||||
|
||||
# Regression test for per-module-backend module-init ORDERING.
|
||||
#
|
||||
# NimMain must call each module's init in DEPENDENCY (post-order) order: an
|
||||
# imported module's init has to run before its importer's. The whole-program
|
||||
# backend gets this for free (it iterates `modulesClosed`, built in module-finish
|
||||
# order); the per-module backend reconstructs it in `nifbackend`. The earlier
|
||||
# code iterated `bl.mods` by POSITION, which runs importers before their
|
||||
# dependencies (an importer gets a lower file position than the modules it
|
||||
# imports) — and the system module (which runs `initGC` in its init) was not
|
||||
# ordered first at all.
|
||||
#
|
||||
# Module chain: tinitorder -> minitordera -> minitorderb. `minitorderb`'s init
|
||||
# sets a global to 42; `minitordera`'s init reads it (and allocates a seq). With
|
||||
# the buggy order `minitordera` runs first and reads 0 (or, under refc, crashes
|
||||
# allocating before the GC is up). Correct order prints `42 3`.
|
||||
|
||||
import minitordera
|
||||
|
||||
echo getASawB(), " ", getACount()
|
||||
46
tests/ic/tmeta_async.nim
Normal file
46
tests/ic/tmeta_async.nim
Normal file
@@ -0,0 +1,46 @@
|
||||
discard """
|
||||
description: '''metamorphic IC: async/await across an edit (continuations + clean==incremental)'''
|
||||
"""
|
||||
|
||||
# Async is the most cache-fragile area under `nim ic`: the `{.async.}` transform
|
||||
# generates continuation closures and lifts environments, and those lowered
|
||||
# bodies must survive incremental edits and converge to exactly what a clean
|
||||
# build produces. This drives an edit to an async proc body and asserts the
|
||||
# importer is NOT rebuilt (the lifted continuation stays local to its module),
|
||||
# a no-op changes nothing, and the final state is byte-identical to a clean
|
||||
# build. See doc/ic_ideas.md and [[ic-nimbus-test]] (async was the long pole).
|
||||
|
||||
#? metamorphic
|
||||
|
||||
#!FILE worker.nim
|
||||
import std/asyncdispatch
|
||||
proc compute*(x: int): Future[int] {.async.} =
|
||||
await sleepAsync(0)
|
||||
result = x * 2
|
||||
|
||||
#!FILE main.nim
|
||||
import std/asyncdispatch, worker
|
||||
echo waitFor compute(21)
|
||||
|
||||
#!STEP expect: 42
|
||||
|
||||
# --- edit the async proc body. The continuation env lives in `worker`, so only
|
||||
# `worker` rebuilds; `main` (the caller) is left untouched -> modules: 1.
|
||||
# (An async body edit does perturb `worker`'s interface cookie via the
|
||||
# generated env type, so this is not asserted as a pure `body-edit`.)
|
||||
#!FILE worker.nim
|
||||
import std/asyncdispatch
|
||||
proc compute*(x: int): Future[int] {.async.} =
|
||||
await sleepAsync(0)
|
||||
result = x * 3
|
||||
|
||||
#!STEP expect: 63; modules: 1
|
||||
|
||||
# --- re-emit identical content: nothing may change, lifted closures included.
|
||||
#!FILE worker.nim
|
||||
import std/asyncdispatch
|
||||
proc compute*(x: int): Future[int] {.async.} =
|
||||
await sleepAsync(0)
|
||||
result = x * 3
|
||||
|
||||
#!STEP expect: 63; noop
|
||||
64
tests/ic/tmeta_exprcase.nim
Normal file
64
tests/ic/tmeta_exprcase.nim
Normal file
@@ -0,0 +1,64 @@
|
||||
discard """
|
||||
description: '''metamorphic IC: expression-based `let x = case ...` idiom'''
|
||||
"""
|
||||
|
||||
# nimbus-eth2 leans on expression-style code (`let x = case ...` rather than
|
||||
# statement assignment). This exercises that construct through the incremental
|
||||
# path: a body edit that adds a `case` branch stays local to the module, while a
|
||||
# return-type change (interface edit) propagates to the importer even though the
|
||||
# importer's source is byte-identical. See doc/ic_ideas.md.
|
||||
|
||||
#? metamorphic
|
||||
|
||||
#!FILE classify.nim
|
||||
proc classify*(n: int): string =
|
||||
let kind = case n
|
||||
of 0: "zero"
|
||||
of 1, 2, 3: "small"
|
||||
else: "big"
|
||||
result = kind & "(" & $n & ")"
|
||||
|
||||
#!FILE main.nim
|
||||
import classify
|
||||
echo classify(0), " ", classify(2), " ", classify(99)
|
||||
|
||||
#!STEP expect: zero(0) small(2) big(99)
|
||||
|
||||
# --- body edit: add a `case` branch and tweak a label. The signature is
|
||||
# unchanged, so no interface cookie changes and only `classify` rebuilds.
|
||||
#!FILE classify.nim
|
||||
proc classify*(n: int): string =
|
||||
let kind = case n
|
||||
of 0: "ZERO"
|
||||
of 1, 2, 3: "small"
|
||||
of 4, 5, 6: "medium"
|
||||
else: "big"
|
||||
result = kind & "(" & $n & ")"
|
||||
|
||||
#!STEP expect: ZERO(0) small(2) big(99); body-edit; modules: 1
|
||||
|
||||
# --- re-emit identical content: nothing may change.
|
||||
#!FILE classify.nim
|
||||
proc classify*(n: int): string =
|
||||
let kind = case n
|
||||
of 0: "ZERO"
|
||||
of 1, 2, 3: "small"
|
||||
of 4, 5, 6: "medium"
|
||||
else: "big"
|
||||
result = kind & "(" & $n & ")"
|
||||
|
||||
#!STEP expect: ZERO(0) small(2) big(99); noop
|
||||
|
||||
# --- interface edit: the expression-`case` now yields `int`, changing
|
||||
# `classify`'s return type. `main`'s source is byte-identical (`echo` prints
|
||||
# either) yet must re-sem & recodegen -> the cookie changes and 2 modules
|
||||
# rebuild. The final step also runs the clean==incremental check.
|
||||
#!FILE classify.nim
|
||||
proc classify*(n: int): int =
|
||||
result = case n
|
||||
of 0: 0
|
||||
of 1, 2, 3: 1
|
||||
of 4, 5, 6: 5
|
||||
else: 9
|
||||
|
||||
#!STEP expect: 0 1 9; iface-edit; modules: 2
|
||||
33
tests/ic/tmeta_generic.nim
Normal file
33
tests/ic/tmeta_generic.nim
Normal file
@@ -0,0 +1,33 @@
|
||||
discard """
|
||||
description: '''metamorphic IC: generic instantiation cache stability across edits'''
|
||||
"""
|
||||
|
||||
# A generic lives in `gen` but is *instantiated* in `main` (the per-module
|
||||
# backend emits instance bodies at the instantiation site). Editing the generic
|
||||
# body must therefore rebuild both `gen` and `main`, and an incremental edit must
|
||||
# still converge to exactly the same artifacts as a clean build. This exercises
|
||||
# the static/generic-instance cache path that has historically been bug-prone.
|
||||
|
||||
#? metamorphic
|
||||
|
||||
#!FILE gen.nim
|
||||
proc box*[T](x: T): seq[T] = @[x, x]
|
||||
|
||||
#!FILE main.nim
|
||||
import gen
|
||||
echo box(3).len, " ", box("hi")[0]
|
||||
|
||||
#!STEP expect: 2 hi
|
||||
|
||||
# --- edit the generic body: the importer holds the instantiations, so both the
|
||||
# definer and the instantiation site rebuild (2 modules).
|
||||
#!FILE gen.nim
|
||||
proc box*[T](x: T): seq[T] = @[x, x, x]
|
||||
|
||||
#!STEP expect: 3 hi; modules: 2
|
||||
|
||||
# --- re-emit identical content: nothing may change.
|
||||
#!FILE gen.nim
|
||||
proc box*[T](x: T): seq[T] = @[x, x, x]
|
||||
|
||||
#!STEP expect: 3 hi; noop
|
||||
61
tests/ic/tmeta_ortype.nim
Normal file
61
tests/ic/tmeta_ortype.nim
Normal file
@@ -0,0 +1,61 @@
|
||||
discard """
|
||||
description: '''metamorphic IC: or-type (type-class union) idiom from nimbus forks.nim'''
|
||||
"""
|
||||
|
||||
# nimbus-eth2 writes a lot of generic code over `A | B | C` type-class unions
|
||||
# (consensus forks). This models that idiom: a `Fruit = Apple | Banana` union
|
||||
# with a generic `describe[T: Fruit]` dispatched by `when T is ...`. The generic
|
||||
# is instantiated in `main`, so editing its body rebuilds both modules, and the
|
||||
# incremental result must match a clean build. See doc/ic_ideas.md.
|
||||
|
||||
#? metamorphic
|
||||
|
||||
#!FILE forks.nim
|
||||
type
|
||||
Apple* = object
|
||||
weight*: int
|
||||
Banana* = object
|
||||
length*: int
|
||||
Fruit* = Apple | Banana
|
||||
|
||||
proc describe*[T: Fruit](x: T): string =
|
||||
when T is Apple: "apple " & $x.weight
|
||||
else: "banana " & $x.length
|
||||
|
||||
#!FILE main.nim
|
||||
import forks
|
||||
echo describe(Apple(weight: 5)), " | ", describe(Banana(length: 9))
|
||||
|
||||
#!STEP expect: apple 5 | banana 9
|
||||
|
||||
# --- edit the generic body (no signature change). The union/constraint is
|
||||
# untouched, so no interface cookie changes; but `main` holds the two
|
||||
# instantiations, so both modules' codegen rebuilds.
|
||||
#!FILE forks.nim
|
||||
type
|
||||
Apple* = object
|
||||
weight*: int
|
||||
Banana* = object
|
||||
length*: int
|
||||
Fruit* = Apple | Banana
|
||||
|
||||
proc describe*[T: Fruit](x: T): string =
|
||||
when T is Apple: "APPLE " & $x.weight
|
||||
else: "BANANA " & $x.length
|
||||
|
||||
#!STEP expect: APPLE 5 | BANANA 9; body-edit; modules: 2
|
||||
|
||||
# --- re-emit identical content: nothing may change.
|
||||
#!FILE forks.nim
|
||||
type
|
||||
Apple* = object
|
||||
weight*: int
|
||||
Banana* = object
|
||||
length*: int
|
||||
Fruit* = Apple | Banana
|
||||
|
||||
proc describe*[T: Fruit](x: T): string =
|
||||
when T is Apple: "APPLE " & $x.weight
|
||||
else: "BANANA " & $x.length
|
||||
|
||||
#!STEP expect: APPLE 5 | BANANA 9; noop
|
||||
93
tests/ic/tmeta_result.nim
Normal file
93
tests/ic/tmeta_result.nim
Normal file
@@ -0,0 +1,93 @@
|
||||
discard """
|
||||
description: '''metamorphic IC: Result[T, E] (nim-results style) variant-object idiom'''
|
||||
"""
|
||||
|
||||
# nimbus-eth2 threads errors through nim-results' `Result[T, E]`, a generic
|
||||
# *variant* (case) object. This models a minimal hermetic version and exercises
|
||||
# the generic-variant cache path across edits: a body edit of a generic accessor,
|
||||
# adding a public overload (an interface edit), and a no-op — with a final
|
||||
# clean==incremental check. See doc/ic_ideas.md.
|
||||
|
||||
#? metamorphic
|
||||
|
||||
#!FILE results.nim
|
||||
type
|
||||
ResultKind = enum rOk, rErr
|
||||
Result*[T, E] = object
|
||||
case kind: ResultKind
|
||||
of rOk: v: T
|
||||
of rErr: e: E
|
||||
|
||||
proc ok*[T, E](x: T): Result[T, E] = Result[T, E](kind: rOk, v: x)
|
||||
proc err*[T, E](x: E): Result[T, E] = Result[T, E](kind: rErr, e: x)
|
||||
proc isOk*[T, E](r: Result[T, E]): bool = r.kind == rOk
|
||||
proc get*[T, E](r: Result[T, E]): T = r.v
|
||||
proc error*[T, E](r: Result[T, E]): E = r.e
|
||||
|
||||
#!FILE main.nim
|
||||
import results
|
||||
proc parse(s: string): Result[int, string] =
|
||||
if s == "42": ok[int, string](42)
|
||||
else: err[int, string]("bad: " & s)
|
||||
let a = parse("42")
|
||||
let b = parse("x")
|
||||
echo (if a.isOk: $a.get else: a.error), " ", (if b.isOk: $b.get else: b.error)
|
||||
|
||||
#!STEP expect: 42 bad: x
|
||||
|
||||
# --- body-only edit of a generic accessor (`error`): signature unchanged, so no
|
||||
# interface cookie changes; the importer holds the instantiation, so both
|
||||
# modules' codegen rebuilds.
|
||||
#!FILE results.nim
|
||||
type
|
||||
ResultKind = enum rOk, rErr
|
||||
Result*[T, E] = object
|
||||
case kind: ResultKind
|
||||
of rOk: v: T
|
||||
of rErr: e: E
|
||||
|
||||
proc ok*[T, E](x: T): Result[T, E] = Result[T, E](kind: rOk, v: x)
|
||||
proc err*[T, E](x: E): Result[T, E] = Result[T, E](kind: rErr, e: x)
|
||||
proc isOk*[T, E](r: Result[T, E]): bool = r.kind == rOk
|
||||
proc get*[T, E](r: Result[T, E]): T = r.v
|
||||
proc error*[T, E](r: Result[T, E]): E = "ERR:" & r.e
|
||||
|
||||
#!STEP expect: 42 ERR:bad: x; body-edit; modules: 2
|
||||
|
||||
# --- re-emit identical content: nothing may change.
|
||||
#!FILE results.nim
|
||||
type
|
||||
ResultKind = enum rOk, rErr
|
||||
Result*[T, E] = object
|
||||
case kind: ResultKind
|
||||
of rOk: v: T
|
||||
of rErr: e: E
|
||||
|
||||
proc ok*[T, E](x: T): Result[T, E] = Result[T, E](kind: rOk, v: x)
|
||||
proc err*[T, E](x: E): Result[T, E] = Result[T, E](kind: rErr, e: x)
|
||||
proc isOk*[T, E](r: Result[T, E]): bool = r.kind == rOk
|
||||
proc get*[T, E](r: Result[T, E]): T = r.v
|
||||
proc error*[T, E](r: Result[T, E]): E = "ERR:" & r.e
|
||||
|
||||
#!STEP expect: 42 ERR:bad: x; noop
|
||||
|
||||
# --- interface edit: add a public `get` overload (a new exported signature).
|
||||
# `main` doesn't call it, yet importing `results` whose interface changed
|
||||
# forces a re-sem; the cookie changes and >= 2 modules rebuild. The final
|
||||
# step also runs the clean==incremental check.
|
||||
#!FILE results.nim
|
||||
type
|
||||
ResultKind = enum rOk, rErr
|
||||
Result*[T, E] = object
|
||||
case kind: ResultKind
|
||||
of rOk: v: T
|
||||
of rErr: e: E
|
||||
|
||||
proc ok*[T, E](x: T): Result[T, E] = Result[T, E](kind: rOk, v: x)
|
||||
proc err*[T, E](x: E): Result[T, E] = Result[T, E](kind: rErr, e: x)
|
||||
proc isOk*[T, E](r: Result[T, E]): bool = r.kind == rOk
|
||||
proc get*[T, E](r: Result[T, E], fallback: T): T = (if r.kind == rOk: r.v else: fallback)
|
||||
proc get*[T, E](r: Result[T, E]): T = r.v
|
||||
proc error*[T, E](r: Result[T, E]): E = "ERR:" & r.e
|
||||
|
||||
#!STEP expect: 42 ERR:bad: x; iface-edit; modules: 2
|
||||
50
tests/ic/tmeta_smoke.nim
Normal file
50
tests/ic/tmeta_smoke.nim
Normal file
@@ -0,0 +1,50 @@
|
||||
discard """
|
||||
description: '''metamorphic IC: clean==incremental, no-op stability, body vs interface boundary'''
|
||||
"""
|
||||
|
||||
# This is a *metamorphic* IC test (see the `#? metamorphic` marker and the
|
||||
# runner in testament/categories.nim). It drives a sequence of cross-module
|
||||
# edits through `nim ic` in one fixed build directory and checks the invariants
|
||||
# the incremental backend must uphold (doc/ic_ideas.md).
|
||||
|
||||
#? metamorphic
|
||||
|
||||
#!FILE a.nim
|
||||
proc greet*(): string = "hi"
|
||||
proc secret(): int = 41 # private, body-only churn target
|
||||
proc value*(): int = secret() + 1
|
||||
|
||||
#!FILE main.nim
|
||||
import a
|
||||
echo greet(), " ", value()
|
||||
|
||||
#!STEP expect: hi 42
|
||||
|
||||
# --- body-only edit: a private body changes, no signature does.
|
||||
# => no `*.iface.bif` cookie changes, exactly 1 module's codegen rebuilds,
|
||||
# the importer is left untouched.
|
||||
#!FILE a.nim
|
||||
proc greet*(): string = "hi"
|
||||
proc secret(): int = 999
|
||||
proc value*(): int = secret() + 1
|
||||
|
||||
#!STEP expect: hi 1000; body-edit; modules: 1
|
||||
|
||||
# --- no-op edit: re-emit byte-identical content. Nothing downstream may change.
|
||||
#!FILE a.nim
|
||||
proc greet*(): string = "hi"
|
||||
proc secret(): int = 999
|
||||
proc value*(): int = secret() + 1
|
||||
|
||||
#!STEP expect: hi 1000; noop
|
||||
|
||||
# --- interface edit: `value`'s return type changes (a signature change) while
|
||||
# main.nim's source stays byte-identical. The interface cookie must change
|
||||
# and the importer must be re-sem'd & recodegen'd (>= 2 modules rebuilt).
|
||||
# The final step also runs the clean==incremental check.
|
||||
#!FILE a.nim
|
||||
proc greet*(): string = "hi"
|
||||
proc secret(): int = 999
|
||||
proc value*(): int64 = secret().int64 + 1
|
||||
|
||||
#!STEP expect: hi 1000; iface-edit
|
||||
39
tests/ic/tmeta_transitive.nim
Normal file
39
tests/ic/tmeta_transitive.nim
Normal file
@@ -0,0 +1,39 @@
|
||||
discard """
|
||||
description: '''metamorphic IC: edit propagation across a 3-module import chain'''
|
||||
"""
|
||||
|
||||
# Chain: main -> b -> a. Demonstrates that a body edit stays local to the edited
|
||||
# module, while an interface edit propagates to its direct importer but stops
|
||||
# where the next signature is unchanged. See doc/ic_ideas.md and the runner in
|
||||
# testament/categories.nim.
|
||||
|
||||
#? metamorphic
|
||||
|
||||
#!FILE a.nim
|
||||
proc base*(): int = 1
|
||||
|
||||
#!FILE b.nim
|
||||
import a
|
||||
proc mid*(): int = base() + 10
|
||||
|
||||
#!FILE main.nim
|
||||
import b
|
||||
echo mid()
|
||||
|
||||
#!STEP expect: 11
|
||||
|
||||
# --- body-only edit of `a.base`: no signature changes, so nothing re-sems;
|
||||
# only module `a`'s own codegen rebuilds.
|
||||
#!FILE a.nim
|
||||
proc base*(): int = 7
|
||||
|
||||
#!STEP expect: 17; body-edit; modules: 1
|
||||
|
||||
# --- interface edit of `a.base` (return type int -> int64). `b` uses `base`, so
|
||||
# `a`'s cookie change forces `b` to re-sem & recodegen; but `b.mid`'s own
|
||||
# signature is unchanged, so `main` is NOT rebuilt -> exactly 2 modules.
|
||||
# The final step also runs the clean==incremental check.
|
||||
#!FILE a.nim
|
||||
proc base*(): int64 = 7
|
||||
|
||||
#!STEP expect: 17; iface-edit; modules: 2
|
||||
33
tests/ic/tmethitanium.nim
Normal file
33
tests/ic/tmethitanium.nim
Normal file
@@ -0,0 +1,33 @@
|
||||
discard """
|
||||
output: '''woof
|
||||
generic-animal-sound
|
||||
generic-animal-sound/woof'''
|
||||
"""
|
||||
|
||||
# NOTE: the `--debugger:native` that triggers the Itanium mangling lives in the
|
||||
# sibling `tmethitanium_temp.nim.cfg` (the IC test harness compiles the generated
|
||||
# `_temp.nim` and does not thread a `matrix`/`$options` switch into the cg
|
||||
# children; a project cfg is read by the driver, which forwards it).
|
||||
|
||||
# Regression test: under `nim ic --debugger:native` the per-module backend uses
|
||||
# the Itanium C name mangling, which encodes the signature and drops the
|
||||
# `disamb`. A `{.base.}` method (owner module mmethanimal) and its whole-program
|
||||
# dispatcher (synthesized into this main module) then share a signature, so the
|
||||
# clean-name uniqueness probe (`m.g.mangledPrcs`) — which only sees the current
|
||||
# module — gave the base a clean name at its owner but an unstable
|
||||
# `itemId.item`-based `speak_u<n>` at each demander. Result: the base was defined
|
||||
# once (clean) but referenced under names defined nowhere ("undefined reference
|
||||
# to speak_u1") while the dispatcher collided with the clean base ("multiple
|
||||
# definition of speak"). This was the bulk of nimbus-eth2's libp2p method link
|
||||
# failures under `nim ic`. Fixed by making the Itanium scheme use the stable
|
||||
# `disamb` (ccgutils.makeUnique) and always uniquify routine names under the
|
||||
# per-module backend (ccgtypes.fillBackendName), plus forwarding
|
||||
# `--debugger:native` to the cg children (deps.computeForwardedArgs).
|
||||
|
||||
import mmethanimal, mmethdog
|
||||
|
||||
let a: Animal = Dog()
|
||||
echo speak(a) # dispatches -> override
|
||||
let b: Animal = Animal()
|
||||
echo speak(b) # dispatches -> base body
|
||||
echo speakBoth(Dog()) # procCall to base from non-owner module
|
||||
38
tests/ic/tstaticgenfield.nim
Normal file
38
tests/ic/tstaticgenfield.nim
Normal file
@@ -0,0 +1,38 @@
|
||||
discard """
|
||||
output: '''9'''
|
||||
"""
|
||||
|
||||
# Regression test for object-field serialization of static-generic instances
|
||||
# under `nim ic`.
|
||||
#
|
||||
# A generic object's instances SHARE one field PSym (same itemId) while each
|
||||
# instance carries a DISTINCT field type, e.g. `Digest[32].data: array[32,byte]`
|
||||
# vs `Digest[48].data: array[48,byte]` (this is exactly nimcrypto's `MDigest`,
|
||||
# which crashed compiling nimbus's `altair.nim`). The `.s.bif` writer DEFs each
|
||||
# field once inside its owning type's reclist and references it as a bare SymUse
|
||||
# elsewhere, deduping by a per-Writer `emittedFieldSyms` set. That set wrongly
|
||||
# spanned DIFFERENT type reclists: after the first instance's `data` def, every
|
||||
# other instance's reclist got a typeless SymUse stub instead of its own typed
|
||||
# def. On load that field had a nil `typ`/`owner`, and `=destroy` lifting
|
||||
# (`liftdestructors.fillBodyObj`) dereferenced it -> SIGSEGV. The fix scopes the
|
||||
# dedup per-reclist so each instance reclist is a self-contained typed def.
|
||||
#
|
||||
# The `seq` field forces `=destroy` to be lifted for `Outer`, which walks the
|
||||
# reclists of both `Digest` instances (the crash path).
|
||||
|
||||
type
|
||||
Digest[n: static int] = object
|
||||
data: array[n, byte]
|
||||
Outer = object
|
||||
a: Digest[32]
|
||||
b: Digest[48]
|
||||
s: seq[int]
|
||||
|
||||
proc use(o: Outer): int =
|
||||
result = o.a.data[0].int + o.b.data[0].int + o.s.len
|
||||
|
||||
var o: Outer
|
||||
o.a.data[0] = 4
|
||||
o.b.data[0] = 2
|
||||
o.s = @[1, 2, 3]
|
||||
echo use(o)
|
||||
@@ -28,3 +28,17 @@ block:
|
||||
fun[(int, string)]()
|
||||
fun[ref Foo]()
|
||||
fun[seq[int]]()
|
||||
|
||||
block: # shrinking an `@[...]` seq literal in the VM
|
||||
# A `@[a, b, c]` seq value keeps the array-literal type in the VM; shrinking it
|
||||
# to length 1 must not be misread as a broadcast default array (was: the whole
|
||||
# thing collapsed to `len` copies of the first element).
|
||||
proc shrink =
|
||||
var s = @[10, 20, 30]
|
||||
s.setLen(1)
|
||||
doAssert s == @[10]
|
||||
var t = @["foo", "bar"]
|
||||
t.delete(1)
|
||||
doAssert t == @["foo"]
|
||||
static: shrink()
|
||||
shrink()
|
||||
|
||||
Reference in New Issue
Block a user