mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-04 14:38:38 +00:00
IC: performance/loading counters and bugfixes
This commit is contained in:
@@ -11,8 +11,10 @@
|
||||
|
||||
import std / [assertions, tables, sets]
|
||||
from std / strutils import startsWith, endsWith, contains
|
||||
from std / os import fileExists, dirExists, walkFiles
|
||||
from std / syncio import readFile
|
||||
from std / os import fileExists, dirExists, walkFiles, existsEnv,
|
||||
commandLineParams, getCurrentProcessId
|
||||
from std / exitprocs import addExitProc
|
||||
from std / syncio import readFile, stderr, writeLine
|
||||
from std / algorithm import sort
|
||||
import "../dist/checksums/src/checksums" / sha1
|
||||
import astdef, idents, msgs, options
|
||||
@@ -912,6 +914,10 @@ proc writeNode(w: var Writer; dest: var IcBuilder; n: PNode; forAst = false) =
|
||||
if n == nil:
|
||||
dest.addDotToken
|
||||
else:
|
||||
if nfLazyBody in n.flags and forceLazyBodyHook != nil:
|
||||
# Materialize a deferred body before serializing so its real flags/typ and
|
||||
# children are written (never the empty `nfLazyBody` placeholder).
|
||||
forceLazyBodyHook(n)
|
||||
case n.kind
|
||||
of nkNone:
|
||||
assert n.typField == nil, "nkNone should not have a type"
|
||||
@@ -1767,8 +1773,18 @@ type
|
||||
semIndex: Table[string, NifIndexEntry]
|
||||
semTried: bool # `semBuf`/`semIndex` load attempted (idempotent)
|
||||
|
||||
PendingBody = object
|
||||
## A deferred routine body (bodyPos son). `cursor` points AT the body node in
|
||||
## the module buffer (kept alive by the cursor's refcounted owner); `localSyms`
|
||||
## is the snapshot of the enclosing sym def's local symbols so body-local
|
||||
## references resolve to the SAME PSyms the signature already created.
|
||||
cursor: Cursor
|
||||
thisModule: string
|
||||
localSyms: Table[string, PSym]
|
||||
|
||||
DecodeContext* = object
|
||||
infos: LineInfoWriter
|
||||
pendingBodies: Table[int, PendingBody] # nodeId(placeholder) -> deferred body
|
||||
#moduleIds: Table[string, int32]
|
||||
types: Table[string, (PType, NifIndexEntry)]
|
||||
syms: Table[string, (PSym, NifIndexEntry)]
|
||||
@@ -1778,11 +1794,60 @@ type
|
||||
## Mangled module name of the module being compiled fresh (cmdM). Symbols
|
||||
## belonging to it that are re-exported by a dependency must NOT be loaded
|
||||
## as stubs, otherwise they collide with the freshly compiled originals.
|
||||
symLoads, typeLoads: CountTable[FileIndex]
|
||||
## Diagnostics (opt-in via env `NIM_IC_LOADSTATS`): per OWNING-module count
|
||||
## of stub materializations in THIS process. Quantifies the "every backend
|
||||
## worker deserializes system.bif + a bunch of others" cost — breadth (how
|
||||
## many syms) attributed to duplication axis (which shared module).
|
||||
|
||||
proc createDecodeContext*(config: ConfigRef; cache: IdentCache): DecodeContext =
|
||||
## Supposed to be a global variable
|
||||
result = DecodeContext(infos: LineInfoWriter(config: config), cache: cache)
|
||||
|
||||
var loadStatsInit {.threadvar.}: int # 0=unknown 1=on 2=off
|
||||
var statsCtxPtr {.threadvar.}: ptr DecodeContext
|
||||
var loaderCtx {.threadvar.}: ptr DecodeContext # the live `program`; for lazy-body
|
||||
# materialization off the len hook
|
||||
var nodesDecoded {.threadvar.}: int # all PNodes materialized this proc
|
||||
var astFieldNodes {.threadvar.}: int # subset: routine-body (s.ast) subtrees
|
||||
|
||||
proc dumpLoadStatsExit() {.noconv.} =
|
||||
if statsCtxPtr == nil: return
|
||||
let c = statsCtxPtr
|
||||
var merged = initTable[FileIndex, array[2, int]]()
|
||||
for m, cnt in c.symLoads.pairs: merged.mgetOrPut(m, [0, 0])[0] = cnt
|
||||
for m, cnt in c.typeLoads.pairs: merged.mgetOrPut(m, [0, 0])[1] = cnt
|
||||
var order: seq[FileIndex] = @[]
|
||||
var totS, totT: int = 0
|
||||
for m, a in merged:
|
||||
order.add m
|
||||
totS += a[0]; totT += a[1]
|
||||
sort(order, proc (a, b: FileIndex): int =
|
||||
(merged[b][0] + merged[b][1]) - (merged[a][0] + merged[a][1]))
|
||||
let params = commandLineParams()
|
||||
let target = if params.len > 0: params[^1] else: "?"
|
||||
stderr.writeLine "=== IC loadstats pid=" & $getCurrentProcessId() &
|
||||
" main=" & c.mainModuleSuffix & " target=" & target & " ==="
|
||||
stderr.writeLine " TOTAL symLoads=" & $totS & " typeLoads=" & $totT &
|
||||
" modulesTouched=" & $order.len
|
||||
let pct = if nodesDecoded > 0: 100 * astFieldNodes div nodesDecoded else: 0
|
||||
stderr.writeLine " PNODES decoded=" & $nodesDecoded & " routineBody=" &
|
||||
$astFieldNodes & " (" & $pct & "% deferrable via lazy PSym.ast)"
|
||||
for m in order:
|
||||
let a = merged[m]
|
||||
let name = if c.mods.hasKey(m): c.mods[m].suffix else: "?"
|
||||
stderr.writeLine " " & $(a[0] + a[1]) & "\tsym=" & $a[0] & " typ=" & $a[1] &
|
||||
"\t" & name
|
||||
|
||||
proc recordLoad(c: var DecodeContext; m: FileIndex; isType: bool) =
|
||||
if loadStatsInit == 0:
|
||||
loadStatsInit = if existsEnv("NIM_IC_LOADSTATS"): 1 else: 2
|
||||
if loadStatsInit == 1:
|
||||
statsCtxPtr = addr c
|
||||
addExitProc(dumpLoadStatsExit)
|
||||
if loadStatsInit == 2: return
|
||||
if isType: c.typeLoads.inc(m) else: c.symLoads.inc(m)
|
||||
|
||||
proc nextBackendSymItem*(c: var DecodeContext; module: int32): int32 =
|
||||
## Allocate the next backend-minted SYM item for `module` from the SAME
|
||||
## per-module counter the loader uses when it re-homes `@bk` syms loaded from
|
||||
@@ -2317,6 +2382,7 @@ proc loadTypeFromCursor(c: var DecodeContext; n: var Cursor; t: PType; localSyms
|
||||
proc loadType*(c: var DecodeContext; t: PType) =
|
||||
if t.state != Partial: return
|
||||
t.state = c.loadedState
|
||||
recordLoad(c, t.itemId.module.FileIndex, isType = true)
|
||||
# A backend-minted (`@bk`) closure-env type produced by the `lower` stage lives
|
||||
# ONLY in the `.t.nif` and is keyed by its `@bk` name (see nifTypeName), not the
|
||||
# canonical `typeToNifSym` (which asserts non-`@bk`). Reconstruct that name so a
|
||||
@@ -2410,7 +2476,10 @@ proc loadSymFromCursor(c: var DecodeContext; s: PSym; n: var Cursor; thisModule:
|
||||
s.ownerFieldImpl = loadSymStub(c, n, thisModule, localSyms)
|
||||
# Load the AST for routine symbols and constants
|
||||
# Constants need their AST for astdef() to return the constant's value
|
||||
let astNodesBefore = nodesDecoded
|
||||
s.astImpl = loadNode(c, n, thisModule, localSyms)
|
||||
if loadStatsInit == 1 and s.kindImpl in routineKinds:
|
||||
astFieldNodes += nodesDecoded - astNodesBefore
|
||||
loadLoc c, n, s.locImpl
|
||||
s.constraintImpl = loadNode(c, n, thisModule, localSyms)
|
||||
s.instantiatedFromImpl = loadSymStub(c, n, thisModule, localSyms)
|
||||
@@ -2437,6 +2506,8 @@ proc loadSymFromCursor(c: var DecodeContext; s: PSym; n: var Cursor; thisModule:
|
||||
proc loadSym*(c: var DecodeContext; s: PSym) =
|
||||
if s.state != Partial: return
|
||||
s.state = c.loadedState
|
||||
if loaderCtx == nil: loaderCtx = addr c
|
||||
recordLoad(c, s.itemId.module.FileIndex, isType = false)
|
||||
let symsModule = s.itemId.module.FileIndex
|
||||
let nifname = globalName(s, c.infos.config)
|
||||
var n = cursorFromIndexEntry(c, symsModule, c.syms[nifname][1])
|
||||
@@ -2489,6 +2560,7 @@ template withNode(c: var DecodeContext; n: var Cursor; result: PNode; kind: TNod
|
||||
|
||||
proc loadNode(c: var DecodeContext; n: var Cursor; thisModule: string;
|
||||
localSyms: var Table[string, PSym]): PNode =
|
||||
if loadStatsInit == 1: inc nodesDecoded
|
||||
result = nil
|
||||
case n.kind
|
||||
of Symbol:
|
||||
@@ -2677,6 +2749,27 @@ proc loadNode(c: var DecodeContext; n: var Cursor; thisModule: string;
|
||||
of nkNilLit:
|
||||
c.withNode n, result, kind:
|
||||
discard
|
||||
of routineDefs:
|
||||
# Defer the heavy `bodyPos` son: build the routine-def header eagerly, but
|
||||
# install a `nfLazyBody` placeholder (carrying the real body kind, so cheap
|
||||
# `ast[bodyPos].kind != nkEmpty` checks need no load) whose children are
|
||||
# materialized on demand (see `materializeLazyBody`, driven by the `len`
|
||||
# hook). An empty body is a single node — not worth deferring.
|
||||
c.withNode n, result, kind:
|
||||
var idx = 0
|
||||
while n.hasMore:
|
||||
if idx == bodyPos and n.kind == TagLit and
|
||||
n.nodeKind notin {nkEmpty, nkNone}:
|
||||
let info = c.infos.oldLineInfo(n.info, cursorPool(n))
|
||||
let ph = newNodeI(n.nodeKind, info)
|
||||
ph.flags.incl nfLazyBody
|
||||
c.pendingBodies[cast[int](ph)] =
|
||||
PendingBody(cursor: n, thisModule: thisModule, localSyms: localSyms)
|
||||
result.sons.add ph
|
||||
skip n
|
||||
else:
|
||||
result.sons.add c.loadNode(n, thisModule, localSyms)
|
||||
inc idx
|
||||
else:
|
||||
c.withNode n, result, kind:
|
||||
while n.hasMore:
|
||||
@@ -2684,6 +2777,26 @@ proc loadNode(c: var DecodeContext; n: var Cursor; thisModule: string;
|
||||
else:
|
||||
raiseAssert "expected string literal but got " & $n.kind
|
||||
|
||||
proc materializeLazyBody*(c: var DecodeContext; node: PNode) =
|
||||
## Fill a `nfLazyBody` placeholder's children in place (identity-preserving:
|
||||
## callers already hold `node`). Decodes the deferred body from the stashed
|
||||
## cursor with the enclosing def's `localSyms` so param/local refs resolve to
|
||||
## the SAME PSyms the signature created.
|
||||
node.flags.excl nfLazyBody # clear first: the loadNode below calls `len`
|
||||
let key = cast[int](node)
|
||||
var pb = PendingBody()
|
||||
if not c.pendingBodies.pop(key, pb): return
|
||||
var cur = pb.cursor
|
||||
let real = c.loadNode(cur, pb.thisModule, pb.localSyms)
|
||||
# `real` has the same kind as the placeholder (peeked at defer time); graft its
|
||||
# decoded content onto the node the callers hold.
|
||||
node.sons = real.sons
|
||||
node.typField = real.typField
|
||||
node.flags = real.flags
|
||||
|
||||
forceLazyBodyHook = proc (n: PNode) {.nimcall.} =
|
||||
if loaderCtx != nil: materializeLazyBody(loaderCtx[], n)
|
||||
|
||||
proc loadSymFromIndexEntry(c: var DecodeContext; module: FileIndex;
|
||||
nifName: string; entry: NifIndexEntry; thisModule: string): PSym =
|
||||
## Loads a symbol from the NIF index entry using the entry directly.
|
||||
|
||||
@@ -339,6 +339,9 @@ type
|
||||
# because openSym experimental switch is disabled
|
||||
# gives warning instead
|
||||
nfLazyType # node has a lazy type
|
||||
nfLazyBody # IC: this node is a placeholder for a routine body (bodyPos son)
|
||||
# not yet materialized. Reading its children (via `len`/`safeLen`)
|
||||
# triggers `forceLazyBodyHook`. Process-local, stripped on serialize.
|
||||
|
||||
TNodeFlags* = set[TNodeFlag]
|
||||
TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 47)
|
||||
@@ -903,7 +906,15 @@ const
|
||||
defaultOffset* = -1
|
||||
|
||||
|
||||
var forceLazyBodyHook*: proc (n: PNode) {.nimcall.}
|
||||
## Set by the IC loader (ast2nif). When a node carries `nfLazyBody`, any access
|
||||
## to its children through `len` materializes the deferred routine body in place.
|
||||
## `safeLen` delegates to `len`, so it is covered transitively; a lazy body is
|
||||
## never a leaf kind, so the `{nkNone..nkNilLit}` short-circuit never hides it.
|
||||
|
||||
proc len*(n: PNode): int {.inline.} =
|
||||
if nfLazyBody in n.flags and forceLazyBodyHook != nil:
|
||||
forceLazyBodyHook(n)
|
||||
result = n.sons.len
|
||||
|
||||
proc safeLen*(n: PNode): int {.inline.} =
|
||||
|
||||
@@ -3957,6 +3957,13 @@ proc getDefaultValue(p: BProc; typ: PType; info: TLineInfo; result: var Builder)
|
||||
let elemTyp = skipTypes(t.elementType, abstractRange+{tyOwned}-{tyTypeDesc})
|
||||
if isOpaqueImportcType(elemTyp):
|
||||
result.add "{0}"
|
||||
elif toInt(lengthOrd(p.config, t.indexType)) > broadcastArrayThreshold and
|
||||
elemTyp.kind in {tyInt..tyUInt64, tyBool, tyChar, tyFloat..tyFloat128,
|
||||
tyPtr, tyPointer, tyCstring}:
|
||||
# Large array of a scalar whose default is the zero representation: a single
|
||||
# C `{0}` zero-fills all `lengthOrd` slots instead of emitting that many
|
||||
# initializers (keeps huge SSZ-style zero buffers compact in the C output).
|
||||
result.add "{0}"
|
||||
else:
|
||||
var arrInit: StructInitializer
|
||||
result.addStructInitializer(arrInit, kind = siArray):
|
||||
@@ -4243,7 +4250,13 @@ proc genBracedInit(p: BProc, n: PNode; isConst: bool; optionalType: PType; resul
|
||||
var d: TLoc = initLocExpr(p, n)
|
||||
result.add rdLoc(d)
|
||||
of tyArray, tyVarargs:
|
||||
genConstSimpleList(p, n, isConst, result)
|
||||
if isDefaultBroadcastArray(n, p.config):
|
||||
# Compact zero/null-default array (see `isDefaultBroadcastArray`): the
|
||||
# whole thing is the null value of every slot, so a single C `{0}`
|
||||
# zero-fills all `lengthOrd` elements — no need to materialise them.
|
||||
result.add "{0}"
|
||||
else:
|
||||
genConstSimpleList(p, n, isConst, result)
|
||||
of tyTuple:
|
||||
genConstTuple(p, n, isConst, typ, result)
|
||||
of tyOpenArray:
|
||||
|
||||
@@ -819,7 +819,11 @@ proc genRecordFieldsAux(m: BModule; n: PNode,
|
||||
# don't use fieldType here because we need the
|
||||
# tyGenericInst for C++ template support
|
||||
let noInit = sfNoInit in field.flags or (field.typ.sym != nil and sfNoInit in field.typ.sym.flags)
|
||||
if not noInit and (fieldType.isOrHasImportedCppType() or hasCppCtor(m, field.owner.typ)):
|
||||
# Under `nim ic`, object fields are local NIF syms restored without an
|
||||
# `owner`; `rectype` is the owning record type, so fall back to it rather
|
||||
# than deref a nil `field.owner`.
|
||||
let ownerTyp = if field.owner != nil: field.owner.typ else: rectype
|
||||
if not noInit and (fieldType.isOrHasImportedCppType() or hasCppCtor(m, ownerTyp)):
|
||||
var didGenTemp = false
|
||||
initializer = genCppInitializer(m, nil, fieldType, didGenTemp)
|
||||
result.addField(field, sname, typ, isFlexArray, initializer)
|
||||
|
||||
@@ -1473,6 +1473,7 @@ proc genFlags*(s: set[TNodeFlag]; dest: var string) =
|
||||
of nfSkipFieldChecking: dest.add "s0"
|
||||
of nfDisabledOpenSym: dest.add "d3"
|
||||
of nfLazyType: dest.add "l1"
|
||||
of nfLazyBody: discard # process-local placeholder; never serialized
|
||||
|
||||
|
||||
proc parse*(t: typedesc[TNodeFlag]; s: string): set[TNodeFlag] =
|
||||
|
||||
@@ -906,6 +906,10 @@ proc needsCompilation*(g: ModuleGraph, fileIdx: FileIndex): bool =
|
||||
|
||||
proc getBody*(g: ModuleGraph; s: PSym): PNode {.inline.} =
|
||||
result = s.ast[bodyPos]
|
||||
if result != nil and nfLazyBody in result.flags and forceLazyBodyHook != nil:
|
||||
# Sanctioned body-access gate (see astdef.bodyPos): materialize the deferred
|
||||
# IC body so callers may safely touch `.sons` directly, not only via `len`.
|
||||
forceLazyBodyHook(result)
|
||||
assert result != nil
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
|
||||
@@ -29,7 +29,7 @@ const
|
||||
|
||||
nimEnableCovariance* = defined(nimEnableCovariance)
|
||||
|
||||
icFormatVersion* = "26"
|
||||
icFormatVersion* = "27"
|
||||
## Version of the IC cache format (the sem-NIF module layout written by
|
||||
## ast2nif.nim plus the iface/impl/edges side files). Bump it whenever
|
||||
## that layout changes: `commandIc` wipes a nimcache whose `ic.version`
|
||||
|
||||
@@ -476,7 +476,12 @@ proc foldArrayAccess(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNo
|
||||
#localError(g.config, n.info, formatErrorIndexBound(idx, x.len-1) & $n)
|
||||
of nkBracket:
|
||||
idx -= toInt64(firstOrd(g.config, x.typ))
|
||||
if idx >= 0 and idx < x.len: result = x[int(idx)]
|
||||
if isDefaultBroadcastArray(x, g.config):
|
||||
# compact default array: any in-bounds index folds to the default element
|
||||
if idx >= 0 and idx < toInt64(lengthOrd(g.config, x.typ.skipTypes(abstractInst))):
|
||||
result = copyTree(x[0])
|
||||
else: result = nil
|
||||
elif idx >= 0 and idx < x.len: result = x[int(idx)]
|
||||
else:
|
||||
result = nil
|
||||
#localError(g.config, n.info, formatErrorIndexBound(idx, x.len-1) & $n)
|
||||
|
||||
@@ -633,6 +633,31 @@ proc lengthOrd*(conf: ConfigRef; t: PType): Int128 =
|
||||
let first = firstOrd(conf, t)
|
||||
result = last - first + One
|
||||
|
||||
const broadcastArrayThreshold* = 32
|
||||
## `getNullValue` represents the default of an `array[N, T]` with `N` above this
|
||||
## as a single *broadcast* element — a one-son `nkBracket` standing for `N`
|
||||
## identical zero copies — instead of materialising `N` zero nodes. This keeps
|
||||
## huge zeroed arrays (e.g. SSZ byte buffers in nimbus) compact in the IC caches
|
||||
## (`.s.bif`/`.t.bif`), in the VM, and in the generated C (`{0}` zero-fills).
|
||||
|
||||
proc isDefaultBroadcastArray*(n: PNode; conf: ConfigRef): bool =
|
||||
## True iff `n` is a broadcast default array: one son that stands for
|
||||
## `lengthOrd` identical copies. A normal post-sem array literal always has
|
||||
## exactly `lengthOrd` sons, so `len == 1 < lengthOrd` is an unambiguous marker.
|
||||
result = n != nil and n.kind == nkBracket and n.len == 1 and n.typ != nil and
|
||||
n.typ.skipTypes(abstractInst).kind == tyArray and
|
||||
lengthOrd(conf, n.typ.skipTypes(abstractInst)) > One
|
||||
|
||||
proc expandBroadcastArray*(n: PNode; conf: ConfigRef) =
|
||||
## Materialise a broadcast default array (see `isDefaultBroadcastArray`) into a
|
||||
## full `lengthOrd`-son `nkBracket`, each son a copy of the single default
|
||||
## element. Used by VM ops that index-address, mutate, or measure such a node;
|
||||
## the common read-only paths leave it compact.
|
||||
if isDefaultBroadcastArray(n, conf):
|
||||
let total = toInt(lengthOrd(conf, n.typ.skipTypes(abstractInst)))
|
||||
let elem = n[0]
|
||||
for i in 1 ..< total: n.add copyTree(elem)
|
||||
|
||||
# -------------- type equality -----------------------------------------------
|
||||
|
||||
type
|
||||
|
||||
@@ -702,6 +702,10 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||
# A bodge, but this takes in `toOpenArray(rb, rc, rc)` and emits
|
||||
# nkTupleConstr(x, y, z) into the `regs[ra]`. These can later be used for calculating the slice we have taken.
|
||||
decodeBC(rkNode)
|
||||
# Slicing/openArray needs the real length and per-element nodes, so a
|
||||
# compact default array must be materialised first.
|
||||
if isDefaultBroadcastArray(regs[ra].node, c.config):
|
||||
expandBroadcastArray(regs[ra].node, c.config)
|
||||
let
|
||||
collection = regs[ra].node
|
||||
leftInd = regs[rb].intVal
|
||||
@@ -770,6 +774,15 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||
regs[ra].node.intVal = src.strVal[idx].ord
|
||||
else:
|
||||
stackTrace(c, tos, pc, formatErrorIndexBound(idx, src.strVal.len-1))
|
||||
elif isDefaultBroadcastArray(src, c.config):
|
||||
# `a[i]` on a compact default array yields `default(T)` directly, without
|
||||
# ever materialising the (potentially huge) array — the point of the
|
||||
# broadcast form. See `getNullValue`/`isDefaultBroadcastArray`.
|
||||
let total = toInt(lengthOrd(c.config, src.typ.skipTypes(abstractInst)))
|
||||
if idx <% total:
|
||||
regs[ra].node = copyTree(src[0])
|
||||
else:
|
||||
stackTrace(c, tos, pc, formatErrorIndexBound(idx, total-1))
|
||||
elif src.kind notin {nkEmpty..nkFloat128Lit} and idx <% src.len:
|
||||
regs[ra].node = src[idx]
|
||||
else:
|
||||
@@ -781,6 +794,9 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||
stackTrace(c, tos, pc, formatErrorIndexBound(regs[rc].intVal, high(int)))
|
||||
let idx = regs[rc].intVal.int
|
||||
let src = if regs[rb].kind == rkNode: regs[rb].node else: regs[rb].nodeAddr[]
|
||||
# Taking the address of an element needs distinct, stable per-slot nodes, so
|
||||
# a compact default array must be materialised first.
|
||||
if isDefaultBroadcastArray(src, c.config): expandBroadcastArray(src, c.config)
|
||||
case src.kind
|
||||
of nkTupleConstr:
|
||||
let
|
||||
@@ -829,6 +845,8 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||
let idx = regs[rb].intVal.int
|
||||
assert regs[ra].kind == rkNode
|
||||
let arr = regs[ra].node
|
||||
# Writing a slot materialises a compact default array into a full literal.
|
||||
if isDefaultBroadcastArray(arr, c.config): expandBroadcastArray(arr, c.config)
|
||||
case arr.kind
|
||||
of nkTupleConstr: # refer to `opcSlice`
|
||||
let
|
||||
@@ -1031,6 +1049,8 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||
case node.kind
|
||||
of nkTupleConstr: # refer to `of opcSlice`
|
||||
regs[ra].intVal = node[2].intVal - node[1].intVal + 1 - high
|
||||
elif isDefaultBroadcastArray(node, c.config):
|
||||
regs[ra].intVal = toInt(lengthOrd(c.config, node.typ.skipTypes(abstractInst))) - high
|
||||
else:
|
||||
# safeArrLen also return string node len
|
||||
# used when string is passed as openArray in VM
|
||||
|
||||
@@ -2029,8 +2029,16 @@ proc getNullValue(c: PCtx; typ: PType, info: TLineInfo; conf: ConfigRef): PNode
|
||||
getNullValueAux(c, t, t.n, result, conf, currPosition)
|
||||
of tyArray:
|
||||
result = newNodeIT(nkBracket, info, t)
|
||||
for i in 0..<toInt(lengthOrd(conf, t)):
|
||||
let n = toInt(lengthOrd(conf, t))
|
||||
if n > 0:
|
||||
result.add getNullValue(c, elemType(t), info, conf)
|
||||
# For a large array, keep a single broadcast element (the default of every
|
||||
# slot is identical) instead of `n` copies; `isDefaultBroadcastArray`
|
||||
# consumers expand on demand. Small arrays stay fully materialised so the
|
||||
# well-trodden paths are untouched. See `broadcastArrayThreshold`.
|
||||
if n <= broadcastArrayThreshold:
|
||||
for i in 1..<n:
|
||||
result.add getNullValue(c, elemType(t), info, conf)
|
||||
of tyTuple:
|
||||
result = newNodeIT(nkTupleConstr, info, t)
|
||||
for a in t.kids:
|
||||
|
||||
Reference in New Issue
Block a user