Compare commits

..

5 Commits

Author SHA1 Message Date
ringabout
7c1a2f0ab7 Merge branch 'devel' into pr_mumu 2026-08-18 18:27:24 +08:00
ringabout
1124bb88f8 Add tests for nimvm scope handling and undeclared identifiers 2026-08-14 22:45:17 +08:00
ringabout
b6d94353c0 Update nimvm scope test expectation 2026-08-14 21:23:54 +08:00
ringabout
85d5fef236 Merge branch 'devel' into pr_mumu 2026-08-13 19:23:53 +08:00
ringabout
bcd4cb1201 test openShadowScope for nimvm 2026-07-28 18:19:34 +08:00
86 changed files with 517 additions and 2957 deletions

View File

@@ -99,7 +99,6 @@ parameter and result types, not just their source-level shape. Use
works without single-quoting.
- `std/uri`: The `?` operator now appends query parameters to an existing query
string instead of replacing it. Fixes [#19782](https://github.com/nim-lang/Nim/issues/19782).
- `std/jsonutils`: `fromJson` now throws an exception when converting to `array`/`seq` if the JSON isn't an array instead of silently failing
## Language changes
@@ -150,13 +149,6 @@ parameter and result types, not just their source-level shape. Use
The issue was that `hasValuelessStatics` in `semtypinst.nim` didn't recognize
`tyTypeDesc(tyGenericParam)` as an unresolved generic parameter.
- The JS backend now implements write-through for `var openArray` parameters that
receive a `toOpenArray` view (bug #15952): mutations reach the caller's storage
instead of silently writing to a copy. Fixed homogeneous numeric arrays
(`array[N, T]`, JS typed arrays) slice via `subarray`; `seq` and non-numeric
arrays slice via a `{base, off, len}` view. This also covers seq/non-numeric-array
write-through, pass-through, re-slicing and `@` (openArray-to-seq) of such views.
## Tool changes
- Added `--raw` flag when generating JSON docs to not render markup.

View File

@@ -8,7 +8,7 @@ const
nkBracketExpr, nkDerefExpr, nkHiddenDeref,
nkAddr, nkHiddenAddr,
nkObjDownConv, nkObjUpConv}
PathKinds1* = {nkHiddenStdConv, nkHiddenSubConv, nkCast}
PathKinds1* = {nkHiddenStdConv, nkHiddenSubConv}
proc skipConvDfa*(n: PNode): PNode =
result = n
@@ -125,3 +125,4 @@ proc aliases*(obj, field: PNode): AliasKind =
else:
result = maybe
else: assert false # unreachable

View File

@@ -509,8 +509,7 @@ proc getPIdent*(a: PNode): PIdent {.inline.} =
of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: a.sons[0].sym.name
else: nil
template id*(a: PSym): int = toId(a.itemId)
template id*(a: PType): int = toId(a.bindingId)
template id*(a: PType | PSym): int = toId(a.itemId)
type
IdGenerator* = ref object # unfortunately, we really need the 'shared mutable' aspect here.
@@ -1098,7 +1097,7 @@ proc newType*(kind: TTypeKind; idgen: IdGenerator; owner: PSym; son: sink PType
let id = nextTypeId idgen
result = PType(kind: kind, ownerFieldImpl: owner, sizeImpl: defaultSize,
alignImpl: defaultAlignment, itemId: id,
bindingId: id, sonsImpl: @[])
uniqueId: id, sonsImpl: @[])
if son != nil:
assert kind != tyProc
result.sonsImpl.add son
@@ -1174,23 +1173,18 @@ proc copyType*(t: PType, idgen: IdGenerator, owner: PSym): PType =
result.symImpl = t.sym # backend-info should not be copied
proc exactReplica*(t: PType; idgen: IdGenerator): PType =
## Copy that INHERITS `bindingId` — the generic-param binding tables
## (`LayeredIdTable`) key on it, so the copy must keep matching its original
## there — while getting its own `itemId`, like every other type. The two
## remaining callers are `semtypinst.instCopyType` (a partially instantiated
## meta type must still bind in the next instantiation round) and the
## `tfUnresolved` typedesc replica in `semtypes.semTypeIdent`; everything
## else that used to come through here is a plain `copyType`.
##
## Do not "simplify" this to share `itemId` as well: `itemId` is the
## serialization identity, and replicas sharing it serialized as duplicate
## defs under one NIF name, which the loader collapsed into a single type —
## Replica that KEEPS `itemId` — the generic-param binding tables
## (`LayeredIdTable`) key on it, so the copy must keep matching its
## original — but mints a FRESH `uniqueId`: uniqueId is the SERIALIZATION
## identity (NIF type names key on it) and must be unique per instance.
## Replicas sharing the original's uniqueId serialized as duplicate defs
## under one NIF name; the loader collapsed them into a single type,
## losing their flag differences (use-site `tfUnresolved` typedescs) or
## their structure (meta instance bodies shadowing a generic's canonical
## body).
result = PType(kind: t.kind, ownerFieldImpl: t.owner, sizeImpl: defaultSize,
alignImpl: defaultAlignment, itemId: nextTypeId(idgen),
bindingId: t.bindingId)
alignImpl: defaultAlignment, itemId: t.itemId,
uniqueId: nextTypeId(idgen))
assignType(result, t)
result.symImpl = t.sym # backend-info should not be copied

File diff suppressed because it is too large Load Diff

View File

@@ -639,14 +639,9 @@ proc getOrDefault*[T](t: TIdTable[T], key: ItemId): T =
if index >= 0: result = t.data[index].val
else: result = default(T)
template idTableGet*[T](t: TIdTable[T], key: PSym): T =
template idTableGet*[T](t: TIdTable[T], key: PType | PSym): T =
getOrDefault(t, key.itemId)
template idTableGet*[T](t: TIdTable[T], key: PType): T =
## Type-keyed tables are BINDING tables: an `exactReplica` must find what its
## original bound, hence `bindingId` and not the type's own identity.
getOrDefault(t, key.bindingId)
proc idTableRawInsert[T](data: var TIdPairSeq[T], key: ItemId, val: T) =
var h: Hash
let keyId = toId(key)
@@ -677,12 +672,9 @@ proc `[]=`*[T](t: var TIdTable[T], key: ItemId, val: T) =
idTableRawInsert(t.data, key, val)
inc(t.counter)
template idTablePut*[T](t: var TIdTable[T], key: PSym, val: T) =
template idTablePut*[T](t: var TIdTable[T], key: PType | PSym, val: T) =
t[key.itemId] = val
template idTablePut*[T](t: var TIdTable[T], key: PType, val: T) =
t[key.bindingId] = val
iterator idTablePairs*[T](t: TIdTable[T]): tuple[key: ItemId, val: T] =
for i in 0..high(t.data):
if not isNil(t.data[i].key):

View File

@@ -784,16 +784,11 @@ type
# same id; there may be multiple copies of a type
# in memory!
# Keep in sync with PackedType
itemId*: ItemId # THE identity of this type: unique per instance, forever.
# Names the type in the NIF cache and decides which
# module owns its definition.
itemId*: ItemId
kind*: TTypeKind # kind of type
state*: ItemState
bindingId*: ItemId # the id of the type this one is a REPLICA of (its own
# `itemId` when it is not a replica). Only the generic
# binding tables (`LayeredIdTable` & friends) key on it:
# `exactReplica` produces a copy that must keep matching
# its original in those tables. Never an identity.
uniqueId*: ItemId # due to a design mistake, we need to keep the real ID here as it
# is required by the --incremental:on mode.
callConvImpl*: TCallingConvention # for procs
flagsImpl*: TTypeFlags # flags of the type
sonsImpl*: TTypeSeq # base types, etc.
@@ -1049,7 +1044,7 @@ proc newStrNode*(strVal: string; info: TLineInfo): PNode =
type
LogEntryKind* = enum
HookEntry, ConverterEntry, MethodEntry, EnumToStrEntry, GenericInstEntry,
PureEnumEntry, CppMemberEntry
PureEnumEntry
LogEntry* = object
kind*: LogEntryKind
op*: TTypeAttachedOp
@@ -1096,7 +1091,7 @@ proc forcePartial*(s: PSym) =
proc forcePartial*(t: PType) =
## Resets all impl-fields to their default values and sets state to Partial.
## This is useful for creating a stub type that can be lazily loaded later.
## The fields itemId, kind, bindingId are preserved.
## The fields itemId, kind, uniqueId are preserved.
t.state = Partial
t.callConvImpl = ccNimCall
t.flagsImpl = {}

View File

@@ -326,7 +326,7 @@ proc startStruct(obj: var Builder; m: BModule; t: PType; name: string; baseType:
# rest of the options add a field or don't need it due to inheritance,
# we need to add the dummy field for uncheckedarray ahead of time
# so that it remains trailing
if t.bindingId notin m.g.graph.memberProcsPerType and
if t.itemId notin m.g.graph.memberProcsPerType and
t.n != nil and t.n.len == 1 and t.n[0].kind == nkSym and
t.n[0].sym.typ.skipTypes(abstractInst).kind == tyUncheckedArray:
# only consists of flexible array field, add *initial* dummy field
@@ -341,7 +341,7 @@ proc startStruct(obj: var Builder; m: BModule; t: PType; name: string; baseType:
proc finishStruct(obj: var Builder; m: BModule; t: PType; info: StructBuilderInfo) =
if info.baseKind == bcNone and info.preFieldsLen == obj.buf.len and
t.bindingId notin m.g.graph.memberProcsPerType:
t.itemId notin m.g.graph.memberProcsPerType:
# no fields were added, add dummy field
obj.addField(name = "dummy", typ = CChar)
if info.named:

View File

@@ -11,11 +11,7 @@
proc canRaiseDisp(p: BProc; n: PNode): bool =
# we assume things like sysFatal cannot raise themselves
if n.kind == nkSym and n.sym.kind == skMethod:
# A base method may be overridden by a branch with a wider exception set.
# Its inferred effects describe only the base body, not every vtable target.
result = true
elif n.kind == nkSym and {sfNeverRaises, sfImportc, sfCompilerProc} * n.sym.flags != {}:
if n.kind == nkSym and {sfNeverRaises, sfImportc, sfCompilerProc} * n.sym.flags != {}:
result = false
elif optPanics in p.config.globalOptions or
(n.kind == nkSym and sfSystemModule in getModule(n.sym).flags and
@@ -398,7 +394,7 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; n
# variable. Thus, we create a temporary pointer variable instead.
let needsIndirect = mapType(p.config, n.firstSon.typ, mapTypeChooser(n.firstSon) == skParam) != ctArray
if needsIndirect:
n.typ = copyType(n.typ, p.module.idgen, n.typ.owner)
n.typ = n.typ.exactReplica(p.module.idgen)
n.typ.incl tfVarIsPtr
a = initLocExprSingleUse(p, n)
a = withTmpIfNeeded(p, a, needsTmp)

View File

@@ -1048,7 +1048,7 @@ proc genRecordField(p: BProc, e: PNode, d: var TLoc) =
if p.module.compileToCpp and e.kind == nkDotExpr and e[1].kind == nkSym and e[1].typ.kind == tyPtr:
# special case for C++: we need to pull the type of the field as member and friends require the complete type.
let typ = e[1].typ.elementType
if typ.bindingId in p.module.g.graph.memberProcsPerType:
if typ.itemId in p.module.g.graph.memberProcsPerType:
discard getTypeDesc(p.module, typ)
genRecordFieldAux(p, e, d, a)

View File

@@ -742,8 +742,8 @@ proc mangleRecFieldName(m: BModule; field: PSym): Rope =
proc hasCppCtor(m: BModule; typ: PType): bool =
result = false
if m.compileToCpp and typ != nil and typ.bindingId in m.g.graph.memberProcsPerType:
for prc in m.g.graph.memberProcsPerType[typ.bindingId]:
if m.compileToCpp and typ != nil and typ.itemId in m.g.graph.memberProcsPerType:
for prc in m.g.graph.memberProcsPerType[typ.itemId]:
if sfConstructor in prc.flags:
return true
@@ -752,8 +752,8 @@ proc genCppParamsForCtor(p: BProc; call: PNode; didGenTemp: var bool): string
proc genCppInitializer(m: BModule, prc: BProc; typ: PType; didGenTemp: var bool): string =
#To avoid creating a BProc per test when called inside a struct nil BProc is allowed
result = "{}"
if typ.bindingId in m.g.graph.initializersPerType:
let call = m.g.graph.initializersPerType[typ.bindingId]
if typ.itemId in m.g.graph.initializersPerType:
let call = m.g.graph.initializersPerType[typ.itemId]
if call != nil:
var p = prc
if p == nil:
@@ -833,8 +833,8 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool
proc addRecordFields(result: var Builder; m: BModule; typ: PType, check: var IntSet) =
genRecordFieldsAux(m, typ.n, typ, check, result)
if typ.bindingId in m.g.graph.memberProcsPerType:
let procs = m.g.graph.memberProcsPerType[typ.bindingId]
if typ.itemId in m.g.graph.memberProcsPerType:
let procs = m.g.graph.memberProcsPerType[typ.itemId]
var isDefaultCtorGen, isCtorGen: bool = false
for prc in procs:
if sfConstructor in prc.flags:
@@ -1289,14 +1289,6 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool
name = typDesc
if isFnConst:
fnConst = " const"
if not isCtor:
# The call-site form (`x->salute(@)`), not the mangled Nim name. Set it on
# BOTH paths: whole-program cgen always emitted the out-of-class definition
# (the `else` branch) before any caller, but the per-module backend emits a
# foreign member proc's body in ITS OWN module, so the caller's TU only ever
# reaches the in-class declaration below — and called the member by the
# mangled name (`loo->salute_u0__vireouyks1()`, "struct Loo has no member").
prc.locImpl.snippet = "$1$2(@)" % [memberOp, name]
if isFwdDecl:
if isStatic:
result.add "static "
@@ -1306,7 +1298,9 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool
override = " override"
superCall = ""
else:
if isCtor and superCall != "":
if not isCtor:
prc.locImpl.snippet = "$1$2(@)" % [memberOp, name]
elif superCall != "":
superCall = " : " & superCall
name = "$1::$2" % [typDesc, name]
@@ -1786,7 +1780,7 @@ proc generateRttiDestructor(g: ModuleGraph; typ: PType; owner: PSym; kind: TType
dest.typ = getSysType(g, info, tyPointer)
result.typ = newProcType(info, idgen, result)
result.typ = newProcType(info, idgen, owner)
result.typ.addParam dest
var n = newNodeI(nkProcDef, info, bodyPos+1)
@@ -1897,30 +1891,11 @@ proc genVTable(result: var Builder, seqs: seq[PSym]) =
result.add(cCast(CPointer, seqs[i].loc.snippet))
proc genTypeInfoV2OldImpl(m: BModule; t, origType: PType, name: Rope; info: TLineInfo) =
## The C++/HCR flavour: C++ has no designated initializers, so the RTTI record
## is a bare variable that the module's `DatInit` fills field by field.
cgsym(m, "TNimTypeV2")
m.s[cfsStrData].addDeclWithVisibility(Private):
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
if m.config.cmd == cmdNifC:
# Same emit-everywhere split as `genTypeInfoV2Impl`: every `cg` process that
# demands this type declares it `extern`, and the DEFINITION is a droppable
# `'d'` unit the merge stage gives a single owner. Without the split the bare
# `TNimTypeV2 x;` in each TU is a tentative definition — which C's linker
# merges but C++'s does not, so `nim cpp --ic:on` died at link with
# "multiple definition of NTIv2__…". The field ASSIGNMENTS stay in every
# TU's `DatInit`: they are top-level code, not a definition, and every module
# computes the same values.
m.s[cfsStrData].addDeclWithVisibility(Extern):
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
m.s[cfsVars].add(cnifDefDirective(name, "d", icNifName(m, origType)))
var def = newBuilder("")
def.addDeclWithVisibility(Private):
def.addVar(kind = Local, name = name, typ = "TNimTypeV2")
m.s[cfsVars].add extract(def)
m.s[cfsVars].add(cnifEndDefs())
m.icDataDefs.add (name, icNifName(m, origType))
else:
m.s[cfsStrData].addDeclWithVisibility(Private):
m.s[cfsStrData].addVar(kind = Local, name = name, typ = "TNimTypeV2")
var flags = 0
if not canFormAcycle(m.g.graph, t): flags = flags or 1
@@ -2095,7 +2070,7 @@ proc genTypeInfoV2(m: BModule; t: PType; info: TLineInfo): Rope =
result = "NTIv2$1_" % [rope($sig)]
m.typeInfoMarkerV2[sig] = result
let owner = t.skipTypes(typedescPtrs).bindingId.module
let owner = t.skipTypes(typedescPtrs).itemId.module
# In the per-module backend (`cg`) RTTI is emit-everywhere like procs and
# consts: every demanding module emits the `'d'` definition (deduped to one
# owner by the merge stage). The owner-routing below would instead push the
@@ -2198,7 +2173,7 @@ proc genTypeInfoV1(m: BModule; t: PType; info: TLineInfo): Rope =
declareNimType(m, "TNimType", result, old.int)
return prefixTI(result)
var owner = t.skipTypes(typedescPtrs).bindingId.module
var owner = t.skipTypes(typedescPtrs).itemId.module
# In the per-module backend (`cg`) V1 RTTI is emit-everywhere like procs,
# consts and V2 type info: every demanding module emits the `'d'` definition
# (deduped to one owner by the merge stage). The owner-routing below would
@@ -2314,8 +2289,8 @@ proc genTypeSection(m: BModule, n: PNode) =
# declarations where the type is already written separately before the initializer.
proc genCppConstructorExpr(m: BModule, prc: BProc; typ: PType; didGenTemp: var bool): Snippet =
var params = ""
if typ.bindingId in m.g.graph.initializersPerType:
let call = m.g.graph.initializersPerType[typ.bindingId]
if typ.itemId in m.g.graph.initializersPerType:
let call = m.g.graph.initializersPerType[typ.itemId]
if call != nil:
var p = prc
if p == nil:

View File

@@ -1635,17 +1635,8 @@ proc genProcLvl3*(m: BModule, prc: PSym) =
# `extern`/`rtl` pragma at sem time), so its uses are invisible to the
# artifact's liveness walk — conservatively keep the definition.
defFlags.add 'x'
# A C++ member's `loc.snippet` is a CALL PATTERN (`#->salute(@)`), not a
# linker name — and every member of that name, in every class, mints the
# same one. Ownership is assigned per name, so `Loo::salute` and `Foo::salute`
# collided: the merge stage handed both to one artifact and the other TU's
# definition was dropped (undefined vtable at link). Key member definitions by
# their NIF name instead, which is unique by construction. Dots cannot occur
# in a mangled C name, so the two namespaces stay disjoint.
let defName =
if sfCppMember * prc.flags != {}: icNifName(m, prc)
else: stripCnifMarks(prc.loc.snippet)
m.s[cfsProcs].add(cnifDefDirective(defName, defFlags, icNifName(m, prc)))
m.s[cfsProcs].add(cnifDefDirective(stripCnifMarks(prc.loc.snippet), defFlags,
icNifName(m, prc)))
m.s[cfsProcs].add(extract(generatedProc))
m.s[cfsProcs].add(cnifEndDefs())
else:
@@ -1670,20 +1661,7 @@ proc requiresExternC(m: BModule; sym: PSym): bool {.inline.} =
proc genProcPrototype(m: BModule, sym: PSym) =
useHeader(m, sym)
if lfNoDecl in sym.loc.flags: return
if sfCppMember * sym.flags != {}:
# A C++ member is declared INSIDE its class, never as a free prototype — but
# this TU still needs its CALL-SITE name (`x->salute(@)`), and only
# `genMemberProcHeader` derives that (from the pragma's declaration pattern).
# Whole-program cgen got it for free: the module defining the member was code
# generated in the same process, ahead of any caller. The per-module backend
# emits that body in ANOTHER process, so the caller was left with the mangled
# Nim name `fillBackendName` minted and C++ rejected
# `loo->salute_u0__vireouyks1()` ("struct Loo has no member named ...").
if m.compileToCpp:
var scratch = newBuilder("")
genMemberProcHeader(m, sym, scratch, false, true)
return
if lfNoDecl in sym.loc.flags or sfCppMember * sym.flags != {}: return
if lfDynamicLib in sym.loc.flags:
if m.config.cmd == cmdNifC and m.config.icBackendStage == "cg":
# Under IC per-module cg every demander emits the dynlib proc's DEFINITION
@@ -1876,16 +1854,10 @@ proc genVarPrototype(m: BModule, n: PNode) =
typ = ptrType(typ)
if lfDynamicLib in sym.loc.flags:
typ = ptrType(typ)
if sfCodegenDecl in sym.flags:
m.s[cfsVars].addDeclWithVisibility(vis):
m.s[cfsVars].addVar(m, sym,
name = sym.loc.snippet,
typ = typ)
else:
m.s[cfsVars].addVar(m, sym,
name = sym.loc.snippet,
typ = typ,
visibility = vis)
m.s[cfsVars].addVar(m, sym,
name = sym.loc.snippet,
typ = typ,
visibility = vis)
if m.hcrOn:
m.initProc.procSec(cpsLocals).add('\t')
m.initProc.procSec(cpsLocals).addAssignment(sym.loc.snippet,
@@ -2633,7 +2605,6 @@ proc genModule(m: BModule, cfile: Cfile): Rope =
m.icDataDefs,
semmedNif = toNifFilename(m.config, FileIndex m.module.position),
moduleBase = getSomeNameForModule(m),
globalDtor = m.icGlobalDtorName,
implDeps = implDeps)
m.g.graph.icCnifFiles.add artifact
# NB: under cmdNifC the returned text still carries the cnif marks; the
@@ -2733,7 +2704,7 @@ proc getCFile*(m: BModule): AbsoluteFile =
let ext =
if m.compileToCpp: ".nim.cpp"
elif m.config.backend == backendObjc or sfCompileToObjc in m.module.flags: ".nim.m"
else: icCFileExt(m.config)
else: ".nim.c"
result = changeFileExt(completeCfilePath(m.config, mangleModuleName(m.config, m.cfilename).AbsoluteFile), ext)
when false:
@@ -2864,7 +2835,7 @@ proc generateLibraryDestroyGlobals(graph: ModuleGraph; m: BModule; body: PNode;
let prefixedName = m.config.nimMainPrefix & "NimDestroyGlobals"
let procname = getIdent(graph.cache, prefixedName)
result = newSym(skProc, procname, m.idgen, m.module.owner, m.module.info)
result.typ = newProcType(m.module.info, m.idgen, result)
result.typ = newProcType(m.module.info, m.idgen, m.module.owner)
result.typ.callConv = ccCDecl
backendEnsureMutable result
incl result.flagsImpl, sfExportc
@@ -2878,42 +2849,6 @@ proc generateLibraryDestroyGlobals(graph: ModuleGraph; m: BModule; body: PNode;
theProc[bodyPos] = body
result.ast = theProc
proc genIcModuleDestroyGlobals*(graph: ModuleGraph; m: BModule): string =
## Per-module backend (`cg` stage), non-main module: wrap this module's
## accumulated top-level global destructors in a nullary exported proc and
## return its C name ("" when there are none).
##
## `graph.globalDestructors` is filled while a module's own `cg` process
## injects destructors into its top level, but the teardown code is emitted
## by the MAIN module's `cg` — a different process, whose `graph` only ever
## sees its own entries. So each module emits its own teardown here and
## records the name in its `.c.nif` meta head; the main module's `cg` reads
## the heads (like it already does for init/datInit) and calls them.
result = ""
if graph.globalDestructors.len == 0: return
var body = newNodeI(nkStmtList, m.module.info)
for i in countdown(high(graph.globalDestructors), 0):
body.add graph.globalDestructors[i]
body.flags.incl nfTransf # should not be further transformed
graph.globalDestructors.setLen 0
result = m.config.nimMainPrefix & "NimDestroyGlobals__" & $getSomeNameForModule(m)
let procname = getIdent(graph.cache, result)
var dtor = newSym(skProc, procname, m.idgen, m.module.owner, m.module.info)
dtor.typ = newProcType(m.module.info, m.idgen, dtor)
dtor.typ.callConv = ccNimCall
backendEnsureMutable dtor
incl dtor.flagsImpl, sfExportc # a root for the merge stage's DCE: nothing
# inside this TU calls it, only main does
dtor.locImpl.snippet = result
let theProc = newNodeI(nkProcDef, m.module.info, bodyPos+1)
for i in 0..<theProc.len: theProc[i] = newNodeI(nkEmpty, m.module.info)
theProc[namePos] = newSymNode(dtor)
theProc[bodyPos] = body
dtor.ast = theProc
genProcLvl3(m, dtor)
proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode) =
## Also called from IC.
if sfMainModule in m.module.flags:
@@ -2941,22 +2876,6 @@ proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode) =
m.initProc.options = initProcOptions(m)
genProcBody(m.initProc, n)
if graph.icModuleDtors.len > 0 and sfMainModule in m.module.flags and
{optGenStaticLib, optGenDynLib, optNoMain} * m.config.globalOptions == {}:
# Per-module backend: the other modules' top-level global destructors were
# emitted into their own TUs (`genIcModuleDestroyGlobals`); call them from
# the end of the main module's init proc — which IS the program body — right
# after main's own destructors, in the order `generateCgStage` computed
# (reverse dependency order, mirroring whole-program cgen's single reversed
# `globalDestructors` list). The lib/noMain flavour — where the whole-program
# backend collects the destructors into an exported `NimDestroyGlobals`
# instead — is not reachable: `nim ic` only builds executables.
for dn in graph.icModuleDtors:
m.g.mainModProcs.addDeclWithVisibility(Private):
m.g.mainModProcs.addProcHeader(ccNimCall, dn, CVoid, cProcParams())
m.g.mainModProcs.finishProcHeaderAsProto()
m.initProc.s(cpsStmts).addCallStmt(markCName(dn))
if m.hcrOn:
# make sure this is pulled in (meaning hcrGetGlobal() is called for it during init)
let sym = magicsys.getCompilerProc(m.g.graph, "programResult")

View File

@@ -186,10 +186,6 @@ type
# embeds (redirected defs, shared instances,
# hooks); recorded as the artifact's cdeps so
# the reuse gate can check their impl cookies
icGlobalDtorName*: string # per-module backend: the C name of this
# module's global-destructor proc, recorded in
# the artifact's meta head so the main module's
# `cg` — a different process — can call it
icDataDefs*: seq[tuple[cname, nifname: string]]
# C names of data definitions (consts, globals,
# RTTI) this TU embeds plus their NIF symbol

View File

@@ -197,10 +197,10 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
if witness.isNil: witness = g.methods[i].methods[0]
# create a new dispatcher:
# stores the id and the position
if s.typ.firstParamType.skipTypes(skipPtrs).bindingId notin g.bucketTable:
g.bucketTable[s.typ.firstParamType.skipTypes(skipPtrs).bindingId] = 1
if s.typ.firstParamType.skipTypes(skipPtrs).itemId notin g.bucketTable:
g.bucketTable[s.typ.firstParamType.skipTypes(skipPtrs).itemId] = 1
else:
g.bucketTable.inc(s.typ.firstParamType.skipTypes(skipPtrs).bindingId)
g.bucketTable.inc(s.typ.firstParamType.skipTypes(skipPtrs).itemId)
g.methods.add((methods: @[s], dispatcher: createDispatcher(s, g, idgen)))
logMethodDef(g, s)
#echo "adding ", s.info

View File

@@ -73,15 +73,14 @@ proc stripCnifMarks*(s: string): string =
inc i
const
CnifVersion* = "5"
CnifVersion* = "4"
## Artifact format version, stored in the meta head. Artifacts written
## by an older compiler lack the NIF names and the cref group the
## def-retention check needs (v2), the cdeps group the fine-grained
## reuse gate needs (v3), the type NIF names and cnif-marked extern
## reuse gate needs (v3), or the type NIF names and cnif-marked extern
## RTTI references the typeinfo flavor of the def-retention check
## needs (v4), or the global-destructor name the main module's `cg`
## calls at teardown (v5); `readCnifHeads` reports them as invalid so
## their TUs simply regenerate once.
## needs (v4); `readCnifHeads` reports them as invalid so their TUs
## simply regenerate once.
proc cnifDefDirective*(name, flags, nifName: string): string =
CnifDefStart & name & CnifDefSep & flags & CnifDefSep & nifName & CnifDefEnd
@@ -92,17 +91,15 @@ proc cnifEndDefs*(): string =
proc writeCnifArtifact*(code: string; outfile: string;
initRequired = false; datInitRequired = false;
dataDefs: openArray[tuple[cname, nifname: string]] = [];
semmedNif = ""; moduleBase = ""; globalDtor = "";
semmedNif = ""; moduleBase = "";
implDeps: openArray[string] = []) =
## Splits the marked module text into the `.c.nif` artifact.
## The artifact starts with a `(meta <flags> "semmedNif" "moduleBase"
## "version" "globalDtor")` head — whether the module has an init/datInit
## proc ('i'/'d'), which semmed NIF it was generated from, the module's
## "version")` head — whether the module has an init/datInit proc
## ('i'/'d'), which semmed NIF it was generated from and the module's
## mangled base name (what `registerModuleToMain` and the reuse decision
## need when the TU is reused in a later run, possibly without the module
## ever being loaded again) and the C name of the module's global-destructor
## proc, if any (what the main module's `cg` calls at program teardown; see
## `cgen.genIcModuleDestroyGlobals`) — a `(cdata (SymbolDef StrLit)*)` group naming
## ever being loaded again) — a `(cdata (SymbolDef StrLit)*)` group naming
## the data definitions (consts, globals, RTTI) the TU embeds together
## with their NIF names, a `(cref Ident*)` group naming every C name
## the TU references but does not define itself (what the def-retention
@@ -156,7 +153,6 @@ proc writeCnifArtifact*(code: string; outfile: string;
b.addStrLit semmedNif
b.addStrLit moduleBase
b.addStrLit CnifVersion
b.addStrLit globalDtor
b.withTree "cdata":
for d in dataDefs:
b.addSymbolDef d.cname
@@ -265,8 +261,6 @@ type
datInitRequired*: bool
semmedNif*: string ## the semmed NIF this TU was generated from
moduleBase*: string ## the module's mangled base name
globalDtor*: string ## C name of the module's global-destructor proc
## ("" when the module has no global destructors)
cdefs*: seq[tuple[cname, nifname: string]] ## the proc definitions
cdata*: seq[tuple[cname, nifname: string]] ## the data definitions
crefs*: seq[string] ## C names referenced but not defined here
@@ -309,7 +303,6 @@ proc readCnifHeads*(f: string): CnifHeads =
if strIdx == 0: result.semmedNif = strVal(c)
elif strIdx == 1: result.moduleBase = strVal(c)
elif strIdx == 2: version = strVal(c)
elif strIdx == 3: result.globalDtor = strVal(c)
inc strIdx
inc c
else:
@@ -592,13 +585,6 @@ proc computeMergeDecision*(files: openArray[string]): MergeDecision =
if d in result.live: inc result.liveDefs
const MergeDecisionFile* = "ic.backend.merge.nif"
const LiveModulesFile* = "ic.backend.live.txt"
## One `.c.nif` path per line: exactly the artifacts of the modules the CURRENT
## build graph considers live. The `merge` stage reads this instead of globbing
## `*.c.nif` off the nimcache, so a leftover artifact from an unrelated build
## that happens to share the cache directory cannot be merged in (which is what
## made a shared prebuilt cache unusable: merge picked owners in modules the
## program does not import, and the link then wanted their objects).
## Fixed name of the merge stage's output in the nimcache, read by `emit`.
proc writeMergeDecision*(outfile: string; d: MergeDecision) =

View File

@@ -627,11 +627,7 @@ proc processMemoryManagementOption(switch, arg: string, pass: TCmdLinePass,
conf.selectedGC = gcHooks
defineSymbol(conf.symbols, "gchooks")
incl conf.globalOptions, optSeqDestructors
# (The `arg` here is the mm MODE — "hooks" — so feeding it to an on/off
# switch made `--mm:hooks` fail outright with "'on' or 'off' expected, but
# 'hooks' found". The `incl` above is what that call was meant to do.
# Reachable only via the explicit switch: `--newruntime` sets
# `selectedGC` directly, which is why this stayed hidden.)
processOnOffSwitchG(conf, {optSeqDestructors}, arg, pass, info)
if pass in {passCmd2, passPP}:
defineSymbol(conf.symbols, "nimSeqsV2")
of "go":
@@ -1092,14 +1088,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
expectNoArg(conf, switch, arg, pass, info)
helpOnError(conf, pass)
of "symbolfiles", "incremental", "ic":
if pass in {passCmd2, passPP} and switch.normalize == "symbolfiles":
deprecatedAlias(switch, "incremental")
if switch.normalize == "symbolfiles": deprecatedAlias(switch, "incremental")
# xxx maybe also ic, since not in help?
# `--ic:on` is read in passCmd1 too: `nim.nim` decides BEFORE config loading
# whether this run is an IC driver (`ensureIcConfig` must produce the
# precompiled config the driver itself then replays), and passCmd1 is the
# only pass that has run by then.
if pass in {passCmd1, passCmd2, passPP}:
if pass in {passCmd2, passPP}:
case arg.normalize
of "on": conf.ic = true
of "legacy": conf.symbolFiles = v2Sf

View File

@@ -11,8 +11,7 @@
## for details. Note this is a first implementation and only the "Concept matching"
## section has been implemented.
import ast, semdata, lookups, lineinfos, idents, msgs, renderer, types,
layeredtable, semtypinst
import ast, semdata, lookups, lineinfos, idents, msgs, renderer, types, layeredtable
import std/sets
@@ -72,8 +71,7 @@ proc semConceptDeclaration*(c: PContext; n: PNode): PNode =
type
MatchFlags* = enum
mfDontBind # Do not export bindings from the concept match
mfBindGenericParam # Export inferred invocation parameters despite mfDontBind
mfDontBind # Do not bind generic parameters
mfCheckGeneric # formal <- formal comparison as opposed to formal <- operand
ConceptTypePair = tuple[conceptId, typeId: ItemId]
@@ -207,7 +205,7 @@ proc matchConceptToImpl(c: PContext, f, potentialImpl: PType; m: var MatchCon):
# Cycle detection: track (concept, type) pairs to prevent infinite recursion.
# Returns true on cycle (coinductive semantics) to support co-dependent concepts.
let pair: ConceptTypePair = (concpt.bindingId, potentialImpl.bindingId)
let pair: ConceptTypePair = (concpt.itemId, potentialImpl.itemId)
if pair in m.marker:
return true
m.marker.incl pair
@@ -575,17 +573,7 @@ proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool =
# error was reported earlier.
result = false
proc resolvedBinding(c: PContext; t: PType; m: MatchCon): PType =
## An inferred concept parameter can refer to an implementation-local
## generic parameter, for example `Elem[Impl.T]`. Resolve it while the
## matcher's private bindings (`Impl.T -> int`) are still available.
if t.containsUnresolvedType:
prepareMetatypeForSigmatch(c, m.bindings, m.concpt.sym.info, t)
else:
t
proc fixBindings(c: PContext; bindings: var LayeredIdTable; concpt: PType;
invocation: PType; m: var MatchCon) =
proc fixBindings(bindings: var LayeredIdTable; concpt: PType; invocation: PType; m: var MatchCon) =
# invocation != nil means we have a non-atomic concept:
if invocation != nil and invocation.kind == tyGenericInvocation:
assert concpt.sym.typ.kind == tyGenericBody
@@ -597,9 +585,8 @@ proc fixBindings(c: PContext; bindings: var LayeredIdTable; concpt: PType;
continue
let found = m.bindings.lookup(thisSym)
if found != nil:
let resolved = resolvedBinding(c, found, m)
when logBindings: echo "Invocation bind: ", thisSym, " ", resolved
bindings.put(thisSym, resolved)
when logBindings: echo "Invocation bind: ", thisSym, " ", found
bindings.put(thisSym, found)
# bind even more generic parameters
let genBody = invocation.base
@@ -615,20 +602,6 @@ proc fixBindings(c: PContext; bindings: var LayeredIdTable; concpt: PType;
bindings.put(invocation[i], boundV)
bindings.put(concpt, m.potentialImplementation)
proc fixConstraintBindings(c: PContext; bindings: var LayeredIdTable;
invocation: PType; m: MatchCon) =
## Propagates only the dependent parameters of a concept constraint. The
## concept itself and its private matcher bindings must remain unbound so
## that independent constraints using the same concept don't get coupled.
if invocation != nil and invocation.kind == tyGenericInvocation:
let genBody = invocation.base
assert genBody.kind == tyGenericBody
for i in FirstGenericParamAt ..< invocation.kidsLen:
if lookup(bindings, invocation[i]) == nil:
let boundValue = m.bindings.lookup(genBody[i - 1])
if boundValue != nil:
bindings.put(invocation[i], resolvedBinding(c, boundValue, m))
proc processConcept(c: PContext; concpt, invocation: PType, bindings: var LayeredIdTable; m: var MatchCon): bool =
m.bindings = m.bindings.newTypeMapLayer()
if invocation != nil and invocation.kind == tyGenericInst:
@@ -638,11 +611,8 @@ proc processConcept(c: PContext; concpt, invocation: PType, bindings: var Layere
if invocation[i].kind != tyVoid:
bindParam(c, m, genericBody[i-1], invocation[i])
result = conceptMatchNode(c, concpt.conceptBody, m)
if result:
if mfDontBind notin m.flags:
fixBindings(c, bindings, concpt, invocation, m)
elif mfBindGenericParam in m.flags:
fixConstraintBindings(c, bindings, invocation, m)
if result and mfDontBind notin m.flags:
fixBindings(bindings, concpt, invocation, m)
proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var LayeredIdTable; invocation: PType, flags: set[MatchFlags] = {}): bool =
## Entry point from sigmatch. 'concpt' is the concept we try to match (here still a PType but

View File

@@ -11,7 +11,6 @@
## This enables incremental and parallel compilation using the `m` switch.
import std / [os, tables, sets, times, osproc, algorithm, strtabs, strutils, syncio]
from std/sha1 import secureHash, `$`
import options, msgs, lineinfos, pathutils, condsyms,
modulepaths, extccomp, cnif, platform
@@ -27,14 +26,6 @@ type
Node = ref object
files: seq[FilePair] # main file + includes
deps: seq[int] # indices into DepContext.nodes
specDeps: seq[int] # the subset of `deps` reached ONLY through a `when`
# condition the scanner could not evaluate
missingImport: string # an `import` path this module's source names, under a
# `when` the scanner could not decide, that does not
# exist on disk (empty when all resolved)
missingHardImport: string ## ditto but NOT under any undecidable `when`: the
## real compile would reach this `import`, so it is
## a genuine "cannot open file" error
id: int
DepContext = object
@@ -50,9 +41,6 @@ type
scanningMain: bool # currently scanning the project main module's deps;
# makes `when isMainModule` conditions evaluate true
# only there (every other module is imported)
speculating: int # nesting depth of `when` guards the scanner could not
# decide; every import edge added while this is > 0 is
# recorded as speculative (see pruneDeadSpeculative)
proc toPair(c: DepContext; f: string): FilePair =
FilePair(nimFile: f, modname: moduleSuffix(f, cast[seq[string]](c.config.searchPaths)))
@@ -218,18 +206,6 @@ proc getsImplicitImports(c: DepContext; nimFile: string): bool =
## system.nim and never reaches them). Stdlib == under conf.libpath.
not isRelativeTo(nimFile, c.config.libpath.string)
proc addDepEdge(c: DepContext; current: Node; depId: int) =
## Record `current -> depId`. While the scanner is inside a `when` guard it
## could not evaluate (`c.speculating > 0`) the edge is *speculative*: it may
## not exist in the real compile at all. An edge seen at least once outside
## such a guard is hard and stays hard.
if depId notin current.deps: current.deps.add depId
if c.speculating > 0:
if depId notin current.specDeps: current.specDeps.add depId
else:
let i = current.specDeps.find(depId)
if i >= 0: current.specDeps.delete i
proc processImport(c: var DepContext; importPath: string; current: Node; origin: string) =
# `origin` = the file the `import` literally appears in. Crucial for imports
# inside `include`d files: e.g. `system.nim` includes `system/excpt.nim`, which
@@ -241,14 +217,6 @@ proc processImport(c: var DepContext; importPath: string; current: Node; origin:
# only after the post-sem `.s.deps` revealed the edge.
let resolved = resolveImport(c, origin, importPath)
if resolved.len == 0 or not fileExists(resolved):
# The module does not exist on disk. Silently ignoring this is right for the
# scanner (the `import` may sit in a dead `when` branch and the real compile
# never looks at it), but remember it: `pruneDeadSpeculative` uses it to tell
# a module that is merely unused apart from one that cannot compile at all.
if c.speculating > 0:
if current.missingImport.len == 0: current.missingImport = importPath
elif current.missingHardImport.len == 0:
current.missingHardImport = importPath
return
let pair = c.toPair(resolved)
@@ -257,7 +225,7 @@ proc processImport(c: var DepContext; importPath: string; current: Node; origin:
if existingIdx == -1:
# New module - create node and process it
let newNode = Node(files: @[pair], id: c.nodes.len)
addDepEdge(c, current, newNode.id)
current.deps.add newNode.id
# Every module depends on system.nim
if c.systemNodeId >= 0:
newNode.deps.add c.systemNodeId
@@ -275,7 +243,8 @@ proc processImport(c: var DepContext; importPath: string; current: Node; origin:
traverseDeps(c, pair, newNode)
else:
# Already processed - just add dependency
addDepEdge(c, current, existingIdx)
if existingIdx notin current.deps:
current.deps.add existingIdx
proc skipSubtree(s: var Stream; first: PackedToken) =
## Consume tokens until the ParLe at `first` is balanced. Caller has
@@ -564,19 +533,14 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
# entirely. Otherwise advance past the marker and parse the path.
t = next(s)
var live = true
var speculative = false
if t.kind == ParLe and pool.tags[t.tagId] == "when":
# whenMarkerHolds consumes everything up to and including the
# closing `)` of the `(when ...)` subtree. Drop the import only when
# the condition is PROVABLY false; a `cvUnknown` condition (e.g. an
# `else:` branch guarded by `not <unevaluatable call>`, as in
# `when tryImport x: ... else: import x`) keeps the dependency so the
# static graph never misses a real import — but marks every edge it
# creates speculative, so `pruneDeadSpeculative` can still drop a
# subtree that provably cannot compile in this configuration.
let cond = whenMarkerHolds(c, s)
live = cond != cvFalse
speculative = cond == cvUnknown
# static graph never misses a real import.
live = whenMarkerHolds(c, s) != cvFalse
t = next(s)
if not live:
# Drain the rest of this import/include node.
@@ -594,7 +558,6 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
# that expand to several imports. A plain `import a, b, c` lists several
# modules as siblings; a `fromimport` has a single path followed by the
# imported symbol list, which must not be treated as modules.
if speculative: inc c.speculating
if tag == "fromimport" or tag == "importexcept":
# `from m import syms` / `import m except syms`: the first child is the
# module path; the rest is the (in/ex)cluded symbol list, which must not
@@ -610,7 +573,6 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
processInclude(c, importPath, current, pair.nimFile)
else:
processImport(c, importPath, current, pair.nimFile)
if speculative: dec c.speculating
# Drain any remaining tokens of this node (e.g. the symbol list of a
# `fromimport`), up to and including the node's closing ')'.
var depth = 1
@@ -727,131 +689,6 @@ proc traverseDeps(c: var DepContext; pair: FilePair; current: Node) =
return
readDepsFile(c, pair, current)
proc pruneDeadSpeculative(c: var DepContext) =
## Drop modules that are reachable only through a `when` guard the scanner
## cannot evaluate AND that cannot possibly compile because they import a
## module which does not exist on disk.
##
## The motivating shape is the ordinary `{.strdefine.}` backend switch:
##
## const figdrawTextBackend* {.strdefine.} = "pixie"
## when figdrawTextBackend == "harfbuzzy":
## import ./textrasters/glyphid_raster # imports `pkg/harfbuzzy`
##
## The value of that const needs sem, so `evalCondCmp` answers `cvUnknown` and
## the conservative rule keeps the import — the right call for an edge, but it
## also gives `glyphid_raster` its own `nim m` rule. The classic compiler never
## looks at that file; IC compiles it, cannot find `pkg/harfbuzzy`, and the
## whole build dies on a package the user never installed because they never
## selected that backend.
##
## Dropping is safe: if the guard *was* live, the importer's own `nim m` fails
## on the missing NIF, records the import in its `.s.deps` sidecar, and the
## discovery fixpoint re-adds the node — this time reporting the honest
## `cannot open file: pkg/harfbuzzy/raw` instead of a cascade of
## `undeclared identifier` noise.
let n = c.nodes.len
if n == 0: return
var roots = @[0]
if c.systemNodeId >= 0: roots.add c.systemNodeId
for i in c.implicitNodeIds: roots.add i
# Reachability through NON-speculative edges only: these modules are compiled
# for certain, so a missing import in them is a genuine user error to report.
var hard = newSeq[bool](n)
var stack = roots
while stack.len > 0:
let v = stack.pop()
if hard[v]: continue
hard[v] = true
for d in c.nodes[v].deps:
if d notin c.nodes[v].specDeps and not hard[d]: stack.add d
# A module the real compile DOES reach, naming an import that is not on disk,
# is a plain user error — and one nifmake cannot notice on its own: deleting
# `effects.nim` moves no mtime, so the importer's `nim m` never re-fires and
# `nim ic` happily relinked a stale binary while `nim c` said "cannot open
# file". Report it here, where the graph scan is the only thing that looks at
# import paths at all.
var reported = false
for i in 0 ..< n:
if hard[i] and c.nodes[i].missingHardImport.len > 0:
rawMessage(c.config, errGenerated,
c.nodes[i].files[0].nimFile & ": cannot open file: " &
c.nodes[i].missingHardImport)
reported = true
if reported: return
var dead = newSeq[bool](n)
var anyDead = false
for i in 0 ..< n:
if not hard[i] and c.nodes[i].missingImport.len > 0:
dead[i] = true
anyDead = true
if not anyDead: return
# Anything left reachable only through a dead node is dead too.
var alive = newSeq[bool](n)
stack = @[]
for r in roots:
if not dead[r]: stack.add r
while stack.len > 0:
let v = stack.pop()
if alive[v]: continue
alive[v] = true
for d in c.nodes[v].deps:
if not dead[d] and not alive[d]: stack.add d
var cascaded = 0
for i in 0 ..< n:
if not alive[i]:
# Drop the scan artifacts of a module that just left the graph. `nifler`
# ran on it during `traverseDeps` (that is how we learned it cannot
# build), and leaving its `.p.nif`/`.deps.nif` behind makes an
# edit-accumulated cache differ from a clean one for no reason. Re-running
# nifler if it ever comes back costs a single parse.
for f in c.nodes[i].files:
removeFile(c.parsedFile(f))
removeFile(c.depsFile(f))
removeFile(c.parsedFile(f).changeFileExt("") & ".deps.nif")
if c.nodes[i].missingImport.len > 0:
rawMessage(c.config, hintSuccess,
"ic: skipping " & c.nodes[i].files[0].nimFile &
" (reached only under an undecidable `when`, and imports " &
c.nodes[i].missingImport & ", which is not installed)")
else:
inc cascaded
if cascaded > 0:
rawMessage(c.config, hintSuccess,
"ic: " & $cascaded & " further module(s) skipped, reachable only through those")
# Compact `c.nodes`; node ids ARE indices everywhere, so remap them all.
var remap = newSeq[int](n)
var newNodes: seq[Node] = @[]
for i in 0 ..< n:
if alive[i]:
remap[i] = newNodes.len
newNodes.add c.nodes[i]
else:
remap[i] = -1
proc remapped(remap: seq[int]; src: seq[int]): seq[int] =
result = @[]
for x in src:
if remap[x] >= 0 and remap[x] notin result: result.add remap[x]
for node in newNodes:
node.id = remap[node.id]
node.deps = remapped(remap, node.deps)
node.specDeps = remapped(remap, node.specDeps)
c.nodes = newNodes
var pm = initTable[string, int]()
for name, idx in c.processedModules:
if idx >= 0 and idx < n and remap[idx] >= 0: pm[name] = remap[idx]
c.processedModules = pm
if c.systemNodeId >= 0: c.systemNodeId = remap[c.systemNodeId]
c.implicitNodeIds = remapped(remap, c.implicitNodeIds)
proc computeSCCs(c: DepContext): seq[seq[int]] =
## Tarjan's strongly-connected-components over the module dependency graph
## (`node.deps`). Each returned component is a list of node indices; a module
@@ -934,19 +771,6 @@ proc computeForwardedArgs(c: DepContext): seq[string] =
# them — phantom outputs that re-fire the build on every rerun).
if c.config.selectedGC != gcUnselected:
result.add "--mm:" & $c.config.selectedGC
# The children are invoked as `nim m` / `nim nifc`, so the driver's own command
# token (`c`, `cpp`, `ic`) is gone and with it the backend it selected. Name it
# explicitly — `nim cpp --ic:on` must not have its stdlib sem'd and its TUs
# emitted as C. The exception model rides along for the same reason: `nim cpp`
# defaults to `--exceptions:cpp`, which changes both codegen and sem.
if c.config.backend != backendInvalid:
result.add "--backend:" & $c.config.backend
if c.config.exc != excNone:
result.add "--exceptions:" & (case c.config.exc
of excGoto: "goto"
of excCpp: "cpp"
of excQuirky: "quirky"
else: "setjmp")
# method dispatch semantics must match across the child processes:
# a child compiled without --multimethods:on builds different dispatch
# buckets (and rejects calls as ambiguous that multi-dispatch accepts)
@@ -974,71 +798,6 @@ proc computeForwardedArgs(c: DepContext): seq[string] =
# replayed (`conf.icPreparsedConfig`); `commandIc` has already guaranteed it
# exists, else it bailed.
result.add "--icPreparsedConfig:" & c.config.icPreparsedConfig
# Everything else the user typed on the `nim ic` command line. The children
# replay the project's CONFIG FILES (ic_config.cfg.nif), never the driver's
# argv, so a switch that exists only there — `--opt:speed`, `--panics:on`,
# `--experimental:…`, `--passC:…` — silently did not reach them: `nim ic
# --opt:speed` produced a byte-identical debug binary. Forward the switches
# verbatim, minus the ones that MUST differ per child (the output/cache paths,
# the command itself, and IC's own per-rule switches, which each rule sets).
const notForwarded = [
"nimcache", "out", "o", "outdir", "usenimcache", "run", "r",
"incremental", "ic", "symbolfiles", "genbif",
"icproject", "icpreparsedconfig", "icconfigout", "icgroup",
"icbackendstage", "icbackendmodule", "ismainmodule",
"help", "h", "fullhelp", "version", "v", "advanced"]
for a in commandLineParams():
if a.len < 2 or a[0] != '-': continue
var i = 1
if i < a.len and a[i] == '-': inc i
var name = ""
while i < a.len and a[i] notin {':', '='}:
name.add a[i]
inc i
if normalize(name) notin notForwarded and a notin result:
result.add a
proc configSignatureFile(c: DepContext; forwardedArgs: seq[string]): string =
## nifmake decides staleness from file mtimes alone — it never looks at a
## rule's command line. So changing `-d:someDefine`, `--mm:` or `--threads:`
## between two `nim ic` runs re-generated the build file with the new switches
## but re-fired nothing: the user got a silently stale binary built with the
## OLD configuration. Reify the configuration as a FILE and make every rule
## that consumes it an input, so a config change moves an mtime like any edit.
## Written `OnlyIfChanged` so a genuine no-op run stays a no-op.
##
## Deliberately EXCLUDES the two per-build path switches (`--icproject:`,
## `--icPreparsedConfig:`): they name where this build lives, not what it
## produces, so including them made the signature differ between two caches
## holding byte-identical artifacts — which defeats prefilling a test's cache
## from a shared warm one (every rule would re-fire on the rewritten
## signature). The precompiled config still counts, by CONTENT: a `nim.cfg`
## edit changes the artifact, hence the hash, hence every rule.
result = getNimcacheDir(c.config).string / "ic_build_args.txt"
var content = ""
for p in c.config.searchPaths:
content.add "--path:" & p.string & "\n"
for a in forwardedArgs:
if a.startsWith("--icproject:") or a.startsWith("--icPreparsedConfig:"):
continue
content.add a & "\n"
if c.config.icPreparsedConfig.len > 0 and fileExists(c.config.icPreparsedConfig):
# Hash the precompiled config MINUS its `(nimcache "...")` entry — the one
# line in the artifact that records where this build's cache lives rather
# than what the config says. Everything else is genuinely config-derived, so
# two builds with the same `nim.cfg`/`config.nims` hash the same no matter
# which directory they run in.
var normalized = ""
try:
for line in lines(c.config.icPreparsedConfig):
if "(nimcache " in line: continue
normalized.add line
normalized.add '\n'
except IOError, OSError:
normalized = c.config.icPreparsedConfig
content.add "config:" & $secureHash(normalized) & "\n"
if not fileExists(result) or readFile(result) != content:
writeFile(result, content)
proc generateFrontendBuildFile(c: DepContext; forwardedArgs: seq[string]): string =
## Frontend build file: the nifler (parse) and `nim m` (sem) rules only. The
@@ -1119,7 +878,6 @@ proc generateFrontendBuildFile(c: DepContext; forwardedArgs: seq[string]): strin
# a NIF for each. Only dependencies *outside* the component become build-graph
# inputs — intra-component edges are produced by this very rule and listing
# them would reintroduce the cycle nifmake just rejected.
let argsFile = configSignatureFile(c, forwardedArgs)
let sccs = computeSCCs(c)
var sccOf = newSeq[int](c.nodes.len)
for sccId, comp in sccs:
@@ -1148,10 +906,6 @@ proc generateFrontendBuildFile(c: DepContext; forwardedArgs: seq[string]): strin
# Input 0 (the project file passed to `nim m`): the representative's .nim.
b.withTree "input":
b.addStrLit repPair.nimFile
# The configuration this child is invoked with (see configSignatureFile).
b.addTree "input"
b.addStrLit argsFile
b.endTree()
# All parsed files of every member (nifler outputs this group consumes).
for m in members:
for f in c.nodes[m].files:
@@ -1245,7 +999,7 @@ proc backendCFile(c: DepContext; node: Node): string =
if node.id == 0: AbsoluteFile node.files[0].nimFile
else: AbsoluteFile node.files[0].modname
result = changeFileExt(completeCfilePath(c.config,
mangleModuleName(c.config, cfilename).AbsoluteFile), icCFileExt(c.config)).string
mangleModuleName(c.config, cfilename).AbsoluteFile), ".nim.c").string
proc computeLiveBackendNodes(c: DepContext): seq[bool] =
## Which nodes the backend must code-generate: the closure reachable from the
@@ -1342,8 +1096,6 @@ proc generateBackendBuildFile(c: DepContext; forwardedArgs: seq[string]): string
if prunedStale:
removeFile(mergeFile)
let argsFile = configSignatureFile(c, forwardedArgs)
var b = nifbuilder.open(result)
defer: b.close()
@@ -1401,7 +1153,6 @@ proc generateBackendBuildFile(c: DepContext; forwardedArgs: seq[string]): string
b.addStrLit "--icBackendStage:lower"
b.addStrLit "--icBackendModule:" & node.files[0].modname
inputStr c.semmedFile(node.files[0])
inputStr argsFile
outputStr tFiles[i]
b.endTree()
@@ -1423,7 +1174,6 @@ proc generateBackendBuildFile(c: DepContext; forwardedArgs: seq[string]): string
b.addStrLit "--icBackendStage:cg"
b.addStrLit "--icBackendModule:" & node.files[0].modname
inputStr tFiles[i]
inputStr argsFile
if node.id == 0:
for j in 0 ..< c.nodes.len:
if c.nodes[j].id != 0 and live[j]:
@@ -1431,29 +1181,13 @@ proc generateBackendBuildFile(c: DepContext; forwardedArgs: seq[string]): string
outputStr cnifFiles[i]
b.endTree()
# merge: read the live modules' `.c.nif`, write the ownership/liveness
# decision. The list is handed over as a FILE (`LiveModulesFile`) because the
# merge child is a separate process that never sees the build file: without it
# merge globbed `*.c.nif` off the nimcache and so silently absorbed artifacts
# belonging to some other program that shares the directory.
let liveFile = nimcache / LiveModulesFile
block:
var manifest = ""
for i in 0 ..< c.nodes.len:
if live[i]:
manifest.add cnifFiles[i]
manifest.add "\n"
# OnlyIfChanged: its mtime is a merge input, so rewriting it every run would
# re-fire merge (and, through the decision, every `emit`) on a no-op build.
if not fileExists(liveFile) or readFile(liveFile) != manifest:
writeFile(liveFile, manifest)
# merge: read every `.c.nif`, write the ownership/liveness decision.
b.addTree "do"
b.addIdent "nim_nifc"
b.withTree "args":
b.addStrLit "--icBackendStage:merge"
for i in 0 ..< c.nodes.len:
if live[i]: inputStr cnifFiles[i]
inputStr liveFile
outputStr mergeFile
b.endTree()
@@ -1487,59 +1221,11 @@ proc generateBackendBuildFile(c: DepContext; forwardedArgs: seq[string]): string
b.addStrLit "--out:" & exeFile
for i in 0 ..< c.nodes.len:
if live[i]: inputStr cFiles[i]
inputStr argsFile
outputStr exeFile
b.endTree()
b.endTree() # stmts
proc deriveFromSemDeps(c: var DepContext): bool =
## Fold every already-compiled module's `.s.deps` sidecar (its REAL post-sem
## imports, macro-generated ones included) back into the graph. Returns true
## if anything new was added.
##
## Run BEFORE the first nifmake pass as well as after a failure. The static
## scanner cannot see `parseStmt("import dyn")`, so on the run that first hits
## it the frontend fails, this recovers the node, and the retry succeeds. But
## the graph is rebuilt from scratch on every `nim ic`, so on the NEXT run the
## frontend succeeds on round one — with `dyn` absent from the graph again,
## hence with no nifler/`nim m` rule of its own and no edge into its importer.
## Editing `dyn.nim` then changed nothing at all: the build silently reused the
## `.s.bif` from the run that discovered it. Seeding from the sidecars makes
## the discovery stick across runs.
##
## The edges are recorded SPECULATIVELY: a sidecar says what the module
## imported the last time it was semmed, which is a statement about the past.
## Flip a `when`, or delete an `import`, and a module that is no longer reached
## would otherwise linger in the graph forever (and fail to build, if what it
## imports is gone). Marking the edge speculative lets `pruneDeadSpeculative`
## drop such a leftover, while a genuinely-needed macro import — which compiles
## fine — stays.
result = false
inc c.speculating
defer: dec c.speculating
let n0 = c.nodes.len # snapshot: new nodes are traversed as they're added
for ni in 0 ..< n0:
for p in readSemDeps(c, c.nodes[ni].files[0]):
let pair = c.toPair(p)
var idx = c.processedModules.getOrDefault(pair.modname, -1)
if idx == -1:
if not fileExists(pair.nimFile): continue
let newNode = Node(files: @[pair], id: c.nodes.len)
if c.systemNodeId >= 0:
newNode.deps.add c.systemNodeId
if getsImplicitImports(c, pair.nimFile):
for impId in c.implicitNodeIds:
if impId != newNode.id: newNode.deps.add impId
c.processedModules[pair.modname] = newNode.id
c.nodes.add newNode
idx = newNode.id
traverseDeps(c, pair, newNode)
result = true
if idx != ni and idx notin c.nodes[ni].deps:
addDepEdge(c, c.nodes[ni], idx)
result = true
proc commandIc*(conf: ConfigRef; frontendOnly = false) =
## Main entry point for `nim ic`. With `frontendOnly` (used by `nim track` for
## IDE queries) it runs only Phase 1 — the incremental nifler + `nim m`
@@ -1637,17 +1323,6 @@ proc commandIc*(conf: ConfigRef; frontendOnly = false) =
# Process dependencies
traverseDeps(c, rootPair, rootNode)
# Re-apply what earlier runs discovered post-sem (macro-generated imports),
# so those modules keep their rules on a warm build instead of vanishing from
# the graph until the next failure. No-op on a cold cache. Runs BEFORE the
# prune so a sidecar entry that has since gone stale is prunable too.
discard deriveFromSemDeps(c)
# Modules that only a `when` the scanner cannot decide pulls in, and that
# import something not installed, are dead in this configuration; scheduling
# them would fail the build over code the classic compiler never reads.
pruneDeadSpeculative(c)
# Discovery via `.s.deps`: imports GENERATED by macros (chronicles builds
# `import chronicles/textlines` via parseStmt from the chronicles_sinks
# define) are invisible to the static scanner. Each `nim m` records the
@@ -1718,20 +1393,28 @@ proc commandIc*(conf: ConfigRef; frontendOnly = false) =
var discovered = false
inc rounds
if rounds <= 20:
discovered = deriveFromSemDeps(c)
let n0 = c.nodes.len # snapshot: new nodes are traversed as they're added
for ni in 0 ..< n0:
for p in readSemDeps(c, c.nodes[ni].files[0]):
let pair = c.toPair(p)
var idx = c.processedModules.getOrDefault(pair.modname, -1)
if idx == -1:
let newNode = Node(files: @[pair], id: c.nodes.len)
if c.systemNodeId >= 0:
newNode.deps.add c.systemNodeId
if getsImplicitImports(c, pair.nimFile):
for impId in c.implicitNodeIds:
if impId != newNode.id: newNode.deps.add impId
c.processedModules[pair.modname] = newNode.id
c.nodes.add newNode
idx = newNode.id
traverseDeps(c, pair, newNode)
discovered = true
if idx != ni and idx notin c.nodes[ni].deps:
c.nodes[ni].deps.add idx
discovered = true
if not discovered:
# The children have already printed the real diagnostics. Adding an
# `Error:` line of our own here made a build-system status the LAST error
# in the stream, hiding the compiler's own message from anything that
# reads the final error (testament's `errormsg:`, editors, CI log
# scrapers) — every `reject`-style test under `nim ic` reported
# "nifmake failed with exit code: 1" instead of what the compiler said.
# The non-zero exit is what signals failure; this line is context.
rawMessage(conf, hintExecuting,
"nifmake reported failures (exit code " & $exitCode & ")")
# Fail the run without printing an `Error:` of our own (see above): the
# exit code is derived from `errorCounter`.
inc conf.errorCounter
rawMessage(conf, errGenerated, "nifmake failed with exit code: " & $exitCode)
break
# Phase 2 — backend (whole-program `nim nifc`), run once over the now-final
@@ -1746,8 +1429,6 @@ proc commandIc*(conf: ConfigRef; frontendOnly = false) =
rawMessage(conf, hintExecuting, cmd)
let exitCode = execShellCmd(cmd)
if exitCode != 0:
rawMessage(conf, hintExecuting,
"nifmake reported backend failures (exit code " & $exitCode & ")")
inc conf.errorCounter
rawMessage(conf, errGenerated, "nifmake (backend) failed with exit code: " & $exitCode)
else:
rawMessage(conf, errGenerated, "nim ic not available in bootstrap build")

View File

@@ -454,7 +454,7 @@ proc gen(c: var Con; n: PNode) =
of nkPragmaBlock: gen(c, n.lastSon)
of nkDiscardStmt, nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString:
gen(c, n[0])
of nkConv, nkExprColonExpr, nkExprEqExpr, PathKinds1:
of nkConv, nkExprColonExpr, nkExprEqExpr, nkCast, PathKinds1:
gen(c, n[1])
of nkVarSection, nkLetSection: genVarSection(c, n)
of nkDefer: raiseAssert "dfa construction pass requires the elimination of 'defer'"

View File

@@ -14,7 +14,7 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener
let res = newSym(skResult, getIdent(g.cache, "result"), idgen, result, info)
res.typ = getSysType(g, info, tyString)
result.typ = newType(tyProc, idgen, result)
result.typ = newType(tyProc, idgen, t.owner)
result.typ.n = newNodeI(nkFormalParams, info)
rawAddSon(result.typ, res.typ)
result.typ.n.add newNodeI(nkEffectList, info)

View File

@@ -32,7 +32,7 @@
## would misresolve.
import options, commands, lineinfos, pathutils, msgs
import std/[algorithm, os, sets, osproc, times, streams, syncio, strutils]
import std/[algorithm, os, sets, osproc, times, streams, syncio]
import "../dist/nimony/src/lib" / [nifbuilder, nifcoreparse]
const
@@ -269,26 +269,11 @@ proc ensureIcConfig*(conf: ConfigRef) =
# verbatim: all `-`-prefixed switches first (in encounter order), then the
# non-switch project token(s). The producer re-reads `nim.cfg` itself.
var pargs = @["icconfig", "--icConfigOut:" & outPath]
# The command token is dropped below, so `nim cpp --ic:on` would hand the
# producer a C-backend config: name the backend explicitly. (`nim ic
# --backend:cpp` already carries the switch; the duplicate is harmless.)
if conf.backend != backendInvalid:
pargs.add "--backend:" & $conf.backend
var rest: seq[string] = @[]
var droppedCmd = false
for a in commandLineParams():
if a.len == 0: continue
if a[0] == '-':
# `--run`/`-r` must not reach the producer: it only serialises the
# resolved config, has no output binary, and `nim.nim`'s run step asserts
# on the empty `outFile` (`nim cpp --ic:on -r foo.nim`).
var name = ""
var i = 1
if i < a.len and a[i] == '-': inc i
while i < a.len and a[i] notin {':', '='}:
name.add a[i]
inc i
if normalize(name) in ["r", "run"]: continue
pargs.add a
elif not droppedCmd:
droppedCmd = true # drop the original command token (`ic`/`track`)

View File

@@ -423,20 +423,6 @@ proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode =
result.add(newSymNode(createMagic(c.graph, c.idgen, "default", mDefault)))
result.typ = t
proc stabilizeBracketIndex(n: PNode; c: var Con; body: var PNode): PNode =
## Evaluate a side-effecting index once and return the stable access.
doAssert n.kind == nkBracketExpr and not isAtom(n[1])
let temp = newSym(skLet, getIdent(c.graph.cache, "bracketTmp"), c.idgen,
c.owner, n[1].info)
temp.typ = n[1].typ
let tempAsNode = newSymNode(temp)
body.add newTree(nkLetSection, n[1].info,
newTree(nkIdentDefs, tempAsNode,
newNodeI(nkEmpty, tempAsNode.info), n[1]))
result = copyNode(n)
result.add n[0]
result.add tempAsNode
proc destructiveMoveVar(n: PNode; c: var Con; s: var Scope): PNode =
# generate: (let tmp = v; reset(v); tmp)
if (not hasDestructor(c, n.typ)) and c.inEnsureMove == 0:
@@ -448,10 +434,6 @@ proc destructiveMoveVar(n: PNode; c: var Con; s: var Scope): PNode =
else:
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
var n = n
if n.kind == nkBracketExpr and not isAtom(n[1]):
n = stabilizeBracketIndex(n, c, result)
var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.idgen, c.owner, n.info)
temp.typ = n.typ
var v = newNodeI(nkLetSection, n.info)
@@ -1137,11 +1119,6 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
result[i] = n[i]
of nkGotoState, nkState, nkAsmStmt:
result = n
of nkReplayAction:
# A `.rod`/NIF replay record. It only ever appears in a NIF-loaded
# module's TOP-LEVEL statements (the loader prepends the `(replay ...)`
# entries there); cgen discards it, so pass it through untouched.
result = n
else:
result = nil
internalError(c.graph.config, n.info, "cannot inject destructors to node kind: " & $n.kind)
@@ -1173,11 +1150,24 @@ proc sameLocation*(a, b: PNode): bool =
else: false
proc genFieldAccessSideEffects(c: var Con; s: var Scope; dest, ri: PNode; flags: set[MoveOrCopyFlag] = {}): PNode =
result = newNodeI(nkStmtList, ri.info)
let newAccess = stabilizeBracketIndex(ri, c, result)
let snk = c.genSink(s, dest, newAccess, flags)
result.add snk
result.add c.genWasMoved(newAccess)
# with side effects
var temp = newSym(skLet, getIdent(c.graph.cache, "bracketTmp"), c.idgen, c.owner, ri[1].info)
temp.typ = ri[1].typ
var v = newNodeI(nkLetSection, ri[1].info)
let tempAsNode = newSymNode(temp)
var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
vpart[0] = tempAsNode
vpart[1] = newNodeI(nkEmpty, tempAsNode.info)
vpart[2] = ri[1]
v.add(vpart)
var newAccess = copyNode(ri)
newAccess.add ri[0]
newAccess.add tempAsNode
var snk = c.genSink(s, dest, newAccess, flags)
result = newTree(nkStmtList, v, snk, c.genWasMoved(newAccess))
proc ownsData(c: var Con; s: var Scope; orig: PNode; flags: set[MoveOrCopyFlag]): PNode =
var n = orig

View File

@@ -1450,20 +1450,6 @@ proc genCheckedFieldOp(p: PProc, n: PNode, addrTyp: PType, r: var TCompRes) =
r.res = "$1.$2" % [tmp, field.loc.snippet]
r.kind = resExpr
proc isVarOpenArrayParam(n: PNode): bool =
## True if `n` resolves to a `var openArray` parameter. The JS backend
## represents such parameters as a `{base, off, len}` slice view so that
## writes through a `toOpenArray` view reach the caller's storage (bug #15952).
var it = n
while true:
case it.kind
of nkHiddenDeref, nkDerefExpr, nkHiddenAddr, nkAddr: it = it[0]
of nkHiddenStdConv, nkConv, nkObjDownConv, nkObjUpConv: it = it[1]
else: break
result = it.kind == nkSym and it.sym.kind == skParam and
it.sym.typ != nil and it.sym.typ.kind == tyVar and
it.sym.typ.len > 0 and it.sym.typ[0].kind == tyOpenArray
proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) =
var
a, b: TCompRes = default(TCompRes)
@@ -1472,19 +1458,6 @@ proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) =
let m = if n.kind == nkHiddenAddr: n[0] else: n
gen(p, m[0], a)
gen(p, m[1], b)
if isVarOpenArrayParam(m[0]):
# `var openArray` param is a `{base, off, len}` view; index the base with
# the offset applied. `m[0]` is a plain param name, safe to reference
# repeatedly (no side effects, so no temp needed).
let pn = a.rdLoc
r.address = "($1).base" % [pn]
if optBoundsCheck in p.options:
useMagic(p, "chckIndx")
r.res = "($1).off + chckIndx($2, 0, ($1).len - 1)" % [pn, b.rdLoc]
else:
r.res = "($1).off + ($2)" % [pn, b.rdLoc]
r.kind = resExpr
return
#internalAssert p.config, a.typ != etyBaseIndex and b.typ != etyBaseIndex
let (x, tmp) = maybeMakeTemp(p, m[0], a)
r.address = x
@@ -1753,49 +1726,8 @@ proc genArgNoParam(p: PProc, n: PNode, r: var TCompRes) =
else:
r.res.add(a.res)
proc genVarOpenArrayArg(p: PProc, n: PNode, r: var TCompRes) =
## Emit a `{base, off, len}` slice view for an argument to a `var openArray`
## parameter (bug #15952). The view always aliases the base storage, so writes
## through the callee's `openArray` reach the caller's array/seq/typed array.
var b, lo, hi, v: TCompRes = default(TCompRes)
# the argument reaches codegen as `addr(toOpenArray(x, lo, hi))` (possibly
# under conversions); unwrap to the actual `toOpenArray` call.
var sl = n
while true:
case sl.kind
of nkHiddenAddr, nkAddr, nkHiddenDeref, nkDerefExpr: sl = sl[0]
of nkHiddenStdConv, nkConv, nkObjDownConv, nkObjUpConv: sl = sl[1]
else: break
if sl.kind in nkCallKinds and getMagic(sl) == mSlice:
gen(p, sl[1], b)
gen(p, sl[2], lo)
gen(p, sl[3], hi)
if isVarOpenArrayParam(sl[1]):
# slicing a `var openArray` view: rebase onto the same underlying storage
r.res = "{base: ($1).base, off: ($1).off + $2, len: $3 - $2 + 1}" % [
b.rdLoc, lo.rdLoc, hi.rdLoc]
else:
r.res = "{base: $1, off: $2, len: $3 - $2 + 1}" % [
b.rdLoc, lo.rdLoc, hi.rdLoc]
elif isVarOpenArrayParam(sl):
# already a view from another `var openArray` param: forward it unchanged
gen(p, sl, b)
r.res = b.rdLoc
else:
# a whole array/seq/typed-array value: wrap with a zero offset
gen(p, n, v)
r.res = "{base: $1, off: 0, len: ($1).length}" % [v.rdLoc]
r.kind = resExpr
proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes;
emitted: ptr int = nil; skipVarOpenArray = false) =
proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes; emitted: ptr int = nil) =
var a: TCompRes = default(TCompRes)
if (not skipVarOpenArray) and param.typ != nil and param.typ.kind == tyVar and
param.typ[0].kind == tyOpenArray:
# `var openArray` params are passed as a `{base, off, len}` slice view.
genVarOpenArrayArg(p, n, a)
r.res.add(a.rdLoc)
return
gen(p, n, a)
if skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs} and
a.typ == etyBaseIndex:
@@ -1805,13 +1737,6 @@ proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes;
r.res.add(", ")
r.res.add(a.res)
if emitted != nil: inc emitted[]
elif skipTypes(param.typ, abstractVar).kind == tyOpenArray and
isVarOpenArrayParam(n):
# a `var openArray` view passed to a read-only `openArray` param: materialize
# a snapshot so the callee sees a plain array.
var w: TCompRes = default(TCompRes)
gen(p, n, w)
r.res.add("(($1).base).slice(($1).off, ($1).off + ($1).len)" % [w.rdLoc])
elif n.typ.kind in {tyVar, tyPtr, tyRef, tyLent, tyOwned} and
n.kind in nkCallKinds and mapType(param.typ) == etyBaseIndex:
# this fixes bug #5608:
@@ -1849,8 +1774,7 @@ proc genArgs(p: PProc, n: PNode, r: var TCompRes; start=1) =
r.kind = resExpr
proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType;
generated: var int; r: var TCompRes;
skipVarOpenArray = false) =
generated: var int; r: var TCompRes) =
if i >= n.len:
globalError(p.config, n.info, "wrong importcpp pattern; expected parameter at position " & $i &
" but got only: " & $(n.len-1))
@@ -1863,12 +1787,11 @@ proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType;
if paramType.isNil:
genArgNoParam(p, it, r)
else:
genArg(p, it, paramType.sym, r, skipVarOpenArray = skipVarOpenArray)
genArg(p, it, paramType.sym, r)
inc generated
proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
r: var TCompRes) =
let skipVarOpenArray = sfImportc in n[0].sym.flags
var i = 0
var j = 1
r.kind = resExpr
@@ -1878,11 +1801,11 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
var generated = 0
for k in j..<n.len:
if generated > 0: r.res.add(", ")
genOtherArg(p, n, k, typ, generated, r, skipVarOpenArray)
genOtherArg(p, n, k, typ, generated, r)
inc i
of '#':
var generated = 0
genOtherArg(p, n, j, typ, generated, r, skipVarOpenArray)
genOtherArg(p, n, j, typ, generated, r)
inc j
inc i
of '\31':
@@ -2448,21 +2371,13 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
useMagic(p, "nimCopy")
r.res = "nimCopy(null, $1, $2)" % [x.rdLoc, genTypeInfo(p, n.typ)]
of mOpenArrayToSeq:
if isVarOpenArrayParam(n[1]):
var x: TCompRes = default(TCompRes)
gen(p, n[1], x)
r.res = "(($1).base).slice(($1).off, ($1).off + ($1).len)" % [x.rdLoc]
r.kind = resExpr
else:
genCall(p, n, r)
genCall(p, n, r)
of mDestroy, mTrace: discard "ignore calls to the default destructor"
of mOrd: genOrd(p, n, r)
of mLengthStr, mLengthSeq, mLengthOpenArray, mLengthArray:
var x: TCompRes = default(TCompRes)
gen(p, n[1], x)
if isVarOpenArrayParam(n[1]):
r.res = "($1).len" % [x.rdLoc]
elif skipTypes(n[1].typ, abstractInst).kind == tyCstring:
if skipTypes(n[1].typ, abstractInst).kind == tyCstring:
let (a, tmp) = maybeMakeTemp(p, n[1], x)
r.res = "(($1) == null ? 0 : ($2).length)" % [a, tmp]
else:
@@ -2471,9 +2386,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
of mHigh:
var x: TCompRes = default(TCompRes)
gen(p, n[1], x)
if isVarOpenArrayParam(n[1]):
r.res = "($1).len - 1" % [x.rdLoc]
elif skipTypes(n[1].typ, abstractInst).kind == tyCstring:
if skipTypes(n[1].typ, abstractInst).kind == tyCstring:
let (a, tmp) = maybeMakeTemp(p, n[1], x)
r.res = "(($1) == null ? -1 : ($2).length - 1)" % [a, tmp]
else:
@@ -2556,24 +2469,11 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
genCall(p, n, r)
of mSlice:
# arr.slice([begin[, end]]): 'end' is exclusive
# Fixed homogeneous numeric arrays lower to JS typed arrays; `slice`
# copies, which silently breaks `var openArray` write-through (bug #15952).
# `subarray` returns a live shared-buffer view with the same
# exclusive-end signature, so use it there; keep `slice` for seqs/strings.
var x, y, z: TCompRes = default(TCompRes)
gen(p, n[1], x)
gen(p, n[2], y)
gen(p, n[3], z)
if isVarOpenArrayParam(n[1]):
# re-slicing a `var openArray` view: materialize from the view's base/offset
r.res = "(($1).base).slice(($1).off + $2, ($1).off + $3 + 1)" % [
x.rdLoc, y.rdLoc, z.rdLoc]
else:
let baseTy = skipTypes(n[1].typ, abstractVarRange + {tyLent})
if baseTy.kind == tyArray and arrayTypeForElemType(p.config, elemType(baseTy)).len > 0:
r.res = "($1.subarray($2, $3 + 1))" % [x.rdLoc, y.rdLoc, z.rdLoc]
else:
r.res = "($1.slice($2, $3 + 1))" % [x.rdLoc, y.rdLoc, z.rdLoc]
r.res = "($1.slice($2, $3 + 1))" % [x.rdLoc, y.rdLoc, z.rdLoc]
r.kind = resExpr
of mMove:
genMove(p, n, r)

View File

@@ -82,7 +82,7 @@ proc lookup(typeMap: ref LayeredIdTableObj, key: ItemId): PType =
template lookup*(typeMap: ref LayeredIdTableObj, key: PType): PType =
## recursively looks up binding of `key` in all parent layers
lookup(typeMap, key.bindingId)
lookup(typeMap, key.itemId)
when not useRef:
proc lookup(typeMap: LayeredIdTableObj, key: ItemId): PType {.inline.} =
@@ -91,11 +91,11 @@ when not useRef:
result = lookup(typeMap.nextLayer, key)
template lookup*(typeMap: LayeredIdTableObj, key: PType): PType =
lookup(typeMap, key.bindingId)
lookup(typeMap, key.itemId)
proc put(typeMap: var LayeredIdTable, key: ItemId, value: PType) {.inline.} =
typeMap.topLayer[key] = value
template put*(typeMap: var LayeredIdTable, key, value: PType) =
## binds `key` to `value` only in current layer
put(typeMap, key.bindingId, value)
put(typeMap, key.itemId, value)

View File

@@ -718,7 +718,7 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
when defined(icDbg):
if t.destructor == nil:
echo "MISSING destructor: ", typeToString(t), " kind=", t.kind,
" itemId=", t.itemId, " bindingId=", t.bindingId, " state=", t.state,
" itemId=", t.itemId, " uniqueId=", t.uniqueId, " state=", t.state,
" owner=", (if t.owner != nil: t.owner.name.s else: "nil")
doAssert t.destructor != nil
body.add destructorCall(c, t.destructor, x)
@@ -1233,7 +1233,7 @@ proc symDupPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttache
res.typ = typ
src.typ = typ
result.typ = newType(tyProc, idgen, result)
result.typ = newType(tyProc, idgen, owner)
result.typ.n = newNodeI(nkFormalParams, info)
rawAddSon(result.typ, res.typ)
result.typ.n.add newNodeI(nkEffectList, info)
@@ -1279,8 +1279,7 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
else:
src.typ = typ
# the hook OWNS its signature, like any routine sem'd from source
result.typ = newProcType(info, idgen, result)
result.typ = newProcType(info, idgen, owner)
result.typ.addParam dest
if kind notin {attachedDestructor, attachedWasMoved}:
result.typ.addParam src

View File

@@ -29,7 +29,7 @@ when defined(nimPreviewSlimSystem):
import ../dist/checksums/src/checksums/sha1
import pipelines
from icconfig import produceIcConfig, ensureIcConfig
from icconfig import produceIcConfig
when not defined(nimKochBootstrap):
import nifbackend
@@ -269,28 +269,6 @@ proc mainCommand*(graph: ModuleGraph) =
proc compileToBackend() =
customizeForBackend(conf.backend)
if isIcDriver(conf):
# `nim c --ic:on` / `nim cpp --ic:on`: same driver as `nim ic`, entered
# through the ordinary compile command so every backend switch the user
# already knows keeps working (`nim cpp`, `--exceptions:`, `-d:`, ...).
# `customizeForBackend` above has already defined the backend symbol and
# picked the exception model, which is exactly what the per-module
# children must inherit — `computeForwardedArgs` forwards both.
setUseIc(true)
wantMainModule(conf)
setOutFile(conf)
when not defined(nimKochBootstrap):
if conf.icPreparsedConfig.len == 0:
# `--ic:on` came from a `nim.cfg`/`config.nims` rather than the command
# line, so `nim.nim` could not see it before config loading and the
# precompiled config the children replay does not exist yet. Produce it
# now. (The driver then keeps the config IT parsed instead of replaying
# the artifact; both come from the same files.)
ensureIcConfig(conf)
commandIc(conf)
else:
rawMessage(conf, errGenerated, "--ic:on not available in bootstrap build")
return
setOutFile(conf)
case conf.backend
of backendC: commandCompileToC(graph)

View File

@@ -136,10 +136,6 @@ type
systemModule*: PSym
sysTypes*: array[TTypeKind, PType]
compilerprocs*: TStrTable
missingCompilerProcs*: HashSet[string]
# `nim nifc` only: compilerproc names no
# loaded module defines, so the whole-program
# index scan in `loadCompilerProc` runs once
exposed*: TStrTable
packageTypes*: TStrTable
emptyNode*: PNode
@@ -169,11 +165,6 @@ type
onDefinitionResolveForward*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
onUsage*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
globalDestructors*: seq[PNode]
icModuleDtors*: seq[string] # per-module backend: the C names of the
# other modules' global-destructor procs
# (`genIcModuleDestroyGlobals`), already in
# call order; only the main module's `cg`
# fills this, from the `.c.nif` meta heads
strongSemCheck*: proc (graph: ModuleGraph; owner: PSym; body: PNode) {.nimcall.}
compatibleProps*: proc (graph: ModuleGraph; formal, actual: PType): bool {.nimcall.}
idgen*: IdGenerator
@@ -352,8 +343,8 @@ iterator procInstCacheItems*(g: ModuleGraph; s: PSym): PInstantiation =
proc getAttachedOp*(g: ModuleGraph; t: PType; op: TTypeAttachedOp): PSym =
## returns the requested attached operation for type `t`. Can return nil
## if no such operation exists.
if g.attachedOps[op].contains(t.bindingId):
result = g.attachedOps[op][t.bindingId]
if g.attachedOps[op].contains(t.itemId):
result = g.attachedOps[op][t.itemId]
elif g.config.cmd in {cmdNifC, cmdM}:
# Fall back to key-based lookup for NIF-loaded hooks
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
@@ -382,7 +373,7 @@ proc setAttachedOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp;
# references derived env-field syms that no module's NIF defines
if g.loadedOps[op].getOrDefault(key) == nil:
g.loadedOps[op][key] = value
g.attachedOps[op][t.bindingId] = value
g.attachedOps[op][t.itemId] = value
return
let existing = g.loadedOps[op].getOrDefault(key)
if existing == nil:
@@ -420,7 +411,7 @@ proc setAttachedOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp;
break
if not updated:
g.opsLog.add LogEntry(kind: HookEntry, op: op, module: module, key: key, sym: value)
g.attachedOps[op][t.bindingId] = value
g.attachedOps[op][t.itemId] = value
proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttachedOp; value: PSym) =
## Overload that takes ItemId directly, useful for registering hooks from NIF index.
@@ -428,7 +419,7 @@ proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttach
proc setAttachedOpPartial*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) =
## we also need to record this to the packed module.
g.attachedOps[op][t.bindingId] = value
g.attachedOps[op][t.itemId] = value
proc completePartialOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) {.inline.} =
discard
@@ -450,19 +441,19 @@ proc addNifReplayAction*(g: ModuleGraph; module: int32; n: PNode) =
g.nifReplayActions.mgetOrPut(module, @[]).add n
iterator getMethodsPerType*(g: ModuleGraph; t: PType): PSym =
if g.methodsPerType.contains(t.bindingId):
for it in mitems g.methodsPerType[t.bindingId]:
if g.methodsPerType.contains(t.itemId):
for it in mitems g.methodsPerType[t.itemId]:
yield it
proc getToStringProc*(g: ModuleGraph; t: PType): PSym =
result = g.enumToStringProcs.getOrDefault(t.bindingId)
result = g.enumToStringProcs.getOrDefault(t.itemId)
if result == nil and g.config.cmd in {cmdNifC, cmdM}:
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
result = g.loadedEnumToStringProcs.getOrDefault(key)
assert result != nil
proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) =
g.enumToStringProcs[t.bindingId] = value
g.enumToStringProcs[t.itemId] = value
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
# Stamp with the module that owns the generated proc, not the enum's def
# module: the def module's process may never have generated it (same
@@ -470,12 +461,12 @@ proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) =
g.opsLog.add LogEntry(kind: EnumToStrEntry, module: value.itemId.module.int, key: key, sym: value)
iterator methodsForGeneric*(g: ModuleGraph; t: PType): (int, PSym) =
if g.methodsPerGenericType.contains(t.bindingId):
for it in mitems g.methodsPerGenericType[t.bindingId]:
if g.methodsPerGenericType.contains(t.itemId):
for it in mitems g.methodsPerGenericType[t.itemId]:
yield (it[0], it[1])
proc addMethodToGeneric*(g: ModuleGraph; module: int; t: PType; col: int; m: PSym) =
g.methodsPerGenericType.mgetOrPut(t.bindingId, @[]).add (col, m)
g.methodsPerGenericType.mgetOrPut(t.itemId, @[]).add (col, m)
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: module
g.opsLog.add LogEntry(kind: MethodEntry, module: ownerModule, key: key, sym: m)
@@ -490,49 +481,6 @@ proc logMethodDef*(g: ModuleGraph; s: PSym) =
g.opsLog.add LogEntry(kind: MethodEntry, module: s.itemId.module.int,
key: "", sym: s)
proc logCppMember*(g: ModuleGraph; s: PSym) =
## Log a C++ `{.member.}`/`{.virtual.}`/`{.constructor.}` registration (and the
## `importcpp` default-initializer flavour) so the NIF backend can rebuild
## `memberProcsPerType`/`initializersPerType`, which live only in the sem
## process. Without them the per-module backend emitted the struct WITHOUT its
## in-class member declarations and the out-of-class definitions did not match
## ("no declaration matches 'void Doo::memberProc()'").
##
## No type key: `replayCppMember` re-derives the type from the routine's
## signature exactly as `semCppMember` does, so nothing has to survive the
## round trip except the routine itself.
if g.config.cmd in {cmdNifC, cmdM}:
g.opsLog.add LogEntry(kind: CppMemberEntry, module: s.itemId.module.int,
key: "", sym: s)
proc replayCppMember*(g: ModuleGraph; s: PSym) =
## Inverse of `logCppMember`, mirroring `semstmts.semCppMember`'s derivation.
if s == nil or s.typ == nil: return
if sfImportc notin s.flags:
var typ = if sfConstructor in s.flags: s.typ.returnType else: s.typ.firstParamType
if typ != nil and typ.kind == tyPtr and sfConstructor notin s.flags:
typ = typ.elementType
if typ != nil and typ.kind == tyObject:
let procs = addr g.memberProcsPerType.mgetOrPut(typ.bindingId, @[])
for prc in procs[]:
if prc == s: return
procs[].add s
else:
let typ = s.typ.returnType
if typ != nil and typ.kind == tyObject and
typ.bindingId notin g.initializersPerType and s.typ.n != nil:
# The default values sem read off the `nkIdentDefs` live on the param syms.
var call = newTree(nkCall, newSymNode(s))
var isInitializer = s.typ.n.len > 1
for i in 1 ..< s.typ.n.len:
let p = s.typ.n[i]
if p.kind != nkSym or p.sym.ast == nil or p.sym.ast.kind == nkEmpty:
isInitializer = false
break
call.add p.sym.ast
if isInitializer:
g.initializersPerType[typ.bindingId] = call
proc registerLoadedMethod*(g: ModuleGraph; m: PSym) =
## Rebuild the dispatch buckets from a serialized method registration.
## Buckets group the methods sharing a dispatcher; the dispatcher's BODY
@@ -690,29 +638,6 @@ proc loadCompilerProc*(g: ModuleGraph; name: string): PSym =
strTableAdd(g.compilerprocs, result)
return result
# `nim nifc`: a module loaded from a NIF is named by its mangled suffix
# (`thrkxstl4`), not by its source name, and its file index resolves to
# that suffix too — so the `"threadpool"` match below can never fire and
# `spawn`, expanded at codegen time, died on `system module needs:
# nimArgsPassingDone`. The backend loads the WHOLE program before
# codegen starts, so just consult every loaded module's index; a miss is
# final for the rest of the process (nothing more gets loaded) and is
# remembered, because `getCompilerProc` is also used as a mere presence
# probe and would otherwise rescan every index on every call.
if g.config.cmd == cmdNifC:
if name in g.missingCompilerProcs: return nil
for moduleIdx in 0..<g.ifaces.len:
let module = g.ifaces[moduleIdx].module
if module == nil or module.position.FileIndex == systemFileIdx: continue
if not fileExists(toNifFilename(g.config, module.position.FileIndex)):
continue
result = tryResolveCompilerProc(ast.program, name, module.position.FileIndex)
if result != nil:
strTableAdd(g.compilerprocs, result)
return result
g.missingCompilerProcs.incl name
return nil
# Try threadpool module (some compilerprocs like FlowVar are there)
# Find threadpool module by searching loaded modules
for moduleIdx in 0..<g.ifaces.len:
@@ -1015,8 +940,6 @@ when not defined(nimKochBootstrap):
g.loadedOps[x.op][x.key] = x.sym
of EnumToStrEntry:
g.loadedEnumToStringProcs[x.key] = x.sym
of CppMemberEntry:
replayCppMember(g, x.sym)
of MethodEntry:
# only `methodDef` registrations (empty key) rebuild dispatch
# buckets; the `addMethodToGeneric` flavor (typeKey key) announces
@@ -1142,24 +1065,11 @@ when not defined(nimKochBootstrap):
setOwner(m, getPackage(g.config, g.cache, fileIdx))
# Register module in graph
registerModule(g, m)
# ... and, in the BACKEND, bind its NIF name to THIS symbol before anything
# in the file is decoded, so the loader never mints a second `skModule` for
# it (see `registerModuleSelfSym`). Backend-only: under `nim m` a module is
# loaded for its INTERFACE, and re-pointing the owner slot of every loaded
# symbol at the freshly built module sym changes what sem sees for an
# imported routine — `times.toDateTimeByWeek` then lost its inferred
# `raises` and the importer failed with "can raise an unlisted exception".
if g.config.cmd == cmdNifC:
registerModuleSelfSym(ast.program, cachedModuleSuffix(g.config, fileIdx), m)
result = loadNifModule(ast.program, fileIdx,
g.ifaces[fileIdx.int].interf,
g.ifaces[fileIdx.int].interfHidden, flags)
result.module = m
# Restore the module symbol's persisted flags (see ast2nif `(modflags)`);
# `cgen.genTopLevelStmt` gates the destructor pass on `sfInjectDestructors`.
if (result.moduleFlags and ModFlagInjectDestructors) != 0:
m.incl sfInjectDestructors
for (mname, msuffix) in result.reexportedModules:
let ms = materializeReexportedModule(g, mname, msuffix)
if ms != nil:
@@ -1207,7 +1117,7 @@ when not defined(nimKochBootstrap):
discard "dispatch buckets already rebuilt by registerLoadedHooks"
of GenericInstEntry:
raiseAssert "GenericInstEntry should not be in the NIF index"
of HookEntry, EnumToStrEntry, CppMemberEntry:
of HookEntry, EnumToStrEntry:
discard "already done by registerLoadedHooks"
# Register methods per type from NIF index
discard "todo"

View File

@@ -635,14 +635,6 @@ proc generateCgStage(g: ModuleGraph; mainFileIdx: FileIndex) =
# lifted hooks via moduleFromNifFile's registerLoadedHooks. Nothing to apply.
generateCodeForModule(g, target)
let bl = BModuleList(g.backend)
if sfMainModule notin target.module.flags:
# This module's top-level `var`s with a `=destroy` registered their teardown
# in `graph.globalDestructors` during `genTopLevelStmt` above. Main's `cg` is
# a different process and never sees them, so emit them as this TU's own
# exported proc and announce the name in the meta head.
let tbm = bl.mods[target.module.position]
if tbm != nil:
tbm.icGlobalDtorName = genIcModuleDestroyGlobals(g, tbm)
# The main module also owns the whole-program method dispatchers + NimMain.
if sfMainModule in target.module.flags:
emitMethodDispatchers(g)
@@ -703,13 +695,6 @@ proc generateCgStage(g: ModuleGraph; mainFileIdx: FileIndex) =
for m in ordered:
let heads = readCnifHeads(getCFile(m).string & ".nif")
registerReusedModuleToMain(bl, m, heads.initRequired, heads.datInitRequired)
if heads.globalDtor.len > 0: g.icModuleDtors.add heads.globalDtor
# `ordered` is dependency (post-order) init order; teardown runs in reverse,
# so an importer's globals are destroyed before the ones it may still point
# at. This mirrors whole-program cgen, which walks its single accumulated
# `globalDestructors` list backwards. Main's own destructors come first and
# are added by `finalCodegenActions` itself.
reverse g.icModuleDtors
let tb = bl.mods[target.module.position]
if tb != nil:
finishModule(g, tb)
@@ -739,19 +724,8 @@ proc generateMergeStage(g: ModuleGraph) =
## in-process first-claimant/DCE coordination.
let nimcache = getNimcacheDir(g.config).string
var files: seq[string] = @[]
# The driver lists the live modules' artifacts explicitly (deps.nim's
# `writeLiveModules`); only fall back to globbing when that manifest is
# absent (a cache written by an older compiler). Globbing merges whatever
# `.c.nif` happens to sit in the directory, which is wrong the moment the
# cache is shared with another program — see `LiveModulesFile`.
let manifest = nimcache / LiveModulesFile
if fileExists(manifest):
for line in lines(manifest):
let p = line.strip()
if p.len > 0: files.add p
else:
for artifact in walkFiles(nimcache / ("*" & icCFileExt(g.config) & ".nif")):
files.add artifact
for artifact in walkFiles(nimcache / "*.c.nif"):
files.add artifact
sort files
let decision = computeMergeDecision(files)
if decision.broken:
@@ -790,7 +764,7 @@ proc generateEmitStage(g: ModuleGraph; mainFileIdx: FileIndex) =
if targetIsMain: AbsoluteFile toFullPath(g.config, mainFileIdx)
else: AbsoluteFile g.config.icBackendModule
let cfile = changeFileExt(completeCfilePath(g.config,
mangleModuleName(g.config, cfilename).AbsoluteFile), icCFileExt(g.config)).string
mangleModuleName(g.config, cfilename).AbsoluteFile), ".nim.c").string
let artifact = cfile & ".nif"
if not fileExists(artifact):
rawMessage(g.config, errGenerated,
@@ -873,7 +847,7 @@ proc generateLinkStage(g: ModuleGraph; mainFileIdx: FileIndex) =
if not decision.broken:
var liveOwners = initHashSet[string]()
for cname, owner in decision.owners:
if owner.endsWith(icCFileExt(g.config) & ".nif") and cname in decision.live:
if owner.endsWith(".c.nif") and cname in decision.live:
liveOwners.incl owner
for owner in liveOwners:
let cbase = owner[0 ..< owner.len - ".nif".len] # "@m….nim.c.nif" -> ".c"

View File

@@ -120,7 +120,7 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
# so `loadConfigs` replays it instead of re-parsing the `nim.cfg` chain — the
# driver runs on the exact same config its children will. See icconfig.nim.
when not defined(nimKochBootstrap):
if conf.cmd in {cmdIc, cmdTrack} or isIcDriver(conf):
if conf.cmd in {cmdIc, cmdTrack}:
ensureIcConfig(conf)
var graph = newModuleGraph(cache, conf)

View File

@@ -29,7 +29,7 @@ const
nimEnableCovariance* = defined(nimEnableCovariance)
icFormatVersion* = "37"
icFormatVersion* = "30"
## Version of the IC cache format (the sem-NIF module layout written by
## ast2nif.nim plus the iface/impl/edges side files). Bump it whenever
## that layout changes: `commandIc` wipes a nimcache whose `ic.version`
@@ -54,16 +54,6 @@ const
## id, so its hash is stable across the NIF boundary (was breaking
## nim-serialization's auto-serialization lookup under IC). The sem-NIF
## macrocache entries and baked generic-instance bodies hold the old hashes.
## v7 (=31): anonymous wrapper types (`var T`, `lent T`, `sink T`, tuples)
## are named by their CONTENT instead of `itemId.item`, the module-wide
## type-mint counter (see ast2nif.CanonTypeKinds). Old caches name the same
## type differently, so every `.s.bif` reference would dangle.
## v8 (=32): the same for `tyProc`, except that a proc type which is a
## routine's SIGNATURE is named after that routine rather than by content
## (see ast2nif.sigRoutineOf). Renames types, so old caches dangle again.
## v9 (=33): and for the per-module `int`/`float` LITERAL COPIES (see
## ast2nif.CanonLitCopyKinds), the last mover that broke a build outright
## (`symbol has no offset` out of a cached `.t.bif`). Renames types again.
type # please make sure we have under 32 options
# (improves code efficiency a lot!)
@@ -940,24 +930,6 @@ proc getOsCacheDir(): string =
else:
result = getHomeDir() / genSubDir.string
proc isIcDriver*(conf: ConfigRef): bool =
## True for `nim c --ic:on` / `nim cpp --ic:on`: this process is the `nim ic`
## DRIVER (it builds the nifmake graph and spawns the per-module children),
## not a compilation. `nim ic` itself keeps its own `cmdIc` branch.
conf.ic and conf.cmd in {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC}
proc icCFileExt*(conf: ConfigRef): string =
## The extension the per-module backend gives a module's translation unit.
## Mirrors `cgen.getCFile` at BACKEND granularity, which is all the `nim ic`
## driver can know: it DECLARES every module's `.c`/`.cpp` output to nifmake
## without loading a single module, so a per-module `{.compile: cpp.}`
## (`sfCompileToCpp`) is out of reach — and `nim cpp` selects the backend for
## the whole program anyway.
case conf.backend
of backendCpp: ".nim.cpp"
of backendObjc: ".nim.m"
else: ".nim.c"
proc getNimcacheDir*(conf: ConfigRef): AbsoluteDir =
proc nimcacheSuffix(conf: ConfigRef): string =
if conf.ideActive: "_nimsuggest" # dedicated cache, never shared with `nim c`

View File

@@ -248,15 +248,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
# current strongly-connected import group (`--icGroup`) are the exception:
# they are compiled from source here, so each must write its own NIF.
let shouldWriteNif =
if graph.config.errorCounter > 0:
# Never persist an artifact built from erroneous AST. `nim m` does exit
# non-zero, but its outputs would still land on disk NEWER than their
# inputs, so nifmake sees the rule as satisfied on the next run: the
# build then "succeeds" from a poisoned NIF — a silently wrong binary,
# or an internal error once codegen meets an `nkError` body. Leaving the
# outputs missing keeps the rule dirty so it re-fires and re-reports.
false
elif graph.config.ideActive:
if graph.config.ideActive:
# nimsuggest (cmdM): persist NIF for cleanly-compiled, SAVED modules so
# later queries load them instead of recompiling. Never persist the
# actively edited buffer (it may hold unsaved/incomplete code) nor a
@@ -313,7 +305,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
var typeOffers: seq[tuple[generic: PSym; inst: PType]] = @[]
for genItemId, instList in graph.typeInstCache:
for inst in instList:
if inst != nil and inst.itemId.module == module.position and
if inst != nil and inst.uniqueId.module == module.position and
inst.kidsLen > 0 and inst[0] != nil and
inst[0].kind == tyGenericBody and inst[0].sym != nil:
typeOffers.add (inst[0].sym, inst)
@@ -328,17 +320,10 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
let firstUnusedId = max(idgen.symId, idgen.typeId)
var expansions: seq[(PSym, TLineInfo)] = @[]
discard graph.nifExpansions.take(module.position.int32, expansions)
# The module symbol's own backend-relevant flags. `sfInjectDestructors` is
# set by sempass2 when the module's TOP-LEVEL statements need the
# destructor pass; `moduleFromNifFile` builds a fresh module PSym, so
# without persisting it `cgen.genTopLevelStmt` skipped
# `injectDestructorCalls` and top-level locals were never destroyed.
let moduleFlags =
if sfInjectDestructors in module.flags: ModFlagInjectDestructors else: 0'i32
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog,
replayActions, implDeps, reexportedModuleSyms(graph, module),
genericOffers, typeOffers, resolvedImportDeps, firstUnusedId,
expansions, moduleFlags)
expansions)
# The module's REAL direct imports (incl. macro-generated) for `nim ic`'s
# graph re-derivation; see ast2nif.writeSemDeps / semdata.addImportFileDep.
var semDepPaths: seq[string] = @[]

View File

@@ -77,7 +77,7 @@ proc isAttachableRoutineTo(prc: PSym, arg: PType): bool =
# has default value, parameter is not considered in type attachment
continue
let t = nominalRoot(prc.typ[i])
if t != nil and t.bindingId == arg.bindingId:
if t != nil and t.itemId == arg.itemId:
# parameter `i` is a nominal type in this module
# attachable if the nominal root `t` has the same id as `arg`
return true
@@ -735,10 +735,10 @@ proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode =
when defined(icDbg):
if result == nil and f != nil and a != nil and f.kind == tyEnum:
echo "INDEXMISMATCH f=", typeToString(f), " itemId=", f.itemId,
" bindingId=", f.bindingId, " mod=", toFullPath(c.config, f.itemId.module.FileIndex),
" uniqueId=", f.uniqueId, " mod=", toFullPath(c.config, f.itemId.module.FileIndex),
" sym=", (if f.sym != nil: $f.sym.itemId else: "nil"), " state=", f.state
let a2 = a.skipTypes({tyRange})
echo " a=", typeToString(a), " itemId=", a2.itemId, " bindingId=", a2.bindingId,
echo " a=", typeToString(a), " itemId=", a2.itemId, " uniqueId=", a2.uniqueId,
" mod=", toFullPath(c.config, a2.itemId.module.FileIndex),
" sym=", (if a2.sym != nil: $a2.sym.itemId else: "nil"), " state=", a2.state

View File

@@ -1931,7 +1931,7 @@ proc borrowCheck(c: PContext, n, le, ri: PNode) =
PathKinds0 = {nkDotExpr, nkCheckedFieldExpr,
nkBracketExpr, nkAddr, nkHiddenAddr,
nkObjDownConv, nkObjUpConv}
PathKinds1 = {nkHiddenStdConv, nkHiddenSubConv, nkCast}
PathKinds1 = {nkHiddenStdConv, nkHiddenSubConv}
proc getRoot(n: PNode; followDeref: bool): PNode =
result = n
@@ -2187,7 +2187,7 @@ proc semProcBody(c: PContext, n: PNode; expectedType: PType = nil): PNode =
echo "[icMetaRet] meta result type for ", c.p.owner.name.s, ": ",
typeToString(c.p.resultSym.typ), " kind=", c.p.resultSym.typ.kind,
" flags=", c.p.resultSym.typ.flags,
" itemId=", c.p.resultSym.typ.itemId.module, ".", c.p.resultSym.typ.itemId.item,
" uid=", c.p.resultSym.typ.uniqueId.module, ".", c.p.resultSym.typ.uniqueId.item,
" state=", c.p.resultSym.typ.state
if isEmptyType(result.typ):
# we inferred a 'void' return type:
@@ -2715,9 +2715,13 @@ proc semNimvmBranch(c: PContext, n: PNode, flags: TExprFlags): PNode =
oldNotes = c.config.notes
oldWarningAsErrors = c.config.warningAsErrors
oldFeatures = c.features
# Both branches are checked, but their declarations cannot affect later code.
c.openShadowScope()
try:
result = semExpr(c, n, flags)
finally:
c.closeScope()
c.optionStack = oldOptionStack
c.config.options = oldOptions
c.config.notes = oldNotes

View File

@@ -129,12 +129,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
result.typ = nil
onUse(n.info, s)
of skParam:
if s.typ != nil and s.typ.kind == tyStatic and s.typ.n != nil:
# The enclosing routine gives this static parameter a concrete value.
# Keep that value so the nested generic can fold it as a compile-time
# expression instead of generating a runtime parameter reference.
result = s.typ.n
elif s.owner == c.p.owner:
if s.owner == c.p.owner:
# Parameters of the routine currently being semchecked stay as local
# identifiers
result = n
@@ -686,3 +681,4 @@ proc semConceptBody(c: PContext, n: PNode): PNode =
)
result = semGenericStmt(c, n, {withinConcept}, ctx)
semIdeForTemplateOrGeneric(c, result, ctx.cursorInBody)

View File

@@ -349,7 +349,7 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
when defined(icDbgRefc):
echo "[icInst] ", prc.name.s, " param ", oldParam.name.s,
": ", typeToString(resulti), " (kind=", resulti.kind,
" itemId=", resulti.itemId.module, ".", resulti.itemId.item,
" uid=", resulti.uniqueId.module, ".", resulti.uniqueId.item,
" flags=", resulti.flags, ") -> ", typeToString(paramType),
" (kind=", paramType.kind, ")"
@@ -407,10 +407,6 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
eraseVoidParams(result)
skipIntLiteralParams(result, c.idgen)
# The signature belongs to the INSTANCE, not to the generic it was copied
# from: `instCopyType` above kept the generic's owner, and every parameter has
# already been re-owned with `setOwner(param, prc)`.
setOwner(result, prc)
prc.typ = result
popInfoContext(c.config)

View File

@@ -478,15 +478,6 @@ proc turnFinalizerIntoDestructor(c: PContext; orig: PSym; info: TLineInfo): PSym
# proc signature:
result.typ = newProcType(result.info, c.idgen, result)
result.typ.addParam newParam
# `transform` only rewrites the PARAMETER, so the copied AST still names `orig`
# at `namePos`. Make the definition name itself, the invariant every other
# routine AST keeps: the NIF writer re-derives a routine's serialized AST from
# `ast[namePos].sym.ast` (ast2nif's `nkProcDef` branch), so a stale name node
# made this proc serialize `orig`'s body — whose parameter belongs to `orig`.
# Lambda lifting then saw the body's parameter as a variable captured from
# another proc and aborted with "internal error: environment misses: x".
if result.ast != nil and result.ast.safeLen > namePos:
result.ast[namePos] = newSymNode(result, result.info)
proc semQuantifier(c: PContext; n: PNode): PNode =
checkSonsLen(n, 2, c.config)

View File

@@ -109,7 +109,7 @@ proc getObjDepth(t: PType): (int, ItemId) =
x = skipTypes(x, skipPtrs)
if x.kind != tyObject:
return (-3, default(ItemId))
stack.add x.bindingId
stack.add x.itemId
x = x.baseClass
inc(result[0])
result[1] = stack[^2]

View File

@@ -1819,7 +1819,7 @@ proc typeSectionFinalPass(c: PContext, n: PNode) =
var reified = semTypeNode(c, typeNode, nil)
assert reified != nil
assignType(typ, reified)
typ.bindingId = reified.bindingId # same id
typ.itemId = reified.itemId # same id
if containsForwardType(typ):
c.forwardTypeUpdates.add (owner, typ, typeNode)
elif not remainingOwners.missingOrExcl(owner.id):
@@ -2160,54 +2160,47 @@ proc checkedForDestructor(t: PType): bool =
return true
result = false
proc normalizeTypeHook(t: PType; markAsgn = false): PType =
proc whereToBindTypeHook(c: PContext; t: PType): PType =
result = t
while true:
if markAsgn:
incl(result, tfHasAsgn)
if result.kind == tyCompositeTypeClass and result.base.kind == tyGenericBody:
result = result.base
elif result.kind in {tyGenericBody, tyGenericInst}:
result = result.skipModifier
elif result.kind == tyGenericInvocation:
result = result.genericHead
else:
break
proc whereToBindTypeHook(c: PContext; t: PType): PType =
result = normalizeTypeHook(t)
if result.kind in {tyGenericBody, tyGenericInst}: result = result.skipModifier
elif result.kind == tyGenericInvocation: result = result[0]
else: break
if result.kind in {tyObject, tyDistinct, tySequence, tyString}:
result = canonType(c, result)
proc bindHookToType(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp;
typeToBind: PType): bool =
var obj = typeToBind
if obj.kind notin {tyObject, tyDistinct, tySequence, tyString}:
return false
obj = canonType(c, obj)
let ao = getAttachedOp(c.graph, obj, op)
if ao == s:
discard "forward declared hook"
elif ao.isNil and not checkedForDestructor(obj):
setAttachedOp(c.graph, c.module.position, obj, op, s)
else:
prevDestructor(c, op, ao, obj, n.info)
if obj.owner.getModule != s.getModule:
localError(c.config, n.info, errGenerated,
"type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
result = true
proc bindDupHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
let t = s.typ
var noError = false
let cond = t.len == 2 and t.returnType != nil
if cond:
var obj = normalizeTypeHook(t.firstParamType, markAsgn = true)
let res = normalizeTypeHook(t.returnType)
var obj = t.firstParamType
while true:
incl(obj, tfHasAsgn)
if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.skipModifier
elif obj.kind == tyGenericInvocation: obj = obj.genericHead
else: break
if sameType(obj, res):
noError = bindHookToType(c, s, n, op, obj)
var res = t.returnType
while true:
if res.kind in {tyGenericBody, tyGenericInst}: res = res.skipModifier
elif res.kind == tyGenericInvocation: res = res.genericHead
else: break
if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, res):
obj = canonType(c, obj)
let ao = getAttachedOp(c.graph, obj, op)
if ao == s:
discard "forward declared destructor"
elif ao.isNil and not checkedForDestructor(obj):
setAttachedOp(c.graph, c.module.position, obj, op, s)
else:
prevDestructor(c, op, ao, obj, n.info)
noError = true
if obj.owner.getModule != s.getModule:
localError(c.config, n.info, errGenerated,
"type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
if not noError and sfSystemModule notin s.owner.flags:
localError(c.config, n.info, errGenerated,
@@ -2237,8 +2230,25 @@ proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
t.len >= 2 and t.returnType == nil
if cond:
var obj = normalizeTypeHook(t.firstParamType.skipTypes({tyVar}), markAsgn = true)
noError = bindHookToType(c, s, n, op, obj)
var obj = t.firstParamType.skipTypes({tyVar})
while true:
incl(obj, tfHasAsgn)
if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.skipModifier
elif obj.kind == tyGenericInvocation: obj = obj.genericHead
else: break
if obj.kind in {tyObject, tyDistinct, tySequence, tyString}:
obj = canonType(c, obj)
let ao = getAttachedOp(c.graph, obj, op)
if ao == s:
discard "forward declared destructor"
elif ao.isNil and not checkedForDestructor(obj):
setAttachedOp(c.graph, c.module.position, obj, op, s)
else:
prevDestructor(c, op, ao, obj, n.info)
noError = true
if obj.owner.getModule != s.getModule:
localError(c.config, n.info, errGenerated,
"type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
if not noError and sfSystemModule notin s.owner.flags:
case op
of attachedTrace:
@@ -2305,12 +2315,35 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
message(c.config, n.info, warnDeprecated, "Overriding `=` hook is deprecated; Override `=copy` hook instead")
let t = s.typ
if t.len == 3 and t.returnType == nil and t.firstParamType.kind == tyVar:
var obj = normalizeTypeHook(t.firstParamType.elementType, markAsgn = true)
let objB = normalizeTypeHook(t[2])
if sameType(obj, objB):
var obj = t.firstParamType.elementType
while true:
incl(obj, tfHasAsgn)
if obj.kind == tyGenericBody: obj = obj.skipModifier
elif obj.kind == tyGenericInvocation: obj = obj.genericHead
else: break
var objB = t[2]
while true:
if objB.kind == tyGenericBody: objB = objB.skipModifier
elif objB.kind in {tyGenericInvocation, tyGenericInst}:
objB = objB.genericHead
else: break
if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, objB):
# attach these ops to the canonical tySequence
obj = canonType(c, obj)
#echo "ATTACHING TO ", obj.id, " ", s.name.s, " ", cast[int](obj)
let k = if name == "=" or name == "=copy": attachedAsgn else: attachedSink
if bindHookToType(c, s, n, k, obj): return
let ao = getAttachedOp(c.graph, obj, k)
if ao == s:
discard "forward declared op"
elif ao.isNil and not checkedForDestructor(obj):
setAttachedOp(c.graph, c.module.position, obj, k, s)
else:
prevDestructor(c, k, ao, obj, n.info)
if obj.owner.getModule != s.getModule:
localError(c.config, n.info, errGenerated,
"type bound operation `" & name & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
return
if sfSystemModule notin s.owner.flags:
localError(c.config, n.info, errGenerated,
"signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)")
@@ -2376,8 +2409,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) =
if typ.kind != tyObject:
localError(c.config, n.info, pragmaName & " must be either ptr to object or object type.")
if sameOwners(typ.owner, s.owner) and sameOwners(c.module, s.owner):
c.graph.memberProcsPerType.mgetOrPut(typ.bindingId, @[]).add s
logCppMember(c.graph, s)
c.graph.memberProcsPerType.mgetOrPut(typ.itemId, @[]).add s
else:
localError(c.config, n.info,
pragmaName & " procs must be defined in the same scope as the type they are virtual for and it must be a top level scope")
@@ -2385,7 +2417,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) =
localError(c.config, n.info, pragmaName & " procs are only supported in C++")
else:
var typ = s.typ.returnType
if typ != nil and typ.kind == tyObject and typ.bindingId notin c.graph.initializersPerType:
if typ != nil and typ.kind == tyObject and typ.itemId notin c.graph.initializersPerType:
var initializerCall = newTree(nkCall, newSymNode(s))
var isInitializer = n[paramsPos].len > 1
for i in 1..<n[paramsPos].len:
@@ -2399,8 +2431,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) =
initializerCall.add val
inc j
if isInitializer:
c.graph.initializersPerType[typ.bindingId] = initializerCall
logCppMember(c.graph, s)
c.graph.initializersPerType[typ.itemId] = initializerCall
proc semMethodPrototype(c: PContext; s: PSym; n: PNode) =
if s.isGenericRoutine:

View File

@@ -1379,7 +1379,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
for i in 0..<paramType.len - 1:
if paramType[i].kind == tyStatic:
var staticCopy = copyType(paramType[i], c.idgen, paramType[i].owner)
var staticCopy = paramType[i].exactReplica(c.idgen)
staticCopy.incl tfInferrableStatic
result.rawAddSon staticCopy
else:
@@ -2481,7 +2481,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
# bugfix: keep the fresh id for aliases to integral types:
if s.typ.kind notin {tyBool, tyChar, tyInt..tyInt64, tyFloat..tyFloat128,
tyUInt..tyUInt64}:
prev.bindingId = s.typ.bindingId
prev.itemId = s.typ.itemId
result = prev
of nkSym:
let s = getGenSym(c, n.sym)

View File

@@ -376,8 +376,8 @@ proc lookupTypeVar(cl: var TReplTypeVars, t: PType): PType =
result = cl.typeMap.lookup(t)
when defined(icDbgRefc):
if t.kind in {tyGenericParam, tyTypeDesc}:
echo "[icBind] lookup ", t.kind, " ", typeToString(t), " itemId=", t.itemId.module, ".",
t.itemId.item, " bindingId=", t.bindingId.module, ".", t.bindingId.item,
echo "[icBind] lookup ", t.kind, " ", typeToString(t), " uid=", t.uniqueId.module, ".",
t.uniqueId.item, " itemId=", t.itemId.module, ".", t.itemId.item,
" state=", t.state, " flags=", t.flags, " -> ",
(if result != nil: typeToString(result) else: "MISS"),
" allowMeta=", cl.allowMetaTypes
@@ -423,7 +423,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
var header = t
# search for some instantiation here:
if cl.allowMetaTypes:
result = getOrDefault(cl.localCache, t.bindingId)
result = getOrDefault(cl.localCache, t.itemId)
else:
result = searchInstTypes(cl.c.graph, t)
@@ -473,7 +473,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
if not cl.allowMetaTypes:
cacheTypeInst(cl.c, result)
else:
cl.localCache[t.bindingId] = result
cl.localCache[t.itemId] = result
let oldSkipTypedesc = cl.skipTypedesc
cl.skipTypedesc = true
@@ -647,7 +647,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
# type
# Vector[N: static[int]] = array[N, float64]
# TwoVectors[Na, Nb: static[int]] = (Vector[Na], Vector[Nb])
result = getOrDefault(cl.localCache, t.bindingId)
result = getOrDefault(cl.localCache, t.itemId)
if result != nil: return result
inc cl.recursionLimit
@@ -739,7 +739,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
return
bailout()
result = instCopyType(cl, t)
cl.localCache[t.bindingId] = result
cl.localCache[t.itemId] = result
for i in FirstGenericParamAt..<result.kidsLen:
var r = result[i]
if r != nil:
@@ -755,7 +755,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
of tyGenericInst, tyUserTypeClassInst:
bailout()
result = instCopyType(cl, t)
cl.localCache[t.bindingId] = result
cl.localCache[t.itemId] = result
for i in FirstGenericParamAt..<result.kidsLen:
result[i] = replaceTypeVarsT(cl, result[i])
propagateToOwner(result, result.last)
@@ -770,7 +770,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
result = instCopyType(cl, t)
result.size = -1 # needs to be recomputed
#if not cl.allowMetaTypes:
cl.localCache[t.bindingId] = result
cl.localCache[t.itemId] = result
let propagateInstValue = isInstValue and isRefPtrObject(t)
for i, resulti in result.ikids:
@@ -819,7 +819,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
result = t
# Slow path, we have some work to do. CRUCIAL: only ever mutate a type that
# is LOCAL to the module we are instantiating in (`itemId.module ==
# is LOCAL to the module we are instantiating in (`uniqueId.module ==
# idgen.module`). A type loaded from another module's NIF (foreign) already
# had its object branches resolved when it was originally compiled; mutating
# it in place here is an old→new heap write that re-homes the loaded type to
@@ -828,11 +828,11 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
# prior `state != Sealed` guard was insufficient: a freshly-LOADED type is
# `Complete`, not `Sealed` (`Sealed` only means "already re-written to a NIF").
if t.kind == tyRef and t.hasElementType and t.elementType.kind == tyObject and
t.elementType.n != nil and t.elementType.itemId.module == cl.c.idgen.module.int:
t.elementType.n != nil and t.elementType.uniqueId.module == cl.c.idgen.module.int:
discard replaceObjBranches(cl, t.elementType.n)
elif result.n != nil and t.kind == tyObject and result.state != Sealed and
result.itemId.module == cl.c.idgen.module.int:
result.uniqueId.module == cl.c.idgen.module.int:
# Invalidate the type size as we may alter its structure
result.size = -1
result.n = replaceObjBranches(cl, result.n)

View File

@@ -150,7 +150,7 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
if hashDepth > hashMaxDepth: hashMaxDepth = hashDepth
if hashCalls >= 500_000_000 and hashCalls <= 500_000_300:
echo "HASHLOOP n=", hashCalls, " d=", hashDepth, " kind=", t.kind, " id=", t.itemId,
" bindingId=", t.bindingId, " sym=", (if t.sym != nil: t.sym.name.s else: "NIL"),
" uniq=", t.uniqueId, " sym=", (if t.sym != nil: t.sym.name.s else: "NIL"),
" state=", t.state, " owner=", (if t.owner != nil: t.owner.name.s else: "NIL")
elif hashCalls == 500_000_301:
echo "HASHLOOP maxDepth=", hashMaxDepth
@@ -209,11 +209,7 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
# backend spelling instead of collapsing into the generic Nim builtin:
c &= char(t.kind)
if t.sym != nil and {sfImportc, sfExportc} * t.sym.flags != {}:
# Aliases inherit the external name, but have a different symbol.
if t.sym.loc.snippet != "":
c &= t.sym.loc.snippet
else:
c.hashSym(t.sym)
c.hashSym(t.sym)
of tyObject, tyEnum:
if t.typeInstImpl != nil:
# prevent against infinite recursions here, see bug #8883:

View File

@@ -137,8 +137,8 @@ proc put(c: var TCandidate, key, val: PType) {.inline.} =
echo "binding ", key, " -> ", val
when defined(icDbgRefc):
if key.kind in {tyGenericParam, tyTypeDesc}:
echo "[icBind] put ", key.kind, " ", typeToString(key), " itemId=", key.itemId.module, ".",
key.itemId.item, " bindingId=", key.bindingId.module, ".", key.bindingId.item,
echo "[icBind] put ", key.kind, " ", typeToString(key), " uid=", key.uniqueId.module, ".",
key.uniqueId.item, " itemId=", key.itemId.module, ".", key.itemId.item,
" state=", key.state, " -> ", typeToString(val)
put(c.bindings, key, val.skipIntLit(c.c.idgen))
@@ -913,14 +913,16 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
case typ.kind
of tyStatic:
param = paramSym skConst
param.typ = copyType(typ, m.c.idgen, typ.owner)
param.typ = typ.exactReplica(m.c.idgen)
#copyType(typ, c.idgen, typ.owner)
if typ.n == nil:
param.typ.incl tfInferrableStatic
else:
param.ast = typ.n
of tyFromExpr:
param = paramSym skVar
param.typ = copyType(typ, m.c.idgen, typ.owner)
param.typ = typ.exactReplica(m.c.idgen)
#copyType(typ, c.idgen, typ.owner)
else:
param = paramSym skType
param.typ = if typ.isMetaType:
@@ -972,7 +974,8 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
if ff.kind == tyUserTypeClassInst:
result = generateTypeInstance(c, m.bindings, typeClass.sym.info, ff)
else:
result = copyType(ff, m.c.idgen, ff.owner)
result = ff.exactReplica(m.c.idgen)
#copyType(ff, c.idgen, ff.owner)
result.n = checkedBody
@@ -1166,8 +1169,6 @@ proc enterConceptMatch(c: var TCandidate; f,a: PType, flags: TTypeRelFlags): TTy
return typeRel(c, prev, a, flags)
if trDontBind in flags:
conceptFlags.incl mfDontBind
if trBindGenericParam in flags:
conceptFlags.incl mfBindGenericParam
if trCheckGeneric in flags:
conceptFlags.incl mfCheckGeneric
let mres = concepts.conceptMatch(c.c, concpt, a, c.bindings, container, flags = conceptFlags)
@@ -1238,17 +1239,11 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
tfConceptMatchedTypeSym notin aOrig.flags
template skipTypeCursor(it, kinds: untyped) =
# `ast.last`, not a hand-inlined copy of it. What this replaces was `last`'s
# body verbatim MINUS its `if state == Partial: loadType` line -- and that
# line is the whole point: a NIF-loaded stub answers `kind` off its NIF name
# while `sonsImpl` is still EMPTY, so `sonsImpl[^1]` raised IndexDefect.
# nimbus-eth2 died on it in the very first `nim ic` pass, inside the `x is T`
# under a chronos `{.async.}` iterator's `when`. The second call site below
# is unguarded and runs on EVERY `typeRel`, so this is not a concept-only
# corner: a probe counts 195 Partial `tyVar`/`tyLent` arrivals across one
# nimbus frontend, each of which was an IndexDefect waiting for its turn.
while it.kind in kinds:
it = it.last
if it.kind == tyProc and it.nImpl.len > 1:
it = it.nImpl[^1].sym.typ
else:
it = it.sonsImpl[^1]
var aOrig {.cursor.} = aOrig
if useTypeLoweringRuleInTypeClass:
@@ -2694,7 +2689,7 @@ proc staticAwareTypeRel(m: var TCandidate, f: PType, arg: var PNode): TTypeRelat
# The ast of the type does not point to the symbol.
# Without this we will never resolve a `static proc` with overloads
let copiedNode = copyNode(arg)
copiedNode.typ = copyType(copiedNode.typ, m.c.idgen, copiedNode.typ.owner)
copiedNode.typ = exactReplica(copiedNode.typ, m.c.idgen)
copiedNode.typ.n = arg
arg = copiedNode
typeRel(m, f, arg.typ)

View File

@@ -27,7 +27,7 @@ proc hashTree*(n: PNode): Hash =
of nkCharLit..nkUInt64Lit: result = result !& hash(n.intVal)
of nkFloatLit..nkFloat64Lit: result = result !& hash(cast[uint64](n.floatVal))
of nkStrLit..nkTripleStrLit: result = result !& hash(n.strVal)
of nkType, nkNilLit: result = result !& hash(n.typ.bindingId)
of nkType, nkNilLit: result = result !& hash(n.typ.itemId)
else:
for i in 0..<n.len:
result = result !& hashTree(n[i])

View File

@@ -172,9 +172,9 @@ proc backendTypeName(t: PType; conf: ConfigRef): string =
result = "`t"
result.addInt ord(t.kind)
result.add '.'
result.addInt t.itemId.item
result.addInt t.uniqueId.item
result.add '.'
result.add modname(t.itemId.module, conf)
result.add modname(t.uniqueId.module, conf)
result.add "@bk"
proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef) =
@@ -186,7 +186,7 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
assert c.tl != nil
c.tl(t)
if t.itemId.isBackendMinted:
if t.uniqueId.isBackendMinted:
# Backend-minted (lower-stage) closure-env types key by their stable NIF name,
# never by structure (which diverges across the NIF boundary). An env `ref`
# that is itself NOT backend-minted still keys stably: it recurses here and
@@ -335,9 +335,9 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
# mutation that an assertion deeper in `treeKey` left unrestored would
# corrupt the type. `symKey` above already emitted the type's identity,
# so on a back-reference we simply stop.
if not containsOrIncl(c.visited, t.bindingId):
if not containsOrIncl(c.visited, t.itemId):
c.treeKey(t.nImpl, flags + {CoHashTypeInsideNode}, conf)
c.visited.excl t.bindingId
c.visited.excl t.itemId
else:
c.m.addIdent "´empty"
# Object inheritance is part of identity: key the base class too.

View File

@@ -90,30 +90,30 @@ proc collectVTableDispatchers*(g: ModuleGraph) =
sortBucket(g.methods[bucket].methods, relevantCols)
let base = g.methods[bucket].methods[^1]
let baseType = base.typ.firstParamType.skipTypes(skipPtrs-{tyTypeDesc})
if baseType.bindingId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.bindingId]):
let methodIndexLen = g.bucketTable[baseType.bindingId]
if baseType.bindingId notin itemTable: # once is enough
if baseType.itemId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.itemId]):
let methodIndexLen = g.bucketTable[baseType.itemId]
if baseType.itemId notin itemTable: # once is enough
rootTypeSeq.add baseType
itemTable[baseType.bindingId] = newSeq[PSym](methodIndexLen)
itemTable[baseType.itemId] = newSeq[PSym](methodIndexLen)
sort(g.objectTree[baseType.bindingId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
sort(g.objectTree[baseType.itemId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
if x.depth >= y.depth: 1
else: -1
)
for item in g.objectTree[baseType.bindingId]:
if item.value.bindingId notin itemTable:
itemTable[item.value.bindingId] = newSeq[PSym](methodIndexLen)
for item in g.objectTree[baseType.itemId]:
if item.value.itemId notin itemTable:
itemTable[item.value.itemId] = newSeq[PSym](methodIndexLen)
var mIndex = 0 # here is the correpsonding index
if baseType.bindingId notin rootItemIdCount:
rootItemIdCount[baseType.bindingId] = 1
if baseType.itemId notin rootItemIdCount:
rootItemIdCount[baseType.itemId] = 1
else:
mIndex = rootItemIdCount[baseType.bindingId]
rootItemIdCount.inc(baseType.bindingId)
mIndex = rootItemIdCount[baseType.itemId]
rootItemIdCount.inc(baseType.itemId)
for idx in 0..<g.methods[bucket].methods.len:
let obj = g.methods[bucket].methods[idx].typ.firstParamType.skipTypes(skipPtrs)
itemTable[obj.bindingId][mIndex] = g.methods[bucket].methods[idx]
itemTable[obj.itemId][mIndex] = g.methods[bucket].methods[idx]
g.addDispatchers genVTableDispatcher(g, g.methods[bucket].methods, mIndex)
else: # if the base object doesn't have this method
g.addDispatchers genIfDispatcher(g, g.methods[bucket].methods, relevantCols, g.idgen)
@@ -128,40 +128,40 @@ proc sortVTableDispatchers*(g: ModuleGraph) =
sortBucket(g.methods[bucket].methods, relevantCols)
let base = g.methods[bucket].methods[^1]
let baseType = base.typ.firstParamType.skipTypes(skipPtrs-{tyTypeDesc})
if baseType.bindingId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.bindingId]):
let methodIndexLen = g.bucketTable[baseType.bindingId]
if baseType.bindingId notin itemTable: # once is enough
rootTypeSeq.add baseType.bindingId
itemTable[baseType.bindingId] = newSeq[PSym](methodIndexLen)
if baseType.itemId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.itemId]):
let methodIndexLen = g.bucketTable[baseType.itemId]
if baseType.itemId notin itemTable: # once is enough
rootTypeSeq.add baseType.itemId
itemTable[baseType.itemId] = newSeq[PSym](methodIndexLen)
sort(g.objectTree[baseType.bindingId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
sort(g.objectTree[baseType.itemId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
if x.depth >= y.depth: 1
else: -1
)
for item in g.objectTree[baseType.bindingId]:
if item.value.bindingId notin itemTable:
itemTable[item.value.bindingId] = newSeq[PSym](methodIndexLen)
for item in g.objectTree[baseType.itemId]:
if item.value.itemId notin itemTable:
itemTable[item.value.itemId] = newSeq[PSym](methodIndexLen)
var mIndex = 0 # here is the correpsonding index
if baseType.bindingId notin rootItemIdCount:
rootItemIdCount[baseType.bindingId] = 1
if baseType.itemId notin rootItemIdCount:
rootItemIdCount[baseType.itemId] = 1
else:
mIndex = rootItemIdCount[baseType.bindingId]
rootItemIdCount.inc(baseType.bindingId)
mIndex = rootItemIdCount[baseType.itemId]
rootItemIdCount.inc(baseType.itemId)
for idx in 0..<g.methods[bucket].methods.len:
let obj = g.methods[bucket].methods[idx].typ.firstParamType.skipTypes(skipPtrs)
if obj.bindingId notin itemTable:
itemTable[obj.bindingId] = newSeq[PSym](methodIndexLen)
itemTable[obj.bindingId][mIndex] = g.methods[bucket].methods[idx]
if obj.itemId notin itemTable:
itemTable[obj.itemId] = newSeq[PSym](methodIndexLen)
itemTable[obj.itemId][mIndex] = g.methods[bucket].methods[idx]
for baseType in rootTypeSeq:
g.setMethodsPerType(baseType, itemTable[baseType])
for item in g.objectTree[baseType]:
let typ = item.value.skipTypes(skipPtrs)
let idx = typ.bindingId
let idx = typ.itemId
for mIndex in 0..<itemTable[idx].len:
if itemTable[idx][mIndex] == nil:
let parentIndex = typ.baseClass.skipTypes(skipPtrs).bindingId
let parentIndex = typ.baseClass.skipTypes(skipPtrs).itemId
itemTable[idx][mIndex] = itemTable[parentIndex][mIndex]
g.setMethodsPerType(idx, itemTable[idx])

133
doc/ic.md
View File

@@ -2,23 +2,12 @@
Incremental Compilation (IC)
======================================
``--ic:on`` turns an ordinary compile into an incremental one. It decomposes
compilation into per-module steps whose results are cached as NIF files, and
uses the external ``nifmake`` build tool to re-run only the steps whose inputs
changed.
The ``nim ic`` command provides incremental compilation for Nim projects. It
decomposes compilation into per-module steps whose results are cached as NIF
files, and uses the external ``nifmake`` build tool to re-run only the steps
whose inputs changed.
.. code-block:: cmd
nim c --ic:on myproject.nim
nim cpp --ic:on myproject.nim
It is a switch on the normal compile commands, not a command of its own, so
everything else keeps working unchanged: ``cpp`` and ``objc`` backends, ``-r``,
``-d:release``, ``--exceptions:``, and a project-wide opt-in from ``nim.cfg`` /
``config.nims``. The older spelling ``nim ic`` still works and drives the same
code, but it is the C backend only and cannot run the binary it built.
This document describes **how IC works today**, including the edge cases
This document describes **how `nim ic` works today**, including the edge cases
that shaped the current design. The per-module backend rewrite that earlier
editions of this document listed as a *Plan* has **landed**: the whole-program,
reuse/redirect/def-retention backend is gone and codegen is now a set of
@@ -27,7 +16,7 @@ reuse/redirect/def-retention backend is gone and codegen is now a set of
Overview
========
The pipeline has two halves driven by one process (the *driver*, `commandIc` in
The pipeline has two halves driven by one process (`nim ic`, `commandIc` in
``compiler/deps.nim``) that constructs a dependency graph, writes a build file,
and hands it to ``nifmake``:
@@ -221,17 +210,15 @@ Edge cases (and why the machinery exists)
- **`nil` sons of loaded ASTs.** NIF dot-tokens load as `nil` where from-source
ASTs have `nkEmpty`; several passes gained `nil` guards.
- **Sealed loaded types.** Loaded types are `Sealed`; sem/transform mutate via
`unsealForTransform`/`copyType`, or -- where the copy must still answer to the
original in the generic binding tables -- `exactReplica(idgen)`, which gives the
copy its own `itemId` (so serialized replicas don't collapse) while inheriting
the original's `bindingId`.
`unsealForTransform`/`exactReplica(idgen)` (the latter mints a fresh `uniqueId`
so serialized replicas don't collapse).
- **Methods/RTTI ownership.** RTTI and type-bound hooks are emit-everywhere at
`cg` and deduplicated by the `merge` stage, like generic instances; the main
module's `cg` owns the whole-program method dispatchers.
- **Config cost.** Each child re-parsing `nim.cfg` + re-running `config.nims` in
the VM was ~80 ms; replaced by a precompiled `ic_config.cfg.nif` replayed in
`loadConfigs` (`compiler/icconfig.nim`).
- **`koch bootic`** bootstraps the compiler through `--ic:on` (a 3-iteration
- **`koch bootic`** bootstraps the compiler through `nim ic` (a 3-iteration
fixed-point check). It writes its binary to ``bin/nim_ic`` and never clobbers
``bin/nim``.
@@ -258,7 +245,7 @@ Known residual hack
Status and performance
======================
IC self-builds the compiler (`koch bootic`'s byte-identical fixed-point
`nim ic` self-builds the compiler (`koch bootic`'s byte-identical fixed-point
check) under both `orc` and `--mm:refc`, and passes the external-package CI set.
Cold full bootstrap on a 32-core box (`-d:release`, **no edits** — IC's worst
@@ -267,7 +254,7 @@ case, since incremental reuse is not exercised):
| | wall | notes |
| - | ---- | ----- |
| `koch boot` (classic) | ~1m00s | reference |
| `koch bootic` (`--ic:on`) | ~1m39s | **~1.66×** |
| `koch bootic` (`nim ic`) | ~1m39s | **~1.66×** |
This is down from ~7.5× in the whole-program-backend era. IC does modestly more
aggregate work (more processes, NIF re-parsing of imports per process), but on a
@@ -416,101 +403,3 @@ See also
- NIF format spec: [nifspec/doc/nif-spec.md](../nifspec/doc/nif-spec.md)
- NIFC (C-like target) spec: dist/nimony/doc/nifc-spec.md
Testing IC
==========
Two mechanisms, at very different scales.
**`tests/ic` — metamorphic tests.** A `t*.nim` whose body contains `#? metamorphic`
drives a sequence of cross-module edits through the IC driver in one fixed build
directory (see `testament/categories.nim`, `runMetamorphicIcTest`). Directives:
| directive | effect |
| --------- | ------ |
| ``#!FILE <name>`` | (re)write a module in the virtual file system |
| ``#!DELETE <name>`` | remove a module, from the vfs and from disk |
| ``#!FLAGS <switches>`` | change the compiler switches from here on |
| ``#!STEP <attrs>`` | materialise the files, build, run, check |
Step attributes: ``expect: <stdout>``, ``fails: <substring>`` (BOTH compilers must
reject it, with that text), ``noop``, ``body-edit``, ``iface-edit``,
``modules: <n>``, ``clean``, ``no-oracle``.
Every successful step is **also compiled with `nim c` and run, and the two
outputs must agree**. That oracle is the only check in the suite that is not
IC-against-IC: `clean == incremental`, `noop changes nothing` and the cookie
invariants are all satisfied by an IC that is *consistently* wrong, which is how
two silent miscompilations survived (a NIF-loaded module's `sfInjectDestructors`
was lost, so top-level destructors were never injected; `nfFirstWrite`/`nfLastRead`
had nowhere to live on a serialized sym node, so every first assignment to a
destructor-bearing local became `=sink` over zeroed memory). `koch bootic` has the
same blind spot — it proves the compiler reproduces *itself*.
**`testament --ic` — the whole corpus.** Appends `--ic:on` to every C and C++
test compile, so IC inherits the existing ~10k programs and their expected
output instead of the handful written for it by hand. Because it is a switch and
not a command, a test that overrides the command wholesale (`cmd: "nim cpp -r
$file"`) simply gains the switch — no verb rewriting, and the C++ corpus comes
along for free. Each also gets a private nimcache; without one they would share
a cache and thrash it.
To keep that affordable, testament borrows nimony's hastur model
(`warmupSharedCache` + `prefillFromWarmup`): a generated warmup program pulling in
`system` and the most-imported stdlib modules is compiled once per distinct
compile configuration into `nimcache/ic_warmup_<hash>`, and each test's empty
cache is seeded from it with **mtimes preserved** (nifmake compares
output-mtime > input-mtime, so stamping the copies "now" would re-fire the whole
graph). Only program-independent artifacts are copied — the frontend NIFs and
cookies plus the per-module `lower`/`cg` outputs. The `.c`/`.o` are deliberately
left behind: the merge decision (which module owns each emit-everywhere
definition) is whole-program, so those are re-rendered for every program anyway.
Measured on `tests/destructor` (97 test runs, 32-core box):
| | cold | warm |
| - | ---- | ---- |
| `nim c` | 35s | 32s |
| `--ic:on` | ~3m30 | **9.8s** |
The warm number is the developer loop and it is 3.2x faster than the classic
backend; the cold number is paid once per configuration and then cached on disk.
The disk cost is real and worth knowing: ~3.4 GB of nimcache for that one
category.
One property of an incremental compiler is worth spelling out because it looks
like a test bug: **a cached stage emits no diagnostics**. `--expandArc` output, a
hint, a warning — all of it is produced by the process that actually runs, so a
build that reuses every artifact prints nothing. Tests that check `nimout` (and
anything you are debugging by eye) therefore need a cold cache; running the same
test twice in a row makes the second run's `nimout` empty.
The C++ backend
===============
``nim cpp --ic:on`` works, and `tests/cpp` passes under it. Three things had to
change for that, and they are worth knowing because they are the shape of every
"C++ needs the whole program" problem the per-module backend has:
* **The driver must name the right file.** ``deps.nim`` DECLARES each module's
translation unit to ``nifmake`` without loading a single module, so it cannot
ask ``cgen.getCFile``; ``options.icCFileExt`` mirrors that formula at backend
granularity (``.nim.cpp`` / ``.nim.m`` / ``.nim.c``).
* **C++ has no designated initializers**, so the RTTI record is a bare variable
that ``DatInit`` fills field by field. That bare ``TNimTypeV2 x;`` is a
tentative definition, which C's linker merges and C++'s does not — every TU
that demanded the type defined it. It now gets the same extern-declaration +
owned-``'d'``-definition split the C flavour has.
* **A C++ member is declared inside its class.** ``memberProcsPerType`` and
``initializersPerType`` live only in the sem process, so the backend emitted
the struct WITHOUT its member declarations; they are replayed from a
``(repcppmember …)`` log entry now (``modulegraphs.replayCppMember`` re-derives
the type from the routine's signature, exactly as ``semCppMember`` does).
Two follow-on details: a member's ``loc.snippet`` is a CALL PATTERN
(``#->salute(@)``), so it must be computed even in the TU that only *calls* the
member (whole-program cgen got that for free by generating the defining module
first), and it is not a linker name — every ``salute`` member in every class
mints the same one, so definitions are keyed by their NIF name in the merge
stage instead.

View File

@@ -76,7 +76,7 @@ Options:
--skipIntegrityCheck skips integrity check when booting the compiler
Possible Commands:
boot [options] bootstraps with given command line options
bootic [options] bootstraps via the incremental compiler (`--ic:on`)
bootic [options] bootstraps via the incremental compiler (`nim ic`)
distrohelper [bindir] helper for distro packagers
tools builds Nim related tools
toolsNoExternal builds Nim related tools (except external tools,
@@ -450,7 +450,7 @@ proc bootic(args: string, skipIntegrityCheck: bool) =
# everything.
if i > 0: removeDir smartNimcache
let nimi = if i == 0: nimStart else: i.thVersion
exec "$# c --ic:on --nimcache:$# $# compiler" / "nim.nim" %
exec "$# ic --nimcache:$# $# compiler" / "nim.nim" %
[nimi, smartNimcache, args]
if sameFileContent(output, i.thVersion):
copyExe(output, finalDest)
@@ -615,7 +615,7 @@ proc runIcTestFile(inp: string) =
for fragment in content.split("#!EDIT!#"):
let file = inp.replace(".nim", "_temp.nim")
writeFile(file, fragment)
var cmd = nimExe & " c --ic:on --hint:Conf:off --warnings:off "
var cmd = nimExe & " ic --hint:Conf:off --warnings:off "
cmd.add quoteShell(file)
exec(cmd)
@@ -625,7 +625,7 @@ proc runIcTestFile(inp: string) =
const icSuite = ["thallo", "tconverter", "timp", "tmiscs", "tparseutils",
"tcompiletimeglobal", "tsighashstable", "tpureenum", "tgenericoffer",
"tconverterreexport", "ttypeoffer", "ttransitiveoffer",
"tmodsymref", "tmethupref", "temit", "ttraitparam", "tnestasgn"]
"tmodsymref", "tmethupref", "temit", "ttraitparam"]
proc icTest(args: string) =
temp("")

View File

@@ -17,7 +17,6 @@ __AVR__
__arm__
__riscv
__EMSCRIPTEN__
__unix__
*/
@@ -598,7 +597,7 @@ NIM_STATIC_ASSERT(sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8, "P
#define nimMulInt64(a, b, res) __builtin_smulll_overflow(a, b, (long long int*)res)
#if NIM_INTBITS == 32
#if ((defined(__arm__) && !defined(__unix__)) || defined(__riscv)) && defined(__GNUC__)
#if (defined(__arm__) || defined(__riscv)) && defined(__GNUC__)
/* arm-none-eabi-gcc and riscv32-unknown-elf-gcc targets define int32_t as long int */
#define nimAddInt(a, b, res) __builtin_saddl_overflow(a, b, res)
#define nimSubInt(a, b, res) __builtin_ssubl_overflow(a, b, res)

View File

@@ -3304,21 +3304,13 @@ proc dirInclude(p: var RstParser): PRstNode =
## Only the content before the first occurrence of the specified
## text (but after any after text) will be included. If text is
## not found inclusion will happen until the end of the file.
##
## :literal: flag (empty)
##
## The entire included text is inserted into the document as a single
## literal block (useful for program listings).
##
## :code: language (if empty, `nim` is assumed by default)
##
## The argument and the included content are passed to the code directive
## (useful for program listings).
##
## :encoding: name of text encoding
##
## The text encoding of the external data file. Defaults to the document's
## encoding (if specified).
#literal : flag (empty)
# The entire included text is inserted into the document as a single
# literal block (useful for program listings).
#encoding : name of text encoding
# The text encoding of the external data file. Defaults to the document's
# encoding (if specified).
#
result = nil
var n = parseDirective(p, rnDirective, {hasArg, argIsFile, hasOptions}, nil)
var filename = strip(addNodes(n.sons[0]))
@@ -3327,44 +3319,31 @@ proc dirInclude(p: var RstParser): PRstNode =
rstMessage(p, meCannotOpenFile, filename)
else:
# XXX: error handling; recursive file inclusion!
let inputString = readFile(path)
let startPosition =
block:
let searchFor = n.getFieldValue("start-after").strip()
if searchFor != "":
let pos = inputString.find(searchFor)
if pos != -1: pos + searchFor.len
else: 0
else:
0
let endPosition =
block:
let searchFor = n.getFieldValue("end-before").strip()
if searchFor != "":
let pos = inputString.find(searchFor, start = startPosition)
if pos != -1: pos - 1
else: 0
else:
inputString.len - 1
if getFieldValue(n, "literal") != "":
result = newRstNode(rnLiteralBlock)
result.add newLeaf(inputString[startPosition..endPosition])
elif getFieldValue(n, "code") != "":
result = newRstNode(rnCodeBlock)
result.sons.setLen(3)
let lang = getFieldValue(n, "code").strip()
if lang notin ["", "\x01\x01"]:
var codeArg = newRstNode(rnDirArg)
codeArg.add(newLeaf(lang))
result.sons[0] = codeArg
result.sons[1] = newRstNode(rnFieldList)
defaultCodeLangNim(p, result)
var litBlock = newRstNode(rnLiteralBlock)
litBlock.add newLeaf(inputString[startPosition..endPosition])
result.sons[2] = litBlock
result.add newLeaf(readFile(path))
else:
let inputString = readFile(path)
let startPosition =
block:
let searchFor = n.getFieldValue("start-after").strip()
if searchFor != "":
let pos = inputString.find(searchFor)
if pos != -1: pos + searchFor.len
else: 0
else:
0
let endPosition =
block:
let searchFor = n.getFieldValue("end-before").strip()
if searchFor != "":
let pos = inputString.find(searchFor, start = startPosition)
if pos != -1: pos - 1
else: 0
else:
inputString.len - 1
var q: RstParser
initParser(q, p.s)
let saveFileIdx = p.s.currFileIdx

View File

@@ -152,11 +152,9 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
raise newException(ValueError, "Invalid request protocol. Got: " &
protocol)
result.orig = protocol
var n = protocol.parseSaturatedNatural(result.major, i)
i.inc n
if i < protocol.len and protocol[i] == '.':
inc i
n = protocol.parseSaturatedNatural(result.minor, i)
i.inc protocol.parseSaturatedNatural(result.major, i)
if i < protocol.len: inc i # Skip .
i.inc protocol.parseSaturatedNatural(result.minor, i)
proc sendStatus(client: AsyncSocket, status: string): Future[void] =
client.send("HTTP/1.1 " & status & "\c\L\c\L")

View File

@@ -238,7 +238,6 @@ proc fromJson*[T](a: var T, b: JsonNode, opt = Joptions()) =
a = T()
fromJson(a[], b, opt)
elif T is array:
checkJson b.kind == JArray
checkJson a.len == b.len, "Json array size doesn't match for " & $T
var i = 0
for ai in mitems(a):
@@ -249,7 +248,6 @@ proc fromJson*[T](a: var T, b: JsonNode, opt = Joptions()) =
for val in b.getElems:
incl a, jsonTo(val, E)
elif T is seq:
checkJson b.kind == JArray
a.setLen b.len
for i, val in b.getElems:
fromJson(a[i], val, opt)

View File

@@ -1254,9 +1254,7 @@ proc del*[T](x: var seq[T], i: Natural) {.noSideEffect.} =
a.del(2)
assert a == @[10, 11, 14, 13]
let xl = x.len - 1
# Avoid moving the element onto itself when deleting the last item.
if i != xl:
movingCopy(x[i], x[xl])
movingCopy(x[i], x[xl])
setLen(x, xl)
proc insert*[T](x: var seq[T], item: sink T, i = 0.Natural) {.noSideEffect.} =

View File

@@ -155,17 +155,15 @@ type
MemRegion = object
when usesRegionHandles:
# Keeping the handle here does change the layout, but until proven otherwise
# this layout is more readable and shouldn't regress performance.
regionHandle: ptr RegionHandle
when not defined(gcDestructors):
minLargeObj, maxLargeObj: int
freeSmallChunks: array[0..max(1, SmallChunkSize div MemAlign-1), PSmallChunk]
# List of available chunks per size class. Only one is expected to be active per class.
when defined(gcDestructors) and not usesRegionHandles:
when defined(gcDestructors):
sharedFreeLists: SharedFreeLists
# Remote-free buckets live on the MemRegion when there is no
# RegionHandle. Threaded memory managers with handles keep them on the handle instead.
# Used directly without threads. Threaded builds use RegionHandle but
# retain this 2 KiB spacer: removing it regresses 2-4 KiB allocations.
flBitmap: uint32
slBitmap: array[RealFli, uint32]
matrix: array[RealFli, array[MaxSli, PBigChunk]]
@@ -965,19 +963,13 @@ proc bigChunkAlignOffset(alignment: int): int {.inline.} =
else:
result = align(sizeof(BigChunk) + sizeof(FreeCell), alignment) - sizeof(BigChunk) - sizeof(FreeCell)
template rawAllocAux(aligned: static bool) {.dirty.} =
proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer =
when defined(nimTypeNames):
inc(a.allocCounter)
sysAssert(allocInv(a), "rawAlloc: begin")
sysAssert(roundup(65, 8) == 72, "rawAlloc: roundup broken")
when aligned:
var size = roundup(requestedSize, max(MemAlign, alignment))
let alignOff = smallChunkAlignOffset(alignment)
else:
# Common `alloc` path: no custom alignment. Keep this a separate
# instantiation so clang does not emit `smallChunkAlignOffset(0)`.
var size = (requestedSize + (MemAlign - 1)) and not (MemAlign - 1)
const alignOff = 0
var size = roundup(requestedSize, max(MemAlign, alignment))
let alignOff = smallChunkAlignOffset(alignment)
sysAssert(size >= sizeof(FreeCell), "rawAlloc: requested size too small")
sysAssert(size >= requestedSize, "insufficient allocated size!")
#c_fprintf(stdout, "alloc; size: %ld; %ld\n", requestedSize, size)
@@ -994,13 +986,11 @@ template rawAllocAux(aligned: static bool) {.dirty.} =
if atomicLoadN(sharedHead, ATOMIC_RELAXED) != nil:
tc.freeList = atomicExchangeN(sharedHead, nil, ATOMIC_ACQUIRE)
else:
let sharedHead = addr a.sharedFreeLists[s]
tc.freeList = sharedHead[]
sharedHead[] = nil
# Empty peeks are the common local case; skip the walk and the
# `free += 0` / `occ -= 0` stores clang would otherwise keep.
if tc.freeList != nil:
compensateCounters(a, tc, size)
tc.freeList = a.sharedFreeLists[s]
a.sharedFreeLists[s] = nil
# If `tc.freeList` isn't nil, `tc` gains capacity. Calculate how
# much it gained and how many foreign cells are included.
compensateCounters(a, tc, size)
# allocate a small block: for small chunks, we use only its next pointer
let s = size div MemAlign
@@ -1081,7 +1071,7 @@ template rawAllocAux(aligned: static bool) {.dirty.} =
# For big chunks with custom alignment, allocate extra space.
# Since chunks are page-aligned, the needed padding is a compile-time
# deterministic value rather than a worst-case estimate.
let alignPad = when aligned: bigChunkAlignOffset(alignment) else: 0
let alignPad = bigChunkAlignOffset(alignment)
size = requestedSize + bigChunkOverhead() + alignPad
# allocate a large block
var c = if size >= HugeChunkSize: getHugeChunk(a, size)
@@ -1106,12 +1096,6 @@ template rawAllocAux(aligned: static bool) {.dirty.} =
when defined(heaptrack):
heaptrack_malloc(result, requestedSize)
proc rawAlloc(a: var MemRegion, requestedSize: int): pointer =
rawAllocAux(false)
proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int): pointer =
rawAllocAux(true)
proc rawAlloc0(a: var MemRegion, requestedSize: int): pointer =
result = rawAlloc(a, requestedSize)
zeroMem(result, requestedSize)

View File

@@ -516,15 +516,7 @@ proc testNimblePackages(r: var TResults; cat: Category; packageFilter: string) =
# accumulated file set is materialised before each `#!STEP`. A `#!STEP`'s
# attributes are `;`-separated, each either `key: value` or a bare flag:
# expect: <stdout> noop body-edit iface-edit modules: <n> clean
# fails: <substring> no-oracle
# The last step always also runs the clean==incremental check.
#
# Every successful step is ALSO compiled with `nim c` and run, and the two
# outputs must agree (`no-oracle` opts out). This is the only check in the suite
# that is not IC-against-IC; without it a consistently wrong IC passes
# everything. `#!DELETE <file>` removes a module, `#!FLAGS <switches>` changes
# the compiler switches from that point on, and `fails: <text>` asserts that
# BOTH compilers reject the program with that text.
type MetamorphicError = object of CatchableError
resultKind: TResultEnum
@@ -598,34 +590,16 @@ proc runMetamorphicIcTest(r: var TResults; file: string; cat: Category; options:
let buildDir = (file.changeFileExt("") & "_mm").absolutePath
let nc = buildDir / "nc"
let bin = buildDir / "prog".addFileExt(ExeExt)
# The ORACLE: the same sources compiled by the classic backend. Every
# invariant this runner checked before was IC-against-IC (clean == incremental,
# no-op changes nothing, ...), which a *consistently* wrong IC satisfies
# perfectly — that is how a whole class of silent miscompilations (top-level
# destructors never injected; `nfFirstWrite`/`nfLastRead` dropped by the
# serializer, so every first assignment to a destructor-bearing local became
# `=sink` over zeroed memory) stayed invisible. `nim c` is the reference the
# suite was missing.
let ncRef = buildDir / "ncref"
let binRef = buildDir / "progref".addFileExt(ExeExt)
removeDir(buildDir)
createDir(buildDir)
# Extra switches for both compilers, settable per step via `#!FLAGS`.
var extraFlags: seq[string] = @[]
template compileIc(): untyped =
execCmdEx2(compilerPrefix, @["ic", "--hint:Conf:off", "--warnings:off",
"--nimcache:" & nc, "--out:" & bin] & extraFlags & @["main.nim"],
workingDir = buildDir)
template compileRef(): untyped =
execCmdEx2(compilerPrefix, @["c", "--hint:Conf:off", "--warnings:off",
"--nimcache:" & ncRef, "--out:" & binRef] & extraFlags & @["main.nim"],
execCmdEx2(compilerPrefix, ["ic", "--hint:Conf:off", "--warnings:off",
"--nimcache:" & nc, "--out:" & bin, "main.nim"],
workingDir = buildDir)
# Parse the source into a flat op list: ("file", name, content) | ("step", attrs, "").
type OpKind = enum opFile, opStep, opDelete, opFlags
type OpKind = enum opFile, opStep
type Op = object
kind: OpKind
a, b: string
@@ -641,18 +615,6 @@ proc runMetamorphicIcTest(r: var TResults; file: string; cat: Category; options:
if s.startsWith("#!FILE"):
flushFile()
curName = s["#!FILE".len .. ^1].strip
elif s.startsWith("#!DELETE"):
# Remove a module from the virtual file system AND from disk. Deleting a
# still-imported file moves no mtime, so nothing in an mtime-keyed build
# re-fires: `nim ic` used to relink a stale binary where `nim c` reports
# `cannot open file`. Untestable until the format could express it.
flushFile()
ops.add Op(kind: opDelete, a: s["#!DELETE".len .. ^1].strip)
elif s.startsWith("#!FLAGS"):
# Change the compiler switches for the following steps. Config changes
# are not files, so an mtime-keyed build cannot see them either.
flushFile()
ops.add Op(kind: opFlags, a: s["#!FLAGS".len .. ^1].strip)
elif s.startsWith("#!STEP"):
flushFile()
ops.add Op(kind: opStep, a: s["#!STEP".len .. ^1].strip)
@@ -668,21 +630,11 @@ proc runMetamorphicIcTest(r: var TResults; file: string; cat: Category; options:
var prevSnap = initTable[string, string]()
var prevBin = ""
var stepIdx = 0
var deleted: seq[string] = @[]
try:
for o in ops:
case o.kind
of opFile:
if o.kind == opFile:
vfs[o.a] = o.b
continue
of opDelete:
vfs.del o.a
deleted.add o.a
continue
of opFlags:
extraFlags = o.a.splitWhitespace()
continue
of opStep: discard
inc stepIdx
let where = "step " & $stepIdx
# Parse step attributes.
@@ -694,36 +646,8 @@ proc runMetamorphicIcTest(r: var TResults; file: string; cat: Category; options:
if c >= 0: attrs[p[0 ..< c].strip] = p[c+1 .. ^1].strip
else: attrs[p] = ""
for fn in deleted:
removeFile(buildDir / fn)
deleted.setLen 0
for fn, content in vfs: writeFile(buildDir / fn, content)
let (_, cout, ccode) = compileIc()
# `fails: <substring>` — the build MUST fail, with that text in its output.
# Without this every step had to succeed, so the whole error path was
# untested: a `nim m` that errored still wrote its `.s.bif`, nifmake then
# saw the rule satisfied, and the NEXT run reported success for a program
# that does not compile.
if "fails" in attrs:
if ccode == 0:
mmRaise(reBuildFailed, "a failed build", where & ": `nim ic` unexpectedly succeeded")
let want = attrs["fails"]
if want.len > 0 and want notin cout:
mmRaise(reOutputsDiffer, want, where & ": error text did not contain it:\n" & cout)
# The oracle must reject it too, else the test is asserting an IC-only
# error rather than a real one.
let (_, refOut, refCode) = compileRef()
if refCode == 0:
mmRaise(reBuildFailed, "`nim c` to fail too",
where & ": `nim ic` failed but `nim c` accepted the program:\n" & cout)
if want.len > 0 and want notin refOut:
mmRaise(reOutputsDiffer, want,
where & ": `nim c` failed differently:\n" & refOut)
prevSnap = snapshotDir(nc)
prevBin = ""
continue
if ccode != 0:
mmRaise(reBuildFailed, "", where & ": `nim ic` failed:\n" & cout)
let (_, rout, rcode) = execCmdEx2(bin.absolutePath, [], workingDir = buildDir)
@@ -734,22 +658,6 @@ proc runMetamorphicIcTest(r: var TResults; file: string; cat: Category; options:
if rout.strip == want.strip: discard
else: mmRaise(reOutputsDiffer, want, where & " output:\n" & rout.strip)
# ORACLE: same sources through the classic backend, same observable
# behaviour. Unlike `expect:` this needs no foresight from the test author —
# it compares everything the program does, not only what someone thought to
# print, which is exactly what a silently-skipped destructor evades.
block oracle:
if "no-oracle" in attrs: break oracle
let (_, refCout, refCcode) = compileRef()
if refCcode != 0:
mmRaise(reBuildFailed, "", where & ": `nim c` (oracle) failed:\n" & refCout)
let (_, refRout, refRcode) = execCmdEx2(binRef.absolutePath, [],
workingDir = buildDir)
if refRout.strip != rout.strip or refRcode != rcode:
mmRaise(reOutputsDiffer, "`nim c` output:\n" & refRout.strip,
where & ": `nim ic` disagrees with `nim c`\n ic (exit " & $rcode &
"):\n" & rout.strip & "\n c (exit " & $refRcode & "):\n" & refRout.strip)
let snap = snapshotDir(nc)
let binBytes = stableBinary(bin)
if stepIdx > 1:
@@ -847,12 +755,6 @@ proc processSingleTest(r: var TResults, cat: Category, options, test: string, ta
let target = if cat.string.normalize == "js": targetJS else: targetC
targets = {target}
doAssert fileExists(test), test & " test does not exist"
# `testament r <file>` must dispatch metamorphic IC tests the same way
# `testament cat ic` does, otherwise a single-test run tries to parse the
# header as an ordinary spec and rejects it.
if isMetamorphicIcTest(readFile(test)):
runMetamorphicIcTest(r, test, cat, options)
return
testSpec r, makeTest(test, options, cat), targets
proc isJoinableSpec(spec: TSpec): bool =

View File

@@ -12,7 +12,7 @@
import
std/[strutils, pegs, os, osproc, streams, json,
parseopt, browsers, terminal, exitprocs,
algorithm, times, intsets, macros, tables]
algorithm, times, intsets, macros]
import backend, specs, azure, htmlgen
@@ -35,12 +35,6 @@ var simulate = false
var optVerbose = false
var useMegatest = true
var valgrindEnabled = true
var useIc = false
## `--ic`: compile every C-target test with `nim ic` instead of `nim c`, so the
## incremental compiler inherits the whole existing corpus (~10k programs with
## expected output) instead of the handful of tests written for it by hand.
## Every invariant the `tests/ic` suite checks is IC-against-IC; this is the
## part that compares IC against the reference backend at scale.
proc verboseCmd(cmd: string) =
if optVerbose:
@@ -64,7 +58,6 @@ Arguments:
Options:
--print print results to the console
--verbose print commands (compiling and running tests)
--ic compile C-target tests with `nim ic` (incremental)
--simulate see what tests would be run but don't run them (for debugging)
--failing only show failing/ignored tests
--targets:"c cpp js objc" run tests for specified targets (default: c)
@@ -162,40 +155,11 @@ proc execCmdEx2(command: string, args: openArray[string]; workingDir: string = "
if result.exitCode != -1: break
close(p)
proc nimcacheDir(filename, options: string, target: TTarget,
extraOptions = ""): string =
proc nimcacheDir(filename, options: string, target: TTarget): string =
## Give each test a private nimcache dir so they don't clobber each other's.
## `extraOptions` (a `matrix:` entry) is part of the key: two matrix variants
## of one file are two different compilations, and sharing a cache between them
## means each run invalidates what the previous left. Harmless for the classic
## backend, which caches only object files, but it makes an incremental cache
## useless — every variant re-sems the world every time.
let hashInput = options & extraOptions & $target
let hashInput = options & $target
result = "nimcache" / (filename & '_' & hashInput.getMD5)
const icWarmupSource = """
# Generated by testament for `--ic`. Compiling this once fills a shared IC cache
# with `system` and the stdlib modules the test corpus imports most, so each
# test's own cold build starts from precompiled NIFs instead of re-semming the
# world. Mirrors nimony's hastur `tools/warmup.nim` + `prefillFromWarmup`.
import std/[assertions, macros, strutils, tables, os, typetraits, sequtils,
sugar, math, options, times, json, sets, algorithm, hashes,
strformat, parseutils, streams, unicode]
proc icWarmupAnchor*(): int =
# Reference a few generic instantiations the corpus leans on so their
# `.c.nif` artifacts are precompiled too, not just the modules' interfaces.
var t = initTable[string, int]()
t["a"] = 1
var s = @[1, 2, 3]
s.sort()
result = s.len + t.len + "x".repeat(2).len
"""
var icWarmupCaches: Table[string, string]
## Compile-config key -> shared warm IC cache (or "" when unavailable).
var buildingIcWarmup = false
proc prepareTestCmd(cmdTemplate, filename, options, nimcache: string,
target: TTarget, extraOptions = ""): string =
var options = target.defaultOptions & ' ' & options
@@ -205,103 +169,9 @@ proc prepareTestCmd(cmdTemplate, filename, options, nimcache: string,
result = cmdTemplate % ["target", targetToCmd[target],
"options", options, "file", filename.quoteShell,
"filedir", filename.getFileDir(), "nim", compilerPrefix]
if useIc and target in {targetC, targetCpp}:
# `--ic:on` turns the ordinary compile command into the IC driver, so the
# verb is left alone: roughly half the corpus overrides the command wholesale
# (`cmd: "nim c --gc:arc $file"`), which neither goes through `$target` nor
# picks up `$options`, and such a test now simply gains the switch. Each also
# gets a private nimcache, which is what makes it incremental at all.
#
# Switches must land BEFORE the project file: anything after it is swallowed
# into `config.arguments`, and a non-empty `arguments` without `--run` is a
# hard error ("arguments can only be given if the '--run' option is
# selected").
var switches = "--ic:on "
if nimcache.len > 0 and "--nimCache:" notin result and "--nimcache:" notin result:
switches.add "--nimCache:" & nimcache.quoteShell & " "
# `rfind`, not `find`: the private nimcache path embeds the test's file name
# (`nimcache/tests/destructor/tmove.nim_<hash>`), so the FIRST occurrence is
# inside a switch's value. The project file is the last one.
let fileArg = filename.quoteShell
let at = result.rfind(fileArg)
if at >= 0: result = result[0 ..< at] & switches & result[at .. ^1]
else: result.add " " & switches
proc icWarmupCache(cmdTemplate, filename, options: string, target: TTarget,
extraOptions: string): string =
## The shared warm cache for this test's exact compile configuration, built on
## first use and kept in `nimcache/` across runs. Keyed by the switches AND the
## test's directory, because both decide what the artifacts contain: the
## switches through `-d:`/`--mm:` etc., the directory through the `nim.cfg` /
## `config.nims` it inherits. A cache built under a different configuration
## would just be invalidated wholesale on first use, which is worse than none.
if buildingIcWarmup: return ""
let dir = filename.getFileDir()
let key = options & extraOptions & $target & dir
if icWarmupCaches.hasKey(key): return icWarmupCaches[key]
result = "nimcache" / ("ic_warmup_" & key.getMD5)
icWarmupCaches[key] = result
if dirExists(result / "ic.version"): return # already built by an earlier run
if fileExists(result / "ic.version"): return
# The warmup must live in the test's own directory so it inherits the same
# config files; a stray `.nim` there is not picked up as a test (testament
# only collects `t*.nim`). The name must be a valid Nim identifier.
let src = dir / "icwarmup_generated.nim"
try:
writeFile(src, icWarmupSource)
except IOError, OSError:
icWarmupCaches[key] = ""
return ""
buildingIcWarmup = true
let cmd = prepareTestCmd(cmdTemplate, src, options, result, target, extraOptions)
let (outp, code) = execCmdEx(cmd)
buildingIcWarmup = false
try: removeFile(src)
except OSError: discard
if code != 0:
# Non-fatal: without a warm cache every test just pays its own cold build.
if optVerbose: echo "ic warmup failed: ", cmd, "\n", outp
icWarmupCaches[key] = ""
return ""
proc prefillIcCache(warmup, nimcache: string) =
## Seed a test's empty cache from the shared warm one. Only the artifacts that
## do NOT depend on which program is being built are copied: the frontend NIFs
## and cookies, plus the per-module `lower`/`cg` outputs. The `.c`/`.o` are
## deliberately left out — the merge decision (who owns each emit-everywhere
## definition) is whole-program, so those get re-rendered for every program
## anyway and copying them is pure I/O.
##
## Mtimes are preserved, and that is load-bearing: nifmake decides staleness by
## output-mtime > input-mtime, so stamping every prefilled file with "now"
## would scramble the DAG ordering the warmup established and re-fire the
## whole graph — exactly what the copy is meant to avoid.
if warmup.len == 0 or not dirExists(warmup): return
if dirExists(nimcache): return # the test already has its own cache
const wanted = [".p.nif", ".p.deps.nif", ".deps.nif", ".s.bif", ".iface.bif",
".impl.bif", ".edges.bif", ".s.deps.bif", ".t.bif",
".c.nif", ".cpp.nif"]
try:
createDir(nimcache)
for path in walkFiles(warmup / "*"):
let name = path.extractFilename
var take = name == "ic.version" or name == "ic_build_args.txt"
if not take:
for ext in wanted:
if name.endsWith(ext): take = true; break
if not take: continue
let dst = nimcache / name
copyFile(path, dst)
try: setLastModificationTime(dst, getLastModificationTime(path))
except OSError, IOError: discard
except OSError, IOError:
discard # best effort; a cold build still works
proc callNimCompiler(cmdTemplate, filename, options, nimcache: string,
target: TTarget, extraOptions = ""): TSpec =
if useIc and target in {targetC, targetCpp} and nimcache.len > 0 and not buildingIcWarmup:
prefillIcCache(icWarmupCache(cmdTemplate, filename, options, target, extraOptions),
nimcache)
result = TSpec(cmd: prepareTestCmd(cmdTemplate, filename, options, nimcache, target,
extraOptions))
verboseCmd(result.cmd)
@@ -545,28 +415,21 @@ proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest,
result = r.finishTestRetryable(test, target, extraOptions, expected.msg, given.msg, reSuccess)
inc(r.passed)
proc generatedFile(test: TTest, target: TTarget, extraOptions: string): string =
proc generatedFile(test: TTest, target: TTarget): string =
if target == targetJS:
result = test.name.changeFileExt("js")
else:
let (_, name, _) = test.name.splitFile
let ext = targetToExt[target]
# `extraOptions` must match what `testSpecWithNimcache` passed to the
# compiler — the matrix entry is part of the nimcache key, so leaving it out
# here looks for the `.c` of a DIFFERENT variant's cache (which does not
# exist) and every `ccodeCheck` test with a `matrix:` failed as
# `reCodeNotFound`.
result = nimcacheDir(test.name, test.options, target, extraOptions) /
"@m" & name.changeFileExt(ext)
result = nimcacheDir(test.name, test.options, target) / "@m" & name.changeFileExt(ext)
proc needsCodegenCheck(spec: TSpec): bool =
result = spec.maxCodeSize > 0 or spec.ccodeCheck.len > 0
proc codegenCheck(test: TTest, target: TTarget, extraOptions: string,
spec: TSpec, expectedMsg: var string,
proc codegenCheck(test: TTest, target: TTarget, spec: TSpec, expectedMsg: var string,
given: var TSpec) =
try:
let genFile = generatedFile(test, target, extraOptions)
let genFile = generatedFile(test, target)
let contents = readFile(genFile)
for check in spec.ccodeCheck:
if check.len > 0 and check[0] == '\\':
@@ -594,7 +457,7 @@ proc compilerOutputTests(test: TTest, target: TTarget, extraOptions: string,
var givenmsg: string = ""
if given.err == reSuccess:
if expected.needsCodegenCheck:
codegenCheck(test, target, extraOptions, expected, expectedmsg, given)
codegenCheck(test, target, expected, expectedmsg, given)
givenmsg = given.msg
if not nimoutCheck(expected, given) or
not checkForInlineErrors(expected, given):
@@ -727,7 +590,7 @@ proc targetHelper(r: var TResults, test: TTest, expected: TSpec, extraOptions: s
inc count
echo "testSpec count: ", count, " expected: ", expected
else:
let nimcache = nimcacheDir(test.name, test.options, target, extraOptions)
let nimcache = nimcacheDir(test.name, test.options, target)
var testClone = test
let target = changeTarget(extraOptions, target)
testSpecHelper(r, testClone, expected, target, extraOptions, nimcache)
@@ -828,7 +691,6 @@ proc main() =
case p.key.normalize
of "print": optPrintResults = true
of "verbose": optVerbose = true
of "ic": useIc = true
of "failing": optFailing = true
of "pedantic": discard # deadcode refs https://github.com/nim-lang/Nim/issues/16731
of "targets":

View File

@@ -35,20 +35,6 @@ proc bug20303() =
bug20303()
block: # bug #26143
var indexCalls = 0
proc nextIndex(): int =
result = indexCalls
inc indexCalls
proc consume(value: sink string) =
doAssert value == "A"
var values = @["A", "B"]
consume(values[nextIndex()])
doAssert indexCalls == 1
proc main() = # todo bug with templates
block: # bug #11267
var a: seq[char] = block: @[]

View File

@@ -1,4 +0,0 @@
var codegenDeclGlobal* {.codegenDecl: "$# /* custom declaration */ $#".} = 123
proc readCodegenDeclGlobal*(): int {.inline.} =
codegenDeclGlobal

View File

@@ -1,5 +0,0 @@
proc resizeCints*(s: var seq[cint], n: int) =
s.setLen(n)
proc cintLen*(s: seq[cint]): int =
result = s.len

View File

@@ -1,13 +0,0 @@
discard """
output: '''
123
123
'''
ccodecheck: "'extern NI /* custom declaration */ codegenDeclGlobal'"
targets: "c cpp"
"""
import ./mcodegendeclglobal
echo codegenDeclGlobal
echo readCodegenDeclGlobal()

View File

@@ -1,15 +0,0 @@
discard """
action: run
targets: "c cpp"
"""
import mseq_importc_alias
type CIntAlias = cint
var fds: seq[CIntAlias]
doAssert cintLen(@[1.cint, 2.cint]) == 2
doAssert cintLen(fds) == 0
resizeCints(fds, 3)
fds[1] = CIntAlias(7)
doAssert cintLen(fds) == 3

View File

@@ -1,20 +0,0 @@
discard """
action: run
targets: "c cpp"
"""
type CIntAlias = cint
var x: (cint,) = (1.cint,)
var y: (CIntAlias,) = x
x = y
doAssert x[0] == 1.cint
var a: seq[cint]
var b: seq[CIntAlias]
a.add 1.cint
a.add 2.cint
b = a
a = b
doAssert a[0] == 1.cint
doAssert b[1] == CIntAlias(2)

View File

@@ -1,75 +0,0 @@
discard """
action: run
"""
type Indexable[T] = concept
proc `[]`(a: Self; index: int): T
proc len(a: Self): int
iterator items[T; I: Indexable[T]](indexable: I): T =
for index in 0 ..< indexable.len:
yield indexable[index]
type Dummy[T] = distinct seq[T]
proc `[]`[T](d: Dummy[T], i: int): T = seq[T](d)[i]
proc len[T](d: Dummy[T]): int = seq[T](d).len
var acc = 0
for x in Dummy(@[1, 2, 3]):
acc += x
doAssert acc == 6
# Inferred concept parameters are resolved through the implementation's own
# generic bindings before being exported to the surrounding routine.
type
Elem[T] = object
value: T
NestedDummy[T] = ref object
data: seq[T]
proc `[]`[T](d: NestedDummy[T], i: int): Elem[T] =
Elem[T](value: d.data[i])
proc len[T](d: NestedDummy[T]): int = d.data.len
iterator directItems[T](indexable: Indexable[T]): T =
for index in 0 ..< indexable.len:
yield indexable[index]
var nestedAcc = 0
for x in NestedDummy[int](data: @[4, 5, 6]):
nestedAcc += x.value
doAssert nestedAcc == 15
var directNestedAcc = 0
for x in directItems(NestedDummy[int](data: @[7, 8, 9])):
directNestedAcc += x.value
doAssert directNestedAcc == 24
# All dependent parameters inferred while checking a concept constraint must
# be propagated to the constrained routine.
type
KeyValue[K, V] = concept
proc key(x: Self): K
proc value(x: Self): V
Pair[K, V] = object
k: K
v: V
proc key[K, V](x: Pair[K, V]): K = x.k
proc value[K, V](x: Pair[K, V]): V = x.v
proc unpack[K, V; P: KeyValue[K, V]](x: P): (K, V) =
(x.key, x.value)
let pair = Pair[int, string](k: 7, v: "seven")
doAssert unpack(pair) == (7, "seven")
doAssert not compiles(unpack[string, int](pair))
proc unpackBoth[K1, V1, K2, V2;
P1: KeyValue[K1, V1]; P2: KeyValue[K2, V2]](
x: P1; y: P2): ((K1, V1), (K2, V2)) =
(unpack(x), unpack(y))
let otherPair = Pair[string, float](k: "eight", v: 8.0)
doAssert unpackBoth(pair, otherPair) == ((7, "seven"), ("eight", 8.0))

View File

@@ -1,28 +0,0 @@
discard """
matrix: "--mm:orc"
output: "destroy b"
"""
# bug #26123
type
A = ptr AObj
AObj = object
b: B
B = distinct ptr BObj
BObj = object
a: A
proc `=destroy`(r: var B) =
echo "destroy b"
proc main() =
var a = create(AObj)
var b = B(create(BObj))
a.b = b
cast[ptr BObj](b).a = a
main()

View File

@@ -166,99 +166,3 @@ type Vector*[T] = object
# proc `=destroy`*(x: var Vector[int]) = discard # this will remove error
proc `=destroy`*[T](x: var Vector[T]) = discard
var a: Vector[int] # Error: unresolved generic parameter
# issue #26132
block:
type UnparameterizedGeneric[T] = object
proc `=destroy`(x: var UnparameterizedGeneric) = discard
proc `=wasMoved`(x: var UnparameterizedGeneric) = discard
proc `=trace`(x: var UnparameterizedGeneric; env: pointer) = discard
var x: UnparameterizedGeneric[int]
discard x
# Exercise every type-bound hook with the generic parameter omitted.
block:
type
Generic[T] = object
value: T
var destroys, moves, traces, copies, sinks, dups: int
proc `=destroy`(x: var Generic) = inc destroys
proc `=wasMoved`(x: var Generic) =
inc moves
x.value = default(typeof(x.value))
proc `=trace`(x: var Generic; env: pointer) = inc traces
proc `=copy`(dest: var Generic; src: Generic) =
inc copies
dest.value = src.value
proc `=sink`(dest: var Generic; src: Generic) =
inc sinks
dest.value = src.value
proc `=dup`(src: Generic): Generic =
inc dups
Generic(value: src.value)
proc deepCopy(src: ref Generic): ref Generic = src
proc exercise[T]() =
var first = Generic[T](value: default(T))
var second = Generic[T](value: default(T))
second = first
doAssert second.value == first.value
second = Generic[T](value: default(T))
doAssert second.value == default(T)
`=trace`(first, nil)
`=wasMoved`(first)
let implicitDuplicate = first
discard implicitDuplicate
let duplicate = `=dup`(first)
discard duplicate
let original = new(Generic[T])
doAssert deepCopy(original) == original
exercise[string]()
exercise[int]()
exercise[seq[int]]()
doAssert copies > 0
doAssert sinks > 0
doAssert dups > 0
doAssert moves > 0
doAssert traces > 0
doAssert destroys > 0
block:
type GenericDistinct[T] = distinct Generic[T]
proc `=destroy`(x: var GenericDistinct) = discard
proc `=wasMoved`(x: var GenericDistinct) = discard
proc `=trace`(x: var GenericDistinct; env: pointer) = discard
proc `=copy`(dest: var GenericDistinct; src: GenericDistinct) = discard
proc `=sink`(dest: var GenericDistinct; src: GenericDistinct) = discard
proc `=dup`(src: GenericDistinct): GenericDistinct = src
proc deepCopy(src: ref GenericDistinct): ref GenericDistinct = src
var first = GenericDistinct[string](Generic[string](value: "first"))
var second = GenericDistinct[string](Generic[string](value: "second"))
second = first
second = GenericDistinct[string](Generic[string](value: "third"))
`=trace`(first, nil)
`=wasMoved`(first)
let moved = move(first)
let duplicate = `=dup`(moved)
discard duplicate
let original = new(GenericDistinct[string])
doAssert deepCopy(original) == original
block:
type GenericPair[A, B] = object
left: A
right: B
proc `=destroy`(x: var GenericPair) = discard
var pair = GenericPair[int, string](left: 42, right: "pair")
discard pair

View File

@@ -1,7 +0,0 @@
proc u(k: static int) =
proc r(_: static int) =
while k > 0:
discard
r(0)
u(0)

View File

@@ -1,11 +0,0 @@
# Helper for tnestasgn.nim: a `sink`-param routine containing a nested proc
# whose ENTIRE body is a single assignment, so the body node is a bare `nkAsgn`
# rather than an `nkStmtList` — the shape that used to be deferred behind a
# childless placeholder of that same kind.
proc consume*(s: sink string) =
var x = ""
proc setIt() =
x = s
setIt()
echo x

View File

@@ -1,34 +0,0 @@
discard """
description: '''IC: changing the compiler switches must invalidate the cache'''
"""
#? metamorphic
# nifmake decides staleness from file mtimes and never looks at a rule's command
# line, so `-d:` / `--mm:` / `--opt:` changes re-generated the build file with
# the new switches and re-fired nothing: a silently stale binary built with the
# PREVIOUS configuration. And switches given only on the driver's command line
# never reached the per-module children at all, because they replay the
# project's config files rather than the driver's argv.
#!FILE cfg.nim
const Mode* {.strdefine.} = "plain"
proc describe*(): string =
when Mode == "loud": "LOUD"
elif Mode == "quiet": "quiet"
else: "plain"
#!FILE main.nim
import cfg
echo describe()
#!STEP expect: plain
#!FLAGS -d:Mode=loud
#!STEP expect: LOUD
#!FLAGS -d:Mode=quiet
#!STEP expect: quiet
#!FLAGS
#!STEP expect: plain

View File

@@ -1,37 +0,0 @@
discard """
description: '''IC: an import under an undecidable `when` must not be compiled'''
"""
#? metamorphic
# `when SomeStrdefineConst == "x": import y` is `cvUnknown` to the dependency
# scanner, which conservatively keeps the edge — right for an edge, but it also
# gave `y` its own `nim m` rule. `nim c` never looks at that file, so a build
# died on a package the user never installed because they never selected that
# backend. Selecting it must still produce the honest error.
#!FILE needsmissing.nim
import pkg/definitely_not_an_installed_package
proc unreachable*(): string = "never"
#!FILE guarded.nim
const Backend* {.strdefine.} = "plain"
when Backend == "fancy":
import ./needsmissing
proc pick*(): string =
when Backend == "fancy": unreachable()
else: "plain"
#!FILE main.nim
import guarded
echo pick()
#!STEP expect: plain
# selecting the branch that really does need the missing package must report it
#!FLAGS -d:Backend=fancy
#!STEP fails: cannot open file
#!FLAGS
#!STEP expect: plain

View File

@@ -1,26 +0,0 @@
discard """
description: '''IC: deleting a still-imported module must be an error'''
"""
#? metamorphic
# Deleting a file moves no mtime, so nothing in an mtime-keyed build re-fires:
# `nim ic` relinked a stale binary while `nim c` reported `cannot open file`.
# The dependency scan is the only part of the pipeline that looks at import
# paths at all, so that is where the vanished module has to be noticed.
#!FILE helper.nim
proc help*(): string = "helped"
#!FILE main.nim
import helper
echo help()
#!STEP expect: helped
#!DELETE helper.nim
#!STEP fails: cannot open file
# putting it back recovers
#!FILE helper.nim
proc help*(): string = "back"
#!STEP expect: back

View File

@@ -1,68 +0,0 @@
discard """
description: '''IC vs `nim c`: destructor injection and move analysis must agree'''
"""
#? metamorphic
# Two whole classes of IC miscompilation are invisible to any IC-vs-IC check,
# because IC was *consistently* wrong: warm == cold == not what `nim c` does.
# The oracle is what catches them.
#
# * `sfInjectDestructors` lives on the MODULE symbol, which the NIF loader
# rebuilds from scratch — so `genTopLevelStmt` skipped the destructor pass
# entirely and a module-level `block: let h = ...` never ran `=destroy`.
# * `nfFirstWrite`/`nfLastRead` sit on `nkSym` nodes, which serialize as bare
# NIF `SymUse` tokens with nowhere to put node flags — so the frontend's move
# analysis never reached the backend and EVERY first assignment to a
# destructor-bearing local became `=sink` over still-zeroed memory.
#!FILE res.nim
var log*: seq[string]
type R* = object
tag*: string
proc `=destroy`*(r: R) = log.add "d(" & r.tag & ")"
proc `=copy`*(d: var R, s: R) = (log.add "c(" & s.tag & ")"; d.tag = s.tag)
proc mk*(t: string): R = R(tag: t)
proc mkVia*(t: string): R = (result = R(tag: t))
proc consume*(r: sink R): string = "u:" & r.tag
#!FILE main.nim
import res
# in a proc: worked before
proc inProc() =
let a = mk("proc")
discard a
inProc()
# module top level: the pass was skipped wholesale
block:
let t = mk("toplevel")
discard t
for i in 0 .. 1:
let l = mk("loop" & $i)
discard l
# every `result` shape: each must construct in place, not `=sink` over zeroes
block:
let x = mk("direct")
let y = mkVia("via")
discard x
discard y
# last read is a move, a re-read is a copy
proc moves(): string =
var m = mk("moved")
result = consume(m)
proc copies(): string =
var k = mk("kept")
result = consume(k) & "/" & k.tag
discard moves()
discard copies()
echo log
#!STEP expect: @["d(proc)", "d(toplevel)", "d(loop0)", "d(loop1)", "d(via)", "d(direct)", "d(moved)", "c(kept)", "d(kept)", "d(kept)"]

View File

@@ -1,38 +0,0 @@
discard """
description: '''IC: a macro-generated import stays in the graph across runs'''
"""
#? metamorphic
# The static scanner cannot see `parseStmt("import dyn")`. The discovery
# fixpoint recovers it — but only ran AFTER a failure, and the graph is
# re-derived statically on every run, so on a warm build the discovered module
# had no nifler/`nim m` rule at all: editing it changed nothing, forever.
#!FILE dyn.nim
proc hidden*(): string = "first"
#!FILE gen.nim
import std/macros
macro generatedImport(): untyped =
parseStmt("import dyn")
generatedImport()
proc reveal*(): string = hidden()
#!FILE main.nim
import gen
echo reveal()
#!STEP expect: first
# the warm build must see this edit
#!FILE dyn.nim
proc hidden*(): string = "second"
#!STEP expect: second
# and again, to prove it is not a one-shot recovery
#!FILE dyn.nim
proc hidden*(): string = "third"
#!STEP expect: third

View File

@@ -1,32 +0,0 @@
discard """
description: '''IC: a failed `nim m` must not poison the cache'''
"""
#? metamorphic
# A `nim m` that errored still wrote its `.s.bif` and cookie sidecars. nifmake
# then saw the rule satisfied (outputs newer than inputs) and the NEXT run
# reported success for a program that does not compile — linking a binary
# generated from error-bearing AST, or crashing codegen outright. Expressing
# this needs a step that is allowed to FAIL and a following step that recovers.
#!FILE dep.nim
proc value*(): int = 41
#!FILE main.nim
import dep
echo value() + 1
#!STEP expect: 42
# introduce a real error
#!FILE dep.nim
proc value*(): int = undefinedThing() + 1
#!STEP fails: undeclared identifier: 'undefinedThing'
# ... and again: the second run must NOT decide the rule is up to date.
#!STEP fails: undeclared identifier: 'undefinedThing'
# fixing it must rebuild rather than serve the poisoned artifact
#!FILE dep.nim
proc value*(): int = 100
#!STEP expect: 101

View File

@@ -1,49 +0,0 @@
discard """
description: '''IC vs `nim c`: module-level globals must be destroyed at exit'''
"""
#? metamorphic
# `graph.globalDestructors` is filled while a module's top level goes through
# `injectDestructorCalls`, and whole-program cgen empties the list into the main
# module's init proc — which IS the program body, so the calls land at program
# exit. Under `nim ic` every module's `cg` is a separate process, so the main
# module's `cg` only ever saw its OWN entries and a module-level `var` with a
# `=destroy` in any imported module was simply never destroyed.
#
# The teardown ORDER is the other half: it must be the reverse of the init order
# (importers before their dependencies), which is what the oracle pins down here
# — three modules in a chain plus main, each with a global of its own.
#!FILE gdlog.nim
type G* = object
tag*: string
proc `=destroy`*(g: G) = echo "destroy ", g.tag
proc mk*(t: string): G = G(tag: t)
#!FILE gda.nim
import gdlog
var ga* = mk("a")
#!FILE gdb.nim
import gdlog, gda
var gb* = mk("b:" & ga.tag)
#!FILE gdc.nim
import gdlog, gdb
var gcv* = mk("c:" & gb.tag)
#!FILE main.nim
import gdlog, gda, gdb, gdc
var gmain = mk("main")
echo "body ", ga.tag, " ", gb.tag, " ", gcv.tag, " ", gmain.tag
#!STEP
# touching a leaf module must not lose anyone's teardown
#!FILE gda.nim
import gdlog
var ga* = mk("a2")
#!STEP

View File

@@ -1,7 +0,0 @@
import ../ccgbugs/mseq_importc_alias
type CIntAlias = cint
var values: seq[CIntAlias]
resizeCints(values, 2)
doAssert cintLen(values) == 2

View File

@@ -1,18 +0,0 @@
discard """
output: '''hi'''
"""
# Regression test, minimized from a nimbus-eth2 `nim ic` crash by
# https://github.com/nim-lang/Nim/pull/26106 (the only one of that PR's eight
# repros that reproduces on its own base).
#
# A NIF-loaded routine's body is installed as a `nfLazyBody` placeholder. The
# placeholder used to carry the REAL body kind while holding no children, which
# breaks the compiler's most basic invariant — a node's kind implies its arity.
# `trees.getPotentialWrites` walks the outer routine because it has a `sink`
# parameter, reaches the nested proc's body under `of nkAsgn`, and reads
# `n[0]`/`n[1]` as every such reader is entitled to: "index out of bounds, the
# container is empty [IndexDefect]".
import mnestasgn
consume("hi")

View File

@@ -63,7 +63,7 @@ proc foo2 =
discard
else:
let x = 1
doAssert x == 1
doAssert not declared(x)
when false:
discard

View File

@@ -49,13 +49,6 @@ proc bar(s: var seq[int], a: int) =
s.bar(5)
doAssert(s == @[123, 1])
# Imported JavaScript patterns must receive the underlying array, not the
# `{base, off, len}` view used for regular `var openArray` parameters.
proc jsSort[T](x: var openArray[T], cmp: proc(a, b: T): int) {.importcpp: "#.sort(#)", nodecl.}
var sorted = @[2, 1]
sorted.jsSort(proc(a, b: int): int = a - b)
doAssert(sorted == @[1, 2])
import tables
block: # Test get addr of byvar return value
var t = initTable[string, int]()

View File

@@ -1,20 +0,0 @@
discard """
output: '''caught'''
"""
type
Base = ref object of RootObj
Child = ref object of Base
method run(value: Base): string {.base.} =
result = "base"
method run(value: Child): string =
raise newException(ValueError, "child")
let value: Base = Child()
try:
discard value.run()
quit "virtual method did not raise"
except ValueError:
echo "caught"

View File

@@ -11,9 +11,6 @@ proc fn2[T](a: var openArray[T]): seq[T] =
proc fn3[T](a: var openArray[T]) =
for i, ai in mpairs(a): ai = i * 10
proc wr[T](a: var openArray[T]; v: T) =
a[0] = v
proc main =
var a = [1,2,3,4,5]
@@ -23,22 +20,8 @@ proc main =
doAssert fn2(a.toOpenArray(1,3)) == @[2,3,4]
fn3(a.toOpenArray(1,3))
doAssert a == [1, 0, 10, 20, 5]
block: # bug #15952: `toOpenArray` slices are live views on JS
# Fixed homogeneous numeric arrays lower to JS typed arrays; seqs and
# non-numeric fixed arrays lower to plain JS arrays. In all cases a slice
# passed to a `var openArray` must alias the source so writes propagate
# (JS: subarray view for typed arrays, {base,off,len} view otherwise).
var si = @[1, 2, 3, 4, 5]
fn3(si.toOpenArray(1, 3))
doAssert si == @[1, 0, 10, 20, 5]
var ss = ["a", "b", "c", "d", "e"]
wr(ss.toOpenArray(1, 3), "Z")
doAssert ss == ["a", "Z", "c", "d", "e"]
# read-only slicing must still work and never throw, on every backend.
doAssert fn1(@[1, 2, 3, 4, 5].toOpenArray(1, 3)) == @[2, 3, 4]
doAssert fn1(["a", "b", "c", "d", "e"].toOpenArray(1, 3)) == @["b", "c", "d"]
when defined(js): discard # xxx bug #15952: `a` left unchanged
else: doAssert a == [1, 0, 10, 20, 5]
block: # bug #12521
block:

View File

@@ -1,24 +0,0 @@
discard """
matrix: "--mm:orc --undef:nimPreviewNonVarDestructor"
output: "hello"
"""
# bug #26134
type MyObject = object
proc `=destroy`(v: var MyObject) =
echo "hello"
proc remove(v: var seq[MyObject]) =
v.del(0)
proc aaa(v: var seq[MyObject], i: sink MyObject) =
v.add(i)
proc main =
var v: seq[MyObject]
v.aaa(MyObject())
v.remove()
main()

View File

@@ -451,12 +451,6 @@ template fn() =
let json = inner.toJson(ToJsonOptions(enumMode: joptEnumSymbol))
doAssert $json == """{"x":"hello","y":"A"}"""
block arrayTypeCheck:
let json = """{"key": "value"}""".parseJson()
var output: seq[int]
doAssertRaises(ValueError):
output.fromJson(json)
block: # bug #21638
type Something = object

View File

@@ -1630,38 +1630,6 @@ And this should **NOT** be visible in `docs.html`
doAssert "<em>Visible</em>" == rstToHtml(input, {roSandboxDisabled}, defaultConfig())
removeFile("other.rst")
test "`:literal:` flag":
"code.nim".writeFile("""
discard
""")
let input = """
.. include:: code.nim
:literal:
"""
check "<pre>discard\n</pre>" == rstToHtml(input, {roSandboxDisabled}, defaultConfig())
removeFile("code.nim")
test "Include everything between in `:literal:` mode":
"code.nim".writeFile("""
proc notIncluded = discard
#CodeStart
proc included = discard
#CodeEnd
proc notIncluded = discard
""")
let input = """
.. include:: code.nim
:literal:
:start-after: #CodeStart
:end-before: #CodeEnd
"""
check "<pre>\nproc included = discard\n</pre>" == rstToHtml(input, {roSandboxDisabled}, defaultConfig())
removeFile("code.nim")
suite "RST escaping":
test "backspaces":
check("""\ this""".toAst == dedent"""

9
tests/vm/t26048.nim Normal file
View File

@@ -0,0 +1,9 @@
# issue #26048, `$` declarations must not leak from `when nimvm`
type U = object
when nimvm:
proc `$`(_: U): string = "s"
var n: U
doAssert $n != "s"

View File

@@ -0,0 +1,12 @@
# issue #23687
when nimvm:
proc mytest(a: int) =
echo a
else:
template mytest(a: int) =
echo a + 42
proc xxx() =
mytest(100) #[tt.Error
^ undeclared identifier: 'mytest']#

View File

@@ -0,0 +1,13 @@
# issue #23688
when nimvm:
proc mytest(a: int) =
echo a
else:
template mytest(a: untyped) =
echo a + 42
proc xxx() =
mytest(100) #[tt.Error
^ undeclared identifier: 'mytest']#
xxx()

View File

@@ -0,0 +1,10 @@
# issue #13450, example 3
proc bar() =
when nimvm:
let y = 1
else:
let y = 2
discard y #[tt.Error
^ undeclared identifier: 'y']#
bar()

16
tests/whenstmt/t26044.nim Normal file
View File

@@ -0,0 +1,16 @@
# issue #26044
discard """
cmd: "nim check --hints:off --warnings:off $file"
action: reject
nimout: '''
t26044.nim(15, 11) Error: undeclared identifier: 'g'
t26044.nim(15, 11) Error: expression 'g' has no type (or is ambiguous)
'''
"""
proc p =
when nimvm:
var g: int
discard g
p()