Files
Nim/compiler/ccgreset.nim
araq 9e076d74c0 cgen: no [] on a PNode outside tree construction
Completes the vocabulary migration started with `BNode`: every child READ in
the cgen files now goes through an accessor that a `.bif` `Cursor` can also
serve, so flipping `newIcBackend` is a matter of implementing the vocabulary
rather than rewriting call sites.

  * constant indices -> `firstSon` / `secondSon` / `lastSon` / `son(n, k)`,
    including the `namePos`/`paramsPos`/`bodyPos`/... slot reads;
  * indexed loops -> `sons` / `sonsFrom` / `sonsButLast` and the index-yielding
    `isons` / `isonsButLast`. `for i in 0..<n.len: n[i]` is quadratic once
    `BNode` is a `Cursor`, because reaching child `i` costs one `skip` per
    preceding SUBTREE;
  * `n.len == 0` / `> 0` on a node -> `hasSons`, which does not count.

`astdef` gains `sonsButLast(n, count)` and `isonsButLast` — the
`nkOfBranch`/`nkExceptBranch` shape, whose last child is the branch body, and
with `count = 2` the `nkVarTuple`/`nkIdentDefs` shape.

Three places needed more than a rename:

  * the C++/goto/setjmp try generators re-subscripted `t[i]` up to ten times
    per iteration of their `while i < t.len` walk; the branch node is now
    hoisted once per step;
  * `genParams` scans the arguments BACKWARDS to decide which need a temporary,
    which a `Cursor` cannot do at all. It materializes them in one forward pass
    and indexes that — the same order of work, since `needTmp` already
    allocates per call;
  * loops that stop at a computed position (`casePos`, `until`, `splitPoint`)
    walk forward and break instead of counting up to the bound.

What is left is exactly what a `Cursor` backend will not do: writes that build
a fresh `nkProcDef`, and subscripts of a `PType`, `string`, `seq` or `Table`.
`bnode.nim` records the invariant and the `PType` trap — `ast.sons(t: PType)`
is a proc returning `var TTypeSeq`, not the iterator of the same name — which
the type checker enforces, since the `firstSon`/`secondSon`/`lastSon`/`son`
family exists for `PNode` only.

Pure refactor, verified as one: all 216 generated `.c` files byte-identical to
the parent commit; metamorphic IC 16/16; icSuite 19/19 fragments; categories
gc 78, arc 140, destructor 97, closure 23, iter 71, trmacros 6, cpp 50,
exception 47, casestmt 16 with one pre-existing environmental failure
(tests/cpp/tasync_cpp.nim: `cannot open file: jester`).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMyRHByv7hhaQJ4Pa1bHbE
2026-08-29 06:57:55 +02:00

136 lines
5.3 KiB
Nim

#
#
# The Nim Compiler
# (c) Copyright 2020 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# included from cgen.nim
## Code specialization instead of the old, incredibly slow 'genericReset'
## implementation.
proc specializeResetT(p: BProc, accessor: Rope, typ: PType)
proc specializeResetN(p: BProc, accessor: Rope, n: PNode;
typ: PType) =
if n == nil: return
case n.kind
of nkRecList:
for it in sons(n):
specializeResetN(p, accessor, it, typ)
of nkRecCase:
if (n.firstSon.kind != nkSym): internalError(p.config, n.info, "specializeResetN")
let disc = n.firstSon.sym
if disc.loc.snippet == "": fillObjectFields(p.module, typ)
if disc.loc.t == nil:
internalError(p.config, n.info, "specializeResetN()")
let discField = dotField(accessor, disc.loc.snippet)
p.s(cpsStmts).addSwitchStmt(discField):
for branch in sonsFrom(n, 1):
assert branch.kind in {nkOfBranch, nkElse}
var caseBuilder: SwitchCaseBuilder
p.s(cpsStmts).addSwitchCase(caseBuilder):
if branch.kind == nkOfBranch:
genCaseRange(p, branch, caseBuilder)
else:
p.s(cpsStmts).addCaseElse(caseBuilder)
do:
specializeResetN(p, accessor, lastSon(branch), typ)
p.s(cpsStmts).addBreak()
specializeResetT(p, discField, disc.loc.t)
of nkSym:
let field = n.sym
if field.typ.kind == tyVoid: return
if field.loc.snippet == "": fillObjectFields(p.module, typ)
if field.loc.t == nil:
internalError(p.config, n.info, "specializeResetN()")
specializeResetT(p, dotField(accessor, field.loc.snippet), field.loc.t)
else: internalError(p.config, n.info, "specializeResetN()")
proc specializeResetT(p: BProc, accessor: Rope, typ: PType) =
if typ == nil: return
case typ.kind
of tyGenericInst, tyGenericBody, tyTypeDesc, tyAlias, tyDistinct, tyInferred,
tySink, tyOwned:
specializeResetT(p, accessor, skipModifier(typ))
of tyArray:
let arraySize = lengthOrd(p.config, typ.indexType)
var i: TLoc = getTemp(p, getSysType(p.module.g.graph, unknownLineInfo, tyInt))
p.s(cpsStmts).addForRangeExclusive(i.snippet, cIntValue(0), cIntValue(arraySize)):
specializeResetT(p, subscript(accessor, i.snippet), typ.elementType)
of tyObject:
var x = typ.baseClass
if x != nil: x = x.skipTypes(skipPtrs)
specializeResetT(p, accessor.parentObj(p.module), x)
if typ.n != nil:
if typ.sym != nil and sfImportc in typ.sym.flags:
# imported C struct, nimZeroMem
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"),
cCast(ptrType(CPointer), cAddr(accessor)),
cSizeof(getTypeDesc(p.module, typ)))
else:
specializeResetN(p, accessor, typ.n, typ)
if isCaseObj(typ.n):
# The active branch was released above. Clear the complete object so
# stale bytes from overlapping branches cannot be traced by the GC.
# type
# Foo = object
# case kind: bool
# of true:
# a: ref Bar # 8 bytes (pointer)
# of false:
# b: int # 4 bytes
# specializeResetT for b emits accessor.b = 0 — writes 4 bytes
# But the union is 8 bytes wide (sized by the largest branch)
# The remaining 4 bytes where a used to live are untouched
# Those stale bytes could contain a heap pointer the GC traces → crash
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"),
cCast(CPointer, cAddr(accessor)),
cSizeof(getTypeDesc(p.module, typ)))
of tyTuple:
let typ = getUniqueType(typ)
for i, a in typ.ikids:
specializeResetT(p, dotField(accessor, "Field" & $i), a)
of tyString, tyRef, tySequence:
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "unsureAsgnRef"),
cCast(ptrType(CPointer), cAddr(accessor)),
NimNil)
of tyProc:
if typ.callConv == ccClosure:
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "unsureAsgnRef"),
cCast(ptrType(CPointer), cAddr(dotField(accessor, "ClE_0"))),
NimNil)
p.s(cpsStmts).addFieldAssignment(accessor, "ClP_0", NimNil)
else:
p.s(cpsStmts).addAssignment(accessor, NimNil)
of tyChar, tyBool, tyEnum, tyRange, tyInt..tyUInt64:
p.s(cpsStmts).addAssignment(accessor, cIntValue(0))
of tyCstring, tyPointer, tyPtr, tyVar, tyLent:
p.s(cpsStmts).addAssignment(accessor, NimNil)
of tySet:
case mapSetType(p.config, typ)
of ctArray:
let t = getTypeDesc(p.module, typ)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"),
accessor,
cSizeof(t))
of ctInt8, ctInt16, ctInt32, ctInt64:
p.s(cpsStmts).addAssignment(accessor, cIntValue(0))
else:
raiseAssert "unexpected set type kind"
of tyNone, tyEmpty, tyNil, tyUntyped, tyTyped, tyGenericInvocation,
tyGenericParam, tyOrdinal, tyOpenArray, tyForward, tyVarargs,
tyUncheckedArray, tyError, tyBuiltInTypeClass, tyUserTypeClass,
tyUserTypeClassInst, tyCompositeTypeClass, tyAnd, tyOr, tyNot,
tyAnything, tyStatic, tyFromExpr, tyConcept, tyVoid, tyIterable:
discard
proc specializeReset(p: BProc, a: TLoc) =
specializeResetT(p, rdLoc(a), a.t)