From 8f72860d7d2ad4cf8cb36d32146b2b38847e54fc Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 1 Sep 2026 16:47:03 +0200 Subject: [PATCH] ic fixes3 (#26157) --- compiler/ast.nim | 103 +++++- compiler/ast2nif.nim | 491 +++++++++++++++++++++---- compiler/astalgo.nim | 4 +- compiler/astdef.nim | 97 ++++- compiler/ccgcalls.nim | 142 ++++---- compiler/ccgexprs.nim | 588 +++++++++++++++--------------- compiler/ccgreset.nim | 11 +- compiler/ccgstmts.nim | 351 +++++++++--------- compiler/ccgtrav.nim | 11 +- compiler/ccgtypes.nim | 73 ++-- compiler/ccgutils.nim | 39 +- compiler/cgen.nim | 455 ++++++++++++++++------- compiler/cgendata.nim | 10 +- compiler/commands.nim | 12 +- compiler/deps.nim | 234 ++++++++++-- compiler/ic/replayer.nim | 68 +++- compiler/icprof.nim | 111 ++++++ compiler/lambdalifting.nim | 15 + compiler/main.nim | 5 +- compiler/mangleutils.nim | 20 +- compiler/modulegraphs.nim | 70 +++- compiler/nifbackend.nim | 497 ++++++++++++++----------- compiler/nifstreams.nim | 247 +++++++++++++ compiler/options.nim | 16 +- compiler/pipelines.nim | 14 +- compiler/trees.nim | 26 +- compiler/types.nim | 10 +- doc/ic.md | 53 +++ koch.nim | 34 +- tests/ic/mexportprivate.nim | 4 + tests/ic/mimporthidden.nim | 4 + tests/ic/readme.md | 48 +++ tests/ic/tclosure_hooks.nim | 101 +++++ tests/ic/tclosure_nested_iter.nim | 90 +++++ tests/ic/texportprivate.nim | 14 + tests/ic/timporthidden.nim | 17 + 36 files changed, 2942 insertions(+), 1143 deletions(-) create mode 100644 compiler/icprof.nim create mode 100644 compiler/nifstreams.nim create mode 100644 tests/ic/mexportprivate.nim create mode 100644 tests/ic/mimporthidden.nim create mode 100644 tests/ic/readme.md create mode 100644 tests/ic/tclosure_hooks.nim create mode 100644 tests/ic/tclosure_nested_iter.nim create mode 100644 tests/ic/texportprivate.nim create mode 100644 tests/ic/timporthidden.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index 8f48dacef8..7877d291cb 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -359,6 +359,18 @@ proc `flags=`*(t: PType, val: TTypeFlags) {.inline.} = t.flagsImpl = val proc sons*(t: PType): var TTypeSeq {.inline.} = + ## The RAW child seq. Despite the name this is NOT the counterpart of the + ## `sons` ITERATOR over a `PNode`, and it is not the way to walk a type's + ## children — use `kids` / `ikids` / `paramTypes` / `signature`, or the named + ## accessors (`returnType`, `baseClass`, `elementType`, `indexType`, + ## `genericHead`, ...), which say WHICH child they mean. + ## + ## The difference is not cosmetic. A `tyProc` keeps its parameter types in + ## `n`, not here — `setSons` asserts `sonsImpl.len <= 1` for one — so `[]`, + ## `len` and every iterator built on them route parameters through + ## `n[i].sym.typ`, while this seq holds only the return type. `for x in + ## t.sons` therefore compiles, looks like the `PNode` idiom, and silently + ## visits a different set of types. if t.state == Partial: loadType(t) result = t.sonsImpl @@ -765,10 +777,28 @@ when false: echo k echo v +when defined(icSymCount): + import std / [syncio, exitprocs, tables as symCountTables] + var symMints*: symCountTables.CountTable[string] + var symMintTotal*: int + var symCountHooked = false + proc newSym*(symKind: TSymKind, name: PIdent, idgen: IdGenerator; owner: PSym, info: TLineInfo; options: TOptions = {}): PSym = # generates a symbol and initializes the hash field too assert not name.isNil + when defined(icSymCount): + # Counting symbol MINTS, not their names in the output: a gensym's number is + # its item id, so one extra symbol anywhere shifts every later name. A count + # is therefore far more sensitive than diffing generated C, and it localises + # the extra mint by kind instead of by whatever file happened to show it. + inc symMintTotal + symMints.inc $symKind + if not symCountHooked: + symCountHooked = true + addExitProc proc () = + stderr.writeLine "SYMMINT total=" & $symMintTotal + for k, v in symMints: stderr.writeLine "SYMMINT " & k & "=" & $v let id = nextSymId idgen result = PSym(name: name, kindImpl: symKind, flagsImpl: {}, infoImpl: info, itemId: id, optionsImpl: options, ownerFieldImpl: owner, offsetImpl: defaultOffset, @@ -1634,7 +1664,7 @@ proc isImportedException*(t: PType; conf: ConfigRef): bool = result = base.sym != nil and {sfCompileToCpp, sfImportc} * base.sym.flags != {} proc isInfixAs*(n: PNode): bool = - return n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.id == ord(wAs) + return n.kind == nkInfix and n.firstSon.kind == nkIdent and n.firstSon.ident.id == ord(wAs) proc skipColon*(n: PNode): PNode = result = n @@ -1714,32 +1744,83 @@ proc addParam*(procType: PType; param: PSym) = const magicsThatCanRaise = { mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst, mEcho} +# `canRaise` reaches the effect list through `effectsOf` / `raisesNothing` +# rather than by subscripting `fn.typ.n`, so the layout is written down in one +# place. Under `--ic:on` that list came back from a `.bif`, and whether it came +# back intact is checked separately: `-d:icCanRaiseLog` logs every verdict, and +# the same program built with and without `--ic:on` must produce the same ones. + +when defined(icCanRaiseLog): + var canRaiseBranch* = 0 + ## Which branch decided the last answer: 1 = the symbol's magic/flags, + ## 2 = `mEcho`, 3 = the EFFECT LIST reached through `effectsOf`, 4 = the + ## conservative predicate, 5 = short-circuited in `canRaiseDisp` before + ## either predicate ran, 0 = fell through. Only branch 3 reads anything + ## that had to survive a `.bif` round trip, so a differential in which no + ## callee reaches it would prove nothing about the writer — which is the + ## whole point of running the differential. See `-d:icCanRaiseLog`. + +template markCanRaiseBranch*(n: int) = + when defined(icCanRaiseLog): canRaiseBranch = n + proc canRaiseConservative*(fn: PNode): bool = - if fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise: - result = false - else: - result = true + markCanRaiseBranch 4 + result = not (fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise) + +proc effectsOf*(t: PType): PNode {.inline.} = + ## The `nkEffectList` a proc type carries as child 0 of its formal-params + ## node, with the parameters following from index 1 (`newProcType` builds it + ## that way; `cgen` reads the params back with `sonsFrom(prc.typ.n, 1)`). + ## + ## Named rather than subscripted so that the layout is written down in ONE + ## place. `.n` here is a TYPE's node, never a routine body, so it is always + ## fully materialised and `firstSon` is safe — the `nfLazyBody` hazard that + ## makes raw child access dangerous elsewhere (see `astdef.sons`) cannot reach + ## it. A proc type always has this child; `t.n` with no children is not a + ## shape the writer or sem produces, and this deliberately does not paper over + ## one appearing. + result = if t.n == nil: nil else: t.n.firstSon + +proc raisesNothing*(effects: PNode): bool = + ## Whether an effect list says DEFINITIVELY that nothing is raised: it is long + ## enough to have a raises slot at all, the slot is present, and it is empty. + ## + ## Every other shape — a list too short to carry the slot, an absent slot, a + ## non-empty one — means the effects are unspecified or non-empty, and a + ## caller must assume a raise. Stating it as the NEGATIVE is the point: the + ## safe default has to be "can raise", so the one narrow case that licenses + ## dropping an exception check is the one spelled out here, and a shape nobody + ## anticipated falls on the conservative side by construction rather than by + ## luck. + result = effects != nil and effects.len >= effectListLen and + effects[exceptionEffects] != nil and + effects[exceptionEffects].safeLen == 0 proc canRaise*(fn: PNode): bool = if fn.kind == nkSym and (fn.sym.magic notin magicsThatCanRaise or {sfImportc, sfInfixCall} * fn.sym.flags == {sfImportc} or sfGeneratedOp in fn.sym.flags): + markCanRaiseBranch 1 result = false elif fn.kind == nkSym and fn.sym.magic == mEcho: + markCanRaiseBranch 2 result = true elif fn.typ != nil and fn.typ.kind == tyProc and fn.typ.n != nil: - # TODO check for n having sons? or just return false for now if not - if fn.typ.n[0].kind == nkSym: + markCanRaiseBranch 3 + let effects = effectsOf(fn.typ) + if effects.kind == nkSym: + # The historical shape: slot 0 used to be an `nkType` before the effects + # moved in (see `newProcType`). Nothing to read, so nothing licenses a + # raise. result = false else: # A proc-typed value with no explicit raises slot still has # unspecified effects, which sempass2 treats conservatively. # Codegen needs to do the same in order to keep goto-exception # checks after indirect/closure calls. - result = ((fn.typ.n[0].len < effectListLen) or - fn.typ.n[0][exceptionEffects] == nil or - fn.typ.n[0][exceptionEffects].safeLen > 0) + result = not raisesNothing(effects) else: + markCanRaiseBranch 0 result = false proc toHumanStrImpl[T](kind: T, num: static int): string = @@ -1756,7 +1837,7 @@ proc toHumanStr*(kind: TTypeKind): string = result = toHumanStrImpl(kind, 2) proc skipHiddenAddr*(n: PNode): PNode {.inline.} = - (if n.kind == nkHiddenAddr: n[0] else: n) + (if n.kind == nkHiddenAddr: n.firstSon else: n) proc isNewStyleConcept*(n: PNode): bool {.inline.} = assert n.kind == nkTypeClassTy diff --git a/compiler/ast2nif.nim b/compiler/ast2nif.nim index f6d2094090..b7b926353c 100644 --- a/compiler/ast2nif.nim +++ b/compiler/ast2nif.nim @@ -20,7 +20,8 @@ import "../dist/checksums/src/checksums" / sha1 import astdef, idents, msgs, options import lineinfos as astli import pathutils #, modulegraphs -import "../dist/nimony/src/lib" / [bitabs, nifstreams, lineinfos, +import nifstreams +import "../dist/nimony/src/lib" / [bitabs, lineinfos, nifindexes, nifreader] # Step 2b: the READER speaks nifcore; the WRITER keeps nifstreams (global `pool`, # PackedToken/PackedLineInfo). nifstreams does NOT export Cursor/TokenBuf/NifKind, @@ -28,12 +29,13 @@ import "../dist/nimony/src/lib" / [bitabs, nifstreams, lineinfos, # `pool(c: Cursor)` accessor would shadow nifstreams' global `pool` var the writer # uses; the reader reaches pools via `symName(c)`/`strVal(c)` etc. import "../dist/nimony/src/lib/nifcore" except pool -from "../dist/nimony/src/lib" / bif import load, BifModule +from "../dist/nimony/src/lib" / bif import load, BifModule, IndexVis, ivHidden import icmodnames import "../dist/nimony/src/models" / nifindex_tags import typekeys import icnifcore import ic / [enum2nif] +import icprof const SysModuleSuffix* = "@sys" const BackendLocalMarker* = "@bk" @@ -130,6 +132,16 @@ type revTab: Table[FileId, FileIndex] # reverse mapping for oldLineInfo man: LineInfoManager config: ConfigRef + # The READ direction's cache, which `revTab` cannot serve: `revTab` is keyed + # by a `FileId` in the WRITER's global `pool.files`, while a decoded token's + # `FileId` indexes the buffer's OWN filename pool. So the cache has to be + # keyed by (pool, FileId), and it is a `seq` because `FileId`s are small and + # dense within one pool. `readPool` holds a REFERENCE rather than a raw + # pointer on purpose: it keeps the pool alive, so a freed pool cannot be + # replaced by a new one at the same address and silently answer from the + # wrong file table. + readPool: Pool + readTab: seq[FileIndex] proc newLineInfoWriter(config: ConfigRef): LineInfoWriter = # `fileK` starts invalid so the one-entry cache never collides with a real @@ -178,12 +190,26 @@ proc oldLineInfo(w: var LineInfoWriter; info: NifLineInfo; p: Pool): TLineInfo = ## it to a `TLineInfo`. `info.file` indexes the loaded buffer's OWN filename ## pool `p` (= `cursorPool(n)`), which is the shared `icPool` for a text-parsed ## module but a fresh per-file pool for a `bif`-loaded one. + ## + ## Memoized per pool. Resolving a name costs a string copy out of the pool + ## plus a hash of a full path, and the generator asks for a node's line info + ## on essentially every statement it emits — 259k times on a 68-module build, + ## which was 1.36s of the 1.88s the cursor-driven generator spent. if info.file == NoFile: result = unknownLineInfo else: - let filePath = p.filenames[info.file] - let fileIdx = msgs.fileInfoIdx(w.config, AbsoluteFile filePath) - result = TLineInfo(line: info.line.uint16, col: info.col.int16, fileIndex: fileIdx) + if p != w.readPool: + w.readPool = p + w.readTab = @[] + let id = int(uint32(info.file)) + if id >= w.readTab.len: + let oldLen = w.readTab.len + w.readTab.setLen(id + 1) + for i in oldLen ..< w.readTab.len: w.readTab[i] = astli.InvalidFileIdx + if w.readTab[id] == astli.InvalidFileIdx: + w.readTab[id] = msgs.fileInfoIdx(w.config, AbsoluteFile p.filenames[info.file]) + result = TLineInfo(line: info.line.uint16, col: info.col.int16, + fileIndex: w.readTab[id]) # ------------- Writer --------------------------------------------------------------- @@ -205,11 +231,23 @@ will tell us the precise offsets anyway. ]# const - hiddenTypeTagName = "ht" - symDefTagName = "sd" - typeDefTagName = "td" + hiddenTypeTagName* = "ht" + symDefTagName* = "sd" + typeDefTagName* = "td" bindingIdTagName = "bid" + bridgeSymTagName* = "bsym" + ## `(bsym )` — a symbol reference in the IN-PROCESS bridge format + ## (`nodebridge.nim`), where the payload is an INDEX into the bridge's own + ## `seq[PSym]` rather than a NIF name. Never written to a file: a `.bif` has + ## to name symbols because the reader is a different process, but a bridged + ## buffer is read by the process that built it, so it can hand back the very + ## same `PSym` object. That is what makes the bridge lossless, and + ## incidentally what makes `sym` idempotent for FIELDS on a bridged buffer — + ## the file path cannot be, because `loadFieldStub` mints per use. + bridgeTypeTagName* = "btyp" + ## `(btyp )` — the same for a node's type slot. + var sdefTag = registerTag(symDefTagName) tdefTag = registerTag(typeDefTagName) @@ -239,6 +277,9 @@ type inTypeReclist: int # >0 while writing a type's OWN reclist: fields must be SELF-CONTAINED # defs (the type can be seek-loaded in isolation), not entry-deduped uses emittedCanonTypes: Table[string, int32] # canonical type name -> itemId.item of the def + extraExports: HashSet[ItemId] # symbols made importable by an explicit `export s` + # rather than by a `*` on the declaration; see + # `modulegraphs.reexportedLocalSyms` proc isLocalSym(sym: PSym): bool {.inline.} = @@ -314,22 +355,15 @@ proc toNifSymName(w: var Writer; sym: PSym): string = # during a VM transform): re-home to the current module with the `@bk` # marker so each referencing module self-contains it. See transformBody. # - # Use `itemId.item` (the writer's dedup identity, see `emittedBackendSyms`) - # as the numeric name component, NOT `disamb`: closure `:env` syms in one - # module are minted from TWO id spaces — the backend lower stage's - # `tb.idgen` and sem's `vmTransfIdgen` (transf.transformBody) — whose - # `disambTable`s each start `:env` at the same low count, so a macro-lowered - # `:env` (e.g. `implementSendProcBody`) and a backend-lowered one - # (`peerTrimmerHeartbeat`) collide on `:env.2.@bk`. Two distinct syms - # then share a NIF name; the loader's name-keyed index/`c.syms` return the - # first for both, so one proc's `:env` gets the OTHER proc's env type - # (mismatched-pointer C, "has no member colonup_" at link). `itemId.item` is - # unique per `@bk` sym (both are emitted as defs, see writeSym), mirroring - # how `@bk` TYPES already key off `itemId.item` (nifTypeName). The loader - # copies this back into `disamb` (sn.count), so `globalName` round-trips. + # The numeric name component comes from `astdef.backendMintedDisamb` — the + # ONE definition of which integer identifies a backend-minted symbol, shared + # with the two C-name manglers (`mangleProcNameExt`, `ccgutils.makeUnique`) + # so the NIF name and the C name cannot disagree. `@bk` TYPES key off + # `itemId.item` the same way (see `nifTypeName`). The loader copies this back + # into `disamb` (sn.count), so `globalName` round-trips. result = sym.name.s result.add '.' - result.addInt sym.itemId.item + result.addInt backendMintedDisamb(sym) result.add '.' result.add modname(w.currentModule, w.infos.config) result.add BackendLocalMarker @@ -401,7 +435,7 @@ proc stripFieldMarker(rawName: string): string {.inline.} = else: rawName[0 ..< rawName.len - FieldMarker.len] -proc isFieldNifName(name: string): bool {.inline.} = +proc isFieldNifName*(name: string): bool {.inline.} = ## True for an object field's local NIF name ``f.` (see ## `FieldMarker`): no module suffix, marker on the ident. let sn = parseSymName(name) @@ -1062,8 +1096,13 @@ proc writeSymDef(w: var Writer; dest: var IcBuilder; sym: PSym) = # ("undeclared field 'Number'"). let isPureEnumField = sym.kindImpl == skEnumField and sym.typImpl != nil and sym.typImpl.symImpl != nil and sfPure in sym.typImpl.symImpl.flagsImpl + # `sfExported` is the declaration's `*`. An explicit `export s` makes a symbol + # importable WITHOUT it (semExport -> reexportSym -> the interface table only), + # so ask the interface as well or those symbols ship as non-importable and the + # importer reports "undeclared identifier". if sym.kindImpl != skField and not isPureEnumField and - {sfExported, sfFromGeneric} * sym.flagsImpl == {sfExported}: + ({sfExported, sfFromGeneric} * sym.flagsImpl == {sfExported} or + sym.itemId in w.extraExports): dest.addIdent "x" else: dest.addDotToken @@ -1370,7 +1409,7 @@ var modFlagsTag = registerTag("modflags") # instead of a plain construction, and no read was ever recognised as a move. # Only wrap when there is something to say, so the common sym use stays a bare # token. -const symNodeFlagsTagName = "nflags" +const symNodeFlagsTagName* = "nflags" var symNodeFlagsTag = registerTag(symNodeFlagsTagName) const PersistedSymNodeFlags = PersistentNodeFlags - {nfLazyType, nfHasComment} @@ -2144,8 +2183,10 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode; resolvedImportDeps: seq[FileIndex] = @[]; firstUnusedId: int32 = 0; expansions: seq[(PSym, TLineInfo)] = @[]; - moduleFlags: int32 = 0) = + moduleFlags: int32 = 0; + extraExports: seq[ItemId] = @[]) = var w = Writer(infos: newLineInfoWriter(config), currentModule: thisModule) + for id in extraExports: w.extraExports.incl id w.deps = newIcBuilder(64) var content = newIcBuilder(300) @@ -2552,6 +2593,18 @@ proc cursorFromIndexEntry(c: var DecodeContext; module: FileIndex; entry: NifInd type LoadFlag* = enum LoadFullAst, AlwaysLoadInterface + SkipInterfaceTables + ## Do not eagerly build the module's interface string tables. Set by + ## `modulegraphs.loadTransitiveHooks`, which loads a module only to + ## register its hooks / macro-cache replay / generic-instance offers and + ## throws the tables away — the module is a dep-of-a-dep, not an import, so + ## none of its symbols are visible to the module being semchecked. + ## + ## The eager pass calls `loadSymFromIndexEntry` for EVERY index entry, and + ## its only other effect is pre-populating the name-keyed `c.syms` cache — + ## which `resolveSym` fills lazily on a miss anyway, straight from the same + ## index. So for these loads it is pure work: on a 219-module program a + ## one-line edit paid it 209 times over. proc isGlobalIndexSym(s, dottedSuffix: string): bool = ## Mirror of `nifbuilder.addSymbolDefRetIsGlobal` / `bif.isGlobalSymbol`: a sym @@ -2566,14 +2619,9 @@ proc isGlobalIndexSym(s, dottedSuffix: string): bool = if s[i] == '.': inc dots dots >= 2 -proc buildPosIndex(buf: var TokenBuf; suffix: string): Table[string, NifIndexEntry] = - ## Step 2a token-position index: scan the eagerly-parsed module `buf` for the - ## global `SymbolDef`s it OWNS and record each at the token position of its - ## enclosing tag (`(sd`/`(td`), with visibility from the marker that follows - ## the def. Replaces `readEmbeddedIndex` (whose byte offsets are meaningless - ## once the file is parsed); mirrors `bif.buildIndex` and the text writer's - ## `(.index …)`. Foreign symbols appear only as `Symbol` uses (never - ## `SymbolDef`s) so they are naturally excluded. +proc rescanPosIndex(buf: var TokenBuf; suffix: string): Table[string, NifIndexEntry] = + ## VERIFICATION ONLY (`-d:icIndexCheck`): the old full-token-stream rescan, + ## kept so `indexFromBif` can be graded against it over a whole real build. result = initTable[string, NifIndexEntry]() let dotted = "." & suffix if buf.len == 0: return @@ -2583,17 +2631,53 @@ proc buildPosIndex(buf: var TokenBuf; suffix: string): Table[string, NifIndexEnt case c.kind of TagLit: mostRecentTagPos = cursorToPosition(buf, c) - inc c # descend into the body (visit every token) + inc c of SymbolDef: let nm = symName(c) let tagPos = mostRecentTagPos - inc c # advance to the marker / next sibling + inc c if isGlobalIndexSym(nm, dotted): let vis = if c.hasMore and c.kind == DotToken: Hidden else: Exported result[nm] = NifIndexEntry(offset: tagPos, info: NoLineInfo, vis: vis) else: inc c +proc indexFromBif(m: BifModule): Table[string, NifIndexEntry] = + ## The module's name -> token-position index, taken from the index the `.bif` + ## ALREADY CARRIES rather than recomputed. + ## + ## `bif.store` builds that index in one forward traversal at write time + ## (`bif.buildIndex`) and writes it into the file; `bif.load` reads it back as + ## `BifModule.index`, with `pos` already a TOKEN index of the declaration's + ## enclosing tag — the very thing this used to rescan the whole token stream + ## to recompute, once per module per backend process. That rescan was 909ms of + ## a 10.1s cold `--ic:on` build (`-d:icBNodeProf`, `tPosIndex`). + ## + ## The two agree by construction, and it is worth saying exactly why, because + ## "the file has an index" would not be enough on its own: the writer filters + ## with `bif.isGlobalSymbol(name, dottedSuffix)` and every `storeBif` call site + ## passes `"." & extractModuleSuffix(path)`, which is the same `dottedSuffix` + ## the reader would have formed — so the two filters select the same symbols, + ## and the `vis` rule (a `DotToken` marker after the def means hidden) is the + ## same test on the same token. + result = initTable[string, NifIndexEntry](m.index.len) + for e in m.index: + result[poolSym(m.buf.pool, e.sym)] = + NifIndexEntry(offset: int(e.pos), info: NoLineInfo, + vis: (if e.vis == ivHidden: Hidden else: Exported)) + +proc indexFromBif(m: var BifModule; suffix: string): Table[string, NifIndexEntry] = + result = indexFromBif(m) + when defined(icIndexCheck): + let want = rescanPosIndex(m.buf, suffix) + doAssert result.len == want.len, + "index size differs for " & suffix & ": carried " & $result.len & + " rescanned " & $want.len + for k, v in want: + let got = result.getOrDefault(k) + doAssert got.offset == v.offset and got.vis == v.vis, + "index entry differs for " & k & " in " & suffix + proc readUnusedId(buf: var TokenBuf): int32 = ## Find the module's `(unusedid )` directive — emitted as the FIRST child ## of the top-level `(stmts ...)` by writeNifModule/writeLoweredModule — and @@ -2632,7 +2716,7 @@ proc moduleId(c: var DecodeContext; suffix: string; flags: set[LoadFlag] = {}): # This mirrors `toNifFilename` (kept in sync). `bif.load` mints FRESH per-file # pools, so the buffer's literals/tags resolve through its own # `cursorPool(n)`/`n.tags` (the reader is pool-agnostic); the token-position - # index is rebuilt name-based via `buildPosIndex`. + # index is taken from the one the file carries (`indexFromBif`). let conf = c.infos.config let useLowered = conf.cmd == cmdNifC and (conf.icBackendStage == "cg" or conf.icBackendStage == "emit") @@ -2644,8 +2728,12 @@ proc moduleId(c: var DecodeContext; suffix: string; flags: set[LoadFlag] = {}): raiseAssert "NIF file not found for module suffix '" & suffix & "': " & modFile & ". This can happen when loading a module from NIF that references another module " & "whose NIF file hasn't been written yet." + icProfStart(tBifLoad) var m = bif.load(modFile) - let index = buildPosIndex(m.buf, suffix) + icProfStop(tBifLoad) + icProfStart(tPosIndex) + let index = indexFromBif(m, suffix) + icProfStop(tPosIndex) # Seed the backend id counters ABOVE every id the file already uses, so a # freshly-minted backend sym/type (closure env, RTTI hook, temp) can never # share a `toId` with a loaded one. See `readUnusedId` / `(unusedid)`. @@ -2670,7 +2758,7 @@ proc ensureSemBuf(c: var DecodeContext; module: FileIndex) = let semFile = (getNimcacheDir(c.infos.config) / RelativeFile(m.suffix & ".s.bif")).string if not fileExists(semFile): return var sm = bif.load(semFile) - m.semIndex = buildPosIndex(sm.buf, m.suffix) + m.semIndex = indexFromBif(sm, m.suffix) m.semBuf = ensureMove sm.buf proc hasTypeOffset(c: var DecodeContext; module: FileIndex; nifName: string): bool = @@ -3258,6 +3346,22 @@ proc loadNode(c: var DecodeContext; n: var Cursor; thisModule: string; s = c.loadSymStub(n, thisModule, localSyms) result = newSymNode(s, info) result.typField = typ + # `(ht . )` — an EXPLICITLY nil node type — is left exactly as the + # writer meant it: NIL. The wrapper is only emitted when the node's own + # type differed from its symbol's (`writeSymNode`), so a nil here says + # the node genuinely had no type while the symbol had one, and that is + # load-bearing: a type symbol used as a VALUE (`newException(KeyError, + # ...)`) is exactly that shape, and handing it `sym.typ` makes sem read + # the typedesc as an expression of the type it denotes ("only a 'ref + # object' can be raised"). + # + # There IS a load-order dependence here — `newSymNode` above marks the + # node lazy when the symbol was still an unloaded stub, so `ast.typ` + # answers `sym.typ` for that population and `nil` for the rest — and it + # is NOT fixed by pinning the flag either way: setting it breaks sem as + # above, and clearing it would strip the fallback from the stub + # population that `nifcBackendActive` exists to serve. Left alone + # deliberately. elif tagIs(n, symDefTagName): let info = c.infos.oldLineInfo(n.info, cursorPool(n)) let name = n.firstSon @@ -3501,23 +3605,60 @@ proc populateInterfaceTablesFromIndex(c: var DecodeContext; module: FileIndex; # (moduleId can add to c.mods which would invalidate Table iterators) var indexTab = move c.mods[module].index - # Add all symbols to interf (exported interface) and interfHidden + # Only the EXPORTED half; `buildHiddenInterface` below does the rest, on + # demand. Exported symbols go into both tables, which costs little and leaves + # `interfHidden` a coherent view of a module with no hidden symbols rather + # than an empty one. + prof pIfaceModules for nifName, entry in indexTab: if entry.vis == Exported: + prof pIfaceExported let sym = loadSymFromIndexEntry(c, module, nifName, entry, thisModule) if sym != nil: strTableAdd(interf, sym) strTableAdd(interfHidden, sym) - elif not nifName.startsWith("`t"): - # do not load types, they are not part of an interface but an implementation detail! - #echo "LOADING SYM ", nifName, " ", entry.offset - let sym = loadSymFromIndexEntry(c, module, nifName, entry, thisModule) - if sym != nil: - strTableAdd(interfHidden, sym) # Move index table back c.mods[module].index = move indexTab +proc buildHiddenInterface*(c: var DecodeContext; suffix: string; + interfHidden: var TStrTable): bool {.discardable.} = + ## The hidden-only half of a loaded module's interface, materialised on + ## demand. Deferred because almost nothing reads it: `interfHidden` is reached + ## exclusively through `modulegraphs.interfSelect`, which picks it only when + ## `optImportHidden` is in the module's options, and that flag is set in + ## exactly one place — an `import x {.all.}`. Building it eagerly was 1.05s of + ## a cold Atlas build: 1.70M hidden stubs against 0.29M exported ones, made by + ## every `nim m` for every module it imports and read by none of them. + ## + ## Takes the module SUFFIX, not a FileIndex, and that is the whole trick. A + ## module has TWO FileIndexes: `registerNifSuffix` keys + ## `filenameToIndexTbl` by the suffix string and mints a `fikNifModule` entry, + ## while the graph indexes `g.ifaces` by the module's `fikSource` file. `c.mods` + ## is keyed by the former. Asking it with the latter misses every single time, + ## silently, and an `import x {.all.}` then reports "undeclared identifier" + ## for a symbol that is right there. + ## + ## Returns false when the artifact is not on disk yet — an import the build + ## has not produced. The caller must leave the request PENDING then: writing + ## it off on that first miss costs the module its hidden symbols for the rest + ## of the process. + let conf = c.infos.config + if not fileExists((getNimcacheDir(conf) / RelativeFile(suffix & ".s.bif")).string): + return false + let module = moduleId(c, suffix, {}) + if not c.mods.hasKey(module): return false + var indexTab = move c.mods[module].index + for nifName, entry in indexTab: + if entry.vis != Exported and not nifName.startsWith("`t"): + prof pIfaceHidden + # do not load types, they are not part of an interface but an implementation detail! + let sym = loadSymFromIndexEntry(c, module, nifName, entry, suffix) + if sym != nil: + strTableAdd(interfHidden, sym) + c.mods[module].index = move indexTab + result = true + proc moduleSymbolStubs*(c: var DecodeContext; module: FileIndex): seq[PSym] = ## Stubs for every non-type symbol serialized in `module`'s NIF index. The ## per-module backend uses this to emit the routines a module OWNS: procs are @@ -3772,12 +3913,71 @@ proc nifModuleHasIncludes*(config: ConfigRef; fileIdx: FileIndex): bool = done = true skip c -proc addReexportedEnumFields(c: var DecodeContext; sym: PSym; interf: var TStrTable) = +proc peekSymKind(c: var DecodeContext; module: FileIndex; + entry: NifIndexEntry): TSymKind = + ## The kind a symbol's `(sd …)` header records, WITHOUT decoding the symbol. + ## + ## The layout is `(sd …)`, which is + ## exactly what `loadSymFromCursor` walks — that proc is the definition this + ## mirrors, so the two must be changed together. Anything unexpected answers + ## `skUnknown` and the caller falls back to a real load rather than guessing. + var n = cursorFromIndexEntry(c, module, entry) + if n.kind != TagLit or not tagIs(n, symDefTagName): return skUnknown + var k = childCursor(n) + if not k.hasMore or k.kind != SymbolDef: return skUnknown + skip k # the name + if not k.hasMore: return skUnknown + skip k # the `x` / `.` export marker + if not k.hasMore or k.kind != TagLit: return skUnknown + result = parse(TSymKind, cursorTag(k)) + +proc symKindFast(c: var DecodeContext; sym: PSym; symAsStr: string): TSymKind = + ## `sym`'s kind, taken from its def header while it is still `Partial` rather + ## than by forcing the full decode. An already-loaded symbol answers from the + ## field, and anything the peek cannot read falls back to loading. + ## + ## `-d:icPeekKindCheck` grades the peek against the load it replaces, on every + ## call: the loaded kind is authoritative, so a disagreement is the peek's bug. + ## The oracle has to be run for the answer to mean anything — and broken on + ## purpose once, to confirm it fires. + if sym.state != Partial: + prof pPeekLoaded + return sym.kindImpl + let e = c.syms.getOrDefault(symAsStr) + if e[1].offset == 0: + prof pPeekFallback + loadSym(c, sym) + return sym.kindImpl + result = peekSymKind(c, sym.itemId.module.FileIndex, e[1]) + if result == skUnknown: + # The peek could not read the header. Correct, but it is also how a walk + # that has drifted out of step with `loadSymFromCursor` would present, so + # the rate is counted rather than shrugged at: `-d:icBNodeProf` reports + # `PeekFallback` beside `PeekKind`, and it should stay at zero. + prof pPeekFallback + loadSym(c, sym) + return sym.kindImpl + prof pPeekKind + when defined(icPeekKindCheck): + let peeked = result + loadSym(c, sym) + doAssert peeked == sym.kindImpl, + "peekSymKind disagrees for " & symAsStr & ": peeked " & $peeked & + " but the load says " & $sym.kindImpl + +proc addReexportedEnumFields(c: var DecodeContext; sym: PSym; symAsStr: string; + interf: var TStrTable) = ## When a non-pure enum type is (re-)exported, its fields must also become ## visible (unqualified) to importers. In a from-source build this happens via ## `rawImportSymbol`'s enum handling when the type is imported; the lazy IC ## importer never runs that, so we materialise the fields into the interface ## here, when the export list is processed. + ## + ## Only a TYPE can contribute fields, and almost none of an export list is + ## types — so the kind is read off the def header first (`symKindFast`) rather + ## than by forcing every exported symbol through a full decode to find out. + ## That decode was 290ms of an 8.6s build over 34815 symbols. + if symKindFast(c, sym, symAsStr) != skType: return loadSym(c, sym) if sym.kindImpl != skType or sfPure in sym.flagsImpl: return let et = sym.typImpl @@ -3791,6 +3991,79 @@ proc addReexportedEnumFields(c: var DecodeContext; sym: PSym; interf: var TStrTa if f != nil and f.kind == nkSym and f.sym != nil: strTableAdd(interf, f.sym) +type + TopTag = enum + ## Which top-level directive a tag names. `processTopLevel` used to decide + ## this with an `elif` chain of ~20 `tagIs` calls, i.e. up to twenty tag-NAME + ## string comparisons per node, and the common cases (a real statement, or + ## `implementation`) sit at the END of the chain so the average node walked + ## all of it — 1.46M nodes on a 68-module build. Resolved once per tag id + ## instead, and the chain becomes a `case`. + ttOther, ttReplay, ttUnusedId, ttModFlags, + ttRepConverter, ttRepDestroy, ttRepWasMoved, ttRepCopy, ttRepSink, ttRepDup, + ttRepTrace, ttRepDeepCopy, ttRepEnumToStr, ttRepMethod, ttRepPureEnum, + ttRepCppMember, ttExport, ttInclude, ttImport, ttReexpMod, ttOffer, ttTOffer, + ttModuleSrc, ttExpansion, ttSig, ttImplementation, + ttLetSection, ttVarSection, ttPragma + +const + letSectionTag = toNifTag(nkLetSection) + varSectionTag = toNifTag(nkVarSection) + pragmaTag = toNifTag(nkPragma) + +proc classifyTopTag(name: string): TopTag = + case name + of "replay": ttReplay + of "unusedid": ttUnusedId + of "modflags": ttModFlags + of "repconverter": ttRepConverter + of "repdestroy": ttRepDestroy + of "repwasmoved": ttRepWasMoved + of "repcopy": ttRepCopy + of "repsink": ttRepSink + of "repdup": ttRepDup + of "reptrace": ttRepTrace + of "repdeepcopy": ttRepDeepCopy + of "repenumtostr": ttRepEnumToStr + of "repmethod": ttRepMethod + of "reppureenum": ttRepPureEnum + of "repcppmember": ttRepCppMember + of "export": ttExport + of "include": ttInclude + of "import": ttImport + of "reexpmod": ttReexpMod + of "offer": ttOffer + of "toffer": ttTOffer + of "modulesrc": ttModuleSrc + of "expansion": ttExpansion + of "sig": ttSig + of "implementation": ttImplementation + else: + if name == letSectionTag: ttLetSection + elif name == varSectionTag: ttVarSection + elif name == pragmaTag: ttPragma + else: ttOther + +var topTagPool: TagPool = nil +var topTagCache: seq[int8] = @[] + ## `TagId -> TopTag`, -1 unresolved, for ONE tag pool. `topTagPool` holds the + ## pool by REFERENCE so it stays alive and a freed pool cannot be replaced at + ## the same address — the same argument `indexFromBif`'s memo rests on. + +proc topTagAt(cur: Cursor): TopTag = + let pool {.cursor.} = cur.tags + if pool != topTagPool: + topTagPool = pool + topTagCache = @[] + let id = int(uint32(cursorTagId(cur))) + if id >= topTagCache.len: + let oldLen = topTagCache.len + topTagCache.setLen(id + 1) + for i in oldLen ..< topTagCache.len: topTagCache[i] = -1'i8 + if topTagCache[id] < 0: + topTagCache[id] = int8(ord(classifyTopTag(pool.tagName(cursorTagId(cur))))) + result = TopTag(topTagCache[id]) + proc processTopLevel(c: var DecodeContext; cur: var Cursor; flags: set[LoadFlag]; interf: var TStrTable; suffix: string; module: int): PrecompiledModule = ## Step 2 phase 2: walk the module body directly over the resident `buf` cursor @@ -3808,59 +4081,101 @@ proc processTopLevel(c: var DecodeContext; cur: var Cursor; flags: set[LoadFlag] # `topLevel`. They sit in the module header before `(implementation)`. var cont = true while cont and cur.hasMore: + prof pTopNodes if cur.kind != TagLit: cont = false else: - if tagIs(cur, "replay"): + case topTagAt(cur) + of ttReplay: # Always load replay actions (macro cache operations) + icProfStart(tTopReplay) cur.into: while cur.hasMore: let replayNode = loadNode(c, cur, suffix, localSyms) if replayNode != nil: result.topLevel.sons.add replayNode - elif tagIs(cur, "unusedid"): + icProfStop(tTopReplay) + of ttUnusedId: # backend id seed — consumed eagerly by `moduleId`/`readUnusedId`; just # skip past it here so the rest of the header still loads. skip cur - elif tagIs(cur, "modflags"): + of ttModFlags: cur.into: if cur.hasMore and cur.kind == IntLit: result.moduleFlags = int32 intVal(cur) skip cur while cur.hasMore: skip cur - elif tagIs(cur, "repconverter"): loadLogOp(c, result.logOps, cur, ConverterEntry, attachedTrace, module) - elif tagIs(cur, "repdestroy"): loadLogOp(c, result.logOps, cur, HookEntry, attachedDestructor, module) - elif tagIs(cur, "repwasmoved"): loadLogOp(c, result.logOps, cur, HookEntry, attachedWasMoved, module) - elif tagIs(cur, "repcopy"): loadLogOp(c, result.logOps, cur, HookEntry, attachedAsgn, module) - elif tagIs(cur, "repsink"): loadLogOp(c, result.logOps, cur, HookEntry, attachedSink, module) - elif tagIs(cur, "repdup"): loadLogOp(c, result.logOps, cur, HookEntry, attachedDup, module) - elif tagIs(cur, "reptrace"): loadLogOp(c, result.logOps, cur, HookEntry, attachedTrace, module) - elif tagIs(cur, "repdeepcopy"): loadLogOp(c, result.logOps, cur, HookEntry, attachedDeepCopy, module) - elif tagIs(cur, "repenumtostr"): loadLogOp(c, result.logOps, cur, EnumToStrEntry, attachedTrace, module) - elif tagIs(cur, "repmethod"): loadLogOp(c, result.logOps, cur, MethodEntry, attachedTrace, module) - elif tagIs(cur, "reppureenum"): loadLogOp(c, result.logOps, cur, PureEnumEntry, attachedTrace, module) - elif tagIs(cur, "repcppmember"): loadLogOp(c, result.logOps, cur, CppMemberEntry, attachedTrace, module) - elif tagIs(cur, "export"): + of ttRepConverter: + timed tTopLogOps: + loadLogOp(c, result.logOps, cur, ConverterEntry, attachedTrace, module) + of ttRepDestroy: + timed tTopLogOps: + loadLogOp(c, result.logOps, cur, HookEntry, attachedDestructor, module) + of ttRepWasMoved: + timed tTopLogOps: + loadLogOp(c, result.logOps, cur, HookEntry, attachedWasMoved, module) + of ttRepCopy: + timed tTopLogOps: + loadLogOp(c, result.logOps, cur, HookEntry, attachedAsgn, module) + of ttRepSink: + timed tTopLogOps: + loadLogOp(c, result.logOps, cur, HookEntry, attachedSink, module) + of ttRepDup: + timed tTopLogOps: + loadLogOp(c, result.logOps, cur, HookEntry, attachedDup, module) + of ttRepTrace: + timed tTopLogOps: + loadLogOp(c, result.logOps, cur, HookEntry, attachedTrace, module) + of ttRepDeepCopy: + timed tTopLogOps: + loadLogOp(c, result.logOps, cur, HookEntry, attachedDeepCopy, module) + of ttRepEnumToStr: + timed tTopLogOps: + loadLogOp(c, result.logOps, cur, EnumToStrEntry, attachedTrace, module) + of ttRepMethod: + timed tTopLogOps: + loadLogOp(c, result.logOps, cur, MethodEntry, attachedTrace, module) + of ttRepPureEnum: + timed tTopLogOps: + loadLogOp(c, result.logOps, cur, PureEnumEntry, attachedTrace, module) + of ttRepCppMember: + timed tTopLogOps: + loadLogOp(c, result.logOps, cur, CppMemberEntry, attachedTrace, module) + of ttExport: + if SkipInterfaceTables in flags: + # Same reason the interface tables are skipped: `interf` is a scratch + # table this caller throws away, so every `resolveSym` here (one per + # exported symbol, plus `addReexportedEnumFields`) only warms the + # name-keyed `c.syms` cache that `resolveSym` refills lazily on a miss. + skip cur + continue + icProfStart(tExportBranch) cur.into: while cur.hasMore and cur.kind == DotToken: skip cur # flags / type while cur.hasMore: if cur.kind == Symbol: + prof pExportSyms let symAsStr = symName(cur) # Skip symbols re-exported by this dependency but owned by the module # being compiled fresh (they would collide with the fresh originals). if c.mainModuleSuffix.len == 0 or parseSymName(symAsStr).module != c.mainModuleSuffix: + icProfStart(tResolveSym) let sym = resolveSym(c, symAsStr, false) + icProfStop(tResolveSym) if sym != nil: strTableAdd(interf, sym) - addReexportedEnumFields(c, sym, interf) + icProfStart(tEnumFields) + addReexportedEnumFields(c, sym, symAsStr, interf) + icProfStop(tEnumFields) skip cur else: raiseAssert "expected Symbol or ParRi but got " & $cur.kind & " in export list of module " & suffix - elif tagIs(cur, "include"): loadInclude(c, cur, result.includes) - elif tagIs(cur, "import"): loadImport(c, cur, result.deps) - elif tagIs(cur, "reexpmod"): + icProfStop(tExportBranch) + of ttInclude: loadInclude(c, cur, result.includes) + of ttImport: loadImport(c, cur, result.deps) + of ttReexpMod: # a re-exported MODULE: (reexpmod "name" "suffix"); the module sym is a # qualifier in this module's interface — materialized by modulegraphs. var mname, msuffix = "" @@ -3869,7 +4184,7 @@ proc processTopLevel(c: var DecodeContext; cur: var Cursor; flags: set[LoadFlag] if cur.hasMore and cur.kind == StrLit: (msuffix = strVal(cur); skip cur) if mname.len > 0 and msuffix.len > 0: result.reexportedModules.add (mname, msuffix) - elif tagIs(cur, "offer"): + of ttOffer: # (offer ...) — resolve # to PSyms/PTypes; modulegraphs registers them into `procInstCache`. # Best-effort: a type that fails to resolve drops the whole offer. @@ -3878,6 +4193,7 @@ proc processTopLevel(c: var DecodeContext; cur: var Cursor; flags: set[LoadFlag] var cts: seq[PType] = @[] var idx = 0 var ok = true + icProfStart(tTopOffers) cur.into: while cur.hasMore: if cur.kind == Symbol: @@ -3895,12 +4211,14 @@ proc processTopLevel(c: var DecodeContext; cur: var Cursor; flags: set[LoadFlag] else: skip cur if ok and genSym != nil and instSym != nil: result.genericOffers.add (genSym, instSym, cts, paramsCount) - elif tagIs(cur, "toffer"): + icProfStop(tTopOffers) + of ttTOffer: # (toffer "" "") — intern the two full names, # resolve, FULLY load the instance (so `searchInstTypes` can match its # params). Best-effort: a failure to resolve drops the offer. var genName, instName = "" var idx = 0 + icProfStart(tTopOffers) cur.into: while cur.hasMore: if cur.kind == StrLit: @@ -3915,33 +4233,43 @@ proc processTopLevel(c: var DecodeContext; cur: var Cursor; flags: set[LoadFlag] if genSym != nil and inst != nil: loadType(c, inst) result.typeOffers.add (genSym, inst) - elif tagIs(cur, "modulesrc"): + icProfStop(tTopOffers) + of ttModuleSrc: + prof pTopToolingSkip # self-identification record for the standalone include-graph scanner; # not needed by the loader, just skip past it. skip cur - elif tagIs(cur, "expansion"): + of ttExpansion: + prof pTopToolingSkip # template/macro expansion usage record for tooling (`idetools` scans it # as a `Symbol` use); the loader itself needs nothing from it. skip cur - elif tagIs(cur, "sig"): + of ttSig: + prof pTopToolingSkip # signature-symbol occurrence record for tooling (`idetools` scans it as a # `Symbol` use); the loader itself needs nothing from it. skip cur - elif tagIs(cur, "implementation"): + of ttImplementation: cont = false - elif LoadFullAst in flags or tagIs(cur, toNifTag(nkLetSection)) or - tagIs(cur, toNifTag(nkVarSection)) or tagIs(cur, toNifTag(nkPragma)): + of ttLetSection, ttVarSection, ttPragma: # Parse the full statement. let/var sections are loaded unconditionally # (see above) so `{.compileTime.}` globals reach the eager initializer. # Top-level pragmas are loaded too: a module-level `{.emit.}` (and the # `{.push/pop.}` around it) must reach the `cg` stage's genPragma/genEmit, # else e.g. a `#include` is dropped and the generated C won't compile. # writeToplevelNode routes these into this header section. + icProfStart(tTopStmts) let stmtNode = loadNode(c, cur, suffix, localSyms) if stmtNode != nil: result.topLevel.sons.add stmtNode - else: - cont = false + icProfStop(tTopStmts) + of ttOther: + if LoadFullAst in flags: + let stmtNode = loadNode(c, cur, suffix, localSyms) + if stmtNode != nil: + result.topLevel.sons.add stmtNode + else: + cont = false proc registerModuleSelfSym*(c: var DecodeContext; suffix: string; m: PSym) = ## Bind the module's NIF name to the ONE module symbol the graph registered. @@ -3963,7 +4291,9 @@ proc registerModuleSelfSym*(c: var DecodeContext; suffix: string; m: PSym) = proc loadNifModule*(c: var DecodeContext; suffix: ModuleSuffix; interf, interfHidden: var TStrTable; flags: set[LoadFlag] = {}): PrecompiledModule = # Ensure module index is loaded - moduleId returns the FileIndex for this suffix + icProfStart(tModuleId) let module = moduleId(c, string(suffix), flags) + icProfStop(tModuleId) # Load the module AST (or just replay actions if loadFullAst is false). # processTopLevel also collects export instructions. Step 2 phase 2: read the @@ -3973,14 +4303,19 @@ proc loadNifModule*(c: var DecodeContext; suffix: ModuleSuffix; interf, interfHi if cur.kind == TagLit and tagIs(cur, toNifTag(nkStmtList)): inc cur # enter (stmts (past the tag head, onto the flags dot) skip cur # flags dot (processTopLevel skips the type dot itself) + icProfStart(tTopLevel) result = processTopLevel(c, cur, flags, interf, string(suffix), module.int) + icProfStop(tTopLevel) else: result = PrecompiledModule(topLevel: newNode(nkStmtList)) # Populate interface tables from the NIF index structure # Symbols are created as stubs (Partial state) and will be loaded lazily via loadSym # Use exports collected by processTopLevel - populateInterfaceTablesFromIndex(c, module, interf, interfHidden, string(suffix)) + if SkipInterfaceTables notin flags: + icProfStart(tInterfTables) + populateInterfaceTablesFromIndex(c, module, interf, interfHidden, string(suffix)) + icProfStop(tInterfTables) proc loadNifModule*(c: var DecodeContext; f: FileIndex; interf, interfHidden: var TStrTable; flags: set[LoadFlag] = {}): PrecompiledModule = diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index ba4396a335..22c930d6f9 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -741,6 +741,6 @@ proc listSymbolNames*(symbols: openArray[PSym]): string = result.add sym.name.s proc isDiscriminantField*(n: PNode): bool = - if n.kind == nkCheckedFieldExpr: sfDiscriminant in n[0][1].sym.flags - elif n.kind == nkDotExpr: sfDiscriminant in n[1].sym.flags + if n.kind == nkCheckedFieldExpr: sfDiscriminant in n.firstSon.secondSon.sym.flags + elif n.kind == nkDotExpr: sfDiscriminant in n.secondSon.sym.flags else: false diff --git a/compiler/astdef.nim b/compiler/astdef.nim index 42ccf4c7b0..341e98a2f1 100644 --- a/compiler/astdef.nim +++ b/compiler/astdef.nim @@ -957,13 +957,54 @@ iterator items*(n: PNode): PNode = iterator sons*(n: PNode): PNode = ## Iterates over the children of `n`. Preferred over `for i in 0.. 0 when defined(useNodeIds): const nodeIdToDebug* = -1 # 2322968 @@ -1046,6 +1087,52 @@ proc newStrNode*(strVal: string; info: TLineInfo): PNode = # handling for IC, they end up in IC indexes etc. Thus we "log" them in the module graph # and to pass them around to the NIF writer. This is not very elegant but it works. +const + InstanceDisambBit* = 0x4000_0000'i32 + ## Set in the `disamb` of routine instances whose value is content-derived + ## (see `modulegraphs.setInstanceDisamb`); keeps them disjoint from the + ## small counter range ordinary symbols draw from, so the NIF name + ## `name.disamb.module` stays collision-free within a module. + HookDisambBit* = 0x2000_0000'i32 + ## Set in the `disamb` of synthesized type-bound operators and `$enum` + ## procs whose value is content-derived (see `modulegraphs.setHookDisamb`); + ## disjoint from both the small counter range and `InstanceDisambBit`. + ## + ## Both live here rather than in `modulegraphs` because `ast2nif` — which + ## cannot import that module — names symbols by them. + +proc backendMintedDisamb*(s: PSym): int32 {.inline.} = + ## The integer that identifies a BACKEND-MINTED symbol (`isBackendMinted`) in + ## every name derived from it: its NIF name (`ast2nif.toNifSymName`) and its C + ## name (`mangleutils.mangleProcNameExt`, `ccgutils.makeUnique`). + ## + ## Two cases, and the whole point of having ONE function is that all three + ## sites take the same one: + ## + ## * A lifted HOOK's `disamb` is CONTENT-derived (`modulegraphs.setHookDisamb`), + ## so it is identical in every process. Such a hook really does cross process + ## boundaries — `lower` mints the env hooks of nested routines while `cg` + ## mints those of the module's top level, and both land in the same + ## translation unit — and its C name is also baked into emit-everywhere RTTI + ## tables. `itemId.item` would differ per process, so two unrelated hooks + ## collided on one `_c` and the merge stage kept a single body for both + ## (C accepted the mistyped call, C++ rejected it). + ## * Otherwise `itemId.item` — the writer's dedup identity, unique per `@bk` + ## sym. `disamb` cannot serve here: a module's `:env` syms are minted from TWO + ## id spaces (the backend `lower` stage's idgen and sem's `vmTransfIdgen`) + ## whose `disambTable`s each start `:env` at the same low count, so a + ## macro-lowered and a backend-lowered `:env` collide on `:env.2.@bk`. + ## + ## The loader copies the name's numeric component back into `disamb`, so after a + ## round trip `disamb` equals this value and `ast2nif.globalName` — which always + ## reads `disamb` — agrees with the name the writer produced. + ## + ## This rule used to be written out at each of the three sites. They drifted: + ## `toNifSymName` lacked the hook exception, so a content-derived value was + ## overwritten by the loader and two backend hooks merged into one C function. + if (s.disamb and HookDisambBit) != 0'i32: s.disamb + else: s.itemId.item + type LogEntryKind* = enum HookEntry, ConverterEntry, MethodEntry, EnumToStrEntry, GenericInstEntry, diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 77c09d82cf..53ae1fee26 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -11,6 +11,12 @@ proc canRaiseDisp(p: BProc; n: PNode): bool = # we assume things like sysFatal cannot raise themselves + # 5 = "decided here, neither predicate ran". Without resetting, the marker + # keeps whatever the PREVIOUS call left in it and the early return below + # attributes this answer to a branch that did not execute — which is how the + # first run of this differential came to claim effect-list coverage it did + # not have. Both short-circuits below leave it at 5. + markCanRaiseBranch 5 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. @@ -25,6 +31,13 @@ proc canRaiseDisp(p: BProc; n: PNode): bool = else: # we have to be *very* conservative: result = canRaiseConservative(n) + when defined(icCanRaiseLog): + # `canRaise` reads the raises spec off `fn.typ.n`, and under `--ic:on` that + # node came back from a `.bif`. The only oracle for whether it came back + # INTACT is the same program built without IC. Log the verdict per callee; + # the two builds must produce the same one. + if n.kind == nkSym: + logCanRaise(n.sym, result) proc preventNrvo(p: BProc; dest, le, ri: PNode): bool = proc locationEscapes(p: BProc; le: PNode; inTryStmt: bool): bool = @@ -46,15 +59,14 @@ proc preventNrvo(p: BProc; dest, le, ri: PNode): bool = nkCheckedFieldExpr: n = n.firstSon of nkHiddenStdConv, nkHiddenSubConv, nkConv: - n = n[1] + n = n.secondSon else: # cannot analyse the location; assume the worst return true result = false if le != nil: - for i in 1.. 0: + while q.kind == nkStmtListExpr and q.hasSons: skipped = true q = q.lastSon if getMagic(q) == mSlice: # magic: pass slice to openArray: if skipped: q = skipConv(n) - while q.kind == nkStmtListExpr and q.len > 0: - for i in 0..= start: result.add(substr(pat, start, i - 1)) -proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) = +proc genInfixCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) = var op = initLocExpr(p, ri.firstSon) # getUniqueType() is too expensive here: var typ = skipTypes(ri.firstSon.typ, abstractInst) @@ -815,7 +833,7 @@ proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) = pl.add(op.snippet) var res = newBuilder("") var call = initCallBuilder(res, extract(pl)) - for i in 2.. 1: pl.add(": ") - genArg(p, ri[1], typ.n[1].sym, ri, pl) + genArg(p, ri.secondSon, typ.n.secondSon.sym, ri, pl) start = 2 else: if ri.len > 1: - genArg(p, ri[1], typ.n[1].sym, ri, pl) + genArg(p, ri.secondSon, typ.n.secondSon.sym, ri, pl) pl.add(" ") pl.add(op.snippet) if ri.len > 2: pl.add(": ") - genArg(p, ri[2], typ.n[2].sym, ri, pl) - for i in start..= typ.n.len: internalError(p.config, ri.info, "varargs for objective C method?") - assert(typ.n[i].kind == nkSym) - var param = typ.n[i].sym + assert(son(typ.n, i).kind == nkSym) + var param = son(typ.n, i).sym pl.add(" ") pl.add(param.name.s) pl.add(": ") - genArg(p, ri[i], param, ri, pl) + genArg(p, it, param, ri, pl) if typ.returnType != nil: if isInvalidReturnType(p.config, typ): if ri.len > 1: pl.add(" ") @@ -907,10 +925,10 @@ proc isInactiveDestructorCall(p: BProc, e: PNode): bool = We want to return early but the 'finally' section is traversed before the 'let args = ...' statement. We exploit this to generate better code for 'return'. ]# - result = e.len == 2 and e.firstSon.kind == nkSym and - e.firstSon.sym.name.s == "=destroy" and notYetAlive(e[1].skipAddr) + result = e.safeLen == 2 and e.firstSon.kind == nkSym and + e.firstSon.sym.name.s == "=destroy" and notYetAlive(e.secondSon.skipAddr) -proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) = +proc genAsgnCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) = if p.withinBlockLeaveActions > 0 and isInactiveDestructorCall(p, ri): return when defined(icDbgHash): diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 3af6e3451e..c38ea3aa60 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -153,7 +153,7 @@ proc canMove(p: BProc, n: PNode; dest: TLoc): bool = if n.kind == nkBracket: # This needs to be kept consistent with 'const' seq code # generation! - if not isDeepConstExpr(n) or n.len == 0: + if not isDeepConstExpr(n) or not n.hasSons: if skipTypes(n.typ, abstractVarRange).kind == tySequence: return true elif n.kind in nkStrKinds and n.strVal.len == 0: @@ -412,7 +412,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = elif not isObjLackingTypeField(ty): genGenericAsgn(p, dest, src, flags) elif containsGarbageCollectedRef(ty): - if ty[0].isNil and asgnComplexity(ty.n) <= 4 and + if ty.baseClass.isNil and asgnComplexity(ty.n) <= 4 and needAssignCall notin flags: # calls might contain side effects discard getTypeDesc(p.module, ty) internalAssert p.config, ty.n != nil @@ -569,49 +569,49 @@ proc putIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope; s=OnUnknown) = proc binaryStmt(p: BProc, e: PNode, d: var TLoc, op: TypedBinaryOp) = if d.k != locNone: internalError(p.config, e.info, "binaryStmt") - var a = initLocExpr(p, e[1]) - var b = initLocExpr(p, e[2]) + var a = initLocExpr(p, e.secondSon) + var b = initLocExpr(p, son(e, 2)) let ra = rdLoc(a) let rb = rdLoc(b) - p.s(cpsStmts).addInPlaceOp(op, getSimpleTypeDesc(p.module, e[1].typ), ra, rb) + p.s(cpsStmts).addInPlaceOp(op, getSimpleTypeDesc(p.module, e.secondSon.typ), ra, rb) proc binaryStmtAddr(p: BProc, e: PNode, d: var TLoc, cpname: string) = if d.k != locNone: internalError(p.config, e.info, "binaryStmtAddr") - var a = initLocExpr(p, e[1]) - var b = initLocExpr(p, e[2]) + var a = initLocExpr(p, e.secondSon) + var b = initLocExpr(p, son(e, 2)) let bra = byRefLoc(p, a) let rb = rdLoc(b) p.s(cpsStmts).addCallStmt(cgsymValue(p.module, cpname), bra, rb) template binaryExpr(p: BProc, e: PNode, d: var TLoc, frmt: untyped) = - assert(e[1].typ != nil) - assert(e[2].typ != nil) + assert(e.secondSon.typ != nil) + assert(son(e, 2).typ != nil) block: - var a = initLocExpr(p, e[1]) - var b = initLocExpr(p, e[2]) + var a = initLocExpr(p, e.secondSon) + var b = initLocExpr(p, son(e, 2)) let ra {.inject.} = rdLoc(a) let rb {.inject.} = rdLoc(b) putIntoDest(p, d, e, frmt) template binaryExprChar(p: BProc, e: PNode, d: var TLoc, frmt: untyped) = - assert(e[1].typ != nil) - assert(e[2].typ != nil) + assert(e.secondSon.typ != nil) + assert(son(e, 2).typ != nil) block: - var a = initLocExpr(p, e[1]) - var b = initLocExpr(p, e[2]) + var a = initLocExpr(p, e.secondSon) + var b = initLocExpr(p, son(e, 2)) let ra {.inject.} = rdCharLoc(a) let rb {.inject.} = rdCharLoc(b) putIntoDest(p, d, e, frmt) template unaryExpr(p: BProc, e: PNode, d: var TLoc, frmt: untyped) = block: - var a: TLoc = initLocExpr(p, e[1]) + var a: TLoc = initLocExpr(p, e.secondSon) let ra {.inject.} = rdLoc(a) putIntoDest(p, d, e, frmt) template unaryExprChar(p: BProc, e: PNode, d: var TLoc, frmt: untyped) = block: - var a: TLoc = initLocExpr(p, e[1]) + var a: TLoc = initLocExpr(p, e.secondSon) let ra {.inject.} = rdCharLoc(a) putIntoDest(p, d, e, frmt) @@ -659,10 +659,10 @@ proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = "nimAddInt64", "nimSubInt64" ] opr: array[mAddI..mPred, TypedBinaryOp] = [Add, Sub, Mul, Div, Mod, Add, Sub] - assert(e[1].typ != nil) - assert(e[2].typ != nil) - var a = initLocExpr(p, e[1]) - var b = initLocExpr(p, e[2]) + assert(e.secondSon.typ != nil) + assert(son(e, 2).typ != nil) + var a = initLocExpr(p, e.secondSon) + var b = initLocExpr(p, son(e, 2)) # skipping 'range' is correct here as we'll generate a proper range check # later via 'chckRange' let t = e.typ.skipTypes(abstractRange) @@ -676,10 +676,10 @@ proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = var needsOverflowCheck = true if m in {mDivI, mModI}: var canBeZero = true - if e[2].kind in {nkIntLit..nkUInt64Lit}: - canBeZero = e[2].intVal == 0 - if e[2].kind in {nkIntLit..nkInt64Lit}: - needsOverflowCheck = e[2].intVal == -1 + if son(e, 2).kind in {nkIntLit..nkUInt64Lit}: + canBeZero = son(e, 2).intVal == 0 + if son(e, 2).kind in {nkIntLit..nkInt64Lit}: + needsOverflowCheck = son(e, 2).intVal == -1 if canBeZero: # remove extra paren from `==` op here to avoid Wparentheses-equality: p.s(cpsStmts).addSingleIfStmt(removeSinglePar(cOp(Equal, rdLoc(b), cIntValue(0)))): @@ -696,8 +696,8 @@ proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = var t: PType - assert(e[1].typ != nil) - var a: TLoc = initLocExpr(p, e[1]) + assert(e.secondSon.typ != nil) + var a: TLoc = initLocExpr(p, e.secondSon) t = skipTypes(e.typ, abstractRange) let ra = rdLoc(a) if optOverflowCheck in p.options: @@ -724,10 +724,10 @@ proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = var s, k: BiggestInt = 0 - assert(e[1].typ != nil) - assert(e[2].typ != nil) - var a = initLocExpr(p, e[1]) - var b = initLocExpr(p, e[2]) + assert(e.secondSon.typ != nil) + assert(son(e, 2).typ != nil) + var a = initLocExpr(p, e.secondSon) + var b = initLocExpr(p, son(e, 2)) # BUGFIX: cannot use result-type here, as it may be a boolean s = max(getSize(p.config, a.t), getSize(p.config, b.t)) * 8 k = getSize(p.config, a.t) * 8 @@ -847,10 +847,10 @@ proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = putIntoDest(p, d, e, res) proc genEqProc(p: BProc, e: PNode, d: var TLoc) = - assert(e[1].typ != nil) - assert(e[2].typ != nil) - var a = initLocExpr(p, e[1]) - var b = initLocExpr(p, e[2]) + assert(e.secondSon.typ != nil) + assert(son(e, 2).typ != nil) + var a = initLocExpr(p, e.secondSon) + var b = initLocExpr(p, son(e, 2)) let ra = rdLoc(a) let rb = rdLoc(b) if a.t.skipTypes(abstractInstOwned).callConv == ccClosure: @@ -861,8 +861,8 @@ proc genEqProc(p: BProc, e: PNode, d: var TLoc) = putIntoDest(p, d, e, cOp(Equal, ra, rb)) proc genIsNil(p: BProc, e: PNode, d: var TLoc) = - let t = skipTypes(e[1].typ, abstractRange) - var a: TLoc = initLocExpr(p, e[1]) + let t = skipTypes(e.secondSon.typ, abstractRange) + var a: TLoc = initLocExpr(p, e.secondSon) let ra = rdLoc(a) var res = "" if t.kind == tyProc and t.callConv == ccClosure: @@ -874,8 +874,8 @@ proc genIsNil(p: BProc, e: PNode, d: var TLoc) = proc unaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = var t: PType - assert(e[1].typ != nil) - var a = initLocExpr(p, e[1]) + assert(e.secondSon.typ != nil) + var a = initLocExpr(p, e.secondSon) t = skipTypes(e.typ, abstractRange) var res = "" @@ -975,7 +975,7 @@ proc cow(p: BProc; n: PNode) {.inline.} = template ignoreConv(e: PNode): bool = let destType = e.typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) - let srcType = e[1].typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) + let srcType = e.secondSon.typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) sameBackendTypePickyAliases(destType, srcType) proc genAddr(p: BProc, e: PNode, d: var TLoc) = @@ -995,7 +995,7 @@ proc genAddr(p: BProc, e: PNode, d: var TLoc) = if e.firstSon.kind in {nkHiddenStdConv, nkHiddenSubConv, nkConv} and not ignoreConv(e.firstSon): # addr (conv x) introduces a temp because `conv x` is not a rvalue # transform addr ( conv ( x ) ) -> conv ( addr ( x ) ) - var exprLoc: TLoc = initLocExpr(p, e.firstSon[1]) + var exprLoc: TLoc = initLocExpr(p, e.firstSon.secondSon) var tmp = getTemp(p, e.typ, needsInit=false) putIntoDest(p, tmp, e, cCast(getTypeDesc(p.module, e.typ), addrLoc(p.config, exprLoc))) putIntoDest(p, d, e, rdLoc(tmp)) @@ -1007,7 +1007,7 @@ template inheritLocation(d: var TLoc, a: TLoc) = proc genRecordFieldAux(p: BProc, e: PNode, d: var TLoc, a: var TLoc) = a = initLocExpr(p, e.firstSon) - if e[1].kind != nkSym: internalError(p.config, e.info, "genRecordFieldAux") + if e.secondSon.kind != nkSym: internalError(p.config, e.info, "genRecordFieldAux") d.inheritLocation(a) discard getTypeDesc(p.module, a.t) # fill the record's fields.loc @@ -1020,8 +1020,8 @@ proc genTupleElem(p: BProc, e: PNode, d: var TLoc) = d.inheritLocation(a) discard getTypeDesc(p.module, a.t) # fill the record's fields.loc var r = rdLoc(a) - case e[1].kind - of nkIntLit..nkUInt64Lit: i = int(e[1].intVal) + case e.secondSon.kind + of nkIntLit..nkUInt64Lit: i = int(e.secondSon.intVal) else: internalError(p.config, e.info, "genTupleElem") r = dotField(r, "Field" & $i) putIntoDest(p, d, e, r, a.storage) @@ -1040,20 +1040,20 @@ proc lookupFieldAgain(p: BProc, ty: PType; field: PSym; r: var Rope; break if not p.module.compileToCpp: r = dotField(r, "Sup") - ty = ty[0] + ty = ty.baseClass if result == nil: internalError(p.config, field.info, "genCheckedRecordField") proc genRecordField(p: BProc, e: PNode, d: var TLoc) = var a: TLoc = default(TLoc) - if p.module.compileToCpp and e.kind == nkDotExpr and e[1].kind == nkSym and e[1].typ.kind == tyPtr: + if p.module.compileToCpp and e.kind == nkDotExpr and e.secondSon.kind == nkSym and e.secondSon.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 + let typ = e.secondSon.typ.elementType if typ.bindingId in p.module.g.graph.memberProcsPerType: discard getTypeDesc(p.module, typ) genRecordFieldAux(p, e, d, a) var r = rdLoc(a) - var f = e[1].sym + var f = e.secondSon.sym let ty = skipTypes(a.t, abstractInstOwned + tyUserTypeClasses) if ty.kind == tyTuple: # we found a unique tuple type which lacks field information @@ -1073,13 +1073,13 @@ proc genInExprAux(p: BProc, e: PNode, a, b, d: var TLoc) proc genFieldCheck(p: BProc, e: PNode, obj: Rope, field: PSym, ty: PType) = var test, u, v: TLoc - for i in 1.. # seq = (typeof seq) incrSeq(&seq->Sup, sizeof(x)); # seq->data[seq->len-1] = x; - var a = initLocExpr(p, e[1]) - var b = initLocExpr(p, e[2]) - let seqType = skipTypes(e[1].typ, {tyVar}) + var a = initLocExpr(p, e.secondSon) + var b = initLocExpr(p, son(e, 2)) + let seqType = skipTypes(e.secondSon.typ, {tyVar}) var call = initLoc(locCall, e, OnHeap) let ra = rdLoc(a) - call.snippet = cCast(getTypeDesc(p.module, e[1].typ), + call.snippet = cCast(getTypeDesc(p.module, e.secondSon.typ), cgCall(p, "incrSeqV3", if not p.module.compileToCpp: cCast(ptrType("TGenericSeq"), ra) else: ra, genTypeInfoV1(p.module, seqType, e.info))) @@ -1606,7 +1606,7 @@ proc genSeqElemAppend(p: BProc, e: PNode, d: var TLoc) = genRefAssign(p, a, call) #if bt != b.t: # echo "YES ", e.info, " new: ", typeToString(bt), " old: ", typeToString(b.t) - var dest = initLoc(locExpr, e[2], OnHeap) + var dest = initLoc(locExpr, son(e, 2), OnHeap) var tmpL = getIntTemp(p) p.s(cpsStmts).addAssignment(tmpL.snippet, lenField(p, ra)) p.s(cpsStmts).addIncr(lenField(p, ra)) @@ -1621,19 +1621,19 @@ proc genSeqElemAppendV2(p: BProc, e: PNode, d: var TLoc) = # s.p = (PayloadType*)prepareSeqAddUninit(oldLen, s.p, 1, sizeof(T), alignof(T)); # s.len = oldLen + 1; # s.p->data[oldLen] = x; // direct assignment, no function call overhead - let seqtype = skipTypes(e[1].typ, abstractVarRange) - var a = initLocExpr(p, e[1]) + let seqtype = skipTypes(e.secondSon.typ, abstractVarRange) + var a = initLocExpr(p, e.secondSon) let pt = getSeqPayloadType(p.module, seqtype) let pe = seqPayloadElem(p.module, seqtype) - # Capture a stable pointer to the seq BEFORE evaluating the element (e[2]). - # Evaluating e[2] may emit move semantics (eqwasMoved) that nil a variable - # through which e[1]'s snippet is accessed (e.g. a closure env pointer). + # Capture a stable pointer to the seq BEFORE evaluating the element (e's child 2). + # Evaluating that element may emit move semantics (eqwasMoved) that nil a variable + # through which e.secondSon's snippet is accessed (e.g. a closure env pointer). inc(p.labels) let seqPtrName = "T" & rope(p.labels) & "_" p.s(cpsLocals).addVar(kind = Local, name = seqPtrName, typ = ptrType(getTypeDesc(p.module, seqtype))) p.s(cpsStmts).addAssignment(seqPtrName, cAddr(rdLoc(a))) - var b = initLocExpr(p, e[2]) + var b = initLocExpr(p, son(e, 2)) # All seq operations now go through the stable seqPtrName pointer. let ra = wrapPar(cDeref(seqPtrName)) var tmpL = getIntTemp(p) @@ -1655,7 +1655,7 @@ proc genSeqElemAppendV2(p: BProc, e: PNode, d: var TLoc) = cAlignof(pe)) p.s(cpsStmts).addFieldAssignment(ra, "len", cOp(Add, NimInt, tmpL.snippet, cIntValue(1))) - var dest = initLoc(locExpr, e[2], OnHeap) + var dest = initLoc(locExpr, son(e, 2), OnHeap) dest.snippet = subscript(dataField(p, ra), tmpL.snippet) genAssignment(p, dest, b, {}) @@ -1737,10 +1737,10 @@ proc rawGenNew(p: BProc, a: var TLoc, sizeExpr: Rope; needsInit: bool) = genObjectInit(p, cpsStmts, bt, a, constructRefObj) proc genNew(p: BProc, e: PNode) = - var a: TLoc = initLocExpr(p, e[1]) + var a: TLoc = initLocExpr(p, e.secondSon) # 'genNew' also handles 'unsafeNew': if e.len == 3: - var se: TLoc = initLocExpr(p, e[2]) + var se: TLoc = initLocExpr(p, son(e, 2)) rawGenNew(p, a, se.rdLoc, needsInit = true) else: rawGenNew(p, a, "", needsInit = true) @@ -1788,10 +1788,10 @@ proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope; lenIsZero: bool) = genAssignment(p, dest, call, {}) proc genNewSeq(p: BProc, e: PNode) = - var a = initLocExpr(p, e[1]) - var b = initLocExpr(p, e[2]) + var a = initLocExpr(p, e.secondSon) + var b = initLocExpr(p, son(e, 2)) if optSeqDestructors in p.config.globalOptions: - let seqtype = skipTypes(e[1].typ, abstractVarRange) + let seqtype = skipTypes(e.secondSon.typ, abstractVarRange) let ra = a.rdLoc let rb = b.rdLoc let pt = getSeqPayloadType(p.module, seqtype) @@ -1804,13 +1804,13 @@ proc genNewSeq(p: BProc, e: PNode) = cSizeof(pe), cAlignof(pe)) else: - let lenIsZero = e[2].kind == nkIntLit and e[2].intVal == 0 + let lenIsZero = son(e, 2).kind == nkIntLit and son(e, 2).intVal == 0 genNewSeqAux(p, a, b.rdLoc, lenIsZero) gcUsage(p.config, e) proc genNewSeqOfCap(p: BProc; e: PNode; d: var TLoc) = let seqtype = skipTypes(e.typ, abstractVarRange) - var a: TLoc = initLocExpr(p, e[1]) + var a: TLoc = initLocExpr(p, e.secondSon) if optSeqDestructors in p.config.globalOptions: if d.k == locNone: d = getTemp(p, e.typ, needsInit=false) let rd = d.rdLoc @@ -1859,7 +1859,7 @@ proc handleConstExpr(p: BProc, n: PNode, d: var TLoc): bool = result = false -proc genFieldObjConstr(p: BProc; ty: PType; useTemp, isRef: bool; nField, val, check: PNode; d: var TLoc; r: Rope; info: TLineInfo) = +proc genFieldObjConstr(p: BProc; ty: PType; useTemp, isRef: bool; nField: PNode; val: PNode; check: PNode; d: var TLoc; r: Rope; info: TLineInfo) = var tmp2 = TLoc(snippet: r) let field = lookupFieldAgain(p, ty, nField.sym, tmp2.snippet) if field.loc.snippet == "": fillObjectFields(p.module, ty) @@ -1932,15 +1932,15 @@ proc genObjConstr(p: BProc, e: PNode, d: var TLoc) = r = rdLoc(d) discard getTypeDesc(p.module, t) let ty = getUniqueType(t) - for i in 1.. 0: + if e.secondSon.hasSons: var val: Snippet = "" - for i in 0.. use a union. inc(p.labels) @@ -2662,7 +2662,7 @@ proc genCast(p: BProc, e: PNode, d: var TLoc) = let destsize = getSize(p.config, destt) let srcsize = getSize(p.config, srct) - let srcTyp = getTypeDesc(p.module, e[1].typ) + let srcTyp = getTypeDesc(p.module, e.secondSon.typ) let destTyp = getTypeDesc(p.module, e.typ) if destsize > srcsize: p.s(cpsLocals).addVarWithType(kind = Local, name = "LOC" & lbl): @@ -2681,7 +2681,7 @@ proc genCast(p: BProc, e: PNode, d: var TLoc) = tmp.lode = lodeTyp srct tmp.storage = OnStack tmp.flags = {} - expr(p, e[1], tmp) + expr(p, e.secondSon, tmp) putIntoDest(p, d, e, dotField("LOC" & lbl, "dest"), tmp.storage) else: # I prefer the shorter cast version for pointer types -> generate less @@ -2700,9 +2700,9 @@ proc genRangeChck(p: BProc, n: PNode, d: var TLoc) = # emit range check: if n0t.kind in {tyUInt, tyUInt64}: var first = newBuilder("") - genLiteral(p, n[1], dest, first) + genLiteral(p, n.secondSon, dest, first) var last = newBuilder("") - genLiteral(p, n[2], dest, last) + genLiteral(p, son(n, 2), dest, last) let rca = rdCharLoc(a) let rt = getTypeDesc(p.module, n0t) p.s(cpsStmts).addSingleIfStmt(cOp(GreaterThan, rca, cCast(rt, extract(last)))): @@ -2718,9 +2718,9 @@ proc genRangeChck(p: BProc, n: PNode, d: var TLoc) = cgsym(p.module, raiser) var first = newBuilder("") - genLiteral(p, n[1], dest, first) + genLiteral(p, n.secondSon, dest, first) var last = newBuilder("") - genLiteral(p, n[2], dest, last) + genLiteral(p, son(n, 2), dest, last) let rca = rdCharLoc(a) let boundRca = if n0t.skipTypes(abstractVarRange).kind in {tyUInt, tyUInt32, tyUInt64}: @@ -2746,7 +2746,7 @@ proc genRangeChck(p: BProc, n: PNode, d: var TLoc) = proc genConv(p: BProc, e: PNode, d: var TLoc) = if ignoreConv(e): - expr(p, e[1], d) + expr(p, e.secondSon, d) else: genSomeCast(p, e, d) @@ -2772,14 +2772,14 @@ proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) = proc genStrEquals(p: BProc, e: PNode, d: var TLoc) = var x: TLoc - var a = e[1] - var b = e[2] + var a = e.secondSon + var b = son(e, 2) if a.kind in {nkStrLit..nkTripleStrLit} and a.strVal == "": - x = initLocExpr(p, e[2]) + x = initLocExpr(p, son(e, 2)) let lx = lenExpr(p, x) putIntoDest(p, d, e, cOp(Equal, lx, cIntValue(0))) elif b.kind in {nkStrLit..nkTripleStrLit} and b.strVal == "": - x = initLocExpr(p, e[1]) + x = initLocExpr(p, e.secondSon) let lx = lenExpr(p, x) putIntoDest(p, d, e, cOp(Equal, lx, cIntValue(0))) else: @@ -2788,13 +2788,13 @@ proc genStrEquals(p: BProc, e: PNode, d: var TLoc) = proc binaryFloatArith(p: BProc, e: PNode, d: var TLoc, m: TMagic) = if {optNaNCheck, optInfCheck} * p.options != {}: const opr: array[mAddF64..mDivF64, TypedBinaryOp] = [Add, Sub, Mul, Div] - assert(e[1].typ != nil) - assert(e[2].typ != nil) - var a = initLocExpr(p, e[1]) - var b = initLocExpr(p, e[2]) + assert(e.secondSon.typ != nil) + assert(son(e, 2).typ != nil) + var a = initLocExpr(p, e.secondSon) + var b = initLocExpr(p, son(e, 2)) let ra = rdLoc(a) let rb = rdLoc(b) - let rt = getSimpleTypeDesc(p.module, e[1].typ) + let rt = getSimpleTypeDesc(p.module, e.secondSon.typ) putIntoDest(p, d, e, cOp(opr[m], rt, cCast(rt, ra), cCast(rt, rb))) if optNaNCheck in p.options: let rd = rdLoc(d) @@ -2815,7 +2815,7 @@ proc binaryFloatArith(p: BProc, e: PNode, d: var TLoc, m: TMagic) = proc genWasMoved(p: BProc; n: PNode) = var a: TLoc - let n1 = n[1].skipAddr + let n1 = n.secondSon.skipAddr if p.withinBlockLeaveActions > 0 and notYetAlive(n1): discard else: @@ -2827,21 +2827,21 @@ proc genWasMoved(p: BProc; n: PNode) = proc genMove(p: BProc; n: PNode; d: var TLoc) = if n.len == 4: # generated by liftdestructors: - var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation}) - var src: TLoc = initLocExpr(p, n[2]) + var a: TLoc = initLocExpr(p, n.secondSon.skipAddr, {lfEnforceDeref, lfPrepareForMutation}) + var src: TLoc = initLocExpr(p, son(n, 2)) let destVal = rdLoc(a) let srcVal = rdLoc(src) if p.config.usesSso() and - n[1].typ.skipTypes(abstractVar).kind == tyString: + n.secondSon.typ.skipTypes(abstractVar).kind == tyString: # SmallString: destroy dst then struct-copy src; no .p field aliasing needed - genStmts(p, n[3]) + genStmts(p, son(n, 3)) genAssignment(p, a, src, {}) else: p.s(cpsStmts).addSingleIfStmt( cOp(NotEqual, dotField(destVal, "p"), dotField(srcVal, "p"))): - genStmts(p, n[3]) + genStmts(p, son(n, 3)) p.s(cpsStmts).addFieldAssignment(destVal, "len", dotField(srcVal, "len")) p.s(cpsStmts).addFieldAssignment(destVal, "p", dotField(srcVal, "p")) else: @@ -2849,20 +2849,20 @@ proc genMove(p: BProc; n: PNode; d: var TLoc) = if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}: var op = getAttachedOp(p.module.g.graph, n.typ, attachedWasMoved) if op == nil or sfOverridden notin op.flags: - var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation}) + var a: TLoc = initLocExpr(p, n.secondSon.skipAddr, {lfEnforceDeref, lfPrepareForMutation}) genAssignment(p, d, a, {}) resetLoc(p, a) else: - n[1] = makeAddr(n[1], p.module.idgen) + n.secondSon = makeAddr(n.secondSon, p.module.idgen) genCall(p, n, d) else: - var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation}) + var a: TLoc = initLocExpr(p, n.secondSon.skipAddr, {lfEnforceDeref, lfPrepareForMutation}) genAssignment(p, d, a, {}) resetLoc(p, a) proc genDestroy(p: BProc; n: PNode) = if optSeqDestructors in p.config.globalOptions: - let arg = n[1].skipAddr + let arg = n.secondSon.skipAddr let t = arg.typ.skipTypes(abstractInst) case t.kind of tyString: @@ -2895,7 +2895,7 @@ proc genDestroy(p: BProc; n: PNode) = cAlignof(rt)) else: discard "nothing to do" else: - let t = n[1].typ.skipTypes(abstractVar) + let t = n.secondSon.typ.skipTypes(abstractVar) let op = getAttachedOp(p.module.g.graph, t, attachedDestructor) if op != nil and getBody(p.module.g.graph, op).len != 0: internalError(p.config, n.info, "destructor turned out to be not trivial") @@ -2903,8 +2903,8 @@ proc genDestroy(p: BProc; n: PNode) = proc genSlice(p: BProc; e: PNode; d: var TLoc) = let (x, y) = genOpenArraySlice(p, e, e.typ, e.typ.elementType, - prepareForMutation = e[1].kind == nkHiddenDeref and - e[1].typ.skipTypes(abstractInst).kind == tyString and + prepareForMutation = e.secondSon.kind == nkHiddenDeref and + e.secondSon.typ.skipTypes(abstractInst).kind == tyString and p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}) if d.k == locNone: d = getTemp(p, e.typ) let dest = rdLoc(d) @@ -2915,7 +2915,7 @@ proc genSlice(p: BProc; e: PNode; d: var TLoc) = "'toOpenArray' is only valid within a call expression") proc genEnumToStr(p: BProc, e: PNode, d: var TLoc) = - let t = e[1].typ.skipTypes(abstractInst+{tyRange}) + let t = e.secondSon.typ.skipTypes(abstractInst+{tyRange}) let toStrProc = getToStringProc(p.module.g.graph, t) # XXX need to modify this logic for IC. var n = copyTree(e) @@ -2926,10 +2926,10 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = case op of mAsgn: let kind = if e.firstSon.sym.name.s == "=sink": nkSinkAsgn else: nkAsgn - let lhs = e[1].skipHiddenAddr - let n = newTreeI(kind, e.info, lhs, e[2]) + let lhs = e.secondSon.skipHiddenAddr + let n = newTreeI(kind, e.info, lhs, son(e, 2)) n.typ = e.typ - cow(p, e[2]) + cow(p, son(e, 2)) genAsgn(p, n, fastAsgn = kind != nkAsgn) of mOr, mAnd: genAndOr(p, e, d, op) of mNot..mUnaryMinusF64: unaryArith(p, e, d, op) @@ -2946,21 +2946,21 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = const opr: array[mInc..mDec, TypedBinaryOp] = [Add, Sub] const fun64: array[mInc..mDec, string] = ["nimAddInt64", "nimSubInt64"] const fun: array[mInc..mDec, string] = ["nimAddInt","nimSubInt"] - let underlying = skipTypes(e[1].typ, {tyGenericInst, tyAlias, tySink, tyVar, tyLent, tyRange, tyDistinct}) + let underlying = skipTypes(e.secondSon.typ, {tyGenericInst, tyAlias, tySink, tyVar, tyLent, tyRange, tyDistinct}) if optOverflowCheck notin p.options or underlying.kind in {tyUInt..tyUInt64}: binaryStmt(p, e, d, opr[op]) else: - assert(e[1].typ != nil) - assert(e[2].typ != nil) - var a = initLocExpr(p, e[1]) - var b = initLocExpr(p, e[2]) + assert(e.secondSon.typ != nil) + assert(son(e, 2).typ != nil) + var a = initLocExpr(p, e.secondSon) + var b = initLocExpr(p, son(e, 2)) - let ranged = skipTypes(e[1].typ, {tyGenericInst, tyAlias, tySink, tyVar, tyLent, tyDistinct}) + let ranged = skipTypes(e.secondSon.typ, {tyGenericInst, tyAlias, tySink, tyVar, tyLent, tyDistinct}) let res = binaryArithOverflowRaw(p, ranged, a, b, if underlying.kind == tyInt64: fun64[op] else: fun[op]) let destTyp = getTypeDesc(p.module, ranged) - putIntoDest(p, a, e[1], cCast(destTyp, wrapPar(res))) + putIntoDest(p, a, e.secondSon, cCast(destTyp, wrapPar(res))) of mConStrStr: genStrConcat(p, e, d) of mAppendStrCh: @@ -2968,8 +2968,8 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = binaryStmtAddr(p, e, d, "nimAddCharV1") else: var call = initLoc(locCall, e, OnHeap) - var dest = initLocExpr(p, e[1]) - var b = initLocExpr(p, e[2]) + var dest = initLocExpr(p, e.secondSon) + var b = initLocExpr(p, son(e, 2)) call.snippet = cgCall(p, "addChar", rdLoc(dest), rdLoc(b)) genAssignment(p, dest, call, {}) of mAppendStrStr: genStrAppend(p, e, d) @@ -2982,7 +2982,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = # gcYrc is excluded because its add() acquires a striped reader lock. genSeqElemAppendV2(p, e, d) else: - e[1] = makeAddr(e[1], p.module.idgen) + e.secondSon = makeAddr(e.secondSon, p.module.idgen) genCall(p, e, d) else: genSeqElemAppend(p, e, d) @@ -3006,7 +3006,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = genDollarIt(p, e, d, cgCall(p, "cstrToNimstr", cCast(NimCstring, it))) else: genDollarIt(p, e, d, cgCall(p, "cstrToNimstr", it)) - of mStrToStr, mUnown: expr(p, e[1], d) + of mStrToStr, mUnown: expr(p, e.secondSon, d) of generatedMagics: genCall(p, e, d) of mEnumToStr: if optTinyRtti in p.config.globalOptions: @@ -3017,30 +3017,30 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = of mNew: genNew(p, e) of mNewFinalize: if optTinyRtti in p.config.globalOptions: - var a: TLoc = initLocExpr(p, e[1]) + var a: TLoc = initLocExpr(p, e.secondSon) rawGenNew(p, a, "", needsInit = true) gcUsage(p.config, e) else: genNewFinalize(p, e) of mNewSeq: if optSeqDestructors in p.config.globalOptions: - e[1] = makeAddr(e[1], p.module.idgen) + e.secondSon = makeAddr(e.secondSon, p.module.idgen) genCall(p, e, d) else: genNewSeq(p, e) of mNewSeqOfCap: genNewSeqOfCap(p, e, d) of mSizeOf: - let t = e[1].typ.skipTypes({tyTypeDesc}) + let t = e.secondSon.typ.skipTypes({tyTypeDesc}) putIntoDest(p, d, e, cCast(NimInt, cSizeof(getTypeDesc(p.module, t, dkVar)))) of mAlignOf: - let t = e[1].typ.skipTypes({tyTypeDesc}) + let t = e.secondSon.typ.skipTypes({tyTypeDesc}) putIntoDest(p, d, e, cCast(NimInt, cAlignof(getTypeDesc(p.module, t, dkVar)))) of mOffsetOf: var dotExpr: PNode - if e[1].kind == nkDotExpr: - dotExpr = e[1] - elif e[1].kind == nkCheckedFieldExpr: - dotExpr = e[1].firstSon + if e.secondSon.kind == nkDotExpr: + dotExpr = e.secondSon + elif e.secondSon.kind == nkCheckedFieldExpr: + dotExpr = e.secondSon.firstSon else: dotExpr = nil internalError(p.config, e.info, "unknown ast") @@ -3048,8 +3048,8 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = let tname = getTypeDesc(p.module, t, dkVar) let member = if t.kind == tyTuple: - "Field" & rope(dotExpr[1].sym.position) - else: dotExpr[1].sym.loc.snippet + "Field" & rope(dotExpr.secondSon.sym.position) + else: dotExpr.secondSon.sym.loc.snippet putIntoDest(p,d,e, cCast(NimInt, cOffsetof(tname, member))) of mChr: genSomeCast(p, e, d) of mOrd: genOrd(p, e, d) @@ -3057,13 +3057,13 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = genArrayLen(p, e, d, op) of mGCref: # only a magic for the old GCs - var a: TLoc = initLocExpr(p, e[1]) + var a: TLoc = initLocExpr(p, e.secondSon) let ra = rdLoc(a) p.s(cpsStmts).addSingleIfStmt(ra): p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimGCref"), ra) of mGCunref: # only a magic for the old GCs - var a: TLoc = initLocExpr(p, e[1]) + var a: TLoc = initLocExpr(p, e.secondSon) let ra = rdLoc(a) p.s(cpsStmts).addSingleIfStmt(ra): p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimGCunref"), ra) @@ -3106,7 +3106,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = '"' & name & '"') genCall(p, e, d) of mDefault, mZeroDefault: genDefault(p, e, d) - of mEcho: genEcho(p, e[1].skipConv) + of mEcho: genEcho(p, e.secondSon.skipConv) of mArrToSeq: genArrToSeq(p, e, d) of mNLen..mNError, mSlurp..mQuoteAst: localError(p.config, e.info, strutils.`%`(errXMustBeCompileTime, e.firstSon.sym.name.s)) @@ -3127,15 +3127,15 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = localError(p.config, e.info, "for --mm:arc|atomicArc|orc 'deepcopy' support has to be enabled with --deepcopy:on") - let typ = e[1].typ.skipTypes({tyVar, tyRef, tyGenericInst, tyTypeDesc, + let typ = e.secondSon.typ.skipTypes({tyVar, tyRef, tyGenericInst, tyTypeDesc, tyAlias, tyInferred, tySink, tyLent, tyOwned}) if hasDisabledAsgn(p.module.g.graph, typ): localError(p.config, e.info, "'deepCopy' is not available for type <" & typeToString(typ) & ">") - let x = if e[1].kind in {nkAddr, nkHiddenAddr}: e[1][0] else: e[1] + let x = if e.secondSon.kind in {nkAddr, nkHiddenAddr}: e.secondSon.firstSon else: e.secondSon var a = initLocExpr(p, x) - var b = initLocExpr(p, e[2]) + var b = initLocExpr(p, son(e, 2)) genDeepCopy(p, a, b) of mDotDot, mEqCString: genCall(p, e, d) of mWasMoved: genWasMoved(p, e) @@ -3146,12 +3146,12 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = of mSlice: genSlice(p, e, d) of mTrace: discard "no code to generate" of mEnsureMove: - expr(p, e[1], d) + expr(p, e.secondSon, d) of mDup: - expr(p, e[1], d) + expr(p, e.secondSon, d) else: when defined(debugMagics): - echo p.prc.name.s, " ", p.prc.id, " ", p.prc.flags, " ", p.prc.ast[genericParamsPos].kind + echo p.prc.name.s, " ", p.prc.id, " ", p.prc.flags, " ", son(p.prc.ast, genericParamsPos).kind internalError(p.config, e.info, "genMagicExpr: " & $op) proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = @@ -3174,11 +3174,11 @@ proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"), rdLoc(d), cSizeof(getTypeDesc(p.module, e.typ))) - for it in e.sons: + for it in sons(e): if it.kind == nkRange: idx = getTemp(p, getSysType(p.module.g.graph, unknownLineInfo, tyInt)) # our counter a = initLocExpr(p, it.firstSon) - b = initLocExpr(p, it[1]) + b = initLocExpr(p, it.secondSon) var aa: Snippet = "" rdSetElemLoc(p.config, a, e.typ, aa) var bb: Snippet = "" @@ -3203,11 +3203,11 @@ proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = # small set var ts = cUintType(size * 8) p.s(cpsStmts).addAssignment(rdLoc(d), cIntValue(0)) - for it in e.sons: + for it in sons(e): if it.kind == nkRange: idx = getTemp(p, getSysType(p.module.g.graph, unknownLineInfo, tyInt)) # our counter a = initLocExpr(p, it.firstSon) - b = initLocExpr(p, it[1]) + b = initLocExpr(p, it.secondSon) var aa: Snippet = "" rdSetElemLoc(p.config, a, e.typ, aa) var bb: Snippet = "" @@ -3244,7 +3244,7 @@ proc genTupleConstr(p: BProc, n: PNode, d: var TLoc) = for i, ni in isons(n): var it = ni - if it.kind == nkExprColonExpr: it = it[1] + if it.kind == nkExprColonExpr: it = it.secondSon # Do not produce code for void types if it.typ != nil and isEmptyType(it.typ): continue rec = initLoc(locExpr, it, dest[].storage) @@ -3260,7 +3260,7 @@ proc genTupleConstr(p: BProc, n: PNode, d: var TLoc) = proc isConstClosure(n: PNode): bool {.inline.} = result = n.firstSon.kind == nkSym and isRoutine(n.firstSon.sym) and - n[1].kind == nkNilLit + n.secondSon.kind == nkNilLit proc genClosure(p: BProc, n: PNode, d: var TLoc) = assert n.kind in {nkPar, nkTupleConstr, nkClosure} @@ -3277,7 +3277,7 @@ proc genClosure(p: BProc, n: PNode, d: var TLoc) = else: var tmp: TLoc var a = initLocExpr(p, n.firstSon) - var b = initLocExpr(p, n[1]) + var b = initLocExpr(p, n.secondSon) if n.firstSon.skipConv.kind == nkClosure: internalError(p.config, n.info, "closure to closure created") # tasyncawait.nim breaks with this optimization: @@ -3314,8 +3314,7 @@ template genStmtListExprImpl(exprOrStmt) {.dirty.} = sfSystemModule notin p.module.module.flags and optStackTrace in p.prc.options var frameName: Rope = "" - for i in 0.. 0: exprOrStmt + if n.hasSons: exprOrStmt if frameName != "": p.s(cpsStmts).add deinitFrameNoDebug(p, frameName) proc genStmtListExpr(p: BProc, n: PNode, d: var TLoc) = genStmtListExprImpl: - expr(p, n[^1], d) + expr(p, n.lastSon, d) proc genStmtList(p: BProc, n: PNode) = genStmtListExprImpl: - genStmts(p, n[^1]) + genStmts(p, n.lastSon) from parampatterns import isLValue @@ -3548,7 +3547,7 @@ proc genConstStmt(p: BProc, n: PNode) = # This code is only used in the new DCE implementation. assert delayedCodegen(p.module) let m = p.module - for it in n: + for it in sons(n): if it.firstSon.kind == nkSym: let sym = it.firstSon.sym if not isSimpleConst(sym.typ) and sym.itemId.item in m.alive and genConstSetup(p, sym): @@ -3696,14 +3695,14 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = else: genCall(p, n, d) of nkCurly: - if isDeepConstExpr(n) and n.len != 0: + if isDeepConstExpr(n) and n.hasSons: var lit = newBuilder("") genSetNode(p, n, lit) putIntoDest(p, d, n, extract(lit)) else: genSetConstr(p, n, d) of nkBracket: - if isDeepConstExpr(n) and n.len != 0: + if isDeepConstExpr(n) and n.hasSons: exprComplexConst(p, n, d) elif skipTypes(n.typ, abstractVarRange).kind == tySequence: genSeqConstr(p, n, d) @@ -3712,7 +3711,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = of nkPar, nkTupleConstr: if n.typ != nil and n.typ.kind == tyProc and n.len == 2: genClosure(p, n, d) - elif isDeepConstExpr(n) and n.len != 0: + elif isDeepConstExpr(n) and n.hasSons: exprComplexConst(p, n, d) else: genTupleConstr(p, n, d) @@ -3730,14 +3729,14 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = of nkIfExpr, nkIfStmt: genIf(p, n, d) of nkWhen: # This should be a "when nimvm" node. - expr(p, n[1].firstSon, d) + expr(p, n.secondSon.firstSon, d) of nkObjDownConv: downConv(p, n, d) of nkObjUpConv: upConv(p, n, d) of nkChckRangeF, nkChckRange64, nkChckRange: genRangeChck(p, n, d) of nkStringToCString: convStrToCStr(p, n, d) of nkCStringToString: convCStrToStr(p, n, d) of nkLambdaKinds: - var sym = n[namePos].sym + var sym = son(n, namePos).sym genProc(p.module, sym) if sym.loc.snippet == "" or sym.loc.lode == nil: internalError(p.config, n.info, "expr: proc not init " & sym.name.s) @@ -3751,7 +3750,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = if delayedCodegen(p.module): genConstStmt(p, n) else: # enforce addressable consts for exportc - for it in n: + for it in sons(n): let symNode = skipPragmaExpr(it.firstSon) if symNode.kind == nkSym and sfExportc in symNode.sym.flags: requestConstImpl(p, symNode.sym) @@ -3761,11 +3760,11 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = of nkReturnStmt: genReturnStmt(p, n) of nkBreakStmt: genBreakStmt(p, n) of nkAsgn: - cow(p, n[1]) + cow(p, n.secondSon) if nfPreventCg notin n.flags: genAsgn(p, n, fastAsgn=false) of nkFastAsgn, nkSinkAsgn: - cow(p, n[1]) + cow(p, n.secondSon) if nfPreventCg notin n.flags: # transf is overly aggressive with 'nkFastAsgn', so we work around here. # See tests/run/tcnstseq3 for an example that would fail otherwise. @@ -3798,9 +3797,9 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = of nkPragmaBlock: var inUncheckedAssignSection = 0 let pragmaList = n.firstSon - for pi in pragmaList: + for pi in sons(pragmaList): if whichPragma(pi) == wCast: - case whichPragma(pi[1]) + case whichPragma(pi.secondSon) of wUncheckedAssign: inUncheckedAssignSection = 1 else: @@ -3811,8 +3810,8 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = dec p.inUncheckedAssignSection, inUncheckedAssignSection of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef: - if n[genericParamsPos].kind == nkEmpty: - var prc = n[namePos].sym + if son(n, genericParamsPos).kind == nkEmpty: + var prc = son(n, namePos).sym if optCompress in p.config.globalOptions: if prc.magic in generatedMagics: genProc(p.module, prc) @@ -3845,24 +3844,24 @@ proc isOpaqueImportcType(t: PType): bool = if tfCompleteStruct notin t.flags: if tfIncompleteStruct in t.flags: return true - if t.kind == tyObject and (t.n == nil or t.n.len == 0): + if t.kind == tyObject and (t.n == nil or not t.n.hasSons): return true return false proc containsOpaqueImportcField(typ: PType): bool proc containsOpaqueImportcFieldAux(t: PType; n: PNode): bool = + ## Also a type-record walk; `n` is `t.n`. if n == nil: return false case n.kind of nkRecList: - for child in n.sons: + for child in sons(n): if containsOpaqueImportcFieldAux(t, child): return true of nkRecCase: if containsOpaqueImportcFieldAux(t, n.firstSon): return true - for i in 1.. 0: + if n.hasSons: def.addField(structInit, name = "data"): var arrInit: StructInitializer def.addStructInitializer(arrInit, kind = siArray): - for ni in n.sons: + for ni in sons(n): def.addField(arrInit, name = ""): genBracedInit(p, ni, isConst, base, def) p.module.s[cfsStrData].add extract(def) @@ -4163,7 +4157,7 @@ proc genConstSeq(p: BProc, n: PNode, t: PType; isConst: bool; result: var Builde result.add cCast(typ = getTypeDesc(p.module, t), value = cAddr(tmpName)) proc genConstSeqV2(p: BProc, n: PNode, t: PType; isConst: bool; result: var Builder) = - let base = t.skipTypes(abstractInst)[0] + let base = t.skipTypes(abstractInst).elementType let payload = getTempName(p.module) # genBracedInit can modify cfsStrData, we need an intermediate builder: @@ -4179,11 +4173,11 @@ proc genConstSeqV2(p: BProc, n: PNode, t: PType; isConst: bool; result: var Buil def.addStructInitializer(structInit, kind = siOrderedStruct): def.addField(structInit, name = "cap"): def.add(cOp(BitOr, NimInt, cIntValue(n.len), NimStrlitFlag)) - if n.len > 0: + if n.hasSons: def.addField(structInit, name = "data"): var arrInit: StructInitializer def.addStructInitializer(arrInit, kind = siArray): - for ni in n.sons: + for ni in sons(n): def.addField(arrInit, name = ""): genBracedInit(p, ni, isConst, base, def) p.module.s[cfsStrData].add extract(def) @@ -4198,7 +4192,7 @@ proc genConstSeqV2(p: BProc, n: PNode, t: PType; isConst: bool; result: var Buil proc genBracedInit(p: BProc, n: PNode; isConst: bool; optionalType: PType; result: var Builder) = case n.kind of nkHiddenStdConv, nkHiddenSubConv: - genBracedInit(p, n[1], isConst, n.typ, result) + genBracedInit(p, n.secondSon, isConst, n.typ, result) else: var ty = tyNone var typ: PType = nil @@ -4220,7 +4214,7 @@ proc genBracedInit(p: BProc, n: PNode; isConst: bool; optionalType: PType; resul else: genConstSeq(p, n, typ, isConst, result) of tyProc: - if typ.callConv == ccClosure and n.safeLen > 1 and n[1].kind == nkNilLit: + if typ.callConv == ccClosure and n.safeLen > 1 and n.secondSon.kind == nkNilLit: # n.kind could be: nkClosure, nkTupleConstr and maybe others; `n.safeLen` # guards against the case of `nkSym`, refs bug #14340. # Conversion: nimcall -> closure. diff --git a/compiler/ccgreset.nim b/compiler/ccgreset.nim index 57ea5fc793..88a81b7c42 100644 --- a/compiler/ccgreset.nim +++ b/compiler/ccgreset.nim @@ -19,18 +19,17 @@ proc specializeResetN(p: BProc, accessor: Rope, n: PNode; if n == nil: return case n.kind of nkRecList: - for i in 0..= 2 and n[1].kind == nkIntLit: - statesCounter = getInt(n[1]) - let prefix = if n.len == 3 and n[2].kind == nkStrLit: n[2].strVal.rope + if n.len >= 2 and n.secondSon.kind == nkIntLit: + statesCounter = getInt(n.secondSon) + let prefix = if n.len == 3 and son(n, 2).kind == nkStrLit: son(n, 2).strVal.rope else: rope"STATE" for i in 0i64..toInt64(statesCounter): p.s(cpsStmts).addSingleSwitchCase(cIntValue(i)): @@ -291,7 +292,7 @@ proc genBreakState(p: BProc, n: PNode, d: var TLoc) = d = initLoc(locExpr, n, OnUnknown) if n.firstSon.kind == nkClosure: - a = initLocExpr(p, n.firstSon[1]) + a = initLocExpr(p, n.firstSon.secondSon) let ra = a.rdLoc d.snippet = cOp(LessThan, subscript( @@ -329,18 +330,18 @@ proc genCppParamsForCtor(p: BProc; call: PNode; didGenTemp: var bool): Snippet = var argBuilder = default(CallBuilder) # not init, only building params let typ = skipTypes(call.firstSon.typ, abstractInst) assert(typ.kind == tyProc) - for i in 1.. 1: p.s(cpsStmts).addGoto(lend) @@ -574,15 +575,15 @@ proc genReturnStmt(p: BProc, t: PNode) = p.s(cpsStmts).addGoto("BeforeRet_") proc genGotoForCase(p: BProc; caseStmt: PNode) = - for i in 1..= casePos: break + genStmts(p, it) - let caseStmt = n[casePos] + let caseStmt = son(n, casePos) var a: TLoc = initLocExpr(p, caseStmt.firstSon) let ra = a.rdLoc # first goto: p.s(cpsStmts).addComputedGoto(subscript(tmp, ra)) - for i in 1..= casePos: break # prevent new local declarations # compile declarations as assignments - let it = n[j] - if it.kind in {nkLetSection, nkVarSection}: - let asgn = copyNode(it) + if before.kind in {nkLetSection, nkVarSection}: + let asgn = copyNode(before) asgn.transitionSonsKind(nkAsgn) asgn.sons.setLen 2 - for sym, value in it.fieldValuePairs: + for sym, value in before.fieldValuePairs: if value.kind != nkEmpty: asgn[0] = sym - asgn[1] = value + asgn.secondSon = value genStmts(p, asgn) else: - genStmts(p, it) + genStmts(p, before) var a: TLoc = initLocExpr(p, caseStmt.firstSon) let ra = a.rdLoc p.s(cpsStmts).addComputedGoto(subscript(tmp, ra)) endSimpleBlock(p, scope) - for j in casePos+1.. until: break # bug #4230: avoid false sharing between branches: if d.k == locTemp and isEmptyType(t.typ): d.k = locNone p.s(cpsStmts).addLabel("LA" & $(labId + i) & "_") - if t[i].kind == nkOfBranch: - exprBlock(p, t[i][^1], d) + if branch.kind == nkOfBranch: + exprBlock(p, branch.lastSon, d) p.s(cpsStmts).addGoto(lend) else: - exprBlock(p, t[i].firstSon, d) + exprBlock(p, branch.firstSon, d) result = lend template genIfForCaseUntil(p: BProc, t: PNode, d: var TLoc, @@ -944,11 +948,12 @@ template genIfForCaseUntil(p: BProc, t: PNode, d: var TLoc, # generate a C-if statement for a Nim case statement var res: TLabel var labId = p.labels - for i in 1..until: + for i, branch in isons(t, 1): + if i > until: break inc(p.labels) let lab = "LA" & $p.labels & "_" - if t[i].kind == nkOfBranch: # else statement - genCaseGenericBranch(p, t[i], a, lab, rangeFormat, eqFormat) + if branch.kind == nkOfBranch: # else statement + genCaseGenericBranch(p, branch, a, lab, rangeFormat, eqFormat) else: p.s(cpsStmts).addGoto(lab) if until < t.len-1: @@ -964,20 +969,20 @@ template genIfForCaseUntil(p: BProc, t: PNode, d: var TLoc, template genCaseGeneric(p: BProc, t: PNode, d: var TLoc, rangeFormat, eqFormat: untyped) = var a: TLoc = initLocExpr(p, t.firstSon) - var lend = genIfForCaseUntil(p, t, d, t.len-1, a, rangeFormat, eqFormat) + var lend = genIfForCaseUntil(p, t, d, t.safeLen-1, a, rangeFormat, eqFormat) fixLabel(p, lend) proc genCaseStringBranch(p: BProc, b: PNode, e: TLoc, labl: TLabel, stringKind: TTypeKind, branches: var openArray[Builder]) = var x: TLoc - for i in 0.. stringCaseThreshold: var bitMask = math.nextPowerOfTwo(strings) - 1 var branches: seq[Builder] newSeq(branches, bitMask + 1) var a: TLoc = initLocExpr(p, t.firstSon) # first pass: generate ifs+goto: var labId = p.labels - for i in 1.. RangeExpandLimit: + it.secondSon.intVal - it.firstSon.intVal > RangeExpandLimit: return true proc ifSwitchSplitPoint(p: BProc, n: PNode): int = result = 0 - for i in 1.. 0 and t[^1].kind == nkFinally: + if t.hasSons and t.lastSon.kind == nkFinally: if not catchAllPresent: startBlockWith(p): p.s(cpsStmts).add("catch (...) {\n") @@ -1350,7 +1354,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = var scope: ScopeBuilder startSimpleBlock(p, scope) - genStmts(p, t[^1].firstSon) + genStmts(p, t.lastSon.firstSon) linefmt(p, cpsStmts, "if (T$1_) std::rethrow_exception(T$1_);$n", [etmp]) endSimpleBlock(p, scope) @@ -1360,23 +1364,23 @@ proc bodyCanRaise(p: BProc; n: PNode): bool = result = canRaiseDisp(p, n.firstSon) if not result: # also check the arguments: - for i in 1 ..< n.len: - if bodyCanRaise(p, n[i]): return true + for it in sonsFrom(n, 1): + if bodyCanRaise(p, it): return true of nkRaiseStmt: result = true of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef: result = false else: - for i in 0 ..< safeLen(n): - if bodyCanRaise(p, n[i]): return true result = false + for it in sons(n): + if bodyCanRaise(p, it): return true proc genTryGoto(p: BProc; t: PNode; d: var TLoc) = - let fin = if t[^1].kind == nkFinally: t[^1] else: nil + let fin = if t.lastSon.kind == nkFinally: t.lastSon else: nil inc p.labels let lab = p.labels - let hasExcept = t[1].kind == nkExceptBranch + let hasExcept = t.secondSon.kind == nkExceptBranch if hasExcept: inc p.withinTryWithExcept p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, Natural lab)) @@ -1390,7 +1394,7 @@ proc genTryGoto(p: BProc; t: PNode; d: var TLoc) = var ifStmt = default(IfBuilder) var scope = default(ScopeBuilder) var isIf = false - if 1 < t.len and t[1].kind == nkExceptBranch: + if 1 < t.len and t.secondSon.kind == nkExceptBranch: startBlockWith(p): isIf = true ifStmt = initIfStmt(p.s(cpsStmts)) @@ -1405,7 +1409,8 @@ proc genTryGoto(p: BProc; t: PNode; d: var TLoc) = var innerIfStmt = default(IfBuilder) var innerScope = default(ScopeBuilder) var innerIsIf = false - while (i < t.len) and (t[i].kind == nkExceptBranch): + while i < t.len and son(t, i).kind == nkExceptBranch: + let exceptBranch = son(t, i) inc p.labels let nextExcept = p.labels @@ -1414,7 +1419,7 @@ proc genTryGoto(p: BProc; t: PNode; d: var TLoc) = var isScope = false # bug #4230: avoid false sharing between branches: if d.k == locTemp and isEmptyType(t.typ): d.k = locNone - if t[i].len == 1: + if exceptBranch.len == 1: # general except section: startBlockWith(p): if innerIsIf: @@ -1424,14 +1429,14 @@ proc genTryGoto(p: BProc; t: PNode; d: var TLoc) = innerScope = initScope(p.s(cpsStmts)) # we handled the exception, remember this: p.s(cpsStmts).addAssignment(cDeref("nimErr_"), NimFalse) - expr(p, t[i].firstSon, d) + expr(p, exceptBranch.firstSon, d) else: if not innerIsIf: innerIsIf = true innerIfStmt = initIfStmt(p.s(cpsStmts)) var orExpr: Snippet = "" - for j in 0.. 1: #we dont care about the return param - for i in 1.. 0: # we dont care about the return param + for _, pt in paramTypes(s.typ): + if pt.isNil: continue + params.add encodeType(m, pt, staticLists) result.add encodeSym(m, s, makeUnique, staticLists) result.add params @@ -311,7 +311,7 @@ proc isInvalidReturnType(conf: ConfigRef; typ: PType, isProc = true): bool = var rettype = typ var isAllowedCall = true if isProc: - rettype = rettype[0] + rettype = rettype.returnType isAllowedCall = typ.callConv in {ccClosure, ccInline, ccNimCall} if rettype == nil or (isAllowedCall and getSize(conf, rettype) > conf.target.floatSize*3): @@ -480,7 +480,7 @@ proc getTypeDescWeak(m: BModule; t: PType; check: var IntSet; kind: TypeDescKind of tySequence: let sig = hashType(t, m.config) if optSeqDestructors in m.config.globalOptions: - if skipTypes(etB[0], typedescInst).kind == tyEmpty: + if skipTypes(etB.elementType, typedescInst).kind == tyEmpty: internalError(m.config, "cannot map the empty seq type to a C type") result = cacheGetType(m.forwTypeCache, sig) @@ -524,7 +524,7 @@ proc seqV2ContentType(m: BModule; t: PType; check: var IntSet) = if result == "": discard getTypeDescAux(m, t, check, dkVar) else: - let dataTyp = getTypeDescAux(m, t.skipTypes(abstractInst)[0], check, dkVar) + let dataTyp = getTypeDescAux(m, t.skipTypes(abstractInst).elementType, check, dkVar) m.s[cfsTypes].addSimpleStruct(m, name = result & "_Content", baseType = ""): m.s[cfsTypes].addField(name = "cap", typ = NimInt) m.s[cfsTypes].addField(name = "data", @@ -598,10 +598,10 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params rettype = runtimeFormat(rettype.replace("'0", "$1"), [getTypeDescAux(m, t.returnType, check, dkResult)]) var types, names, args: seq[string] = @[] if not isCtor: - var this = t.n[1].sym + var this = t.n.secondSon.sym backendEnsureMutable this fillParamName(m, this) - fillLoc(this.locImpl, locParam, t.n[1], + fillLoc(this.locImpl, locParam, t.n.secondSon, this.paramStorageLoc) if this.typ.kind == tyPtr: this.locImpl.snippet = "this" @@ -611,9 +611,9 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params types.add getTypeDescWeak(m, this.typ, check, dkParam) let firstParam = if isCtor: 1 else: 2 - for i in firstParam..