mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-02 03:43:41 +00:00
`trees.nim` can import `bnode` — nothing in `bnode`'s import closure reaches
`trees`, checked rather than assumed — so the shared helpers move to `AnyNode`
instead of being reimplemented behind the seam: `getMagic`, `whichPragma`,
`getRoot`, `isDeepConstExpr`, plus `ccgutils.stmtsContainPragma`. That unblocks
three more codegen procs, `canMove`, `notYetAlive` and `ifSwitchSplitPoint`,
which needed them and nothing else.
`stmtsContainPragma` could not simply stay `getPragmaStmt(n, w) != nil`, and
the reason is worth recording because it will recur: a proc that returns a node
OR NIL is the one shape the seam cannot serve. `.bif` spells a missing child as
a `DotToken` *inside* a tree; there is no nil token to hand back as a return
value and a `Cursor` is not nilable. So the predicate is split out — and,
because that leaves two copies of one traversal, `grindPredicates` now asserts
the two agree at every node instead of trusting them to.
Measuring the answers, not just the agreement, again earned its keep. Six of
the new checks came back with a wide spread (`getMagic` 7780 non-`mNone` over
many magics, `getRoot` 19506 non-nil syms compared by identity, `isDeepConstExpr`
7917 true, `notYetAlive` 9653 true). Two came back CONSTANT — `stmtsContainPragma`
false at all 67_721 nodes and `ifSwitchSplitPoint` zero at all 24 — because
nothing in the closure uses `{.linearScanEnd.}` or `{.computedGoto.}`. Both are
now exercised on both answers by shapes added to `tools/icgrind`. A check that
grades a constant is indistinguishable from a passing check in the output, so
this only shows up if the distribution is looked at.
Verified: grind clean over the whole `--ic:on` closure (67_857 nodes, 0
disagreements); the target's `--ic:on` output matches its `nim c` output;
215/215 byte-identical `.c` against HEAD on the default path; all four build
configurations compile.
Sabotaging `bnode.secondSon` — an accessor the lockstep walk does NOT itself
use, since it descends by index — is caught only by this layer, and is: it
fires on `getRoot`, `isDeepConstExpr`, `reifiedOpenArray` and
`skipTrivialIndirections`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEF7FJvUkGKvG9LSGuEaNR
208 lines
7.2 KiB
Nim
208 lines
7.2 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2012 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
# This module declares some helpers for the C code generator.
|
|
|
|
import
|
|
ast, types, msgs, wordrecg,
|
|
platform, trees, options, cgendata, mangleutils, renderer, modulegraphs, bnode
|
|
|
|
import std/[hashes, strutils, formatfloat]
|
|
|
|
when defined(nimPreviewSlimSystem):
|
|
import std/assertions
|
|
|
|
proc getPragmaStmt*(n: PNode, w: TSpecialWord): PNode =
|
|
case n.kind
|
|
of nkStmtList:
|
|
result = nil
|
|
for it in sons(n):
|
|
result = getPragmaStmt(it, w)
|
|
if result != nil: break
|
|
of nkPragma:
|
|
result = nil
|
|
for it in sons(n):
|
|
if whichPragma(it) == w: return it
|
|
else:
|
|
result = nil
|
|
|
|
proc stmtsContainPragma*(n: AnyNode, w: TSpecialWord): bool =
|
|
## Deliberately NOT `getPragmaStmt(n, w) != nil`, and the reason is the one
|
|
## shape the `AnyNode` seam cannot serve: a proc that returns a node OR nil.
|
|
## `.bif` spells a missing child as a `DotToken` *inside* a tree, so there is
|
|
## no nil token to hand back as a return value, and a `Cursor` is not nilable.
|
|
## Predicates split out from such a proc are the way across.
|
|
##
|
|
## The duplicated traversal is the cost, and it is checked rather than
|
|
## trusted: `grindPredicates` asserts this answers exactly
|
|
## `getPragmaStmt(n, w) != nil` at every node, so the two cannot drift apart
|
|
## silently.
|
|
case n.kind
|
|
of nkStmtList:
|
|
result = false
|
|
for it in sons(n):
|
|
if stmtsContainPragma(it, w): return true
|
|
of nkPragma:
|
|
result = false
|
|
for it in sons(n):
|
|
if whichPragma(it) == w: return true
|
|
else:
|
|
result = false
|
|
|
|
proc hashString*(conf: ConfigRef; s: string): BiggestInt =
|
|
# has to be the same algorithm as strmantle.hashString!
|
|
if CPU[conf.target.targetCPU].bit == 64:
|
|
# we have to use the same bitwidth
|
|
# as the target CPU
|
|
var b = 0'u64
|
|
for i in 0..<s.len:
|
|
b = b + uint(s[i])
|
|
b = b + (b shl 10)
|
|
b = b xor (b shr 6)
|
|
b = b + (b shl 3)
|
|
b = b xor (b shr 11)
|
|
b = b + (b shl 15)
|
|
result = cast[Hash](b)
|
|
else:
|
|
var a = 0'u32
|
|
for i in 0..<s.len:
|
|
a = a + uint32(s[i])
|
|
a = a + (a shl 10)
|
|
a = a xor (a shr 6)
|
|
a = a + (a shl 3)
|
|
a = a xor (a shr 11)
|
|
a = a + (a shl 15)
|
|
result = cast[Hash](uint(a))
|
|
|
|
template getUniqueType*(key: PType): PType = key
|
|
|
|
proc makeSingleLineCString*(s: string): string =
|
|
result = "\""
|
|
for c in items(s):
|
|
c.toCChar(result)
|
|
result.add('\"')
|
|
|
|
proc mapSetType(conf: ConfigRef; typ: PType): TCTypeKind =
|
|
case int(getSize(conf, typ))
|
|
of 1: result = ctInt8
|
|
of 2: result = ctInt16
|
|
of 4: result = ctInt32
|
|
of 8: result = ctInt64
|
|
else: result = ctArray
|
|
|
|
proc ccgIntroducedPtr*(conf: ConfigRef; s: PSym, retType: PType): bool =
|
|
var pt = skipTypes(s.typ, typedescInst)
|
|
assert skResult != s.kind
|
|
|
|
#note precedence: params override types
|
|
if optByRef in s.options: return true
|
|
elif sfByCopy in s.flags: return false
|
|
elif tfByRef in pt.flags: return true
|
|
elif tfByCopy in pt.flags: return false
|
|
case pt.kind
|
|
of tyObject:
|
|
if s.typ.sym != nil and sfForward in s.typ.sym.flags:
|
|
# forwarded objects are *always* passed by pointers for consistency!
|
|
result = true
|
|
elif (optByRef in s.options) or (getSize(conf, pt) > conf.target.floatSize * 3):
|
|
result = true # requested anyway
|
|
elif (tfFinal in pt.flags) and (pt.baseClass == nil):
|
|
result = false # no need, because no subtyping possible
|
|
else:
|
|
result = true # ordinary objects are always passed by reference,
|
|
# otherwise casting doesn't work
|
|
of tyTuple:
|
|
result = (getSize(conf, pt) > conf.target.floatSize*3) or (optByRef in s.options)
|
|
else:
|
|
result = false
|
|
# first parameter and return type is 'lent T'? --> use pass by pointer
|
|
if s.position == 0 and retType != nil and retType.kind == tyLent:
|
|
result = not (pt.kind in {tyVar, tyArray, tyOpenArray, tyVarargs, tyRef, tyPtr, tyPointer} or
|
|
pt.kind == tySet and mapSetType(conf, pt) == ctArray)
|
|
|
|
proc encodeName*(name: string): string =
|
|
result = mangle(name)
|
|
result = $result.len & result
|
|
|
|
proc makeUnique(m: BModule; s: PSym, name: string = ""): string =
|
|
result = if name == "": s.name.s else: name
|
|
# keep backend-minted ids out of the `_u` namespace; their item counter
|
|
# restarts at 0 and would collide with loaded symbols' ids. Which integer
|
|
# identifies such a symbol is decided ONCE, in `astdef.backendMintedDisamb`,
|
|
# shared with `mangleProcNameExt` and `ast2nif.toNifSymName`.
|
|
if s.itemId.isBackendMinted:
|
|
result.add "_c"
|
|
result.add $backendMintedDisamb(s)
|
|
else:
|
|
result.add "_u"
|
|
# Mirror `mangleProcNameExt`: use the per-(module,name) `disamb`, NOT
|
|
# `itemId.item`. Under the per-module IC backend the same symbol is loaded
|
|
# from a NIF in many processes and `itemId.item` is a fresh, load-order
|
|
# dependent counter — so a method base would mangle to `_u1` in one module,
|
|
# `_u3` in another and clean at its owner, none of which link. `disamb` is
|
|
# assigned deterministically per (module, name) and is serialized, so every
|
|
# process that touches the symbol derives the identical C name.
|
|
result.add $s.disamb
|
|
# module suffix LAST (a strippable trailing token; see `mangleProcNameExt`)
|
|
result.add "__"
|
|
result.add m.g.graph.ifaces[s.itemId.module].uniqueName
|
|
|
|
proc encodeSym*(m: BModule; s: PSym; makeUnique: bool = false; extra: string = ""): string =
|
|
#Module::Type
|
|
var name = s.name.s & extra
|
|
if makeUnique:
|
|
name = makeUnique(m, s, name)
|
|
"N" & encodeName(s.skipGenericOwner.name.s) & encodeName(name) & "E"
|
|
|
|
proc encodeType*(m: BModule; t: PType; staticLists: var string): string =
|
|
result = ""
|
|
var kindName = ($t.kind)[2..^1]
|
|
kindName[0] = toLower($kindName[0])[0]
|
|
case t.kind
|
|
of tyObject, tyEnum, tyDistinct, tyUserTypeClass, tyGenericParam:
|
|
result = encodeSym(m, t.sym)
|
|
of tyGenericInst, tyUserTypeClassInst, tyGenericBody:
|
|
result = encodeName(t.genericHead.sym.name.s)
|
|
result.add "I"
|
|
for i in 1..<t.len - 1:
|
|
result.add encodeType(m, t[i], staticLists)
|
|
result.add "E"
|
|
of tySequence, tyOpenArray, tyArray, tyVarargs, tyTuple, tyProc, tySet, tyTypeDesc,
|
|
tyPtr, tyRef, tyVar, tyLent, tySink, tyUncheckedArray, tyOr, tyAnd, tyBuiltInTypeClass:
|
|
result =
|
|
case t.kind:
|
|
of tySequence: encodeName("seq")
|
|
else: encodeName(kindName)
|
|
result.add "I"
|
|
for s in kids(t):
|
|
if s.isNil: continue
|
|
result.add encodeType(m, s, staticLists)
|
|
result.add "E"
|
|
of tyStatic:
|
|
if t.n != nil:
|
|
staticLists.add "_s" & renderTree(t.n)
|
|
else:
|
|
raiseAssert "unreachable"
|
|
of tyRange:
|
|
var val = "range_"
|
|
if t.n.firstSon.typ.kind in {tyFloat..tyFloat128}:
|
|
val.addFloat t.n.firstSon.floatVal
|
|
val.add "_"
|
|
val.addFloat t.n.secondSon.floatVal
|
|
else:
|
|
val.add $t.n.firstSon.intVal & "_" & $t.n.secondSon.intVal
|
|
result = encodeName(val)
|
|
of tyString..tyUInt64, tyPointer, tyBool, tyChar, tyVoid, tyAnything, tyNil, tyEmpty:
|
|
result = encodeName(kindName)
|
|
of tyAlias, tyInferred, tyOwned:
|
|
result = encodeType(m, t.elementType, staticLists)
|
|
else:
|
|
assert false, "encodeType " & $t.kind
|
|
|