Files
Nim/compiler/ccgexprs.nim
Araq 06b1bf8f9a IC: take the seam into trees and ccgutils
`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
2026-08-30 10:23:43 +02:00

4288 lines
164 KiB
Nim

#
#
# The Nim Compiler
# (c) Copyright 2013 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# included from cgen.nim
when defined(nimCompilerStacktraceHints):
import std/stackframes
proc getNullValueAuxT(p: BProc; orig, t: PType; obj, constOrNil: PNode,
result: var Builder; init: var StructInitializer;
isConst: bool, info: TLineInfo)
# -------------------------- constant expressions ------------------------
proc rdSetElemLoc(conf: ConfigRef; a: TLoc, typ: PType; result: var Rope)
proc genLiteral(p: BProc, n: PNode, ty: PType; result: var Builder) =
case n.kind
of nkCharLit..nkUInt64Lit:
var k: TTypeKind
if ty != nil:
k = skipTypes(ty, abstractVarRange).kind
else:
case n.kind
of nkCharLit: k = tyChar
of nkUInt64Lit: k = tyUInt64
of nkInt64Lit: k = tyInt64
else: k = tyNil # don't go into the case variant that uses 'ty'
case k
of tyChar, tyNil:
result.addIntLiteral(n.intVal)
of tyBool:
if n.intVal != 0: result.add NimTrue
else: result.add NimFalse
of tyInt64: result.addInt64Literal(n.intVal)
of tyUInt64: result.addUint64Literal(uint64(n.intVal))
else:
result.addCast(getTypeDesc(p.module, ty)):
result.addIntLiteral(n.intVal)
of nkNilLit:
let k = if ty == nil: tyPointer else: skipTypes(ty, abstractVarRange).kind
if k == tyProc and skipTypes(ty, abstractVarRange).callConv == ccClosure:
let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels)
let tmpName = p.module.tmpBase & rope(id)
if id == p.module.labels:
# not found in cache:
inc(p.module.labels)
let t = getTypeDesc(p.module, ty)
p.module.s[cfsStrData].addVarWithInitializer(kind = Const, name = tmpName, typ = t):
var closureInit: StructInitializer
p.module.s[cfsStrData].addStructInitializer(closureInit, kind = siOrderedStruct):
p.module.s[cfsStrData].addField(closureInit, name = "ClP_0"):
p.module.s[cfsStrData].add(NimNil)
p.module.s[cfsStrData].addField(closureInit, name = "ClE_0"):
p.module.s[cfsStrData].add(NimNil)
result.add tmpName
elif k in {tyPointer, tyNil, tyProc}:
result.add NimNil
else:
result.add cCast(getTypeDesc(p.module, ty), NimNil)
of nkStrLit..nkTripleStrLit:
let k = if ty == nil: tyString
else: skipTypes(ty, abstractVarRange + {tyStatic, tyUserTypeClass, tyUserTypeClassInst}).kind
case k
of tyNil:
genNilStringLiteral(p.module, n.info, result)
of tyString:
# with the new semantics for not 'nil' strings, we can map "" to nil and
# save tons of allocations:
if n.strVal.len == 0 and optSeqDestructors notin p.config.globalOptions:
genNilStringLiteral(p.module, n.info, result)
else:
genStringLiteral(p.module, n, result)
else:
result.add makeCString(n.strVal)
of nkFloatLit, nkFloat64Lit:
if ty.kind == tyFloat32:
result.add rope(n.floatVal.float32.toStrMaxPrecision)
else:
result.add rope(n.floatVal.toStrMaxPrecision)
of nkFloat32Lit:
result.add rope(n.floatVal.float32.toStrMaxPrecision)
else:
internalError(p.config, n.info, "genLiteral(" & $n.kind & ')')
proc genLiteral(p: BProc, n: PNode; result: var Builder) =
genLiteral(p, n, n.typ, result)
proc genRawSetData(cs: TBitSet, size: int; result: var Builder) =
if size > 8:
var setInit: StructInitializer
result.addStructInitializer(setInit, kind = siArray):
for i in 0..<size:
if i mod 8 == 0:
result.addNewline()
result.addField(setInit, name = ""):
result.add "0x"
result.add "0123456789abcdef"[cs[i] div 16]
result.add "0123456789abcdef"[cs[i] mod 16]
else:
result.addIntLiteral(cast[BiggestInt](bitSetToWord(cs, size)))
proc genSetNode(p: BProc, n: PNode; result: var Builder) =
var size = int(getSize(p.config, n.typ))
let cs = toBitSet(p.config, n)
if size > 8:
let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels)
let tmpName = p.module.tmpBase & rope(id)
if id == p.module.labels:
# not found in cache:
inc(p.module.labels)
let td = getTypeDesc(p.module, n.typ)
p.module.s[cfsStrData].addVarWithInitializer(kind = Const, name = tmpName, typ = td):
genRawSetData(cs, size, p.module.s[cfsStrData])
result.add tmpName
else:
genRawSetData(cs, size, result)
proc getStorageLoc(n: PNode): TStorageLoc =
## deadcode
case n.kind
of nkSym:
case n.sym.kind
of skParam, skTemp:
result = OnStack
of skVar, skForVar, skResult, skLet:
if sfGlobal in n.sym.flags: result = OnHeap
else: result = OnStack
of skConst:
if sfGlobal in n.sym.flags: result = OnHeap
else: result = OnUnknown
else: result = OnUnknown
of nkDerefExpr, nkHiddenDeref:
case n.firstSon.typ.kind
of tyVar, tyLent: result = OnUnknown
of tyPtr: result = OnStack
of tyRef: result = OnHeap
else:
result = OnUnknown
doAssert(false, "getStorageLoc")
of nkBracketExpr, nkDotExpr, nkObjDownConv, nkObjUpConv:
result = getStorageLoc(n.firstSon)
else: result = OnUnknown
proc canMove(p: BProc, n: AnyNode; dest: TLoc): bool =
# for now we're conservative here:
if n.kind == nkBracket:
# This needs to be kept consistent with 'const' seq code
# generation!
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:
# Empty strings are codegen'd as NIM_NIL so it's just a pointer copy
return true
result = n.kind in nkCallKinds
#if not result and dest.k == locTemp:
# return true
#if result:
# echo n.info, " optimized ", n
# result = false
template simpleAsgn(builder: var Builder, dest, src: TLoc) =
let rd = rdLoc(dest)
let rs = rdLoc(src)
builder.addAssignment(rd, rs)
proc genRefAssign(p: BProc, dest, src: TLoc) =
if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config):
simpleAsgn(p.s(cpsStmts), dest, src)
else:
let fnName =
if dest.storage == OnHeap: cgsymValue(p.module, "asgnRef")
else: cgsymValue(p.module, "unsureAsgnRef")
let rad = addrLoc(p.config, dest)
let rs = rdLoc(src)
p.s(cpsStmts).addCallStmt(fnName, cCast(ptrType(CPointer), rad), rs)
proc asgnComplexity(n: PNode): int =
if n != nil:
case n.kind
of nkSym: result = 1
of nkRecCase:
# 'case objects' are too difficult to inline their assignment operation:
result = 100
of nkRecList:
result = 0
for t in items(n):
result += asgnComplexity(t)
else: result = 0
else:
result = 0
proc optAsgnLoc(a: TLoc, t: PType, field: Rope): TLoc =
assert field != ""
result = TLoc(k: locField,
storage: a.storage,
lode: lodeTyp t,
snippet: rdLoc(a) & "." & field
)
proc genOptAsgnTuple(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
let newflags =
if src.storage == OnStatic:
flags + {needToCopy}
elif tfShallow in dest.t.flags:
flags - {needToCopy}
else:
flags
let t = skipTypes(dest.t, abstractInst).getUniqueType()
for i, t in t.ikids:
# Do not produce code for void types
if isEmptyType(t): continue
let field = "Field$1" % [i.rope]
genAssignment(p, optAsgnLoc(dest, t, field),
optAsgnLoc(src, t, field), newflags)
proc genOptAsgnObject(p: BProc, dest, src: TLoc, flags: TAssignmentFlags,
t: PNode, typ: PType) =
if t == nil: return
let newflags =
if src.storage == OnStatic:
flags + {needToCopy}
elif tfShallow in dest.t.flags:
flags - {needToCopy}
else:
flags
case t.kind
of nkSym:
let field = t.sym
if field.loc.snippet == "": fillObjectFields(p.module, typ)
genAssignment(p, optAsgnLoc(dest, field.typ, field.loc.snippet),
optAsgnLoc(src, field.typ, field.loc.snippet), newflags)
of nkRecList:
for child in items(t): genOptAsgnObject(p, dest, src, newflags, child, typ)
else: discard
proc genGenericAsgn(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
# Consider:
# type TMyFastString {.shallow.} = string
# Due to the implementation of pragmas this would end up to set the
# tfShallow flag for the built-in string type too! So we check only
# here for this flag, where it is reasonably safe to do so
# (for objects, etc.):
if optSeqDestructors in p.config.globalOptions:
simpleAsgn(p.s(cpsStmts), dest, src)
elif needToCopy notin flags or
tfShallow in skipTypes(dest.t, abstractVarRange).flags:
if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config):
let rad = addrLoc(p.config, dest)
let ras = addrLoc(p.config, src)
let rd = rdLoc(dest)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimCopyMem"),
cCast(CPointer, rad),
cCast(CConstPointer, ras),
cSizeof(rd))
else:
let rad = addrLoc(p.config, dest)
let ras = addrLoc(p.config, src)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "genericShallowAssign"),
cCast(CPointer, rad),
cCast(CPointer, ras),
genTypeInfoV1(p.module, dest.t, dest.lode.info))
else:
let rad = addrLoc(p.config, dest)
let ras = addrLoc(p.config, src)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "genericAssign"),
cCast(CPointer, rad),
cCast(CPointer, ras),
genTypeInfoV1(p.module, dest.t, dest.lode.info))
proc genOpenArrayConv(p: BProc; d: TLoc; a: TLoc; flags: TAssignmentFlags) =
assert d.k != locNone
# getTemp(p, d.t, d)
case a.t.skipTypes(abstractVar).kind
of tyOpenArray, tyVarargs:
if reifiedOpenArray(a.lode):
if needTempForOpenArray in flags:
var tmp: TLoc = getTemp(p, a.t)
let rtmp = tmp.rdLoc
let ra = a.rdLoc
p.s(cpsStmts).addAssignment(rtmp, ra)
let rd = d.rdLoc
p.s(cpsStmts).addMutualFieldAssignment(rd, rtmp, "Field0")
p.s(cpsStmts).addMutualFieldAssignment(rd, rtmp, "Field1")
else:
let rd = d.rdLoc
let ra = a.rdLoc
p.s(cpsStmts).addMutualFieldAssignment(rd, ra, "Field0")
p.s(cpsStmts).addMutualFieldAssignment(rd, ra, "Field1")
else:
let rd = d.rdLoc
let ra = a.rdLoc
p.s(cpsStmts).addFieldAssignment(rd, "Field0", ra)
p.s(cpsStmts).addFieldAssignment(rd, "Field1", ra & "Len_0")
of tySequence:
let rd = d.rdLoc
let ra = a.rdLoc
let la = lenExpr(p, a)
p.s(cpsStmts).addFieldAssignment(rd, "Field0",
cIfExpr(dataFieldAccessor(p, ra), dataField(p, ra), NimNil))
p.s(cpsStmts).addFieldAssignment(rd, "Field1", la)
of tyArray:
let rd = d.rdLoc
let ra = a.rdLoc
p.s(cpsStmts).addFieldAssignment(rd, "Field0", ra)
p.s(cpsStmts).addFieldAssignment(rd, "Field1", lengthOrd(p.config, a.t))
of tyString:
let etyp = skipTypes(a.t, abstractInst)
if etyp.kind in {tyVar} and optSeqDestructors in p.config.globalOptions:
let bra = byRefLoc(p, a)
p.s(cpsStmts).addCallStmt(
cgsymValue(p.module, "nimPrepareStrMutationV2"),
bra)
let rd = d.rdLoc
let la = lenExpr(p, a)
if p.config.usesSso():
let bra = byRefLoc(p, a)
p.s(cpsStmts).addFieldAssignment(rd, "Field0",
cCall(cgsymValue(p.module, "nimStrData"), bra))
else:
let ra = a.rdLoc
p.s(cpsStmts).addFieldAssignment(rd, "Field0",
cIfExpr(dataFieldAccessor(p, ra), dataField(p, ra), NimNil))
p.s(cpsStmts).addFieldAssignment(rd, "Field1", la)
else:
internalError(p.config, a.lode.info, "cannot handle " & $a.t.kind)
template cgCall(p: BProc, name: string, args: varargs[untyped]): untyped =
cCall(cgsymValue(p.module, name), args)
proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
# This function replaces all other methods for generating
# the assignment operation in C.
if src.t != nil and src.t.kind == tyPtr:
# little HACK to support the new 'var T' as return type:
simpleAsgn(p.s(cpsStmts), dest, src)
return
let ty = skipTypes(dest.t, abstractRange + tyUserTypeClasses + {tyStatic})
case ty.kind
of tyRef:
genRefAssign(p, dest, src)
of tySequence:
if optSeqDestructors in p.config.globalOptions:
genGenericAsgn(p, dest, src, flags)
elif (needToCopy notin flags and src.storage != OnStatic) or canMove(p, src.lode, dest):
genRefAssign(p, dest, src)
else:
let rad = addrLoc(p.config, dest)
let rs = rdLoc(src)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "genericSeqAssign"),
rad,
rs,
genTypeInfoV1(p.module, dest.t, dest.lode.info))
of tyString:
if optSeqDestructors in p.config.globalOptions:
genGenericAsgn(p, dest, src, flags)
elif (needToCopy notin flags and src.storage != OnStatic) or canMove(p, src.lode, dest):
genRefAssign(p, dest, src)
else:
if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config):
let rd = rdLoc(dest)
let rs = rdLoc(src)
p.s(cpsStmts).addAssignmentWithValue(rd):
p.s(cpsStmts).addCall(cgsymValue(p.module, "copyString"), rs)
elif dest.storage == OnHeap:
let rd = rdLoc(dest)
let rs = rdLoc(src)
# we use a temporary to care for the dreaded self assignment:
var tmp: TLoc = getTemp(p, ty)
let rtmp = rdLoc(tmp)
p.s(cpsStmts).addAssignment(rtmp, rd)
p.s(cpsStmts).addAssignmentWithValue(rd):
p.s(cpsStmts).addCall(cgsymValue(p.module, "copyStringRC1"), rs)
p.s(cpsStmts).addSingleIfStmt(rtmp):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimGCunrefNoCycle"), rtmp)
else:
let rad = addrLoc(p.config, dest)
let rs = rdLoc(src)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "unsureAsgnRef"),
cCast(ptrType(CPointer), rad),
cgCall(p, "copyString", rs))
of tyProc:
if containsGarbageCollectedRef(dest.t):
# optimize closure assignment:
let a = optAsgnLoc(dest, dest.t, "ClE_0".rope)
let b = optAsgnLoc(src, dest.t, "ClE_0".rope)
genRefAssign(p, a, b)
let rd = rdLoc(dest)
let rs = rdLoc(src)
p.s(cpsStmts).addMutualFieldAssignment(rd, rs, "ClP_0")
else:
simpleAsgn(p.s(cpsStmts), dest, src)
of tyTuple:
if containsGarbageCollectedRef(dest.t):
if dest.t.kidsLen <= 4: genOptAsgnTuple(p, dest, src, flags)
else: genGenericAsgn(p, dest, src, flags)
else:
simpleAsgn(p.s(cpsStmts), dest, src)
of tyObject:
# XXX: check for subtyping?
if ty.isImportedCppType:
simpleAsgn(p.s(cpsStmts), dest, src)
elif not isObjLackingTypeField(ty):
genGenericAsgn(p, dest, src, flags)
elif containsGarbageCollectedRef(ty):
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
genOptAsgnObject(p, dest, src, flags, ty.n, ty)
else:
genGenericAsgn(p, dest, src, flags)
else:
simpleAsgn(p.s(cpsStmts), dest, src)
of tyArray:
if containsGarbageCollectedRef(dest.t) and p.config.selectedGC notin {gcArc, gcAtomicArc, gcOrc, gcYrc, gcHooks}:
genGenericAsgn(p, dest, src, flags)
else:
let rd = rdLoc(dest)
let rs = rdLoc(src)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimCopyMem"),
cCast(CPointer, rd),
cCast(CConstPointer, rs),
cSizeof(getTypeDesc(p.module, dest.t)))
of tyOpenArray, tyVarargs:
# open arrays are always on the stack - really? What if a sequence is
# passed to an open array?
if reifiedOpenArray(dest.lode):
genOpenArrayConv(p, dest, src, flags)
elif containsGarbageCollectedRef(dest.t):
let rad = addrLoc(p.config, dest)
let ras = addrLoc(p.config, src)
# XXX: is this correct for arrays?
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "genericAssignOpenArray"),
cCast(CPointer, rad),
cCast(CPointer, ras),
rad & "Len_0",
genTypeInfoV1(p.module, dest.t, dest.lode.info))
else:
simpleAsgn(p.s(cpsStmts), dest, src)
#linefmt(p, cpsStmts,
# bug #4799, keep the nimCopyMem for a while
#"#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($1[0])*$1Len_0);\n")
of tySet:
if mapSetType(p.config, ty) == ctArray:
let rd = rdLoc(dest)
let rs = rdLoc(src)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimCopyMem"),
cCast(CPointer, rd),
cCast(CConstPointer, rs),
cIntValue(getSize(p.config, dest.t)))
else:
simpleAsgn(p.s(cpsStmts), dest, src)
of tyPtr, tyPointer, tyChar, tyBool, tyEnum, tyCstring,
tyInt..tyUInt64, tyRange, tyVar, tyLent, tyNil:
simpleAsgn(p.s(cpsStmts), dest, src)
else: internalError(p.config, "genAssignment: " & $ty.kind)
if optMemTracker in p.options and dest.storage in {OnHeap, OnUnknown}:
#writeStackTrace()
#echo p.currLineInfo, " requesting"
let rad = addrLoc(p.config, dest)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "memTrackerWrite"),
cCast(CPointer, rad),
cIntValue(getSize(p.config, dest.t)),
makeCString(toFullPath(p.config, p.currLineInfo)),
cIntValue(p.currLineInfo.safeLineNm))
proc genDeepCopy(p: BProc; dest, src: TLoc) =
template addrLocOrTemp(a: TLoc): Rope =
if a.k == locExpr:
var tmp: TLoc = getTemp(p, a.t)
genAssignment(p, tmp, a, {})
addrLoc(p.config, tmp)
else:
addrLoc(p.config, a)
var ty = skipTypes(dest.t, abstractVarRange + {tyStatic})
case ty.kind
of tyPtr, tyRef, tyProc, tyTuple, tyObject, tyArray:
# XXX optimize this
let rad = addrLoc(p.config, dest)
let rats = addrLocOrTemp(src)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "genericDeepCopy"),
cCast(CPointer, rad),
cCast(CPointer, rats),
genTypeInfoV1(p.module, dest.t, dest.lode.info))
of tySequence, tyString:
if optTinyRtti in p.config.globalOptions:
let rad = addrLoc(p.config, dest)
let rats = addrLocOrTemp(src)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "genericDeepCopy"),
cCast(CPointer, rad),
cCast(CPointer, rats),
genTypeInfoV1(p.module, dest.t, dest.lode.info))
else:
let rad = addrLoc(p.config, dest)
let rs = rdLoc(src)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "genericSeqDeepCopy"),
rad,
rs,
genTypeInfoV1(p.module, dest.t, dest.lode.info))
of tyOpenArray, tyVarargs:
let source = addrLocOrTemp(src)
let rad = addrLoc(p.config, dest)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "genericDeepCopyOpenArray"),
cCast(CPointer, rad),
cCast(CPointer, source),
derefField(source, "Field1"),
genTypeInfoV1(p.module, dest.t, dest.lode.info))
of tySet:
if mapSetType(p.config, ty) == ctArray:
let rd = rdLoc(dest)
let rs = rdLoc(src)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimCopyMem"),
cCast(CPointer, rd),
cCast(CConstPointer, rs),
cIntValue(getSize(p.config, dest.t)))
else:
simpleAsgn(p.s(cpsStmts), dest, src)
of tyPointer, tyChar, tyBool, tyEnum, tyCstring,
tyInt..tyUInt64, tyRange, tyVar, tyLent:
simpleAsgn(p.s(cpsStmts), dest, src)
else: internalError(p.config, "genDeepCopy: " & $ty.kind)
proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc) =
if d.k != locNone:
if lfNoDeepCopy in d.flags: genAssignment(p, d, s, {})
else: genAssignment(p, d, s, {needToCopy})
else:
d = s # ``d`` is free, so fill it with ``s``
proc putDataIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope) =
if d.k != locNone:
var a: TLoc = initLoc(locData, n, OnStatic)
# need to generate an assignment here
a.snippet = r
if lfNoDeepCopy in d.flags: genAssignment(p, d, a, {})
else: genAssignment(p, d, a, {needToCopy})
else:
# we cannot call initLoc() here as that would overwrite
# the flags field!
d.k = locData
d.lode = n
d.snippet = r
proc putIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope; s=OnUnknown) =
if d.k != locNone:
# need to generate an assignment here
var a: TLoc = initLoc(locExpr, n, s)
a.snippet = r
if lfNoDeepCopy in d.flags: genAssignment(p, d, a, {})
else: genAssignment(p, d, a, {needToCopy})
else:
# we cannot call initLoc() here as that would overwrite
# the flags field!
d.k = locExpr
d.lode = n
d.snippet = r
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.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.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.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.secondSon.typ != nil)
assert(son(e, 2).typ != nil)
block:
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.secondSon.typ != nil)
assert(son(e, 2).typ != nil)
block:
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.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.secondSon)
let ra {.inject.} = rdCharLoc(a)
putIntoDest(p, d, e, frmt)
template binaryArithOverflowRaw(p: BProc, t: PType, a, b: TLoc;
cpname: string): Rope =
var size = getSize(p.config, t)
let storage = if size < p.config.target.intSize: NimInt
else: getTypeDesc(p.module, t)
var result = getTempName(p.module)
p.s(cpsLocals).addVar(kind = Local, name = result, typ = storage)
let rca = rdCharLoc(a)
let rcb = rdCharLoc(b)
p.s(cpsStmts).addSingleIfStmtWithCond():
p.s(cpsStmts).addCall(cgsymValue(p.module, cpname),
rca,
rcb,
cAddr(result))
do:
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseOverflow"))
raiseInstr(p, p.s(cpsStmts))
if size < p.config.target.intSize or t.kind in {tyRange, tyEnum}:
let first = cIntLiteral(firstOrd(p.config, t))
let last = cIntLiteral(lastOrd(p.config, t))
p.s(cpsStmts).addSingleIfStmtWithCond():
p.s(cpsStmts).addOp(Or,
cOp(LessThan, result, first),
cOp(GreaterThan, result, last))
do:
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseOverflow"))
raiseInstr(p, p.s(cpsStmts))
result
proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
const
prc: array[mAddI..mPred, string] = [
"nimAddInt", "nimSubInt",
"nimMulInt", "nimDivInt", "nimModInt",
"nimAddInt", "nimSubInt"
]
prc64: array[mAddI..mPred, string] = [
"nimAddInt64", "nimSubInt64",
"nimMulInt64", "nimDivInt64", "nimModInt64",
"nimAddInt64", "nimSubInt64"
]
opr: array[mAddI..mPred, TypedBinaryOp] = [Add, Sub, Mul, Div, Mod, Add, Sub]
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)
if optOverflowCheck notin p.options or (m in {mSucc, mPred} and t.kind in {tyUInt..tyUInt64}):
let typ = getTypeDesc(p.module, e.typ)
let res = cCast(typ, cOp(opr[m], typ, rdLoc(a), rdLoc(b)))
putIntoDest(p, d, e, res)
else:
# we handle div by zero here so that we know that the compilerproc's
# result is only for overflows.
var needsOverflowCheck = true
if m in {mDivI, mModI}:
var canBeZero = true
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)))):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseDivByZero"))
raiseInstr(p, p.s(cpsStmts))
if needsOverflowCheck:
let res = binaryArithOverflowRaw(p, t, a, b,
if t.kind == tyInt64: prc64[m] else: prc[m])
putIntoDest(p, d, e, cCast(getTypeDesc(p.module, e.typ), res))
else:
let typ = getTypeDesc(p.module, e.typ)
let res = cCast(typ, cOp(opr[m], typ, wrapPar(rdLoc(a)), wrapPar(rdLoc(b))))
putIntoDest(p, d, e, res)
proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
var t: PType
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:
let first = cIntLiteral(firstOrd(p.config, t))
# remove extra paren from `==` op here to avoid Wparentheses-equality:
p.s(cpsStmts).addSingleIfStmt(removeSinglePar(cOp(Equal, ra, first))):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseOverflow"))
raiseInstr(p, p.s(cpsStmts))
case m
of mUnaryMinusI:
let typ = cIntType(getSize(p.config, t) * 8)
putIntoDest(p, d, e, cCast(typ, cOp(Neg, typ, ra)))
of mUnaryMinusI64:
putIntoDest(p, d, e, cOp(Neg, getTypeDesc(p.module, t), ra))
of mAbsI:
putIntoDest(p, d, e,
cIfExpr(cOp(GreaterThan, ra, cIntValue(0)),
wrapPar(ra),
cOp(Neg, getTypeDesc(p.module, t), ra)))
else:
assert(false, $m)
proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
var
s, k: BiggestInt = 0
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
var res = ""
template getType(): untyped =
getSimpleTypeDesc(p.module, e.typ)
let ra = rdLoc(a)
let rb = rdLoc(b)
case op
of mAddF64:
let t = getType()
res = cOp(Add, t, cCast(t, ra), cCast(t, rb))
of mSubF64:
let t = getType()
res = cOp(Sub, t, cCast(t, ra), cCast(t, rb))
of mMulF64:
let t = getType()
res = cOp(Mul, t, cCast(t, ra), cCast(t, rb))
of mDivF64:
let t = getType()
res = cOp(Div, t, cCast(t, ra), cCast(t, rb))
of mShrI:
let t = getType()
let at = cUintType(k)
let bt = cUintType(s)
res = cCast(t, cOp(Shr, at, cCast(at, ra), cOp(BitAnd, at, cCast(bt, rb), cIntLiteral(k - 1))))
of mShlI:
let t = getType()
let at = cUintType(s)
res = cCast(t, cOp(Shl, at, cCast(at, ra), cOp(BitAnd, at, cCast(at, rb), cIntLiteral(k - 1))))
of mAshrI:
let t = getType()
let at = cIntType(s)
let bt = cUintType(s)
res = cCast(t, cOp(Shr, at, cCast(at, ra), cOp(BitAnd, at, cCast(bt, rb), cIntLiteral(k - 1))))
of mBitandI:
let t = getType()
res = cCast(t, cOp(BitAnd, t, ra, rb))
of mBitorI:
let t = getType()
res = cCast(t, cOp(BitOr, t, ra, rb))
of mBitxorI:
let t = getType()
res = cCast(t, cOp(BitXor, t, ra, rb))
of mMinI:
res = cIfExpr(cOp(LessEqual, ra, rb), ra, rb)
of mMaxI:
res = cIfExpr(cOp(GreaterEqual, ra, rb), ra, rb)
of mAddU:
let t = getType()
let ot = cUintType(s)
res = cCast(t, cOp(Add, ot, cCast(ot, ra), cCast(ot, rb)))
of mSubU:
let t = getType()
let ot = cUintType(s)
res = cCast(t, cOp(Sub, ot, cCast(ot, ra), cCast(ot, rb)))
of mMulU:
let t = getType()
let ot = cUintType(s)
res = cCast(t, cOp(Mul, ot, cCast(ot, ra), cCast(ot, rb)))
of mDivU:
let t = getType()
let ot = cUintType(s)
res = cCast(t, cOp(Div, ot, cCast(ot, ra), cCast(ot, rb)))
of mModU:
let t = getType()
let ot = cUintType(s)
res = cCast(t, cOp(Mod, ot, cCast(ot, ra), cCast(ot, rb)))
of mEqI:
res = cOp(Equal, ra, rb)
of mLeI:
res = cOp(LessEqual, ra, rb)
of mLtI:
res = cOp(LessThan, ra, rb)
of mEqF64:
res = cOp(Equal, ra, rb)
of mLeF64:
res = cOp(LessEqual, ra, rb)
of mLtF64:
res = cOp(LessThan, ra, rb)
of mLeU:
let ot = cUintType(s)
res = cOp(LessEqual, cCast(ot, ra), cCast(ot, rb))
of mLtU:
let ot = cUintType(s)
res = cOp(LessThan, cCast(ot, ra), cCast(ot, rb))
of mEqEnum:
res = cOp(Equal, ra, rb)
of mLeEnum:
res = cOp(LessEqual, ra, rb)
of mLtEnum:
res = cOp(LessThan, ra, rb)
of mEqCh:
res = cOp(Equal, cCast(NimUint8, ra), cCast(NimUint8, rb))
of mLeCh:
res = cOp(LessEqual, cCast(NimUint8, ra), cCast(NimUint8, rb))
of mLtCh:
res = cOp(LessThan, cCast(NimUint8, ra), cCast(NimUint8, rb))
of mEqB:
res = cOp(Equal, ra, rb)
of mLeB:
res = cOp(LessEqual, ra, rb)
of mLtB:
res = cOp(LessThan, ra, rb)
of mEqRef:
res = cOp(Equal, ra, rb)
of mLePtr:
res = cOp(LessEqual, ra, rb)
of mLtPtr:
res = cOp(LessThan, ra, rb)
of mXor:
res = cOp(NotEqual, ra, rb)
else:
assert(false, $op)
putIntoDest(p, d, e, res)
proc genEqProc(p: BProc, e: PNode, d: var TLoc) =
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:
putIntoDest(p, d, e, cOp(And,
cOp(Equal, dotField(ra, "ClP_0"), dotField(rb, "ClP_0")),
cOp(Equal, dotField(ra, "ClE_0"), dotField(rb, "ClE_0"))))
else:
putIntoDest(p, d, e, cOp(Equal, ra, rb))
proc genIsNil(p: BProc, e: PNode, d: var TLoc) =
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:
res = cOp(Equal, dotField(ra, "ClP_0"), cIntValue(0))
else:
res = cOp(Equal, ra, cIntValue(0))
putIntoDest(p, d, e, res)
proc unaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
var
t: PType
assert(e.secondSon.typ != nil)
var a = initLocExpr(p, e.secondSon)
t = skipTypes(e.typ, abstractRange)
var res = ""
let ra = rdLoc(a)
case op
of mNot:
res = cOp(Not, ra)
of mUnaryPlusI:
res = ra
of mBitnotI:
let at = cUintType(getSize(p.config, t) * 8)
let t = getSimpleTypeDesc(p.module, e.typ)
res = cCast(t, cCast(at, cOp(BitNot, t, ra)))
of mUnaryPlusF64:
res = ra
of mUnaryMinusF64:
res = cOp(Neg, getSimpleTypeDesc(p.module, e.typ), ra)
else:
assert false, $op
putIntoDest(p, d, e, res)
proc isCppRef(p: BProc; typ: PType): bool {.inline.} =
result = p.module.compileToCpp and
skipTypes(typ, abstractInstOwned).kind in {tyVar} and
tfVarIsPtr notin skipTypes(typ, abstractInstOwned).flags
proc genDeref(p: BProc, e: PNode, d: var TLoc) =
let mt = mapType(p.config, e.firstSon.typ, mapTypeChooser(e.firstSon) == skParam)
if mt in {ctArray, ctPtrToArray} and lfEnforceDeref notin d.flags:
# XXX the amount of hacks for C's arrays is incredible, maybe we should
# simply wrap them in a struct? --> Losing auto vectorization then?
expr(p, e.firstSon, d)
if e.firstSon.typ.skipTypes(abstractInstOwned).kind == tyRef:
d.storage = OnHeap
else:
var a: TLoc
var typ = e.firstSon.typ
if typ.kind in {tyUserTypeClass, tyUserTypeClassInst} and typ.isResolvedUserTypeClass:
typ = typ.last
typ = typ.skipTypes(abstractInstOwned)
if typ.kind in {tyVar} and tfVarIsPtr notin typ.flags and
p.module.compileToCpp and e.firstSon.kind == nkHiddenAddr and
# don't override existing location:
d.k == locNone:
d = initLocExprSingleUse(p, e.firstSon.firstSon)
return
else:
a = initLocExprSingleUse(p, e.firstSon)
# bug #23453 #25265
if e.typ != nil and e.typ.skipTypes(abstractInst).kind == tyObject:
discard getTypeDesc(p.module, e.typ)
if d.k == locNone:
# dest = *a; <-- We do not know that 'dest' is on the heap!
# It is completely wrong to set 'd.storage' here, unless it's not yet
# been assigned to.
case typ.kind
of tyRef:
d.storage = OnHeap
of tyVar, tyLent:
d.storage = OnUnknown
if tfVarIsPtr notin typ.flags and p.module.compileToCpp and
e.kind == nkHiddenDeref:
putIntoDest(p, d, e, rdLoc(a), a.storage)
return
of tyPtr:
d.storage = OnUnknown # BUGFIX!
else:
internalError(p.config, e.info, "genDeref " & $typ.kind)
elif p.module.compileToCpp:
if typ.kind in {tyVar} and tfVarIsPtr notin typ.flags and
e.kind == nkHiddenDeref:
putIntoDest(p, d, e, rdLoc(a), a.storage)
return
if mt == ctPtrToArray and lfEnforceDeref in d.flags:
# we lie about the type for better C interop: 'ptr array[3,T]' is
# translated to 'ptr T', but for deref'ing this produces wrong code.
# See tmissingderef. So we get rid of the deref instead. The codegen
# ends up using 'memcpy' for the array assignment,
# so the '&' and '*' cancel out:
putIntoDest(p, d, e, rdLoc(a), a.storage)
else:
putIntoDest(p, d, e, cDeref(rdLoc(a)), a.storage)
proc cowBracket(p: BProc; n: PNode) =
if n.kind == nkBracketExpr and optSeqDestructors in p.config.globalOptions and
not p.config.usesSso():
let strCandidate = n.firstSon
if strCandidate.typ.skipTypes(abstractInst).kind == tyString:
var a: TLoc = initLocExpr(p, strCandidate)
let raa = byRefLoc(p, a)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"), raa)
proc cow(p: BProc; n: PNode) {.inline.} =
if n.kind == nkHiddenAddr: cowBracket(p, n.firstSon)
template ignoreConv(e: PNode): bool =
let destType = e.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) =
# careful 'addr(myptrToArray)' needs to get the ampersand:
if e.firstSon.typ.skipTypes(abstractInstOwned).kind in {tyRef, tyPtr}:
var a: TLoc = initLocExpr(p, e.firstSon)
putIntoDest(p, d, e, cAddr(a.snippet), a.storage)
#Message(e.info, warnUser, "HERE NEW &")
elif mapType(p.config, e.firstSon.typ, mapTypeChooser(e.firstSon) == skParam) == ctArray or isCppRef(p, e.typ):
expr(p, e.firstSon, d)
# bug #19497
d.lode = e
else:
let ssoStrSub = p.config.usesSso() and e.firstSon.kind == nkBracketExpr and
e.firstSon.firstSon.typ.skipTypes(abstractVar).kind == tyString
var a: TLoc = initLocExpr(p, e.firstSon, if ssoStrSub: {lfEnforceDeref, lfPrepareForMutation} else: {})
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.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))
else:
putIntoDest(p, d, e, addrLoc(p.config, a), a.storage)
template inheritLocation(d: var TLoc, a: TLoc) =
if d.k == locNone: d.storage = a.storage
proc genRecordFieldAux(p: BProc, e: PNode, d: var TLoc, a: var TLoc) =
a = initLocExpr(p, e.firstSon)
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
proc genTupleElem(p: BProc, e: PNode, d: var TLoc) =
var
i: int = 0
var a: TLoc = initLocExpr(p, e.firstSon)
let tupType = a.t.skipTypes(abstractInst+{tyVar}+tyUserTypeClasses) # ref #25227
assert tupType.kind == tyTuple
d.inheritLocation(a)
discard getTypeDesc(p.module, a.t) # fill the record's fields.loc
var r = rdLoc(a)
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)
proc lookupFieldAgain(p: BProc, ty: PType; field: PSym; r: var Rope;
resTyp: ptr PType = nil): PSym =
result = nil
var ty = ty
assert r != ""
while ty != nil:
ty = ty.skipTypes(skipPtrs)
assert(ty.kind in {tyTuple, tyObject})
result = lookupInRecord(ty.n, field.name)
if result != nil:
if resTyp != nil: resTyp[] = ty
break
if not p.module.compileToCpp:
r = dotField(r, "Sup")
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.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.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.secondSon.sym
let ty = skipTypes(a.t, abstractInstOwned + tyUserTypeClasses)
if ty.kind == tyTuple:
# we found a unique tuple type which lacks field information
# so we use Field$i
r = dotField(r, "Field" & $f.position)
putIntoDest(p, d, e, r, a.storage)
else:
var rtyp: PType = nil
let field = lookupFieldAgain(p, ty, f, r, addr rtyp)
if field.loc.snippet == "" and rtyp != nil: fillObjectFields(p.module, rtyp)
if field.loc.snippet == "": internalError(p.config, e.info, "genRecordField 3 " & typeToString(ty))
r = dotField(r, field.loc.snippet)
putIntoDest(p, d, e, r, a.storage)
r.freeze
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 child in sonsFrom(e, 1):
var it = child
assert(it.kind in nkCallKinds)
assert(it.firstSon.kind == nkSym)
let op = it.firstSon.sym
if op.magic == mNot: it = it.secondSon
let disc = son(it, 2).skipConv
assert(disc.kind == nkSym)
# Re-navigate the discriminant in the object type: under `nim ic` `disc.sym` is
# a field-use stub whose `loc.snippet` is empty (the backend fills it on the
# canonical reclist field, not on per-use leaves). Look up the canonical field
# for the C member name; `disc`'s own node still supplies its type (TLoc.t).
# Byte-neutral for non-IC, where re-navigation returns the same field.
var rr = obj
let dfield = lookupFieldAgain(p, ty, disc.sym, rr)
test = initLoc(locNone, it, OnStack)
u = initLocExpr(p, it.secondSon)
v = initLoc(locExpr, disc, OnUnknown)
v.snippet = dotField(obj, dfield.loc.snippet)
genInExprAux(p, it, u, v, test)
var msg = ""
if optDeclaredLocs in p.config.globalOptions:
# xxx this should be controlled by a separate flag, and
# used for other similar defects so that location information is shown
# even without the expensive `--stacktrace`; binary size could be optimized
# by encoding the file names separately from `file(line:col)`, essentially
# passing around `TLineInfo` + the set of files in the project.
msg.add toFileLineCol(p.config, e.info) & " "
msg.add genFieldDefect(p.config, field.name.s, dfield)
var strLitBuilder = newBuilder("")
genStringLiteral(p.module, newStrNode(nkStrLit, msg), strLitBuilder)
let strLit = extract(strLitBuilder)
## discriminant check
let rt = rdLoc(test)
let cond = if op.magic == mNot: rt else: cOp(Not, rt)
p.s(cpsStmts).addSingleIfStmt(cond):
## call raiseFieldError2 on failure
var discIndex = newRopeAppender()
rdSetElemLoc(p.config, v, u.t, discIndex)
if optTinyRtti in p.config.globalOptions:
let base = disc.typ.skipTypes(abstractInst+{tyRange})
case base.kind
of tyEnum:
let toStrProc = getToStringProc(p.module.g.graph, base)
# XXX need to modify this logic for IC.
# need to analyze nkFieldCheckedExpr and marks procs "used" like range checks in dce
var toStr: TLoc = default(TLoc)
expr(p, newSymNode(toStrProc), toStr)
let rToStr = rdLoc(toStr)
let rv = rdLoc(v)
var raiseCall: CallBuilder
p.s(cpsStmts).addStmt():
p.s(cpsStmts).addCall(raiseCall, cgsymValue(p.module, "raiseFieldErrorStr")):
p.s(cpsStmts).addArgument(raiseCall):
p.s(cpsStmts).add(strLit)
p.s(cpsStmts).addArgument(raiseCall):
p.s(cpsStmts).addCall(rToStr, rv)
else:
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseFieldError2"),
strLit,
cCast(NimInt, discIndex))
else:
# complication needed for signed types
let first = p.config.firstOrd(disc.sym.typ)
let firstLit = cInt64Literal(cast[int](first))
let discName = genTypeInfo(p.config, p.module, disc.sym.typ, e.info)
var raiseCall: CallBuilder
p.s(cpsStmts).addStmt():
p.s(cpsStmts).addCall(raiseCall, cgsymValue(p.module, "raiseFieldError2")):
p.s(cpsStmts).addArgument(raiseCall):
p.s(cpsStmts).add(strLit)
p.s(cpsStmts).addArgument(raiseCall):
p.s(cpsStmts).addCall(cgsymValue(p.module, "reprDiscriminant"),
cOp(Add, NimInt, cCast(NimInt, discIndex), cCast(NimInt, firstLit)),
discName)
raiseInstr(p, p.s(cpsStmts))
proc genCheckedRecordField(p: BProc, e: PNode, d: var TLoc) =
assert e.firstSon.kind == nkDotExpr
if optFieldCheck in p.options:
var a: TLoc = default(TLoc)
genRecordFieldAux(p, e.firstSon, d, a)
let ty = skipTypes(a.t, abstractInst + tyUserTypeClasses)
var r = rdLoc(a)
let f = e.firstSon.secondSon.sym
let field = lookupFieldAgain(p, ty, f, r)
if field.loc.snippet == "": fillObjectFields(p.module, ty)
if field.loc.snippet == "":
internalError(p.config, e.info, "genCheckedRecordField") # generate the checks:
genFieldCheck(p, e, r, field, ty)
r = dotField(r, field.loc.snippet)
putIntoDest(p, d, e.firstSon, r, a.storage)
r.freeze
else:
genRecordField(p, e.firstSon, d)
proc genUncheckedArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) =
var a = initLocExpr(p, x)
var b = initLocExpr(p, y)
d.inheritLocation(a)
putIntoDest(p, d, n, subscript(rdLoc(a), rdCharLoc(b)),
a.storage)
proc genArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) =
var a = initLocExpr(p, x)
var b = initLocExpr(p, y)
var ty = skipTypes(a.t, abstractVarRange + abstractPtrs + tyUserTypeClasses)
let first = cIntLiteral(firstOrd(p.config, ty))
# emit range check:
if optBoundsCheck in p.options and ty.kind != tyUncheckedArray:
if not isConstExpr(y):
# semantic pass has already checked for const index expressions
if firstOrd(p.config, ty) == 0 and lastOrd(p.config, ty) >= 0:
if (firstOrd(p.config, b.t) < firstOrd(p.config, ty)) or (lastOrd(p.config, b.t) > lastOrd(p.config, ty)):
let last = cIntLiteral(lastOrd(p.config, ty))
let rcb = rdCharLoc(b)
p.s(cpsStmts).addSingleIfStmt(
cOp(GreaterThan, cCast(NimUint, rcb), cCast(NimUint, last))):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseIndexError2"),
rcb,
last)
raiseInstr(p, p.s(cpsStmts))
else:
let last = cIntLiteral(lastOrd(p.config, ty))
let rcb = rdCharLoc(b)
p.s(cpsStmts).addSingleIfStmt(
cOp(Or, cOp(LessThan, rcb, first), cOp(GreaterThan, rcb, last))):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseIndexError3"),
rcb,
first,
last)
raiseInstr(p, p.s(cpsStmts))
else:
let idx = getOrdValue(y)
if idx < firstOrd(p.config, ty) or idx > lastOrd(p.config, ty):
localError(p.config, x.info, formatErrorIndexBound(idx, firstOrd(p.config, ty), lastOrd(p.config, ty)))
d.inheritLocation(a)
let ra = rdLoc(a)
let rcb = rdCharLoc(b)
putIntoDest(p, d, n, subscript(ra, cOp(Sub, NimInt, rcb, first)), a.storage)
proc genCStringElem(p: BProc, n, x, y: PNode, d: var TLoc) =
var a = initLocExpr(p, x)
var b = initLocExpr(p, y)
inheritLocation(d, a)
let ra = rdLoc(a)
let rcb = rdLoc(b)
putIntoDest(p, d, n, subscript(ra, rcb), a.storage)
proc genBoundsCheck(p: BProc; arr, a, b: TLoc; arrTyp: PType) =
let ty = arrTyp
case ty.kind
of tyOpenArray, tyVarargs:
let ra = rdLoc(a)
let rb = rdLoc(b)
let rarr = rdLoc(arr)
let arrlen =
if reifiedOpenArray(arr.lode):
dotField(rarr, "Field1")
else:
rarr & "Len_0"
p.s(cpsStmts).addSingleIfStmt(cOp(And,
cOp(NotEqual, cOp(Sub, NimInt, rb, ra), cIntValue(-1)),
cOp(Or,
cOp(Or, cOp(LessThan, ra, cIntValue(0)), cOp(GreaterEqual, ra, arrlen)),
cOp(Or, cOp(LessThan, rb, cIntValue(0)), cOp(GreaterEqual, rb, arrlen))))):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseIndexError4"),
ra, rb, arrlen)
raiseInstr(p, p.s(cpsStmts))
of tyArray:
let first = cIntLiteral(firstOrd(p.config, ty))
let last = cIntLiteral(lastOrd(p.config, ty))
let rca = rdCharLoc(a)
let rcb = rdCharLoc(b)
p.s(cpsStmts).addSingleIfStmt(cOp(And,
cOp(NotEqual, cOp(Sub, NimInt, rcb, rca), cIntValue(-1)),
cOp(Or,
cOp(LessThan, cOp(Sub, NimInt, rcb, rca), cIntValue(-1)),
cOp(Or,
cOp(Or, cOp(LessThan, rca, first), cOp(GreaterThan, rca, last)),
cOp(Or, cOp(LessThan, rcb, first), cOp(GreaterThan, rcb, last)))))):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseIndexError"))
raiseInstr(p, p.s(cpsStmts))
of tySequence, tyString:
let ra = rdLoc(a)
let rb = rdLoc(b)
let arrlen = lenExpr(p, arr)
p.s(cpsStmts).addSingleIfStmt(cOp(And,
cOp(NotEqual, cOp(Sub, NimInt, rb, ra), cIntValue(-1)),
cOp(Or,
cOp(Or, cOp(LessThan, ra, cIntValue(0)), cOp(GreaterEqual, ra, arrlen)),
cOp(Or, cOp(LessThan, rb, cIntValue(0)), cOp(GreaterEqual, rb, arrlen))))):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseIndexError4"),
ra, rb, arrlen)
raiseInstr(p, p.s(cpsStmts))
else: discard
proc genOpenArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) =
var a = initLocExpr(p, x)
var b = initLocExpr(p, y)
let ra = rdLoc(a)
let rcb = rdCharLoc(b)
var arrData, arrLen: Snippet
if not reifiedOpenArray(x):
arrData = ra
arrLen = ra & "Len_0"
else:
arrData = dotField(ra, "Field0")
arrLen = dotField(ra, "Field1")
# emit range check:
if optBoundsCheck in p.options:
p.s(cpsStmts).addSingleIfStmt(cOp(Or,
cOp(LessThan, rcb, cIntValue(0)),
cOp(GreaterEqual, rcb, arrLen))): # BUGFIX: ``>=`` and not ``>``!
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseIndexError2"),
rcb,
cOp(Sub, NimInt, arrLen, cIntValue(1)))
raiseInstr(p, p.s(cpsStmts))
inheritLocation(d, a)
putIntoDest(p, d, n, subscript(arrData, rcb), a.storage)
proc genSeqElem(p: BProc, n, x, y: PNode, d: var TLoc) =
var a = initLocExpr(p, x)
var b = initLocExpr(p, y)
var ty = skipTypes(a.t, abstractVarRange)
if ty.kind in {tyRef, tyPtr}:
ty = skipTypes(ty.elementType, abstractVarRange)
let rcb = rdCharLoc(b)
# emit range check:
if optBoundsCheck in p.options:
let arrLen = lenExpr(p, a)
p.s(cpsStmts).addSingleIfStmt(cOp(Or,
cOp(LessThan, rcb, cIntValue(0)),
cOp(GreaterEqual, rcb, arrLen))):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseIndexError2"),
rcb,
cOp(Sub, NimInt, arrLen, cIntValue(1)))
raiseInstr(p, p.s(cpsStmts))
if d.k == locNone: d.storage = OnHeap
if skipTypes(a.t, abstractVar).kind in {tyRef, tyPtr}:
a.snippet = cDeref(a.snippet)
if p.config.usesSso() and ty.kind == tyString:
let bra = byRefLoc(p, a)
if lfPrepareForMutation in d.flags:
# Use nimStrAtMutV3 to get a mutable reference (char*) to the element.
# Only when mutation is requested: avoids calling nimPrepareStrMutationV2
# on const string literals (which would SIGSEGV on write to read-only memory).
putIntoDest(p, d, n,
cDeref(cCall(cgsymValue(p.module, "nimStrAtMutV3"), bra, rcb)), a.storage)
else:
putIntoDest(p, d, n,
cCall(cgsymValue(p.module, "nimStrAtV3"), bra, rcb), a.storage)
else:
if lfPrepareForMutation in d.flags and ty.kind == tyString and
optSeqDestructors in p.config.globalOptions:
let bra = byRefLoc(p, a)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"), bra)
let ra = rdLoc(a)
putIntoDest(p, d, n, subscript(dataField(p, ra), rcb), a.storage)
proc genBracketExpr(p: BProc; n: PNode; d: var TLoc) =
var ty = skipTypes(n.firstSon.typ, abstractVarRange + tyUserTypeClasses)
if ty.kind in {tyRef, tyPtr}: ty = skipTypes(ty.elementType, abstractVarRange)
case ty.kind
of tyUncheckedArray: genUncheckedArrayElem(p, n, n.firstSon, n.secondSon, d)
of tyArray: genArrayElem(p, n, n.firstSon, n.secondSon, d)
of tyOpenArray, tyVarargs: genOpenArrayElem(p, n, n.firstSon, n.secondSon, d)
of tySequence, tyString: genSeqElem(p, n, n.firstSon, n.secondSon, d)
of tyCstring: genCStringElem(p, n, n.firstSon, n.secondSon, d)
of tyTuple: genTupleElem(p, n, d)
else: internalError(p.config, n.info, "expr(nkBracketExpr, " & $ty.kind & ')')
discard getTypeDesc(p.module, n.typ)
proc isSimpleExpr(n: AnyNode): bool =
# calls all the way down --> can stay expression based
case n.kind
of nkCallKinds, nkDotExpr, nkPar, nkTupleConstr,
nkObjConstr, nkBracket, nkCurly, nkHiddenDeref, nkDerefExpr, nkHiddenAddr,
nkHiddenStdConv, nkHiddenSubConv, nkConv, nkAddr:
for c in sons(n):
if not isSimpleExpr(c): return false
result = true
of nkStmtListExpr:
for it in sonsButLast(n):
if it.kind notin {nkCommentStmt, nkEmpty}: return false
result = isSimpleExpr(n.lastSon)
else:
result = n.isAtom
proc genAndOr(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
# how to generate code?
# 'expr1 and expr2' becomes:
# result = expr1
# fjmp result, end
# result = expr2
# end:
# ... (result computed)
# BUGFIX:
# a = b or a
# used to generate:
# a = b
# if a: goto end
# a = a
# end:
# now it generates:
# tmp = b
# if tmp: goto end
# tmp = a
# end:
# a = tmp
when false:
#if isSimpleExpr(e) and p.module.compileToCpp:
#getTemp(p, e.typ, tmpA)
#getTemp(p, e.typ, tmpB)
var tmpA = initLocExprSingleUse(p, e.secondSon)
var tmpB = initLocExprSingleUse(p, son(e, 2))
tmpB.k = locExpr
if m == mOr:
tmpB.snippet = cOp(Or, rdLoc(tmpA), rdLoc(tmpB))
else:
tmpB.snippet = cOp(And, rdLoc(tmpA), rdLoc(tmpB))
if d.k == locNone:
d = tmpB
else:
genAssignment(p, d, tmpB, {})
else:
var
L: TLabel
var tmp: TLoc = getTemp(p, e.typ) # force it into a temp!
inc p.splitDecls
expr(p, e.secondSon, tmp)
L = getLabel(p)
let rtmp = rdLoc(tmp)
let cond = if m == mOr: rtmp else: cOp(Not, rtmp)
p.s(cpsStmts).addSingleIfStmt(cond):
p.s(cpsStmts).addGoto(L)
expr(p, son(e, 2), tmp)
fixLabel(p, L)
if d.k == locNone:
d = tmp
else:
genAssignment(p, d, tmp, {}) # no need for deep copying
dec p.splitDecls
proc genEcho(p: BProc, n: PNode) =
# this unusual way of implementing it ensures that e.g. ``echo("hallo", 45)``
# is threadsafe.
internalAssert p.config, n.kind == nkBracket
if p.config.target.targetOS == osGenode:
# echo directly to the Genode LOG session
p.module.includeHeader("<base/log.h>")
p.module.includeHeader("<util/string.h>")
var a: TLoc
let logName = "Genode::log"
var logCall: CallBuilder
p.s(cpsStmts).addStmt():
p.s(cpsStmts).addCall(logCall, logName):
for it in sons(n):
if it.skipConv.kind == nkNilLit:
p.s(cpsStmts).addArgument(logCall):
p.s(cpsStmts).add("\"\"")
elif n.hasSons:
a = initLocExpr(p, it)
let ra = a.rdLoc
let fnName = "Genode::Cstring"
p.s(cpsStmts).addArgument(logCall):
case detectStrVersion(p.module)
of 2:
p.s(cpsStmts).addCall(fnName,
dotField(derefField(ra, "p"), "data"),
dotField(ra, "len"))
else:
p.s(cpsStmts).addCall(fnName,
derefField(ra, "data"),
derefField(ra, "len"))
else:
if not n.hasSons:
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "echoBinSafe"),
NimNil,
cIntValue(n.len))
else:
var a: TLoc = initLocExpr(p, n)
let ra = a.rdLoc
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "echoBinSafe"),
ra,
cIntValue(n.len))
when false:
p.module.includeHeader("<stdio.h>")
linefmt(p, cpsStmts, "printf($1$2);$n",
makeCString(repeat("%s", n.len) & "\L"), [args])
linefmt(p, cpsStmts, "fflush(stdout);$n", [])
proc gcUsage(conf: ConfigRef; n: PNode) =
if conf.selectedGC == gcNone: message(conf, n.info, warnGcMem, n.renderTree)
proc strLoc(p: BProc; d: TLoc): Rope =
if optSeqDestructors in p.config.globalOptions:
result = byRefLoc(p, d)
else:
result = rdLoc(d)
proc genStrConcat(p: BProc, e: PNode, d: var TLoc) =
# <Nim code>
# s = "Hello " & name & ", how do you feel?" & 'z'
#
# <generated C code>
# {
# string tmp0;
# ...
# tmp0 = rawNewString(6 + 17 + 1 + s2->len);
# // we cannot generate s = rawNewString(...) here, because
# // ``s`` may be used on the right side of the expression
# appendString(tmp0, strlit_1);
# appendString(tmp0, name);
# appendString(tmp0, strlit_2);
# appendChar(tmp0, 'z');
# asgn(s, tmp0);
# }
var a: TLoc
var tmp: TLoc = getTemp(p, e.typ)
var L = 0
var appends: seq[Snippet] = @[]
var lens: seq[Snippet] = @[]
for it in sonsFrom(e, 1):
# compute the length expression:
a = initLocExpr(p, it)
let rstmp = strLoc(p, tmp)
let ra = rdLoc(a)
if skipTypes(it.typ, abstractVarRange).kind == tyChar:
inc(L)
appends.add(cgCall(p, "appendChar", rstmp, ra))
else:
if it.kind in {nkStrLit..nkTripleStrLit}:
inc(L, it.strVal.len)
else:
lens.add(lenExpr(p, a))
appends.add(cgCall(p, "appendString", rstmp, ra))
var exprL = cIntValue(L)
for len in lens:
exprL = cOp(Add, NimInt, exprL, len)
p.s(cpsStmts).addAssignmentWithValue(tmp.snippet):
p.s(cpsStmts).addCall(cgsymValue(p.module, "rawNewString"), exprL)
for append in appends:
p.s(cpsStmts).addStmt():
p.s(cpsStmts).add(append)
if d.k == locNone:
d = tmp
else:
genAssignment(p, d, tmp, {}) # no need for deep copying
gcUsage(p.config, e)
proc genStrAppend(p: BProc, e: PNode, d: var TLoc) =
# <Nim code>
# s &= "Hello " & name & ", how do you feel?" & 'z'
# // BUG: what if s is on the left side too?
# <generated C code>
# {
# s = resizeString(s, 6 + 17 + 1 + name->len);
# appendString(s, strlit_1);
# appendString(s, name);
# appendString(s, strlit_2);
# appendChar(s, 'z');
# }
var
a, call: TLoc
appends: seq[Snippet] = @[]
assert(d.k == locNone)
var L = 0
var lens: seq[Snippet] = @[]
var dest = initLocExpr(p, e.secondSon)
let rsd = strLoc(p, dest)
for it in sonsFrom(e, 2):
# compute the length expression:
a = initLocExpr(p, it)
let ra = rdLoc(a)
if skipTypes(it.typ, abstractVarRange).kind == tyChar:
inc(L)
appends.add(cgCall(p, "appendChar", rsd, ra))
else:
if it.kind in {nkStrLit..nkTripleStrLit}:
inc(L, it.strVal.len)
else:
lens.add(lenExpr(p, a))
appends.add(cgCall(p, "appendString", rsd, ra))
var exprL = cIntValue(L)
for len in lens:
exprL = cOp(Add, NimInt, exprL, len)
if optSeqDestructors in p.config.globalOptions:
let brd = byRefLoc(p, dest)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "prepareAdd"),
brd,
exprL)
else:
call = initLoc(locCall, e, OnHeap)
let rd = rdLoc(dest)
call.snippet = cgCall(p, "resizeString",
rd,
exprL)
genAssignment(p, dest, call, {})
gcUsage(p.config, e)
for append in appends:
p.s(cpsStmts).addStmt():
p.s(cpsStmts).add(append)
proc genSeqElemAppend(p: BProc, e: PNode, d: var TLoc) =
# seq &= x -->
# seq = (typeof seq) incrSeq(&seq->Sup, sizeof(x));
# seq->data[seq->len-1] = x;
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.secondSon.typ),
cgCall(p, "incrSeqV3",
if not p.module.compileToCpp: cCast(ptrType("TGenericSeq"), ra) else: ra,
genTypeInfoV1(p.module, seqType, e.info)))
# emit the write barrier if required, but we can always move here, so
# use 'genRefAssign' for the seq.
genRefAssign(p, a, call)
#if bt != b.t:
# echo "YES ", e.info, " new: ", typeToString(bt), " old: ", typeToString(b.t)
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))
dest.snippet = subscript(dataField(p, ra), tmpL.snippet)
genAssignment(p, dest, b, {needToCopy})
gcUsage(p.config, e)
proc genSeqElemAppendV2(p: BProc, e: PNode, d: var TLoc) =
# s.add(x) with optSeqDestructors (arc/orc), inlined for direct slot construction:
# NI oldLen = s.len;
# if (s.p == NIM_NIL || (s.p->cap & ~NIM_STRLIT_FLAG) < oldLen + 1)
# 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.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'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, son(e, 2))
# All seq operations now go through the stable seqPtrName pointer.
let ra = wrapPar(cDeref(seqPtrName))
var tmpL = getIntTemp(p)
p.s(cpsStmts).addAssignment(tmpL.snippet, dotField(ra, "len"))
let pField = dotField(ra, "p")
p.s(cpsStmts).addSingleIfStmt(
cOp(Or,
cOp(Equal, pField, NimNil),
cOp(LessThan,
cOp(BitAnd, NimInt, derefField(pField, "cap"), cOp(BitNot, NimInt, NimStrlitFlag)),
cOp(Add, NimInt, tmpL.snippet, cIntValue(1))))):
p.s(cpsStmts).addFieldAssignmentWithValue(ra, "p"):
p.s(cpsStmts).addCast(ptrType(pt)):
p.s(cpsStmts).addCall(cgsymValue(p.module, "prepareSeqAddUninit"),
tmpL.snippet,
pField,
cIntValue(1),
cSizeof(pe),
cAlignof(pe))
p.s(cpsStmts).addFieldAssignment(ra, "len",
cOp(Add, NimInt, tmpL.snippet, cIntValue(1)))
var dest = initLoc(locExpr, son(e, 2), OnHeap)
dest.snippet = subscript(dataField(p, ra), tmpL.snippet)
genAssignment(p, dest, b, {})
proc genDefault(p: BProc; n: PNode; d: var TLoc) =
if d.k == locNone: d = getTemp(p, n.typ, needsInit=true)
else: resetLoc(p, d)
proc rawGenNew(p: BProc, a: var TLoc, sizeExpr: Rope; needsInit: bool) =
var sizeExpr = sizeExpr
let typ = a.t
var b: TLoc = initLoc(locExpr, a.lode, OnHeap)
let refType = typ.skipTypes(abstractInstOwned)
assert refType.kind == tyRef
let bt = refType.elementType
if sizeExpr == "":
sizeExpr = cSizeof(getTypeDesc(p.module, bt))
if optTinyRtti in p.config.globalOptions:
let fnName = cgsymValue(p.module, if needsInit: "nimNewObj" else: "nimNewObjUninit")
b.snippet = cCast(getTypeDesc(p.module, typ),
cCall(fnName,
sizeExpr,
cAlignof(getTypeDesc(p.module, bt))))
genAssignment(p, a, b, {})
else:
let ti = genTypeInfoV1(p.module, typ, a.lode.info)
let op = getAttachedOp(p.module.g.graph, bt, attachedDestructor)
if op != nil and not isTrivialProc(p.module.g.graph, op):
# the prototype of a destructor is ``=destroy(x: var T)`` and that of a
# finalizer is: ``proc (x: ref T) {.nimcall.}``. We need to check the calling
# convention at least:
if op.typ == nil or op.typ.callConv != ccNimCall:
localError(p.module.config, a.lode.info,
"the destructor that is turned into a finalizer needs " &
"to have the 'nimcall' calling convention")
var f: TLoc = initLocExpr(p, newSymNode(op))
let rf = rdLoc(f)
p.module.s[cfsTypeInit3].addDerefFieldAssignment(ti, "finalizer",
cCast(CPointer, rf))
if a.storage == OnHeap and usesWriteBarrier(p.config):
let unrefFnName = cgsymValue(p.module,
if canFormAcycle(p.module.g.graph, a.t):
"nimGCunrefRC1"
else:
"nimGCunrefNoCycle")
let ra = a.rdLoc
p.s(cpsStmts).addSingleIfStmt(ra):
p.s(cpsStmts).addCallStmt(unrefFnName, ra)
p.s(cpsStmts).addAssignment(ra, NimNil)
if p.config.selectedGC == gcGo:
# newObjRC1() would clash with unsureAsgnRef() - which is used by gcGo to
# implement the write barrier
b.snippet = cCast(getTypeDesc(p.module, typ),
cgCall(p, "newObj",
ti,
sizeExpr))
let raa = addrLoc(p.config, a)
let rb = b.rdLoc
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "unsureAsgnRef"),
cCast(ptrType(CPointer), raa),
rb)
else:
# use newObjRC1 as an optimization
b.snippet = cCast(getTypeDesc(p.module, typ),
cgCall(p, "newObjRC1",
ti,
sizeExpr))
let ra = a.rdLoc
let rb = b.rdLoc
p.s(cpsStmts).addAssignment(ra, rb)
else:
b.snippet = cCast(getTypeDesc(p.module, typ),
cgCall(p, "newObj",
ti,
sizeExpr))
genAssignment(p, a, b, {})
# set the object type:
genObjectInit(p, cpsStmts, bt, a, constructRefObj)
proc genNew(p: BProc, e: PNode) =
var a: TLoc = initLocExpr(p, e.secondSon)
# 'genNew' also handles 'unsafeNew':
if e.len == 3:
var se: TLoc = initLocExpr(p, son(e, 2))
rawGenNew(p, a, se.rdLoc, needsInit = true)
else:
rawGenNew(p, a, "", needsInit = true)
gcUsage(p.config, e)
proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope; lenIsZero: bool) =
let seqtype = skipTypes(dest.t, abstractVarRange)
var call: TLoc = initLoc(locExpr, dest.lode, OnHeap)
if dest.storage == OnHeap and usesWriteBarrier(p.config):
let unrefFnName = cgsymValue(p.module,
if canFormAcycle(p.module.g.graph, dest.t):
"nimGCunrefRC1"
else:
"nimGCunrefNoCycle")
let rd = dest.rdLoc
p.s(cpsStmts).addSingleIfStmt(rd):
p.s(cpsStmts).addCallStmt(unrefFnName, rd)
p.s(cpsStmts).addAssignment(rd, NimNil)
if not lenIsZero:
let st = getTypeDesc(p.module, seqtype)
let typinfo = genTypeInfoV1(p.module, seqtype, dest.lode.info)
if p.config.selectedGC == gcGo:
# we need the write barrier
call.snippet = cCast(st,
cgCall(p, "newSeq", typinfo, length))
let rad = addrLoc(p.config, dest)
let rc = call.rdLoc
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "unsureAsgnRef"),
cCast(ptrType(CPointer), rad),
rc)
else:
call.snippet = cCast(st,
cgCall(p, "newSeqRC1", typinfo, length))
let rd = dest.rdLoc
let rc = call.rdLoc
p.s(cpsStmts).addAssignment(rd, rc)
else:
if lenIsZero:
call.snippet = NimNil
else:
let st = getTypeDesc(p.module, seqtype)
let typinfo = genTypeInfoV1(p.module, seqtype, dest.lode.info)
call.snippet = cCast(st,
cgCall(p, "newSeq", typinfo, length))
genAssignment(p, dest, call, {})
proc genNewSeq(p: BProc, e: PNode) =
var a = initLocExpr(p, e.secondSon)
var b = initLocExpr(p, son(e, 2))
if optSeqDestructors in p.config.globalOptions:
let seqtype = skipTypes(e.secondSon.typ, abstractVarRange)
let ra = a.rdLoc
let rb = b.rdLoc
let pt = getSeqPayloadType(p.module, seqtype)
let pe = seqPayloadElem(p.module, seqtype)
p.s(cpsStmts).addFieldAssignment(ra, "len", rb)
p.s(cpsStmts).addFieldAssignmentWithValue(ra, "p"):
p.s(cpsStmts).addCast(ptrType(pt)):
p.s(cpsStmts).addCall(cgsymValue(p.module, "newSeqPayload"),
rb,
cSizeof(pe),
cAlignof(pe))
else:
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.secondSon)
if optSeqDestructors in p.config.globalOptions:
if d.k == locNone: d = getTemp(p, e.typ, needsInit=false)
let rd = d.rdLoc
let ra = a.rdLoc
let pt = getSeqPayloadType(p.module, seqtype)
let pe = seqPayloadElem(p.module, seqtype)
p.s(cpsStmts).addFieldAssignment(rd, "len", cIntValue(0))
p.s(cpsStmts).addFieldAssignmentWithValue(rd, "p"):
p.s(cpsStmts).addCast(ptrType(pt)):
p.s(cpsStmts).addCall(cgsymValue(p.module, "newSeqPayloadUninit"),
ra,
cSizeof(pe),
cAlignof(pe))
else:
if d.k == locNone: d = getTemp(p, e.typ, needsInit=false) # bug #22560
let ra = a.rdLoc
let dres = cCast(getTypeDesc(p.module, seqtype),
cgCall(p, "nimNewSeqOfCap",
genTypeInfoV1(p.module, seqtype, e.info),
ra))
putIntoDest(p, d, e, dres)
gcUsage(p.config, e)
proc rawConstExpr(p: BProc, n: PNode; d: var TLoc) =
let t = n.typ
discard getTypeDesc(p.module, t) # so that any fields are initialized
let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels)
fillLoc(d, locData, n, p.module.tmpBase & rope(id), OnStatic)
if id == p.module.labels:
# expression not found in the cache:
inc(p.module.labels)
let td = getTypeDesc(p.module, t)
var data = newBuilder("")
data.addVarWithInitializer(kind = Const, name = d.snippet, typ = td):
# bug #23627; when generating const object fields, it's likely that
# we need to generate type infos for the object, which may be an object with
# custom hooks. We need to generate potential consts in the hooks first.
genBracedInit(p, n, isConst = true, t, data)
p.module.s[cfsData].add(extract(data))
proc handleConstExpr(p: BProc, n: PNode, d: var TLoc): bool =
if d.k == locNone and n.len > ord(n.kind == nkObjConstr) and n.isDeepConstExpr:
rawConstExpr(p, n, d)
result = true
else:
result = false
proc genFieldObjConstr(p: BProc; ty: PType; useTemp, isRef: bool; nField, val, 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)
if field.loc.snippet == "": internalError(p.config, info, "genFieldObjConstr")
if check != nil and optFieldCheck in p.options:
genFieldCheck(p, check, r, field, ty)
tmp2.snippet = dotField(tmp2.snippet, field.loc.snippet)
if useTemp:
tmp2.k = locTemp
tmp2.storage = if isRef: OnHeap else: OnStack
else:
tmp2.k = d.k
tmp2.storage = if isRef: OnHeap else: d.storage
tmp2.lode = val
if nField.typ.skipTypes(abstractVar).kind in {tyOpenArray, tyVarargs}:
var tmp3 = getTemp(p, val.typ)
expr(p, val, tmp3)
genOpenArrayConv(p, tmp2, tmp3, {})
else:
expr(p, val, tmp2)
proc genObjConstr(p: BProc, e: PNode, d: var TLoc) =
# inheritance in C++ does not allow struct initialization so
# we skip this step here:
if not p.module.compileToCpp and optSeqDestructors notin p.config.globalOptions:
# disabled optimization: it is wrong for C++ and now also
# causes trouble for --gc:arc, see bug #13240
#[
var box: seq[Thing]
for i in 0..3:
box.add Thing(s1: "121") # pass by sink can mutate Thing.
]#
if handleConstExpr(p, e, d): return
var t = e.typ.skipTypes(abstractInstOwned)
let isRef = t.kind == tyRef
# check if we need to construct the object in a temporary.
# A temp is needed when:
# - the constructor produces a ref (isRef)
# - the destination is not a writable location (d.k == locNone)
# - the constructed type differs from the destination type (subtype
# assignments need the genAssignment path for ObjectAssignmentDefect)
# - the constructor's field values may alias the destination (isPartOf)
var useTemp =
isRef or
d.k == locNone or
(d.t != nil and not sameBackendType(t, d.t.skipTypes(abstractInstOwned))) or
(isPartOf(d.lode, e, {pfStructural, pfBidirectional}) != arNo)
var tmp: TLoc = default(TLoc)
var r: Rope
let needsZeroMem =
nfAllFieldsSet notin e.flags or
(optSeqDestructors notin p.config.globalOptions and containsGarbageCollectedRef(t))
if useTemp:
tmp = getTemp(p, t)
r = rdLoc(tmp)
if isRef:
rawGenNew(p, tmp, "", needsInit = nfAllFieldsSet notin e.flags)
t = t.elementType.skipTypes(abstractInstOwned)
r = cDeref(r)
gcUsage(p.config, e)
elif needsZeroMem:
constructLoc(p, tmp)
else:
genObjectInit(p, cpsStmts, t, tmp, constructObj)
else:
if needsZeroMem: resetLoc(p, d)
else: genObjectInit(p, cpsStmts, d.t, d, if isRef: constructRefObj else: constructObj)
r = rdLoc(d)
discard getTypeDesc(p.module, t)
let ty = getUniqueType(t)
for it in sonsFrom(e, 1):
if nfPreventCg in it.flags:
# this is an object constructor node generated by the VM and
# this field is in an inactive case branch, don't generate assignment
continue
var check: PNode = nil
if it.len == 3 and optFieldCheck in p.options:
check = son(it, 2)
genFieldObjConstr(p, ty, useTemp, isRef, it.firstSon, it.secondSon, check, d, r, e.info)
if useTemp:
if d.k == locNone:
d = tmp
else:
genAssignment(p, d, tmp, {})
proc lhsDoesAlias(a, b: PNode): bool =
result = false
for y in b:
if isPartOf(a, y) != arNo: return true
proc genSeqConstr(p: BProc, n: PNode, d: var TLoc) =
var arr: TLoc
var tmp: TLoc = default(TLoc)
# bug #668
let doesAlias = lhsDoesAlias(d.lode, n)
let dest = if doesAlias: addr(tmp) else: addr(d)
if doesAlias:
tmp = getTemp(p, n.typ)
elif d.k == locNone:
d = getTemp(p, n.typ)
let lit = cIntLiteral(n.len)
if optSeqDestructors in p.config.globalOptions:
let seqtype = n.typ
let rd = rdLoc dest[]
let pt = getSeqPayloadType(p.module, seqtype)
let pe = seqPayloadElem(p.module, seqtype)
p.s(cpsStmts).addFieldAssignment(rd, "len", lit)
p.s(cpsStmts).addFieldAssignmentWithValue(rd, "p"):
p.s(cpsStmts).addCast(ptrType(pt)):
p.s(cpsStmts).addCall(cgsymValue(p.module, "newSeqPayload"),
lit,
cSizeof(pe),
cAlignof(pe))
else:
# generate call to newSeq before adding the elements per hand:
genNewSeqAux(p, dest[], lit, not n.hasSons)
for i, ni in isons(n):
arr = initLoc(locExpr, ni, OnHeap)
let lit = cIntLiteral(i)
let rd = rdLoc dest[]
arr.snippet = subscript(dataField(p, rd), lit)
arr.storage = OnHeap # we know that sequences are on the heap
expr(p, ni, arr)
gcUsage(p.config, n)
if doesAlias:
if d.k == locNone:
d = tmp
else:
genAssignment(p, d, tmp, {})
proc genArrToSeq(p: BProc, n: PNode, d: var TLoc) =
var elem, arr: TLoc
if n.secondSon.kind == nkBracket:
n.secondSon.typ = n.typ
genSeqConstr(p, n.secondSon, d)
return
if d.k == locNone:
d = getTemp(p, n.typ)
var a = initLocExpr(p, n.secondSon)
# generate call to newSeq before adding the elements per hand:
let L = toInt(lengthOrd(p.config, n.secondSon.typ))
if optSeqDestructors in p.config.globalOptions:
let seqtype = n.typ
let rd = rdLoc d
let valL = cIntValue(L)
let pt = getSeqPayloadType(p.module, seqtype)
let pe = seqPayloadElem(p.module, seqtype)
p.s(cpsStmts).addFieldAssignment(rd, "len", valL)
p.s(cpsStmts).addFieldAssignmentWithValue(rd, "p"):
p.s(cpsStmts).addCast(ptrType(pt)):
p.s(cpsStmts).addCall(cgsymValue(p.module, "newSeqPayload"),
valL,
cSizeof(pe),
cAlignof(pe))
else:
let lit = cIntLiteral(L)
genNewSeqAux(p, d, lit, L == 0)
# bug #5007; do not produce excessive C source code:
if L < 10:
for i in 0..<L:
elem = initLoc(locExpr, lodeTyp elemType(skipTypes(n.typ, abstractInst)), OnHeap)
let lit = cIntLiteral(i)
elem.snippet = subscript(dataField(p, rdLoc(d)), lit)
elem.storage = OnHeap # we know that sequences are on the heap
arr = initLoc(locExpr, lodeTyp elemType(skipTypes(n.secondSon.typ, abstractInst)), a.storage)
arr.snippet = subscript(rdLoc(a), lit)
genAssignment(p, elem, arr, {needToCopy})
else:
var i: TLoc = getTemp(p, getSysType(p.module.g.graph, unknownLineInfo, tyInt))
p.s(cpsStmts).addForRangeExclusive(i.snippet, cIntValue(0), cIntValue(L)):
elem = initLoc(locExpr, lodeTyp elemType(skipTypes(n.typ, abstractInst)), OnHeap)
elem.snippet = subscript(dataField(p, rdLoc(d)), rdLoc(i))
elem.storage = OnHeap # we know that sequences are on the heap
arr = initLoc(locExpr, lodeTyp elemType(skipTypes(n.secondSon.typ, abstractInst)), a.storage)
arr.snippet = subscript(rdLoc(a), rdLoc(i))
genAssignment(p, elem, arr, {needToCopy})
proc genNewFinalize(p: BProc, e: PNode) =
var
b: TLoc
refType, bt: PType
ti: Rope
refType = skipTypes(e.secondSon.typ, abstractVarRange)
var a = initLocExpr(p, e.secondSon)
var f = initLocExpr(p, son(e, 2))
b = initLoc(locExpr, a.lode, OnHeap)
ti = genTypeInfo(p.config, p.module, refType, e.info)
p.module.s[cfsTypeInit3].addDerefFieldAssignment(ti, "finalizer", cCast(CPointer, rdLoc(f)))
b.snippet = cCast(getTypeDesc(p.module, refType),
cgCall(p, "newObj",
ti,
cSizeof(getTypeDesc(p.module, skipTypes(refType.elementType, abstractRange)))))
genAssignment(p, a, b, {}) # set the object type:
bt = skipTypes(refType.elementType, abstractRange)
genObjectInit(p, cpsStmts, bt, a, constructRefObj)
gcUsage(p.config, e)
proc genOfHelper(p: BProc; dest: PType; a: Rope; info: TLineInfo; result: var Builder) =
if optTinyRtti in p.config.globalOptions:
let token = $genDisplayElem(MD5Digest(hashType(dest, p.config)))
result.addCall(cgsymValue(p.module, "isObjDisplayCheck"),
dotField(a, "m_type"),
cIntValue(int(getObjDepth(dest))),
token)
else:
# unfortunately 'genTypeInfoV1' sets tfObjHasKids as a side effect, so we
# have to call it here first:
let ti = genTypeInfoV1(p.module, dest, info)
if tfFinal in dest.flags or (objHasKidsValid in p.module.flags and
tfObjHasKids notin dest.flags):
result.addOp(Equal, dotField(a, "m_type"), ti)
else:
cgsym(p.module, "TNimType")
inc p.module.labels
let cache = "Nim_OfCheck_CACHE" & p.module.labels.rope
p.module.s[cfsVars].addArrayVar(kind = Global,
name = cache,
elementType = ptrType("TNimType"),
len = 2)
result.addCall(cgsymValue(p.module, "isObjWithCache"),
dotField(a, "m_type"),
ti,
cache)
proc genOf(p: BProc, x: PNode, typ: PType, d: var TLoc) =
var a: TLoc = initLocExpr(p, x)
var dest = skipTypes(typ, typedescPtrs)
var r = rdLoc(a)
var nilCheck: Rope = ""
var t = skipTypes(a.t, abstractInstOwned)
while t.kind in {tyVar, tyLent, tyPtr, tyRef}:
if t.kind notin {tyVar, tyLent}: nilCheck = r
if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp:
r = cDeref(r)
t = skipTypes(t.elementType, typedescInst+{tyOwned})
discard getTypeDesc(p.module, t)
if not p.module.compileToCpp:
while t.kind == tyObject and t.baseClass != nil:
r = dotField(r, "Sup")
t = skipTypes(t.baseClass, skipPtrs)
if isObjLackingTypeField(t):
globalError(p.config, x.info,
"no 'of' operator available for pure objects")
var ro = newBuilder("")
genOfHelper(p, dest, r, x.info, ro)
var ofExpr = extract(ro)
if nilCheck != "":
ofExpr = cOp(And, nilCheck, ofExpr)
putIntoDest(p, d, x, ofExpr, a.storage)
proc genOf(p: BProc, n: PNode, d: var TLoc) =
genOf(p, n.secondSon, son(n, 2).typ, d)
proc genRepr(p: BProc, e: PNode, d: var TLoc) =
if optTinyRtti in p.config.globalOptions:
localError(p.config, e.info, "'repr' is not available for --newruntime")
var a: TLoc = initLocExpr(p, e.secondSon)
var t = skipTypes(e.secondSon.typ, abstractVarRange)
template cgCall(name: string, args: varargs[untyped]): untyped =
cCall(cgsymValue(p.module, name), args)
case t.kind
of tyInt..tyInt64, tyUInt..tyUInt64:
let ra = rdLoc(a)
putIntoDest(p, d, e, cgCall("reprInt", cCast(NimInt64, ra)), a.storage)
of tyFloat..tyFloat128:
let ra = rdLoc(a)
putIntoDest(p, d, e, cgCall("reprFloat", ra), a.storage)
of tyBool:
let ra = rdLoc(a)
putIntoDest(p, d, e, cgCall("reprBool", ra), a.storage)
of tyChar:
let ra = rdLoc(a)
putIntoDest(p, d, e, cgCall("reprChar", ra), a.storage)
of tyEnum, tyOrdinal:
let ra = rdLoc(a)
let rti = genTypeInfoV1(p.module, t, e.info)
putIntoDest(p, d, e, cgCall("reprEnum", cCast(NimInt, ra), rti), a.storage)
of tyString:
let ra = rdLoc(a)
putIntoDest(p, d, e, cgCall("reprStr", ra), a.storage)
of tySet:
let raa = addrLoc(p.config, a)
let rti = genTypeInfoV1(p.module, t, e.info)
putIntoDest(p, d, e, cgCall("reprSet", raa, rti), a.storage)
of tyOpenArray, tyVarargs:
var b: TLoc = default(TLoc)
case skipTypes(a.t, abstractVarRange).kind
of tyOpenArray, tyVarargs:
let ra = rdLoc(a)
putIntoDest(p, b, e, ra & cArgumentSeparator & ra & "Len_0", a.storage)
of tyString, tySequence:
let la = lenExpr(p, a)
if p.config.usesSso() and
skipTypes(a.t, abstractVarRange).kind == tyString:
let bra = byRefLoc(p, a)
putIntoDest(p, b, e,
cCall(cgsymValue(p.module, "nimStrData"), bra) &
cArgumentSeparator & la,
a.storage)
else:
let ra = rdLoc(a)
putIntoDest(p, b, e,
cIfExpr(dataFieldAccessor(p, ra), dataField(p, ra), NimNil) &
cArgumentSeparator & la,
a.storage)
of tyArray:
let ra = rdLoc(a)
let la = cIntValue(lengthOrd(p.config, a.t))
putIntoDest(p, b, e, ra & cArgumentSeparator & la, a.storage)
else: internalError(p.config, e.firstSon.info, "genRepr()")
let rb = rdLoc(b)
let rti = genTypeInfoV1(p.module, elemType(t), e.info)
putIntoDest(p, d, e, cgCall("reprOpenArray", rb, rti), a.storage)
of tyCstring, tyArray, tyRef, tyPtr, tyPointer, tyNil, tySequence:
let ra = rdLoc(a)
let rti = genTypeInfoV1(p.module, t, e.info)
putIntoDest(p, d, e, cgCall("reprAny", ra, rti), a.storage)
of tyEmpty, tyVoid:
localError(p.config, e.info, "'repr' doesn't support 'void' type")
else:
let raa = addrLoc(p.config, a)
let rti = genTypeInfoV1(p.module, t, e.info)
putIntoDest(p, d, e, cgCall("reprAny", raa, rti), a.storage)
gcUsage(p.config, e)
proc rdMType(p: BProc; a: TLoc; nilCheck: var Rope; result: var Snippet; enforceV1 = false) =
var derefs = rdLoc(a)
var t = skipTypes(a.t, abstractInst)
while t.kind in {tyVar, tyLent, tyPtr, tyRef}:
if t.kind notin {tyVar, tyLent}: nilCheck = derefs
if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp:
derefs = cDeref(derefs)
t = skipTypes(t.elementType, abstractInst)
result.add derefs
discard getTypeDesc(p.module, t)
if not p.module.compileToCpp:
while t.kind == tyObject and t.baseClass != nil:
result = dotField(result, "Sup")
t = skipTypes(t.baseClass, skipPtrs)
result = dotField(result, "m_type")
if optTinyRtti in p.config.globalOptions and enforceV1:
result = derefField(result, "typeInfoV1")
proc genGetTypeInfo(p: BProc, e: PNode, d: var TLoc) =
cgsym(p.module, "TNimType")
let t = e.secondSon.typ
# ordinary static type information
putIntoDest(p, d, e, genTypeInfoV1(p.module, t, e.info))
proc genGetTypeInfoV2(p: BProc, e: PNode, d: var TLoc) =
let t = e.secondSon.typ
if isFinal(t) or e.firstSon.sym.name.s != "getDynamicTypeInfo":
# ordinary static type information
putIntoDest(p, d, e, genTypeInfoV2(p.module, t, e.info))
else:
var a: TLoc = initLocExpr(p, e.secondSon)
var nilCheck = ""
# use the dynamic type stored at offset 0:
var rt: Snippet = ""
rdMType(p, a, nilCheck, rt)
putIntoDest(p, d, e, rt)
proc genAccessTypeField(p: BProc; e: PNode; d: var TLoc) =
var a: TLoc = initLocExpr(p, e.secondSon)
var nilCheck = ""
# use the dynamic type stored at offset 0:
var rt: Snippet = ""
rdMType(p, a, nilCheck, rt)
putIntoDest(p, d, e, rt)
template genDollarIt(p: BProc, n: PNode, d: var TLoc, frmt: untyped) =
block:
var a: TLoc = initLocExpr(p, n.secondSon)
let it {.inject.} = rdLoc(a)
a.snippet = frmt
a.flags.excl lfIndirect # this flag should not be propagated here (not just for HCR)
if d.k == locNone: d = getTemp(p, n.typ)
genAssignment(p, d, a, {})
gcUsage(p.config, n)
proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
var a = e.secondSon
if a.kind == nkHiddenAddr: a = a.firstSon
var typ = skipTypes(a.typ, abstractVar + tyUserTypeClasses)
case typ.kind
of tyOpenArray, tyVarargs:
# Bug #9279, len(toOpenArray()) has to work:
if a.kind in nkCallKinds and a.firstSon.kind == nkSym and a.firstSon.sym.magic == mSlice:
# magic: pass slice to openArray:
var m = initLocExpr(p, a.secondSon)
var b = initLocExpr(p, son(a, 2))
var c = initLocExpr(p, son(a, 3))
if optBoundsCheck in p.options:
genBoundsCheck(p, m, b, c, skipTypes(m.t, abstractVarRange))
if op == mHigh:
putIntoDest(p, d, e, cOp(Sub, NimInt, rdLoc(c), rdLoc(b)))
else:
putIntoDest(p, d, e, cOp(Add, NimInt, cOp(Sub, NimInt, rdLoc(c), rdLoc(b)), cIntValue(1)))
else:
if not reifiedOpenArray(a):
if op == mHigh: unaryExpr(p, e, d, cOp(Sub, NimInt, ra & "Len_0", cIntValue(1)))
else: unaryExpr(p, e, d, ra & "Len_0")
else:
let isDeref = a.kind in {nkHiddenDeref, nkDerefExpr}
template lenA: untyped =
if isDeref:
derefField(ra, "Field1")
else:
dotField(ra, "Field1")
if op == mHigh:
unaryExpr(p, e, d, cOp(Sub, NimInt, lenA, cIntValue(1)))
else:
unaryExpr(p, e, d, lenA)
of tyCstring:
if op == mHigh:
unaryExpr(p, e, d, cOp(Sub, NimInt, cgCall(p, "nimCStrLen", ra), cIntValue(1)))
else:
unaryExpr(p, e, d, cgCall(p, "nimCStrLen", ra))
of tyString:
var a: TLoc = initLocExpr(p, e.secondSon)
var x = lenExpr(p, a)
if op == mHigh: x = cOp(Sub, NimInt, x, cIntValue(1))
putIntoDest(p, d, e, x)
of tySequence:
# we go through a temporary here because people write bullshit code.
var tmp: TLoc = getIntTemp(p)
var a = initLocExpr(p, e.secondSon)
var x = lenExpr(p, a)
if op == mHigh: x = cOp(Sub, NimInt, x, cIntValue(1))
p.s(cpsStmts).addAssignment(tmp.snippet, x)
putIntoDest(p, d, e, tmp.snippet)
of tyArray:
# YYY: length(sideeffect) is optimized away incorrectly?
if op == mHigh: putIntoDest(p, d, e, cIntValue(lastOrd(p.config, typ)))
else: putIntoDest(p, d, e, cIntValue(lengthOrd(p.config, typ)))
else: internalError(p.config, e.info, "genArrayLen()")
proc isTrivialTypesToSnippet(t: PType): Snippet =
if containsGarbageCollectedRef(t) or
hasDestructor(t):
result = NimFalse
else:
result = NimTrue
proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc, noinit = false) =
if optSeqDestructors in p.config.globalOptions:
e.secondSon = makeAddr(e.secondSon, p.module.idgen)
genCall(p, e, d)
return
assert(d.k == locNone)
var x = e.secondSon
if x.kind in {nkAddr, nkHiddenAddr}: x = x.firstSon
var a = initLocExpr(p, x)
var b = initLocExpr(p, son(e, 2))
let t = skipTypes(e.secondSon.typ, {tyVar})
var call = initLoc(locCall, e, OnHeap)
let ra = rdLoc(a)
let rb = rdLoc(b)
let rt = getTypeDesc(p.module, t)
let rti = genTypeInfoV1(p.module, t.skipTypes(abstractInst), e.info)
var pExpr: Snippet
if not p.module.compileToCpp:
pExpr = cIfExpr(ra, cAddr(derefField(ra, "Sup")), NimNil)
else:
pExpr = ra
let name = if noinit: "setLengthSeqUninit" else: "setLengthSeqV2"
call.snippet = cCast(rt, cgCall(p, name, pExpr, rti, rb,
isTrivialTypesToSnippet(t.skipTypes(abstractInst).elementType)))
genAssignment(p, a, call, {})
gcUsage(p.config, e)
proc genSetLengthStr(p: BProc, e: PNode, d: var TLoc) =
if optSeqDestructors in p.config.globalOptions:
binaryStmtAddr(p, e, d, "setLengthStrV2")
else:
if d.k != locNone: internalError(p.config, e.info, "genSetLengthStr")
var a = initLocExpr(p, e.secondSon)
var b = initLocExpr(p, son(e, 2))
var call = initLoc(locCall, e, OnHeap)
call.snippet = cgCall(p, "setLengthStr", rdLoc(a), rdLoc(b))
genAssignment(p, a, call, {})
gcUsage(p.config, e)
proc genSwap(p: BProc, e: PNode, d: var TLoc) =
# swap(a, b) -->
# temp = a
# a = b
# b = temp
cowBracket(p, e.secondSon)
cowBracket(p, son(e, 2))
var tmp: TLoc = getTemp(p, skipTypes(e.secondSon.typ, abstractVar))
var a = initLocExpr(p, e.secondSon) # eval a
var b = initLocExpr(p, son(e, 2)) # eval b
genAssignment(p, tmp, a, {})
genAssignment(p, a, b, {})
genAssignment(p, b, tmp, {})
proc rdSetElemLoc(conf: ConfigRef; a: TLoc, typ: PType; result: var Snippet) =
# read a location of an set element; it may need a subtraction operation
# before the set operation
result = rdCharLoc(a)
let setType = typ.skipTypes(abstractPtrs)
assert(setType.kind == tySet)
if firstOrd(conf, setType) != 0:
result = cOp(Sub, NimUint, result, cIntValue(firstOrd(conf, setType)))
proc fewCmps(conf: ConfigRef; s: AnyNode): bool =
# this function estimates whether it is better to emit code
# for constructing the set or generating a bunch of comparisons directly
if s.kind != nkCurly: return false
if (getSize(conf, s.typ) <= conf.target.intSize) and (nfAllConst in s.flags):
result = false # it is better to emit the set generation code
elif elemType(s.typ).kind in {tyInt, tyInt16..tyInt64}:
result = true # better not emit the set if int is basetype!
else:
result = s.len <= 8 # 8 seems to be a good value
template binaryExprIn(p: BProc, e: PNode, a, b, d: var TLoc, frmt: untyped) =
var elem {.inject.}: Snippet = ""
rdSetElemLoc(p.config, b, a.t, elem)
let ra {.inject.} = rdLoc(a)
putIntoDest(p, d, e, frmt)
proc genInExprAux(p: BProc, e: PNode, a, b, d: var TLoc) =
let s = int(getSize(p.config, skipTypes(e.secondSon.typ, abstractVar)))
case s
of 1, 2, 4, 8:
let mask = s * 8 - 1
let rt = cUintType(s * 8)
binaryExprIn(p, e, a, b, d,
# ((a & ((NU8) 1 << ((NU) elem & 7U))) != 0)
# ((a & ((NU16) 1 << ((NU) elem & 15U))) != 0)
# ((a & ((NU32) 1 << ((NU) elem & 31U))) != 0)
# ((a & ((NU64) 1 << ((NU) elem & 63U))) != 0)
cOp(NotEqual,
cOp(BitAnd, rt, ra,
cOp(Shl, rt, cCast(rt, cIntValue(1)),
cOp(BitAnd, NimUint, cCast(NimUint, elem), cUintValue(mask.uint)))),
cIntValue(0)))
else:
# ((a[(NU)(elem)>>3] &(1U<<((NU)(elem)&7U)))!=0)
binaryExprIn(p, e, a, b, d,
cOp(NotEqual,
cOp(BitAnd, NimUint8,
subscript(ra, cOp(Shr, NimUint, cCast(NimUint, elem), cIntValue(3))),
cOp(Shl, NimUint8,
cUintValue(1),
cOp(BitAnd, NimUint,
cCast(NimUint, elem),
cUintValue(7)))),
cIntValue(0)))
template binaryStmtInExcl(p: BProc, e: PNode, d: var TLoc, frmt: untyped) =
assert(d.k == locNone)
var a = initLocExpr(p, e.secondSon)
var b = initLocExpr(p, son(e, 2))
var elem {.inject.}: Snippet = ""
rdSetElemLoc(p.config, b, a.t, elem)
let ra {.inject.} = rdLoc(a)
p.s(cpsStmts).add(frmt)
proc genInOp(p: BProc, e: PNode, d: var TLoc) =
var a, b, x, y: TLoc
if (e.secondSon.kind == nkCurly) and fewCmps(p.config, e.secondSon):
# a set constructor but not a constant set:
# do not emit the set, but generate a bunch of comparisons; and if we do
# so, we skip the unnecessary range check: This is a semantical extension
# that code now relies on. :-/ XXX
let ea = if son(e, 2).kind in {nkChckRange, nkChckRange64}:
son(e, 2).firstSon
else:
son(e, 2)
a = initLocExpr(p, ea)
b = initLoc(locExpr, e, OnUnknown)
if e.secondSon.hasSons:
var val: Snippet = ""
for it in sons(e.secondSon):
var currentExpr: Snippet
if it.kind == nkRange:
x = initLocExpr(p, it.firstSon)
y = initLocExpr(p, it.secondSon)
let rca = rdCharLoc(a)
let rcx = rdCharLoc(x)
let rcy = rdCharLoc(y)
currentExpr = cOp(And,
cOp(GreaterEqual, rca, rcx),
cOp(LessEqual, rca, rcy))
else:
x = initLocExpr(p, it)
let rca = rdCharLoc(a)
let rcx = rdCharLoc(x)
currentExpr = cOp(Equal, rca, rcx)
if val.len == 0:
val = currentExpr
else:
val = cOp(Or, val, currentExpr)
b.snippet = val
else:
# handle the case of an empty set
b.snippet = cIntValue(0)
putIntoDest(p, d, e, b.snippet)
else:
assert(e.secondSon.typ != nil)
assert(son(e, 2).typ != nil)
a = initLocExpr(p, e.secondSon)
b = initLocExpr(p, son(e, 2))
genInExprAux(p, e, a, b, d)
proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
var a, b: TLoc
var i: TLoc
var setType = skipTypes(e.secondSon.typ, abstractVar)
var size = int(getSize(p.config, setType))
case size
of 1, 2, 4, 8:
let bits = size * 8
let rt = cUintType(bits)
case op
of mIncl:
let mask = bits - 1
binaryStmtInExcl(p, e, d,
cInPlaceOp(BitOr, rt, ra,
cOp(Shl, rt, cCast(rt, cIntValue(1)),
cOp(BitAnd, NimUint, elem, cIntValue(mask)))))
of mExcl:
let mask = bits - 1
binaryStmtInExcl(p, e, d,
cInPlaceOp(BitAnd, rt, ra, cOp(BitNot, rt,
cOp(Shl, rt, cCast(rt, cIntValue(1)),
cOp(BitAnd, NimUint, elem, cIntValue(mask))))))
of mCard:
let name = if size <= 4: "countBits32" else: "countBits64"
unaryExprChar(p, e, d, cgCall(p, name, ra))
of mLtSet:
binaryExprChar(p, e, d, cOp(And,
cOp(Equal, cOp(BitAnd, rt, ra, cOp(BitNot, rt, rb)), cIntValue(0)),
cOp(NotEqual, ra, rb)))
of mLeSet:
binaryExprChar(p, e, d,
cOp(Equal, cOp(BitAnd, rt, ra, cOp(BitNot, rt, rb)), cIntValue(0)))
of mEqSet: binaryExpr(p, e, d, cOp(Equal, ra, rb))
of mMulSet: binaryExpr(p, e, d, cOp(BitAnd, rt, ra, rb))
of mPlusSet: binaryExpr(p, e, d, cOp(BitOr, rt, ra, rb))
of mMinusSet: binaryExpr(p, e, d, cOp(BitAnd, rt, ra, cOp(BitNot, rt, rb)))
of mXorSet: binaryExpr(p, e, d, cOp(BitXor, rt, ra, rb))
of mInSet:
genInOp(p, e, d)
else: internalError(p.config, e.info, "genSetOp()")
else:
case op
of mIncl:
binaryStmtInExcl(p, e, d, cInPlaceOp(BitOr, NimUint8,
subscript(ra, cOp(Shr, NimUint, cCast(NimUint, elem), cIntValue(3))),
cOp(Shl, NimUint8, cUintValue(1), cOp(BitAnd, NimUint, elem, cUintValue(7)))))
of mExcl:
binaryStmtInExcl(p, e, d, cInPlaceOp(BitAnd, NimUint8,
subscript(ra, cOp(Shr, NimUint, cCast(NimUint, elem), cIntValue(3))),
cOp(BitNot, NimUint8,
cOp(Shl, NimUint8, cUintValue(1), cOp(BitAnd, NimUint, elem, cUintValue(7))))))
of mCard:
var a: TLoc = initLocExpr(p, e.secondSon)
let rca = rdCharLoc(a)
putIntoDest(p, d, e, cgCall(p, "cardSet", rca, cIntValue(size)))
of mLtSet, mLeSet:
i = getTemp(p, getSysType(p.module.g.graph, unknownLineInfo, tyInt)) # our counter
a = initLocExpr(p, e.secondSon)
b = initLocExpr(p, son(e, 2))
if d.k == locNone: d = getTemp(p, getSysType(p.module.g.graph, unknownLineInfo, tyBool))
discard "for ($1 = 0; $1 < $2; $1++) { $n" &
" $3 = (($4[$1] & ~ $5[$1]) == 0);$n" &
" if (!$3) break;}$n"
let ri = rdLoc(i)
let rd = rdLoc(d)
let ra = rdLoc(a)
let rb = rdLoc(b)
p.s(cpsStmts).addForRangeExclusive(ri, cIntValue(0), cIntValue(size)):
p.s(cpsStmts).addAssignment(rd, cOp(Equal,
cOp(BitAnd, NimUint8,
subscript(ra, ri),
cOp(BitNot, NimUint8, subscript(rb, ri))),
cIntValue(0)))
p.s(cpsStmts).addSingleIfStmt(cOp(Not, rd)):
p.s(cpsStmts).addBreak()
if op == mLtSet:
discard "if ($3) $3 = (#nimCmpMem($4, $5, $2) != 0);$n"
p.s(cpsStmts).addSingleIfStmt(rd):
p.s(cpsStmts).addAssignment(rd, cOp(NotEqual,
cgCall(p, "nimCmpMem", ra, rb, cIntValue(size)),
cIntValue(0)))
of mEqSet:
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 rca = a.rdCharLoc
let rcb = b.rdCharLoc
putIntoDest(p, d, e, cOp(Equal,
cgCall(p, "nimCmpMem", rca, rcb, cIntValue(size)),
cIntValue(0)))
of mMulSet, mPlusSet, mMinusSet, mXorSet:
# we inline the simple for loop for better code generation:
i = getTemp(p, getSysType(p.module.g.graph, unknownLineInfo, tyInt)) # our counter
a = initLocExpr(p, e.secondSon)
b = initLocExpr(p, son(e, 2))
if d.k == locNone: d = getTemp(p, setType)
let ri = rdLoc(i)
let rd = rdLoc(d)
let ra = rdLoc(a)
let rb = rdLoc(b)
p.s(cpsStmts).addForRangeExclusive(ri, cIntValue(0), cIntValue(size)):
p.s(cpsStmts).addAssignmentWithValue(subscript(rd, ri)):
let x = subscript(ra, ri)
let y = subscript(rb, ri)
let lookup =
case op
of mMulSet: cOp(BitAnd, NimUint8, x, y)
of mPlusSet: cOp(BitOr, NimUint8, x, y)
of mMinusSet: cOp(BitAnd, NimUint8, x, cOp(BitNot, NimUint8, y))
of mXorSet: cOp(BitXor, NimUint8, x, y)
else: "" # unreachable
p.s(cpsStmts).add(lookup)
of mInSet: genInOp(p, e, d)
else: internalError(p.config, e.info, "genSetOp")
proc genOrd(p: BProc, e: PNode, d: var TLoc) =
unaryExprChar(p, e, d, ra)
proc genSomeCast(p: BProc, e: PNode, d: var TLoc) =
const
ValueTypes = {tyTuple, tyObject, tyArray, tyOpenArray, tyVarargs, tyUncheckedArray}
# we use whatever C gives us. Except if we have a value-type, we need to go
# through its address:
var a: TLoc = initLocExpr(p, e.secondSon)
let etyp = skipTypes(e.typ, abstractRange+{tyOwned})
let srcTyp = skipTypes(e.secondSon.typ, abstractRange)
if etyp.kind in ValueTypes and lfIndirect notin a.flags:
let destTyp = getTypeDesc(p.module, e.typ)
let val = addrLoc(p.config, a)
# (* (destType*) val)
putIntoDest(p, d, e,
cDeref(
cCast(
ptrType(destTyp),
wrapPar(val))),
a.storage)
elif etyp.kind == tyProc and etyp.callConv == ccClosure and srcTyp.callConv != ccClosure:
let destTyp = getClosureType(p.module, etyp, clHalfWithEnv)
let val = rdCharLoc(a)
# (destTyp) val
putIntoDest(p, d, e, cCast(destTyp, wrapPar(val)), a.storage)
else:
# C++ does not like direct casts from pointer to shorter integral types
if srcTyp.kind in {tyPtr, tyPointer} and etyp.kind in IntegralTypes:
let destTyp = getTypeDesc(p.module, e.typ)
let val = rdCharLoc(a)
# (destTyp) (ptrdiff_t) val
putIntoDest(p, d, e, cCast(destTyp, cCast("ptrdiff_t", wrapPar(val))), a.storage)
elif optSeqDestructors in p.config.globalOptions and etyp.kind in {tySequence, tyString}:
let destTyp = getTypeDesc(p.module, e.typ)
let val = rdCharLoc(a)
# (* (destType*) (&val))
putIntoDest(p, d, e, cDeref(cCast(ptrType(destTyp), wrapPar(cAddr(val)))), a.storage)
elif etyp.kind == tyBool and srcTyp.kind in IntegralTypes:
putIntoDest(p, d, e, cOp(NotEqual, rdCharLoc(a), cIntValue(0)), a.storage)
elif etyp.kind == tyProc and srcTyp.kind == tyProc and sameBackendType(etyp, srcTyp):
expr(p, e.secondSon, d)
else:
if etyp.kind == tyPtr:
# generates the definition of structs for casts like cast[ptr object](addr x)[]
let internalType = etyp.skipTypes({tyPtr})
if internalType.kind == tyObject:
discard getTypeDesc(p.module, internalType)
let destTyp = getTypeDesc(p.module, e.typ)
let val = rdCharLoc(a)
putIntoDest(p, d, e, cCast(destTyp, wrapPar(val)), a.storage)
proc genCast(p: BProc, e: PNode, d: var TLoc) =
const ValueTypes = {tyFloat..tyFloat128, tyTuple, tyObject, tyArray}
let
destt = skipTypes(e.typ, abstractRange)
srct = skipTypes(e.secondSon.typ, abstractRange)
if destt.kind in ValueTypes or srct.kind in ValueTypes:
# 'cast' and some float type involved? --> use a union.
inc(p.labels)
var lbl = p.labels.rope
var tmp: TLoc = default(TLoc)
tmp.snippet = dotField("LOC" & lbl, "source")
let destsize = getSize(p.config, destt)
let srcsize = getSize(p.config, srct)
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):
p.s(cpsLocals).addUnionType():
p.s(cpsLocals).addField(name = "dest", typ = destTyp)
p.s(cpsLocals).addField(name = "source", typ = srcTyp)
p.s(cpsLocals).addCallStmt(cgsymValue(p.module, "nimZeroMem"),
cAddr("LOC" & lbl),
cSizeof("LOC" & lbl))
else:
p.s(cpsLocals).addVarWithType(kind = Local, name = "LOC" & lbl):
p.s(cpsLocals).addUnionType():
p.s(cpsLocals).addField(name = "source", typ = srcTyp)
p.s(cpsLocals).addField(name = "dest", typ = destTyp)
tmp.k = locExpr
tmp.lode = lodeTyp srct
tmp.storage = OnStack
tmp.flags = {}
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
# C code; plus it's the right thing to do for closures:
genSomeCast(p, e, d)
proc genRangeChck(p: BProc, n: PNode, d: var TLoc) =
var a: TLoc = initLocExpr(p, n.firstSon)
var dest = skipTypes(n.typ, abstractVar)
if optRangeCheck notin p.options or (dest.kind in {tyUInt..tyUInt64} and
checkUnsignedConversions notin p.config.legacyFeatures):
discard "no need to generate a check because it was disabled"
else:
let n0t = n.firstSon.typ
# emit range check:
if n0t.kind in {tyUInt, tyUInt64}:
var first = newBuilder("")
genLiteral(p, n.secondSon, dest, first)
var last = newBuilder("")
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)))):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseRangeErrorNoArgs"))
raiseInstr(p, p.s(cpsStmts))
else:
let raiser =
case skipTypes(n.typ, abstractVarRange).kind
of tyUInt..tyUInt64, tyChar: "raiseRangeErrorU"
of tyFloat..tyFloat128: "raiseRangeErrorF"
else: "raiseRangeErrorI"
cgsym(p.module, raiser)
var first = newBuilder("")
genLiteral(p, n.secondSon, dest, first)
var last = newBuilder("")
genLiteral(p, son(n, 2), dest, last)
let rca = rdCharLoc(a)
let boundRca =
if n0t.skipTypes(abstractVarRange).kind in {tyUInt, tyUInt32, tyUInt64}:
cCast(NimInt64, rca)
else:
rca
let firstVal = extract(first)
let lastVal = extract(last)
p.s(cpsStmts).addSingleIfStmt(cOp(Or,
cOp(LessThan, boundRca, firstVal),
cOp(GreaterThan, boundRca, lastVal))):
p.s(cpsStmts).addCallStmt(raiser, rca, firstVal, lastVal)
raiseInstr(p, p.s(cpsStmts))
if sameBackendTypeIgnoreRange(dest, n.firstSon.typ):
# don't cast so an address can be taken for `var` conversions
let val = rdCharLoc(a)
putIntoDest(p, d, n, wrapPar(val), a.storage)
else:
let destType = getTypeDesc(p.module, dest)
let val = rdCharLoc(a)
putIntoDest(p, d, n, cCast(destType, wrapPar(val)), a.storage)
proc genConv(p: BProc, e: PNode, d: var TLoc) =
if ignoreConv(e):
expr(p, e.secondSon, d)
else:
genSomeCast(p, e, d)
proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) =
var a: TLoc = initLocExpr(p, n.firstSon)
let arg = if p.config.usesSso(): byRefLoc(p, a) else: rdLoc(a)
putIntoDest(p, d, n,
cgCall(p, "nimToCStringConv", arg),
a.storage)
proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) =
var a: TLoc = initLocExpr(p, n.firstSon)
if p.module.compileToCpp:
# fixes for const qualifier; bug #12703; bug #19588
putIntoDest(p, d, n,
cgCall(p, "cstrToNimstr", cCast(NimCstring, rdLoc(a))),
a.storage)
else:
putIntoDest(p, d, n,
cgCall(p, "cstrToNimstr", rdLoc(a)),
a.storage)
gcUsage(p.config, n)
proc genStrEquals(p: BProc, e: PNode, d: var TLoc) =
var x: TLoc
var a = e.secondSon
var b = son(e, 2)
if a.kind in {nkStrLit..nkTripleStrLit} and a.strVal == "":
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.secondSon)
let lx = lenExpr(p, x)
putIntoDest(p, d, e, cOp(Equal, lx, cIntValue(0)))
else:
binaryExpr(p, e, d, cgCall(p, "eqStrings", ra, rb))
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.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.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)
p.s(cpsStmts).addSingleIfStmt(cOp(NotEqual, rd, rd)):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseFloatInvalidOp"))
raiseInstr(p, p.s(cpsStmts))
if optInfCheck in p.options:
let rd = rdLoc(d)
p.s(cpsStmts).addSingleIfStmt(cOp(And,
cOp(NotEqual, rd, cFloatValue(0.0)),
cOp(Equal, cOp(Mul, rt, rd, cFloatValue(0.5)), rd))):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseFloatOverflow"), rd)
raiseInstr(p, p.s(cpsStmts))
else:
binaryArith(p, e, d, m)
proc genWasMoved(p: BProc; n: PNode) =
var a: TLoc
let n1 = n.secondSon.skipAddr
if p.withinBlockLeaveActions > 0 and notYetAlive(n1):
discard
else:
a = initLocExpr(p, n1, {lfEnforceDeref})
resetLoc(p, a)
#linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n",
# [addrLoc(p.config, a), getTypeDesc(p.module, a.t)])
proc genMove(p: BProc; n: PNode; d: var TLoc) =
if n.len == 4:
# generated by liftdestructors:
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.secondSon.typ.skipTypes(abstractVar).kind == tyString:
# SmallString: destroy dst then struct-copy src; no .p field aliasing needed
genStmts(p, son(n, 3))
genAssignment(p, a, src, {})
else:
p.s(cpsStmts).addSingleIfStmt(
cOp(NotEqual,
dotField(destVal, "p"),
dotField(srcVal, "p"))):
genStmts(p, son(n, 3))
p.s(cpsStmts).addFieldAssignment(destVal, "len", dotField(srcVal, "len"))
p.s(cpsStmts).addFieldAssignment(destVal, "p", dotField(srcVal, "p"))
else:
if d.k == locNone: d = getTemp(p, n.typ)
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.secondSon.skipAddr, {lfEnforceDeref, lfPrepareForMutation})
genAssignment(p, d, a, {})
resetLoc(p, a)
else:
n.secondSon = makeAddr(n.secondSon, p.module.idgen)
genCall(p, n, d)
else:
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.secondSon.skipAddr
let t = arg.typ.skipTypes(abstractInst)
case t.kind
of tyString:
var a: TLoc = initLocExpr(p, arg)
if p.config.usesSso():
# SmallString: delegate to nimDestroyStrV1 (rc-based, handles static strings)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimDestroyStrV1"), rdLoc(a))
else:
let ra = rdLoc(a)
let rp = dotField(ra, "p")
p.s(cpsStmts).addSingleIfStmt(
cOp(And, rp,
cOp(Not, cOp(BitAnd, NimInt,
derefField(rp, "cap"),
NimStrlitFlag)))):
let fn = if optThreads in p.config.globalOptions: "deallocShared" else: "dealloc"
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, fn), rp)
of tySequence:
var a: TLoc = initLocExpr(p, arg)
let ra = rdLoc(a)
let rp = dotField(ra, "p")
let rt = getTypeDesc(p.module, t.elementType)
p.s(cpsStmts).addSingleIfStmt(
cOp(And, rp,
cOp(Not, cOp(BitAnd, NimInt,
derefField(rp, "cap"),
NimStrlitFlag)))):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "alignedDealloc"),
rp,
cAlignof(rt))
else: discard "nothing to do"
else:
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")
discard "ignore calls to the default destructor"
proc genSlice(p: BProc; e: PNode; d: var TLoc) =
let (x, y) = genOpenArraySlice(p, e, e.typ, e.typ.elementType,
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)
p.s(cpsStmts).addFieldAssignment(dest, "Field0", x)
p.s(cpsStmts).addFieldAssignment(dest, "Field1", y)
when false:
localError(p.config, e.info, "invalid context for 'toOpenArray'; " &
"'toOpenArray' is only valid within a call expression")
proc genEnumToStr(p: BProc, e: PNode, d: var TLoc) =
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)
n[0] = newSymNode(toStrProc)
expr(p, n, d)
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.secondSon.skipHiddenAddr
let n = newTreeI(kind, e.info, lhs, son(e, 2))
n.typ = e.typ
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)
of mUnaryMinusI..mAbsI: unaryArithOverflow(p, e, d, op)
of mAddF64..mDivF64: binaryFloatArith(p, e, d, op)
of mShrI..mXor: binaryArith(p, e, d, op)
of mEqProc: genEqProc(p, e, d)
of mAddI..mPred: binaryArithOverflow(p, e, d, op)
of mRepr: genRepr(p, e, d)
of mGetTypeInfo: genGetTypeInfo(p, e, d)
of mGetTypeInfoV2: genGetTypeInfoV2(p, e, d)
of mSwap: genSwap(p, e, d)
of mInc, mDec:
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.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.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.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.secondSon, cCast(destTyp, wrapPar(res)))
of mConStrStr: genStrConcat(p, e, d)
of mAppendStrCh:
if optSeqDestructors in p.config.globalOptions:
binaryStmtAddr(p, e, d, "nimAddCharV1")
else:
var call = initLoc(locCall, e, OnHeap)
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)
of mAppendSeqElem:
if optSeqDestructors in p.config.globalOptions:
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}:
# Inline growth + direct slot assignment: avoids the add() call overhead
# and lets the C compiler see the construction expression at its final
# destination, enabling in-place construction for nkObjConstr etc.
# gcYrc is excluded because its add() acquires a striped reader lock.
genSeqElemAppendV2(p, e, d)
else:
e.secondSon = makeAddr(e.secondSon, p.module.idgen)
genCall(p, e, d)
else:
genSeqElemAppend(p, e, d)
of mEqStr: genStrEquals(p, e, d)
of mLeStr:
binaryExpr(p, e, d, cOp(LessEqual,
cgCall(p, "cmpStrings", ra, rb),
cIntValue(0)))
of mLtStr:
binaryExpr(p, e, d, cOp(LessThan,
cgCall(p, "cmpStrings", ra, rb),
cIntValue(0)))
of mIsNil: genIsNil(p, e, d)
of mBoolToStr:
genDollarIt(p, e, d, cgCall(p, "nimBoolToStr", it))
of mCharToStr:
genDollarIt(p, e, d, cgCall(p, "nimCharToStr", it))
of mCStrToStr:
if p.module.compileToCpp:
# fixes for const qualifier; bug #12703; bug #19588
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.secondSon, d)
of generatedMagics: genCall(p, e, d)
of mEnumToStr:
if optTinyRtti in p.config.globalOptions:
genEnumToStr(p, e, d)
else:
genRepr(p, e, d)
of mOf: genOf(p, e, d)
of mNew: genNew(p, e)
of mNewFinalize:
if optTinyRtti in p.config.globalOptions:
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.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.secondSon.typ.skipTypes({tyTypeDesc})
putIntoDest(p, d, e, cCast(NimInt, cSizeof(getTypeDesc(p.module, t, dkVar))))
of mAlignOf:
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.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")
let t = dotExpr.firstSon.typ.skipTypes({tyTypeDesc})
let tname = getTypeDesc(p.module, t, dkVar)
let member =
if t.kind == tyTuple:
"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)
of mLengthArray, mHigh, mLengthStr, mLengthSeq, mLengthOpenArray:
genArrayLen(p, e, d, op)
of mGCref:
# only a magic for the old GCs
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.secondSon)
let ra = rdLoc(a)
p.s(cpsStmts).addSingleIfStmt(ra):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimGCunref"), ra)
of mSetLengthStr: genSetLengthStr(p, e, d)
of mSetLengthSeq: genSetLengthSeq(p, e, d)
of mSetLengthSeqUninit: genSetLengthSeq(p, e, d, noinit = true)
of mIncl, mExcl, mCard, mLtSet, mLeSet, mEqSet, mMulSet, mPlusSet, mMinusSet,
mInSet, mXorSet:
genSetOp(p, e, d, op)
of mNewString, mNewStringOfCap, mExit, mParseBiggestFloat:
var opr = e.firstSon.sym
# Why would anyone want to set nodecl to one of these hardcoded magics?
# - not sure, and it wouldn't work if the symbol behind the magic isn't
# somehow forward-declared from some other usage, but it is *possible*
if lfNoDecl notin opr.loc.flags:
let prc = magicsys.getCompilerProc(p.module.g.graph, $opr.loc.snippet)
assert prc != nil, $opr.loc.snippet
# HACK:
# Explicitly add this proc as declared here so the cgsym call doesn't
# add a forward declaration - without this we could end up with the same
# 2 forward declarations. That happens because the magic symbol and the original
# one that shall be used have different ids (even though a call to one is
# actually a call to the other) so checking into m.declaredProtos with the 2 different ids doesn't work.
# Why would 2 identical forward declarations be a problem?
# - in the case of hot code-reloading we generate function pointers instead
# of forward declarations and in C++ it is an error to redefine a global
let wasDeclared = containsOrIncl(p.module.declaredProtos, prc.id)
# Make the function behind the magic get actually generated - this will
# not lead to a forward declaration! The genCall will lead to one.
cgsym(p.module, $opr.loc.snippet)
# make sure we have pointer-initialising code for hot code reloading
if not wasDeclared and p.hcrOn:
let name = mangleDynLibProc(prc)
let rt = getTypeDesc(p.module, prc.loc.t)
p.module.s[cfsDynLibInit].add('\t')
p.module.s[cfsDynLibInit].addAssignmentWithValue(name):
p.module.s[cfsDynLibInit].addCast(rt):
p.module.s[cfsDynLibInit].addCall("hcrGetProc",
getModuleDllPath(p.module, prc),
'"' & name & '"')
genCall(p, e, d)
of mDefault, mZeroDefault: genDefault(p, e, d)
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))
of mSpawn:
when defined(leanCompiler):
p.config.quitOrRaise "compiler built without support for the 'spawn' statement"
else:
let n = spawn.wrapProcForSpawn(p.module.g.graph, p.module.idgen, p.module.module, e, e.typ, nil, nil)
expr(p, n, d)
of mParallel:
when defined(leanCompiler):
p.config.quitOrRaise "compiler built without support for the 'parallel' statement"
else:
let n = semparallel.liftParallel(p.module.g.graph, p.module.idgen, p.module.module, e)
expr(p, n, d)
of mDeepCopy:
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc} and optEnableDeepCopy notin p.config.globalOptions:
localError(p.config, e.info,
"for --mm:arc|atomicArc|orc 'deepcopy' support has to be enabled with --deepcopy:on")
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.secondSon.kind in {nkAddr, nkHiddenAddr}: e.secondSon.firstSon else: e.secondSon
var a = initLocExpr(p, x)
var b = initLocExpr(p, son(e, 2))
genDeepCopy(p, a, b)
of mDotDot, mEqCString: genCall(p, e, d)
of mWasMoved: genWasMoved(p, e)
of mMove: genMove(p, e, d)
of mDestroy: genDestroy(p, e)
of mAccessEnv: unaryExpr(p, e, d, dotField(ra, "ClE_0"))
of mAccessTypeField: genAccessTypeField(p, e, d)
of mSlice: genSlice(p, e, d)
of mTrace: discard "no code to generate"
of mEnsureMove:
expr(p, e.secondSon, d)
of mDup:
expr(p, e.secondSon, d)
else:
when defined(debugMagics):
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) =
# example: { a..b, c, d, e, f..g }
# we have to emit an expression of the form:
# nimZeroMem(tmp, sizeof(tmp)); inclRange(tmp, a, b); incl(tmp, c);
# incl(tmp, d); incl(tmp, e); inclRange(tmp, f, g);
var
a, b: TLoc
var idx: TLoc
if nfAllConst in e.flags:
var elem = newBuilder("")
genSetNode(p, e, elem)
putIntoDest(p, d, e, extract(elem))
else:
if d.k == locNone: d = getTemp(p, e.typ)
let size = getSize(p.config, e.typ)
if size > 8:
# big set:
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"),
rdLoc(d),
cSizeof(getTypeDesc(p.module, e.typ)))
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.secondSon)
var aa: Snippet = ""
rdSetElemLoc(p.config, a, e.typ, aa)
var bb: Snippet = ""
rdSetElemLoc(p.config, b, e.typ, bb)
let ri = rdLoc(idx)
let rd = rdLoc(d)
p.s(cpsStmts).addForRangeInclusive(ri, aa, bb):
p.s(cpsStmts).addInPlaceOp(BitOr, NimUint8,
subscript(rd, cOp(Shr, NimUint, cCast(NimUint, ri), cIntValue(3))),
cOp(Shl, NimUint8, cUintValue(1),
cOp(BitAnd, NimUint, cCast(NimUint, ri), cUintValue(7))))
else:
a = initLocExpr(p, it)
var aa: Snippet = ""
rdSetElemLoc(p.config, a, e.typ, aa)
let rd = rdLoc(d)
p.s(cpsStmts).addInPlaceOp(BitOr, NimUint8,
subscript(rd, cOp(Shr, NimUint, cCast(NimUint, aa), cIntValue(3))),
cOp(Shl, NimUint8, cUintValue(1),
cOp(BitAnd, NimUint, cCast(NimUint, aa), cUintValue(7))))
else:
# small set
var ts = cUintType(size * 8)
p.s(cpsStmts).addAssignment(rdLoc(d), cIntValue(0))
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.secondSon)
var aa: Snippet = ""
rdSetElemLoc(p.config, a, e.typ, aa)
var bb: Snippet = ""
rdSetElemLoc(p.config, b, e.typ, bb)
let ri = rdLoc(idx)
let rd = rdLoc(d)
p.s(cpsStmts).addForRangeInclusive(ri, aa, bb):
p.s(cpsStmts).addInPlaceOp(BitOr, ts, rd,
cOp(Shl, ts, cCast(ts, cIntValue(1)),
cOp(Mod, ts, ri, cOp(Mul, ts, cIntValue(size), cIntValue(8)))))
else:
a = initLocExpr(p, it)
var aa: Snippet = ""
rdSetElemLoc(p.config, a, e.typ, aa)
let rd = rdLoc(d)
p.s(cpsStmts).addInPlaceOp(BitOr, ts, rd,
cOp(Shl, ts, cCast(ts, cIntValue(1)),
cOp(Mod, ts, aa, cOp(Mul, ts, cIntValue(size), cIntValue(8)))))
proc genTupleConstr(p: BProc, n: PNode, d: var TLoc) =
var rec: TLoc
if not handleConstExpr(p, n, d):
let t = n.typ
discard getTypeDesc(p.module, t) # so that any fields are initialized
var tmp: TLoc = default(TLoc)
# bug #16331
let doesAlias = lhsDoesAlias(d.lode, n)
let dest = if doesAlias: addr(tmp) else: addr(d)
if doesAlias:
tmp = getTemp(p, n.typ)
elif d.k == locNone:
d = getTemp(p, n.typ)
for i, ni in isons(n):
var it = ni
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)
rec.snippet = dotField(rdLoc(dest[]), "Field" & rope(i))
rec.flags.incl(lfEnforceDeref)
expr(p, it, rec)
if doesAlias:
if d.k == locNone:
d = tmp
else:
genAssignment(p, d, tmp, {})
proc isConstClosure(n: AnyNode): bool {.inline.} =
result = n.firstSon.kind == nkSym and isRoutine(n.firstSon.sym) and
n.secondSon.kind == nkNilLit
proc genClosure(p: BProc, n: PNode, d: var TLoc) =
assert n.kind in {nkPar, nkTupleConstr, nkClosure}
if isConstClosure(n):
inc(p.module.labels)
var tmp = "CNSTCLOSURE" & rope(p.module.labels)
let td = getTypeDesc(p.module, n.typ)
var data = newBuilder("")
data.addVarWithInitializer(kind = Const, name = tmp, typ = td):
genBracedInit(p, n, isConst = true, n.typ, data)
p.module.s[cfsData].add(extract(data))
putIntoDest(p, d, n, tmp, OnStatic)
else:
var tmp: TLoc
var a = initLocExpr(p, n.firstSon)
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:
when false:
if d.k != locNone:
let dest = d.rdLoc
p.s(cpsStmts).addFieldAssignment(dest, "ClP_0", a.rdLoc)
p.s(cpsStmts).addFieldAssignment(dest, "ClE_0", b.rdLoc)
else:
tmp = getTemp(p, n.typ)
let dest = tmp.rdLoc
p.s(cpsStmts).addFieldAssignment(dest, "ClP_0", a.rdLoc)
p.s(cpsStmts).addFieldAssignment(dest, "ClE_0", b.rdLoc)
putLocIntoDest(p, d, tmp)
proc genArrayConstr(p: BProc, n: PNode, d: var TLoc) =
var arr: TLoc
if not handleConstExpr(p, n, d):
if d.k == locNone: d = getTemp(p, n.typ)
for i, ni in isons(n):
arr = initLoc(locExpr, lodeTyp elemType(skipTypes(n.typ, abstractInst)), d.storage)
let lit = cIntLiteral(i)
arr.snippet = subscript(rdLoc(d), lit)
expr(p, ni, arr)
proc genComplexConst(p: BProc, sym: PSym, d: var TLoc) =
requestConstImpl(p, sym)
assert((sym.loc.snippet != "") and (sym.loc.t != nil))
putLocIntoDest(p, d, sym.loc)
template genStmtListExprImpl(exprOrStmt) {.dirty.} =
#let hasNimFrame = magicsys.getCompilerProc("nimFrame") != nil
let hasNimFrame = p.prc != nil and
sfSystemModule notin p.module.module.flags and
optStackTrace in p.prc.options
var frameName: Rope = ""
for it in sonsButLast(n):
if it.kind == nkComesFrom:
if hasNimFrame and frameName == "":
inc p.labels
frameName = "FR" & rope(p.labels) & "_"
let theMacro = it.firstSon.sym
add p.s(cpsStmts), initFrameNoDebug(p, frameName,
makeCString theMacro.name.s,
quotedFilename(p.config, theMacro.info), it.info.line.int)
else:
genStmts(p, it)
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.lastSon, d)
proc genStmtList(p: BProc, n: PNode) =
genStmtListExprImpl:
genStmts(p, n.lastSon)
from parampatterns import isLValue
proc upConv(p: BProc, n: PNode, d: var TLoc) =
var a: TLoc = initLocExpr(p, n.firstSon)
let dest = skipTypes(n.typ, abstractPtrs)
if optObjCheck in p.options and not isObjLackingTypeField(dest):
var nilCheck = ""
var r: Snippet = ""
rdMType(p, a, nilCheck, r)
if optTinyRtti in p.config.globalOptions:
let checkFor = $getObjDepth(dest)
let token = $genDisplayElem(MD5Digest(hashType(dest, p.config)))
let objCheck = cOp(Not, cgCall(p, "isObjDisplayCheck", r, checkFor, token))
let check = if nilCheck != "": cOp(And, nilCheck, objCheck) else: objCheck
p.s(cpsStmts).addSingleIfStmt(check):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseObjectConversionError"))
raiseInstr(p, p.s(cpsStmts))
else:
let checkFor = genTypeInfoV1(p.module, dest, n.info)
let objCheck = cOp(Not, cgCall(p, "isObj", r, checkFor))
let check = if nilCheck != "": cOp(And, nilCheck, objCheck) else: objCheck
p.s(cpsStmts).addSingleIfStmt(check):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseObjectConversionError"))
raiseInstr(p, p.s(cpsStmts))
# skip cast when types map to the same C type
# this avoids invalid C code like `*(T*)&x` for types that can't have their address taken (e.g., WASM __externref_t)
if getTypeDesc(p.module, n.typ) == getTypeDesc(p.module, n.firstSon.typ):
expr(p, n.firstSon, d)
elif n.firstSon.typ.kind != tyObject:
let destTyp = getTypeDesc(p.module, n.typ)
let val = rdLoc(a)
if n.isLValue:
# (*((destType) (&(val))))"
putIntoDest(p, d, n,
cDeref(
cCast(ptrType(destTyp),
wrapPar(cAddr(wrapPar(val))))),
a.storage)
else:
# ((destType) (val))"
putIntoDest(p, d, n, cCast(destTyp, wrapPar(val)), a.storage)
else:
let destTyp = getTypeDesc(p.module, dest)
let val = addrLoc(p.config, a)
# (* (destType*) val)
putIntoDest(p, d, n,
cDeref(
cCast(ptrType(destTyp),
wrapPar(val))),
a.storage)
proc downConv(p: BProc, n: PNode, d: var TLoc) =
var arg = n.firstSon
while arg.kind == nkObjDownConv: arg = arg.firstSon
let dest = skipTypes(n.typ, abstractPtrs)
let src = skipTypes(arg.typ, abstractPtrs)
discard getTypeDesc(p.module, src)
let isRef = skipTypes(arg.typ, abstractInstOwned).kind in {tyRef, tyPtr, tyVar, tyLent}
if isRef and d.k == locNone and n.typ.skipTypes(abstractInstOwned).kind in {tyRef, tyPtr} and n.isLValue:
# it can happen that we end up generating '&&x->Sup' here, so we pack
# the '&x->Sup' into a temporary and then those address is taken
# (see bug #837). However sometimes using a temporary is not correct:
# init(TFigure(my)) # where it is passed to a 'var TFigure'. We test
# this by ensuring the destination is also a pointer:
var a: TLoc = initLocExpr(p, arg)
let destType = getTypeDesc(p.module, n.typ)
let val = rdLoc(a)
# (* ((destType*) (&(val))))
putIntoDest(p, d, n,
cDeref(
cCast(ptrType(destType),
wrapPar(cAddr(wrapPar(val))))),
a.storage)
elif p.module.compileToCpp or isImportedType(src):
# C++ implicitly downcasts for us
expr(p, arg, d)
else:
var a: TLoc = initLocExpr(p, arg)
var r = rdLoc(a)
if isRef:
r = derefField(r, "Sup")
else:
r = dotField(r, "Sup")
for i in 2..abs(inheritanceDiff(dest, src)):
r = dotField(r, "Sup")
if isRef:
r = cAddr(r)
putIntoDest(p, d, n, r, a.storage)
proc exprComplexConst(p: BProc, n: PNode, d: var TLoc) =
let t = n.typ
discard getTypeDesc(p.module, t) # so that any fields are initialized
let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels)
let tmp = p.module.tmpBase & rope(id)
if id == p.module.labels:
# expression not found in the cache:
inc(p.module.labels)
let td = getTypeDesc(p.module, t, dkConst)
var data = newBuilder("")
data.addVarWithInitializer(
kind = Const, name = tmp, typ = td):
genBracedInit(p, n, isConst = true, t, data)
p.module.s[cfsData].add(extract(data))
if d.k == locNone:
fillLoc(d, locData, n, tmp, OnStatic)
else:
putDataIntoDest(p, d, n, tmp)
# This fixes bug #4551, but we really need better dataflow
# analysis to make this 100% safe.
if t.kind notin {tySequence, tyString}:
d.storage = OnStatic
proc genConstSetup(p: BProc; sym: PSym): bool =
let m = p.module
useHeader(m, sym)
if sym.loc.k == locNone:
fillBackendName(p.module, sym)
backendEnsureMutable sym
fillLoc(sym.locImpl, locData, sym.astdef, OnStatic)
if m.hcrOn: incl(sym, lfIndirect)
result = lfNoDecl notin sym.loc.flags
proc genConstHeader(m, q: BModule; p: BProc, sym: PSym) =
if sym.loc.snippet == "":
if not genConstSetup(p, sym): return
assert(sym.loc.snippet != "", $sym.name.s & $sym.itemId)
if m.hcrOn:
m.s[cfsVars].addVar(kind = Global, name = sym.loc.snippet,
typ = ptrType(getTypeDesc(m, sym.loc.t, dkVar)))
m.initProc.procSec(cpsLocals).add('\t')
m.initProc.procSec(cpsLocals).addAssignmentWithValue(sym.loc.snippet):
m.initProc.procSec(cpsLocals).addCast(ptrType(getTypeDesc(m, sym.loc.t, dkVar))):
m.initProc.procSec(cpsLocals).addCall("hcrGetGlobal",
getModuleDllPath(q, sym),
'"' & sym.loc.snippet & '"')
else:
var headerDecl = newBuilder("")
headerDecl.addDeclWithVisibility(Extern):
headerDecl.addVar(kind = Local, name = sym.loc.snippet,
typ = constType(getTypeDesc(m, sym.loc.t, dkVar)))
m.s[cfsData].add(extract(headerDecl))
if sfExportc in sym.flags and p.module.g.generatedHeader != nil:
p.module.g.generatedHeader.s[cfsData].add(extract(headerDecl))
proc genConstDefinition(q: BModule; p: BProc; sym: PSym) =
# add a suffix for hcr - will later init the global pointer with this data
let actualConstName = if q.hcrOn: sym.loc.snippet & "_const" else: sym.loc.snippet
let td = constType(getTypeDesc(q, sym.typ))
var data = newBuilder("")
data.addDeclWithVisibility(Private):
data.addVarWithInitializer(Local, actualConstName, typ = td):
genBracedInit(q.initProc, sym.astdef, isConst = true, sym.typ, data)
if q.config.cmd == cmdNifC:
# Each `cg` process that demands this const emits its definition
# (emit-everywhere). Always declare it first (the data analogue of a proc
# prototype) so a TU whose copy the merge stage drops still has a valid
# declaration; wrap the definition as a droppable `'d'` unit the merge
# stage assigns to a single owner.
let cname = stripCnifMarks(actualConstName)
var decl = newBuilder("")
decl.addDeclWithVisibility(Extern):
decl.addVar(kind = Local, name = actualConstName, typ = td)
q.s[cfsData].add(extract(decl))
q.s[cfsData].add(cnifDefDirective(cname, "d", icNifName(q, sym)))
q.s[cfsData].add(extract(data))
q.s[cfsData].add(cnifEndDefs())
q.icDataDefs.add (cname, icNifName(q, sym))
else:
q.s[cfsData].add(extract(data))
if q.hcrOn:
# generate the global pointer with the real name
q.s[cfsVars].addVar(kind = Global, name = sym.loc.snippet,
typ = ptrType(getTypeDesc(q, sym.loc.t, dkVar)))
# register it (but ignore the boolean result of hcrRegisterGlobal)
q.initProc.procSec(cpsLocals).add('\t')
q.initProc.procSec(cpsLocals).addStmt():
var registerCall: CallBuilder
q.initProc.procSec(cpsLocals).addCall(registerCall, "hcrRegisterGlobal"):
q.initProc.procSec(cpsLocals).addArgument(registerCall):
q.initProc.procSec(cpsLocals).add(getModuleDllPath(q, sym))
q.initProc.procSec(cpsLocals).addArgument(registerCall):
q.initProc.procSec(cpsLocals).add('"' & sym.loc.snippet & '"')
q.initProc.procSec(cpsLocals).addArgument(registerCall):
q.initProc.procSec(cpsLocals).addSizeof(rdLoc(sym.loc))
q.initProc.procSec(cpsLocals).addArgument(registerCall):
q.initProc.procSec(cpsLocals).add(CNil)
q.initProc.procSec(cpsLocals).addArgument(registerCall):
q.initProc.procSec(cpsLocals).addCast(ptrType(CPointer)):
q.initProc.procSec(cpsLocals).add(cAddr(sym.loc.snippet))
# always copy over the contents of the actual constant with the _const
# suffix ==> this means that the constant is reloadable & updatable!
q.initProc.procSec(cpsLocals).add('\t')
q.initProc.procSec(cpsLocals).addStmt():
var copyCall: CallBuilder
q.initProc.procSec(cpsLocals).addCall(copyCall, cgsymValue(q, "nimCopyMem")):
q.initProc.procSec(cpsLocals).addArgument(copyCall):
q.initProc.procSec(cpsLocals).add(cCast(CPointer, sym.loc.snippet))
q.initProc.procSec(cpsLocals).addArgument(copyCall):
q.initProc.procSec(cpsLocals).add(cCast(CConstPointer, cAddr(actualConstName)))
q.initProc.procSec(cpsLocals).addArgument(copyCall):
q.initProc.procSec(cpsLocals).addSizeof(rdLoc(sym.loc))
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:
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):
genConstDefinition(m, p, sym)
proc expr(p: BProc, n: PNode, d: var TLoc) =
when defined(nimCompilerStacktraceHints):
setFrameMsg p.config$n.info & " " & $n.kind
p.currLineInfo = n.info
case n.kind
of nkSym:
var sym = n.sym
case sym.kind
of skMethod:
if delayedCodegen(p.module) or {sfDispatcher, sfForward} * sym.flags != {}:
# we cannot produce code for the dispatcher yet:
fillProcLoc(p.module, n)
genProcPrototype(p.module, sym)
else:
genProc(p.module, sym)
putLocIntoDest(p, d, sym.loc)
of skProc, skConverter, skIterator, skFunc:
#if sym.kind == skIterator:
# echo renderTree(sym.getBody, {renderIds})
if p.config.cmd == cmdNifC and
(isGenericRoutineStrict(sym) or sfCompileTime in sym.flags or
(sym.kind == skIterator and sym.typ.callConv == ccInline)):
# Under IC a module's top-level routine definitions are serialized as bare
# symbol references that reappear in the loaded statement list. Uninstantiated
# generic routines (incl. those with type-class params like `tuple`) and
# `.compileTime` routines have no run-time code, so skip them here.
# Inline iterators likewise have no standalone code — they are always inlined
# at their for-loop call sites by the transformer (only closure iterators get
# a standalone C function), so a bare serialized def reference is a no-op.
return
if sfCompileTime in sym.flags:
localError(p.config, n.info, "request to generate code for .compileTime proc: " &
sym.name.s)
if delayedCodegen(p.module) and sym.typ.callConv != ccInline:
fillProcLoc(p.module, n)
genProcPrototype(p.module, sym)
else:
genProc(p.module, sym)
# For cross-module inline procs with optCompress, ensure prototype is emitted
if sym.typ.callConv == ccInline and optCompress in p.config.globalOptions and
sym.itemId.module != p.module.module.position:
genProcPrototype(p.module, sym)
if sym.loc.snippet == "" or sym.loc.lode == nil:
internalError(p.config, n.info, "expr: proc not init " & sym.name.s)
putLocIntoDest(p, d, sym.loc)
of skConst:
if isSimpleConst(sym.typ):
var lit = newBuilder("")
genLiteral(p, sym.astdef, sym.typ, lit)
putIntoDest(p, d, n, extract(lit), OnStatic)
elif optCompress in p.config.globalOptions:
# With delayed codegen, we need to ensure the definition is generated
# not just the extern header declaration
requestConstImpl(p, sym)
assert((sym.loc.snippet != "") and (sym.loc.t != nil))
putLocIntoDest(p, d, sym.loc)
elif delayedCodegen(p.module):
genConstHeader(p.module, p.module, p, sym)
assert((sym.loc.snippet != "") and (sym.loc.t != nil))
putLocIntoDest(p, d, sym.loc)
else:
genComplexConst(p, sym, d)
of skEnumField:
# we never reach this case - as of the time of this comment,
# skEnumField is folded to an int in semfold.nim, but this code
# remains for robustness
putIntoDest(p, d, n, cIntValue(sym.position))
of skVar, skForVar, skResult, skLet:
if {sfGlobal, sfThread} * sym.flags != {}:
genVarPrototype(p.module, n)
if sfCompileTime in sym.flags:
genSingleVar(p, sym, n, astdef(sym))
if sym.loc.snippet == "" or sym.loc.t == nil:
#echo "FAILED FOR PRCO ", p.prc.name.s
#echo renderTree(p.prc.ast, {renderIds})
internalError p.config, n.info, "expr: var not init " & sym.name.s & "_" & $sym.id
if sfThread in sym.flags:
accessThreadLocalVar(p, sym)
if emulatedThreadVars(p.config):
putIntoDest(p, d, sym.loc.lode, derefField("NimTV_", sym.loc.snippet))
else:
putLocIntoDest(p, d, sym.loc)
else:
putLocIntoDest(p, d, sym.loc)
of skTemp:
when false:
# this is more harmful than helpful.
if sym.loc.snippet == "":
# we now support undeclared 'skTemp' variables for easier
# transformations in other parts of the compiler:
assignLocalVar(p, n)
if sym.loc.snippet == "" or sym.loc.t == nil:
#echo "FAILED FOR PRCO ", p.prc.name.s
#echo renderTree(p.prc.ast, {renderIds})
internalError(p.config, n.info, "expr: temp not init " & sym.name.s & "_" & $sym.id)
putLocIntoDest(p, d, sym.loc)
of skParam:
if sym.loc.snippet == "" or sym.loc.t == nil:
# echo "FAILED FOR PRCO ", p.prc.name.s
# debug p.prc.typ.n
# echo renderTree(p.prc.ast, {renderIds})
internalError(p.config, n.info, "expr: param not init " & sym.name.s & "_" & $sym.id)
putLocIntoDest(p, d, sym.loc)
of skTemplate, skMacro:
# Under IC a module's top-level template/macro definitions are serialized as
# bare symbol references (only their interface matters), so they reappear in
# the loaded statement list. They are compile-time only and produce no code.
discard
else: internalError(p.config, n.info, "expr(" & $sym.kind & "); unknown symbol")
of nkNilLit:
if not isEmptyType(n.typ):
var lit = newBuilder("")
genLiteral(p, n, lit)
putIntoDest(p, d, n, extract(lit))
of nkStrLit..nkTripleStrLit:
var lit = newBuilder("")
genLiteral(p, n, lit)
putDataIntoDest(p, d, n, extract(lit))
of nkIntLit..nkUInt64Lit, nkFloatLit..nkFloat128Lit, nkCharLit:
var lit = newBuilder("")
genLiteral(p, n, lit)
putIntoDest(p, d, n, extract(lit))
of nkCall, nkHiddenCallConv, nkInfix, nkPrefix, nkPostfix, nkCommand,
nkCallStrLit:
genLineDir(p, n) # may be redundant, it is generated in fixupCall as well
let op = n.firstSon
if n.typ.isNil:
# discard the value:
var a: TLoc = default(TLoc)
if op.kind == nkSym and op.sym.magic != mNone:
genMagicExpr(p, n, a, op.sym.magic)
else:
genCall(p, n, a)
else:
# load it into 'd':
if op.kind == nkSym and op.sym.magic != mNone:
genMagicExpr(p, n, d, op.sym.magic)
else:
genCall(p, n, d)
of nkCurly:
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.hasSons:
exprComplexConst(p, n, d)
elif skipTypes(n.typ, abstractVarRange).kind == tySequence:
genSeqConstr(p, n, d)
else:
genArrayConstr(p, n, d)
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.hasSons:
exprComplexConst(p, n, d)
else:
genTupleConstr(p, n, d)
of nkObjConstr: genObjConstr(p, n, d)
of nkCast: genCast(p, n, d)
of nkHiddenStdConv, nkHiddenSubConv, nkConv: genConv(p, n, d)
of nkAddr, nkHiddenAddr: genAddr(p, n, d)
of nkBracketExpr: genBracketExpr(p, n, d)
of nkDerefExpr, nkHiddenDeref: genDeref(p, n, d)
of nkDotExpr: genRecordField(p, n, d)
of nkCheckedFieldExpr: genCheckedRecordField(p, n, d)
of nkBlockExpr, nkBlockStmt: genBlock(p, n, d)
of nkStmtListExpr: genStmtListExpr(p, n, d)
of nkStmtList: genStmtList(p, n)
of nkIfExpr, nkIfStmt: genIf(p, n, d)
of nkWhen:
# This should be a "when nimvm" node.
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 = 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)
putLocIntoDest(p, d, sym.loc)
of nkClosure: genClosure(p, n, d)
of nkEmpty: discard
of nkWhileStmt: genWhileStmt(p, n)
of nkVarSection, nkLetSection: genVarStmt(p, n)
of nkConstSection:
if delayedCodegen(p.module):
genConstStmt(p, n)
else: # enforce addressable consts for exportc
for it in n:
let symNode = skipPragmaExpr(it.firstSon)
if symNode.kind == nkSym and sfExportc in symNode.sym.flags:
requestConstImpl(p, symNode.sym)
# else: consts generated lazily on use
of nkForStmt: internalError(p.config, n.info, "for statement not eliminated")
of nkCaseStmt: genCase(p, n, d)
of nkReturnStmt: genReturnStmt(p, n)
of nkBreakStmt: genBreakStmt(p, n)
of nkAsgn:
cow(p, n.secondSon)
if nfPreventCg notin n.flags:
genAsgn(p, n, fastAsgn=false)
of nkFastAsgn, nkSinkAsgn:
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.
genAsgn(p, n, fastAsgn=p.prc != nil)
of nkDiscardStmt:
let ex = n.firstSon
if ex.kind != nkEmpty:
genLineDir(p, n)
var a: TLoc = initLocExprSingleUse(p, ex)
p.s(cpsStmts).addDiscard(a.snippet)
of nkAsmStmt: genAsmStmt(p, n)
of nkTryStmt, nkHiddenTryStmt:
case p.config.exc
of excGoto:
genTryGoto(p, n, d)
of excCpp:
genTryCpp(p, n, d)
else:
genTrySetjmp(p, n, d)
of nkRaiseStmt: genRaiseStmt(p, n)
of nkTypeSection:
# we have to emit the type information for object types here to support
# separate compilation:
genTypeSection(p.module, n)
of nkCommentStmt, nkIteratorDef, nkIncludeStmt,
nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt,
nkFromStmt, nkTemplateDef, nkMacroDef, nkStaticStmt:
discard
of nkPragma: genPragma(p, n)
of nkPragmaBlock:
var inUncheckedAssignSection = 0
let pragmaList = n.firstSon
for pi in pragmaList:
if whichPragma(pi) == wCast:
case whichPragma(pi.secondSon)
of wUncheckedAssign:
inUncheckedAssignSection = 1
else:
discard
inc p.inUncheckedAssignSection, inUncheckedAssignSection
expr(p, n.lastSon, d)
dec p.inUncheckedAssignSection, inUncheckedAssignSection
of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef:
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)
elif delayedCodegen(p.module):
if p.module.alive.contains(prc.itemId.item) and
prc.magic in generatedMagics:
genProc(p.module, prc)
elif prc.skipGenericOwner.kind == skModule and sfCompileTime notin prc.flags:
if ({sfExportc, sfCompilerProc} * prc.flags == {sfExportc}) or
(sfExportc in prc.flags and lfExportLib in prc.loc.flags) or
(prc.kind == skMethod):
# due to a bug/limitation in the lambda lifting, unused inner procs
# are not transformed correctly. We work around this issue (#411) here
# by ensuring it's no inner proc (owner is a module).
# Generate proc even if empty body, bugfix #11651.
genProc(p.module, prc)
of nkParForStmt: genParForStmt(p, n)
of nkState: genState(p, n)
of nkGotoState:
# simply never set it back to 0 here from here on...
inc p.splitDecls
genGotoState(p, n)
of nkBreakState: genBreakState(p, n, d)
of nkMixinStmt, nkBindStmt, nkReplayAction: discard
else: internalError(p.config, n.info, "expr(" & $n.kind & "); unknown node kind")
proc isOpaqueImportcType(t: PType): bool =
# importc type without completeStruct that can't use aggregate init (e.g. C11 _Atomic)
if t.sym != nil and sfImportc in t.sym.flags:
if tfCompleteStruct notin t.flags:
if tfIncompleteStruct in t.flags:
return true
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 =
if n == nil: return false
case n.kind
of nkRecList:
for child in sons(n):
if containsOpaqueImportcFieldAux(t, child):
return true
of nkRecCase:
if containsOpaqueImportcFieldAux(t, n.firstSon):
return true
for branch in sonsFrom(n, 1):
if branch.kind == nkOfBranch or branch.kind == nkElse:
if containsOpaqueImportcFieldAux(t, branch.lastSon):
return true
of nkSym:
if containsOpaqueImportcField(n.sym.typ):
return true
else:
discard
return false
proc containsOpaqueImportcField(typ: PType): bool =
# Check if type contains opaque importc fields that need designated initializers
if typ == nil: return false
let t = skipTypes(typ, abstractRange+{tyOwned}-{tyTypeDesc})
if isOpaqueImportcType(t):
return true
case t.kind
of tyObject:
if t.baseClass != nil:
if containsOpaqueImportcField(t.baseClass):
return true
if containsOpaqueImportcFieldAux(t, t.n):
return true
of tyTuple:
for i, a in t.ikids:
if isEmptyType(a): continue
if containsOpaqueImportcField(a):
return true
of tyArray:
if containsOpaqueImportcField(t.elementType):
return true
else:
discard
return false
proc getDefaultValue(p: BProc; typ: PType; info: TLineInfo; result: var Builder) =
var t = skipTypes(typ, abstractRange+{tyOwned}-{tyTypeDesc})
case t.kind
of tyBool: result.add NimFalse
of tyEnum, tyChar, tyInt..tyInt64, tyUInt..tyUInt64: result.addIntValue(0)
of tyFloat..tyFloat128: result.addFloatValue(0.0)
of tyCstring, tyVar, tyLent, tyPointer, tyPtr, tyUntyped,
tyTyped, tyTypeDesc, tyStatic, tyRef, tyNil:
result.add NimNil
of tyString, tySequence:
if optSeqDestructors in p.config.globalOptions:
var seqInit: StructInitializer
result.addStructInitializer(seqInit, kind = siOrderedStruct):
result.addField(seqInit, name = "len"):
result.addIntValue(0)
result.addField(seqInit, name = "p"):
result.add(NimNil)
else:
result.add NimNil
of tyProc:
if t.callConv != ccClosure:
result.add NimNil
else:
var closureInit: StructInitializer
result.addStructInitializer(closureInit, kind = siOrderedStruct):
result.addField(closureInit, name = "ClP_0"):
result.add(NimNil)
result.addField(closureInit, name = "ClE_0"):
result.add(NimNil)
of tyObject:
# Use designated initializers when opaque importc fields present
var objInit: StructInitializer
let initKind = if containsOpaqueImportcField(t): siNamedStruct else: siOrderedStruct
result.addStructInitializer(objInit, kind = initKind):
getNullValueAuxT(p, t, t, t.n, nil, result, objInit, true, info)
of tyTuple:
# Use designated initializers when opaque importc fields present
var tupleInit: StructInitializer
let initKind = if containsOpaqueImportcField(t): siNamedStruct else: siOrderedStruct
result.addStructInitializer(tupleInit, kind = initKind):
if p.vccAndC and validTupleTypeFields(t) == 0:
result.addField(tupleInit, name = "dummy"):
result.addIntValue(0)
for i, a in t.ikids:
# Do not produce code for void types
if isEmptyType(a): continue
let elemTyp = skipTypes(a, abstractRange+{tyOwned}-{tyTypeDesc})
if not isOpaqueImportcType(elemTyp):
result.addField(tupleInit, name = "Field" & $i):
getDefaultValue(p, a, info, result)
of tyArray:
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):
for i in 0..<toInt(lengthOrd(p.config, t.indexType)):
result.addField(arrInit, name = ""):
getDefaultValue(p, t.elementType, info, result)
#result = rope"{}"
of tyOpenArray, tyVarargs:
var openArrInit: StructInitializer
result.addStructInitializer(openArrInit, kind = siOrderedStruct):
result.addField(openArrInit, name = "Field0"):
result.add(NimNil)
result.addField(openArrInit, name = "Field1"):
result.addIntValue(0)
of tySet:
if mapSetType(p.config, t) == ctArray:
var setInit: StructInitializer
result.addStructInitializer(setInit, kind = siArray):
discard
else: result.addIntValue(0)
else:
globalError(p.config, info, "cannot create null element for: " & $t.kind)
proc isEmptyCaseObjectBranch(n: PNode): bool =
for it in n:
if it.kind == nkSym and not isEmptyType(it.sym.typ): return false
return true
proc getNullValueAux(p: BProc; t: PType; obj, constOrNil: PNode,
result: var Builder; init: var StructInitializer;
isConst: bool, info: TLineInfo) =
case obj.kind
of nkRecList:
let isUnion = tfUnion in t.flags
for it in sons(obj):
getNullValueAux(p, t, it, constOrNil, result, init, isConst, info)
if isUnion:
# generate only 1 field for default value of union
return
of nkRecCase:
getNullValueAux(p, t, obj.firstSon, constOrNil, result, init, isConst, info)
var branch = Zero
if constOrNil != nil:
## find kind value, default is zero if not specified
for i, it in isons(constOrNil, 1):
if it.kind == nkExprColonExpr:
if it.firstSon.sym.name.id == obj.firstSon.sym.name.id:
branch = getOrdValue(it.secondSon)
break
elif i == obj.firstSon.sym.position:
branch = getOrdValue(it)
break
let selectedBranch = caseObjDefaultBranch(obj, branch)
let b = lastSon(son(obj, selectedBranch))
# designated initilization is the only way to init non first element of unions
# branches are allowed to have no members (b.len == 0), in this case they don't need initializer
var fieldName: string = ""
if b.kind == nkRecList and not isEmptyCaseObjectBranch(b):
fieldName = "_" & mangleRecFieldName(p.module, obj.firstSon.sym) & "_" & $selectedBranch
result.addField(init, name = ""): # anonymous union
var branchInit: StructInitializer
result.addStructInitializer(branchInit, kind = siNamedStruct):
result.addField(branchInit, name = fieldName):
var branchObjInit: StructInitializer
result.addStructInitializer(branchObjInit, kind = siOrderedStruct):
getNullValueAux(p, t, b, constOrNil, result, branchObjInit, isConst, info)
elif b.kind == nkSym:
fieldName = mangleRecFieldName(p.module, b.sym)
result.addField(init, name = ""): # anonymous union
var branchInit: StructInitializer
result.addStructInitializer(branchInit, kind = siNamedStruct):
result.addField(branchInit, name = fieldName):
# we need to generate the default value of the single sym,
# to do this create a dummy wrapper initializer and recurse
var branchFieldInit: StructInitializer
result.addStructInitializer(branchFieldInit, kind = siWrapper):
getNullValueAux(p, t, b, constOrNil, result, branchFieldInit, isConst, info)
else:
# no fields, don't initialize
return
of nkSym:
let field = obj.sym
let fieldTyp = skipTypes(field.typ, abstractRange+{tyOwned}-{tyTypeDesc})
if isOpaqueImportcType(fieldTyp):
return # C zero-initializes omitted fields
let sname = mangleRecFieldName(p.module, field)
result.addField(init, name = sname):
block fieldInit:
if constOrNil != nil:
for i, it in isons(constOrNil, 1):
if it.kind == nkExprColonExpr:
assert it.firstSon.kind == nkSym, "illformed object constr; the field is not a sym"
if it.firstSon.sym.name.id == field.name.id:
genBracedInit(p, it.secondSon, isConst, field.typ, result)
break fieldInit
elif i == field.position:
genBracedInit(p, it, isConst, field.typ, result)
break fieldInit
# not found, produce default value:
getDefaultValue(p, field.typ, info, result)
else:
localError(p.config, info, "cannot create null element for: " & $obj)
proc getNullValueAuxT(p: BProc; orig, t: PType; obj, constOrNil: PNode,
result: var Builder; init: var StructInitializer;
isConst: bool, info: TLineInfo) =
var base = t.baseClass
when false:
let oldRes = result
let oldcount = count
if base != nil:
base = skipTypes(base, skipPtrs)
result.addField(init, name = "Sup"):
var baseInit: StructInitializer
result.addStructInitializer(baseInit, kind = siOrderedStruct):
getNullValueAuxT(p, orig, base, base.n, constOrNil, result, baseInit, isConst, info)
elif not isObjLackingTypeField(t):
result.addField(init, name = "m_type"):
if optTinyRtti in p.config.globalOptions:
result.add genTypeInfoV2(p.module, orig, obj.info)
else:
result.add genTypeInfoV1(p.module, orig, obj.info)
getNullValueAux(p, t, obj, constOrNil, result, init, isConst, info)
when false: # referring to Sup field, hopefully not a problem
# do not emit '{}' as that is not valid C:
if oldcount == count: result = oldRes
proc genConstObjConstr(p: BProc; n: PNode; isConst: bool; result: var Builder) =
let t = n.typ.skipTypes(abstractInstOwned)
# Use designated initializers when opaque importc fields present
var objInit: StructInitializer
let initKind = if t.kind == tyObject and containsOpaqueImportcField(t): siNamedStruct else: siOrderedStruct
result.addStructInitializer(objInit, kind = initKind):
if t.kind == tyObject:
getNullValueAuxT(p, t, t, t.n, n, result, objInit, isConst, n.info)
proc genConstSimpleList(p: BProc, n: PNode; isConst: bool; result: var Builder) =
var arrInit: StructInitializer
result.addStructInitializer(arrInit, kind = siArray):
if p.vccAndC and not n.hasSons and n.typ.kind == tyArray:
result.addField(arrInit, name = ""):
getDefaultValue(p, n.typ.elementType, n.info, result)
for it in sons(n):
var ind, val: PNode
if it.kind == nkExprColonExpr:
ind = it.firstSon
val = it.secondSon
else:
ind = it
val = it
result.addField(arrInit, name = ""):
genBracedInit(p, val, isConst, ind.typ, result)
proc genConstTuple(p: BProc, n: PNode; isConst: bool; tup: PType; result: var Builder) =
var tupleInit: StructInitializer
result.addStructInitializer(tupleInit, kind = siOrderedStruct):
if p.vccAndC and not n.hasSons:
result.addField(tupleInit, name = "dummy"):
result.addIntValue(0)
for i, ni in isons(n):
var it = ni
if it.kind == nkExprColonExpr:
it = it.secondSon
# Do not produce code for void types
if isEmptyType(tup[i]): continue
result.addField(tupleInit, name = "Field" & $i):
genBracedInit(p, it, isConst, tup[i], result)
proc genConstSeq(p: BProc, n: PNode, t: PType; isConst: bool; result: var Builder) =
let base = t.skipTypes(abstractInst).elementType
let tmpName = getTempName(p.module)
# genBracedInit can modify cfsStrData, we need an intermediate builder:
var def = newBuilder("")
def.addVarWithTypeAndInitializer(
if isConst: Const else: Global,
name = tmpName):
def.addSimpleStruct(p.module, name = "", baseType = ""):
def.addField(name = "sup", typ = cgsymValue(p.module, "TGenericSeq"))
def.addArrayField(name = "data", elementType = getTypeDesc(p.module, base), len = n.len)
do:
var structInit: StructInitializer
def.addStructInitializer(structInit, kind = siOrderedStruct):
def.addField(structInit, name = "sup"):
var supInit: StructInitializer
def.addStructInitializer(supInit, kind = siOrderedStruct):
def.addField(supInit, name = "len"):
def.addIntValue(n.len)
def.addField(supInit, name = "reserved"):
def.add(cOp(BitOr, NimInt, cIntValue(n.len), NimStrlitFlag))
if n.hasSons:
def.addField(structInit, name = "data"):
var arrInit: StructInitializer
def.addStructInitializer(arrInit, kind = siArray):
for ni in sons(n):
def.addField(arrInit, name = ""):
genBracedInit(p, ni, isConst, base, def)
p.module.s[cfsStrData].add extract(def)
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).elementType
let payload = getTempName(p.module)
# genBracedInit can modify cfsStrData, we need an intermediate builder:
var def = newBuilder("")
def.addVarWithTypeAndInitializer(
if isConst: AlwaysConst else: Global,
name = payload):
def.addSimpleStruct(p.module, name = "", baseType = ""):
def.addField(name = "cap", typ = NimInt)
def.addArrayField(name = "data", elementType = getTypeDesc(p.module, base), len = n.len)
do:
var structInit: StructInitializer
def.addStructInitializer(structInit, kind = siOrderedStruct):
def.addField(structInit, name = "cap"):
def.add(cOp(BitOr, NimInt, cIntValue(n.len), NimStrlitFlag))
if n.hasSons:
def.addField(structInit, name = "data"):
var arrInit: StructInitializer
def.addStructInitializer(arrInit, kind = siArray):
for ni in sons(n):
def.addField(arrInit, name = ""):
genBracedInit(p, ni, isConst, base, def)
p.module.s[cfsStrData].add extract(def)
var resultInit: StructInitializer
result.addStructInitializer(resultInit, kind = siOrderedStruct):
result.addField(resultInit, name = "len"):
result.addIntValue(n.len)
result.addField(resultInit, name = "p"):
result.add cCast(typ = ptrType(getSeqPayloadType(p.module, t)), value = cAddr(payload))
proc genBracedInit(p: BProc, n: PNode; isConst: bool; optionalType: PType; result: var Builder) =
case n.kind
of nkHiddenStdConv, nkHiddenSubConv:
genBracedInit(p, n.secondSon, isConst, n.typ, result)
else:
var ty = tyNone
var typ: PType = nil
if optionalType == nil:
if n.kind in nkStrKinds:
ty = tyString
else:
internalError(p.config, n.info, "node has no type")
else:
typ = skipTypes(optionalType, abstractInstOwned + {tyStatic})
ty = typ.kind
case ty
of tySet:
let cs = toBitSet(p.config, n)
genRawSetData(cs, int(getSize(p.config, n.typ)), result)
of tySequence:
if optSeqDestructors in p.config.globalOptions:
genConstSeqV2(p, n, typ, isConst, result)
else:
genConstSeq(p, n, typ, isConst, result)
of tyProc:
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.
# this hack fixes issue that nkNilLit is expanded to {NIM_NIL,NIM_NIL}
# this behaviour is needed since closure_var = nil must be
# expanded to {NIM_NIL,NIM_NIL}
# in VM closures are initialized with nkPar(nkNilLit, nkNilLit)
# leading to duplicate code like this:
# "{NIM_NIL,NIM_NIL}, {NIM_NIL,NIM_NIL}"
var closureInit: StructInitializer
result.addStructInitializer(closureInit, kind = siOrderedStruct):
result.addField(closureInit, name = "ClP_0"):
if n.firstSon.kind == nkNilLit:
result.add(NimNil)
else:
var d: TLoc = initLocExpr(p, n.firstSon)
result.add(cCast(typ = getClosureType(p.module, typ, clHalfWithEnv), value = rdLoc(d)))
result.addField(closureInit, name = "ClE_0"):
result.add(NimNil)
else:
var d: TLoc = initLocExpr(p, n)
result.add rdLoc(d)
of tyArray, tyVarargs:
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:
if n.kind != nkBracket:
internalError(p.config, n.info, "const openArray expression is not an array construction")
let payload = getTempName(p.module)
let ctype = getTypeDesc(p.module, typ.elementType)
let arrLen = n.len
# genConstSimpleList can modify cfsStrData, we need an intermediate builder:
var data = newBuilder("")
data.addArrayVarWithInitializer(
kind = if isConst: AlwaysConst else: Global,
name = payload, elementType = ctype, len = arrLen):
genConstSimpleList(p, n, isConst, data)
p.module.s[cfsStrData].add(extract(data))
var openArrInit: StructInitializer
result.addStructInitializer(openArrInit, kind = siOrderedStruct):
result.addField(openArrInit, name = "Field0"):
result.add(cCast(typ = ptrType(ctype), value = cAddr(payload)))
result.addField(openArrInit, name = "Field1"):
result.addIntValue(arrLen)
of tyObject:
genConstObjConstr(p, n, isConst, result)
of tyString, tyCstring:
if optSeqDestructors in p.config.globalOptions and n.kind != nkNilLit and ty == tyString:
if p.config.usesSso():
genStringLiteralV3Const(p.module, n, isConst, result)
else:
genStringLiteralV2Const(p.module, n, isConst, result)
else:
var d: TLoc = initLocExpr(p, n)
result.add rdLoc(d)
else:
var d: TLoc = initLocExpr(p, n)
result.add rdLoc(d)