mirror of
https://github.com/nim-lang/Nim.git
synced 2026-05-30 16:45:38 +00:00
## Bug
When an `except T as e:` handler in the cpp backend raises a new
exception, the enclosing `finally` block is silently dropped under
`--mm:arc` and `--mm:orc`:
```nim
proc main() =
try:
try:
raise newException(CatchableError, "orig")
except CatchableError as e:
echo "inner: ", e.msg
raise newException(CatchableError, "re:" & e.msg)
finally:
echo "finally"
except CatchableError as outer:
echo "outer: ", outer.msg
main()
```
Expected output:
```
inner: orig
finally
outer: re:orig
```
Actual output on `nim cpp --mm:arc` (and `--mm:orc`):
```
inner: orig
outer: re:orig
```
The `finally` line is missing. The bug is specific to memory managers
that use destructor injection (arc/orc); under `--mm:refc` the original
code path works correctly because no destructor wrapper is injected.
## Root cause
When the body of `except T as e:` is processed under ARC/ORC, the
destructor injection pass injects a compiler-generated `nkHiddenTryStmt`
wrapper around the handler body to call `=destroy` on `e` when it goes
out of scope. That wrapper sits at the top of `p.nestedTryStmts` with
`inExcept = false`.
`finallyActions` (which inlines the user-finally body before a raise
propagates) only inspected the topmost entry of `nestedTryStmts`.
Because the wrapper has `inExcept = false`, the check short-circuited
and the user's finally was never inlined.
After the raise, C++'s rule that sibling catch clauses do not catch each
other's throws means the surrounding `catch(...)/finally` emitted by
`genTryCpp` never runs either, so the user's finally is silently
dropped.
## Fix
- Add an `isHidden` flag to `nestedTryStmts` entries, set to `t.kind ==
nkHiddenTryStmt` so compiler-injected try wrappers can be distinguished
from user-written ones.
- In `finallyActions`, walk past `isHidden` wrappers but stop at the
first user try. If that user try is in its except branch with a finally,
inline the finally body before the raise; otherwise leave the raise
untouched (the raise will be caught by that user try's own except
branches and the inner finally will run via normal unwinding, which is
what already happens correctly under refc).
Walking past wrappers fixes the `as e` case under arc/orc. Stopping at
user trys preserves the existing correct behaviour for nested
try/except/finally constructs (e.g. `tests/exception/tfinally.nim`'s
`nested_finally`), which would otherwise see the outer finally inlined
too eagerly when an inner raise is processed.
## Tests
Adds `tests/exception/tcpp_handler_raise_finally.nim` covering:
- `except T as e:` re-raise + outer finally
- typeless `except:` re-raise + outer finally
- try/finally without except (exception propagation through finally)
The test runs on `--mm:arc`, `--mm:orc`, and `--mm:refc`.
Locally verified on both `devel` and `version-2-2`:
- `tests/exception/` — 42 PASS, 0 FAIL, 3 SKIP
- `tests/destructor/` — all PASS
- `tests/cpp/` — all PASS (single unrelated failure: `tasync_cpp.nim`
needs the `jester` package)
- `megatest` — PASS for both `--mm:arc` and `--mm:refc`, including the
previously regressing `tfinally.nim`'s `nested_finally`
## Backport
Tagged `[backport]` in the commit message for inclusion in
`version-2-2`.
---------
Co-authored-by: puffball1567 <17452514+puffball1567@users.noreply.github.com>
1989 lines
70 KiB
Nim
1989 lines
70 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2015 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
# included from cgen.nim
|
|
const
|
|
RangeExpandLimit = 256 # do not generate ranges
|
|
# over 'RangeExpandLimit' elements
|
|
stringCaseThreshold = 8
|
|
# above X strings a hash-switch for strings is generated
|
|
|
|
proc registerTraverseProc(p: BProc, v: PSym) =
|
|
var traverseProc = ""
|
|
if p.config.selectedGC in {gcMarkAndSweep, gcHooks, gcRefc} and
|
|
optOwnedRefs notin p.config.globalOptions and
|
|
containsManagedMemory(v.loc.t):
|
|
# we register a specialized marked proc here; this has the advantage
|
|
# that it works out of the box for thread local storage then :-)
|
|
traverseProc = genTraverseProcForGlobal(p.module, v, v.info)
|
|
|
|
if traverseProc.len != 0 and not p.hcrOn:
|
|
p.module.preInitProc.procSec(cpsInit).add("\n\t")
|
|
let fnName = cgsymValue(p.module,
|
|
if sfThread in v.flags: "nimRegisterThreadLocalMarker"
|
|
else: "nimRegisterGlobalMarker")
|
|
p.module.preInitProc.procSec(cpsInit).addCallStmt(fnName, traverseProc)
|
|
p.module.preInitProc.procSec(cpsInit).add("\n")
|
|
|
|
proc isAssignedImmediately(conf: ConfigRef; n: PNode): bool {.inline.} =
|
|
if n.kind == nkEmpty:
|
|
result = false
|
|
elif n.kind in nkCallKinds and n[0] != nil and n[0].typ != nil and n[0].typ.skipTypes(abstractInst).kind == tyProc:
|
|
if n[0].kind == nkSym and sfConstructor in n[0].sym.flags:
|
|
result = true
|
|
elif isInvalidReturnType(conf, n[0].typ, true):
|
|
# var v = f()
|
|
# is transformed into: var v; f(addr v)
|
|
# where 'f' **does not** initialize the result!
|
|
result = false
|
|
else:
|
|
result = true
|
|
elif isInvalidReturnType(conf, n.typ, false):
|
|
result = false
|
|
else:
|
|
result = true
|
|
|
|
proc inExceptBlockLen(p: BProc): int =
|
|
result = 0
|
|
for x in p.nestedTryStmts:
|
|
if x.inExcept: result.inc
|
|
|
|
proc startBlockInside(p: BProc): int {.discardable.} =
|
|
inc(p.labels)
|
|
result = p.blocks.len
|
|
|
|
p.blocks.add initBlock()
|
|
|
|
p.blocks[result].id = p.labels
|
|
p.blocks[result].nestedTryStmts = p.nestedTryStmts.len.int16
|
|
p.blocks[result].nestedExceptStmts = p.inExceptBlockLen.int16
|
|
|
|
template startBlockWith(p: BProc, body: typed): int =
|
|
body
|
|
startBlockInside(p)
|
|
|
|
proc blockBody(b: var TBlock; result: var Builder) =
|
|
result.add extract(b.sections[cpsLocals])
|
|
if b.frameLen > 0:
|
|
result.addInPlaceOp(Add, NimInt, dotField("FR_", "len"), cIntValue(b.frameLen.int))
|
|
result.add(extract(b.sections[cpsInit]))
|
|
result.add(extract(b.sections[cpsStmts]))
|
|
if b.frameLen > 0:
|
|
result.addInPlaceOp(Sub, NimInt, dotField("FR_", "len"), cIntValue(b.frameLen.int))
|
|
|
|
proc endBlockInside(p: BProc) =
|
|
let topBlock = p.blocks.len-1
|
|
# the block is merged into the parent block
|
|
p.blocks[topBlock].blockBody(p.blocks[topBlock-1].sections[cpsStmts])
|
|
setLen(p.blocks, topBlock)
|
|
|
|
proc endBlockOutside(p: BProc, label: TLabel) =
|
|
if label.len != 0:
|
|
let topBlock = p.blocks.len - 1
|
|
p.blocks[topBlock].sections[cpsStmts].addLabel(label)
|
|
|
|
template endBlockWith(p: BProc, body: typed) =
|
|
let label = p.blocks[p.blocks.len - 1].label
|
|
endBlockInside(p)
|
|
body
|
|
endBlockOutside(p, label)
|
|
|
|
proc genVarTuple(p: BProc, n: PNode) =
|
|
if n.kind != nkVarTuple: internalError(p.config, n.info, "genVarTuple")
|
|
|
|
# if we have a something that's been captured, use the lowering instead:
|
|
for i in 0..<n.len-2:
|
|
if n[i].kind != nkSym:
|
|
genStmts(p, lowerTupleUnpacking(p.module.g.graph, n, p.module.idgen, p.prc))
|
|
return
|
|
|
|
# check only the first son
|
|
var forHcr = treatGlobalDifferentlyForHCR(p.module, n[0].sym)
|
|
let hcrCond = if forHcr: getTempName(p.module) else: ""
|
|
var hcrGlobals: seq[tuple[loc: TLoc, tp: Rope]] = @[]
|
|
# determine if the tuple is constructed at top-level scope or inside of a block (if/while/block)
|
|
let isGlobalInBlock = forHcr and p.blocks.len > 2
|
|
# do not close and reopen blocks if this is a 'global' but inside of a block (if/while/block)
|
|
forHcr = forHcr and not isGlobalInBlock
|
|
|
|
var hcrIf = default(IfBuilder)
|
|
if forHcr:
|
|
startBlockWith(p):
|
|
# check with the boolean if the initializing code for the tuple should be ran
|
|
hcrIf = initIfStmt(p.s(cpsStmts))
|
|
initElifBranch(p.s(cpsStmts), hcrIf, hcrCond)
|
|
|
|
genLineDir(p, n)
|
|
var tup = initLocExpr(p, n[^1])
|
|
var t = tup.t.skipTypes(abstractInst)
|
|
for i in 0..<n.len-2:
|
|
let vn = n[i]
|
|
let v = vn.sym
|
|
if sfCompileTime in v.flags: continue
|
|
backendEnsureMutable v
|
|
if sfGlobal in v.flags:
|
|
assignGlobalVar(p, vn, "")
|
|
genObjectInit(p, cpsInit, v.typ, v.locImpl, constructObj)
|
|
registerTraverseProc(p, v)
|
|
else:
|
|
assignLocalVar(p, vn)
|
|
initLocalVar(p, v, immediateAsgn=isAssignedImmediately(p.config, n[^1]))
|
|
var field = initLoc(locExpr, vn, tup.storage)
|
|
let rtup = rdLoc(tup)
|
|
let fieldName =
|
|
if t.kind == tyTuple:
|
|
"Field" & $i
|
|
else:
|
|
if t.n[i].kind != nkSym: internalError(p.config, n.info, "genVarTuple")
|
|
mangleRecFieldName(p.module, t.n[i].sym)
|
|
field.snippet = dotField(rtup, fieldName)
|
|
putLocIntoDest(p, v.locImpl, field)
|
|
if forHcr or isGlobalInBlock:
|
|
hcrGlobals.add((loc: v.locImpl, tp: CNil))
|
|
|
|
if forHcr:
|
|
# end the block where the tuple gets initialized
|
|
endBlockWith(p):
|
|
finishBranch(p.s(cpsStmts), hcrIf)
|
|
finishIfStmt(p.s(cpsStmts), hcrIf)
|
|
|
|
if forHcr or isGlobalInBlock:
|
|
# insert the registration of the globals for the different parts of the tuple at the
|
|
# start of the current scope (after they have been iterated) and init a boolean to
|
|
# check if any of them is newly introduced and the initializing code has to be ran
|
|
p.s(cpsLocals).addVar(kind = Local,
|
|
name = hcrCond,
|
|
typ = NimBool,
|
|
initializer = NimFalse)
|
|
for curr in hcrGlobals:
|
|
let rc = rdLoc(curr.loc)
|
|
p.s(cpsLocals).addInPlaceOp(BitOr, NimBool,
|
|
hcrCond,
|
|
cCall("hcrRegisterGlobal",
|
|
getModuleDllPath(p.module, n[0].sym),
|
|
'"' & curr.loc.snippet & '"',
|
|
cSizeof(rc),
|
|
curr.tp,
|
|
cCast(ptrType(CPointer), cAddr(curr.loc.snippet))))
|
|
|
|
|
|
proc loadInto(p: BProc, le, ri: PNode, a: var TLoc) {.inline.} =
|
|
if ri.kind in nkCallKinds and (ri[0].kind != nkSym or
|
|
ri[0].sym.magic == mNone):
|
|
genAsgnCall(p, le, ri, a)
|
|
else:
|
|
# this is a hacky way to fix #1181 (tmissingderef)::
|
|
#
|
|
# var arr1 = cast[ptr array[4, int8]](addr foo)[]
|
|
#
|
|
# However, fixing this properly really requires modelling 'array' as
|
|
# a 'struct' in C to preserve dereferencing semantics completely. Not
|
|
# worth the effort until version 1.0 is out.
|
|
a.flags.incl(lfEnforceDeref)
|
|
expr(p, ri, a)
|
|
|
|
proc assignLabel(b: var TBlock; result: var TLabel) {.inline.} =
|
|
b.label = "LA" & b.id.rope
|
|
result = b.label
|
|
|
|
proc startSimpleBlock(p: BProc, scope: out ScopeBuilder): int {.discardable, inline.} =
|
|
startBlockWith(p):
|
|
scope = initScope(p.s(cpsStmts))
|
|
|
|
proc endSimpleBlock(p: BProc, scope: var ScopeBuilder) {.inline.} =
|
|
endBlockWith(p):
|
|
finishScope(p.s(cpsStmts), scope)
|
|
|
|
proc genSimpleBlock(p: BProc, stmts: PNode) {.inline.} =
|
|
var scope: ScopeBuilder
|
|
startSimpleBlock(p, scope)
|
|
genStmts(p, stmts)
|
|
endSimpleBlock(p, scope)
|
|
|
|
proc exprBlock(p: BProc, n: PNode, d: var TLoc) =
|
|
var scope: ScopeBuilder
|
|
startSimpleBlock(p, scope)
|
|
expr(p, n, d)
|
|
endSimpleBlock(p, scope)
|
|
|
|
template preserveBreakIdx(body: untyped): untyped =
|
|
var oldBreakIdx = p.breakIdx
|
|
body
|
|
p.breakIdx = oldBreakIdx
|
|
|
|
proc genState(p: BProc, n: PNode) =
|
|
internalAssert p.config, n.len == 1
|
|
let n0 = n[0]
|
|
if n0.kind == nkIntLit:
|
|
let idx = n[0].intVal
|
|
p.s(cpsStmts).addLabel("STATE" & $idx)
|
|
elif n0.kind == nkStrLit:
|
|
p.s(cpsStmts).addLabel(n0.strVal)
|
|
|
|
proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int, isReturnStmt = false) =
|
|
# Called by return and break stmts.
|
|
# Deals with issues faced when jumping out of try/except/finally stmts.
|
|
|
|
var stack = newSeq[tuple[fin: PNode, inExcept: bool, isHidden: bool, label: Natural]](0)
|
|
|
|
inc p.withinBlockLeaveActions
|
|
for i in 1..howManyTrys:
|
|
let tryStmt = p.nestedTryStmts.pop
|
|
if p.config.exc == excSetjmp:
|
|
# Pop safe points generated by try
|
|
if not tryStmt.inExcept:
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "popSafePoint"))
|
|
|
|
# Pop this try-stmt of the list of nested trys
|
|
# so we don't infinite recurse on it in the next step.
|
|
stack.add(tryStmt)
|
|
|
|
# Find finally-stmt for this try-stmt
|
|
# and generate a copy of its sons
|
|
var finallyStmt = tryStmt.fin
|
|
if finallyStmt != nil:
|
|
genStmts(p, finallyStmt[0])
|
|
|
|
dec p.withinBlockLeaveActions
|
|
|
|
# push old elements again:
|
|
for i in countdown(howManyTrys-1, 0):
|
|
p.nestedTryStmts.add(stack[i])
|
|
|
|
# Pop exceptions that was handled by the
|
|
# except-blocks we are in
|
|
if noSafePoints notin p.flags and not (isReturnStmt and isClosureIterator(p.prc.typ)):
|
|
for i in countdown(howManyExcepts-1, 0):
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "popCurrentException"))
|
|
|
|
proc genGotoState(p: BProc, n: PNode) =
|
|
# we resist the temptation to translate it into duff's device as it later
|
|
# will be translated into computed gotos anyway for GCC at least:
|
|
# switch (x.state) {
|
|
# case 0: goto STATE0;
|
|
# ...
|
|
var a: TLoc = initLocExpr(p, n[0])
|
|
let ra = rdLoc(a)
|
|
p.s(cpsStmts).addSwitchStmt(ra):
|
|
p.flags.incl beforeRetNeeded
|
|
p.s(cpsStmts).addSingleSwitchCase(cIntValue(-1)):
|
|
blockLeaveActions(p,
|
|
howManyTrys = p.nestedTryStmts.len,
|
|
howManyExcepts = p.inExceptBlockLen)
|
|
p.s(cpsStmts).addGoto("BeforeRet_")
|
|
var statesCounter = lastOrd(p.config, n[0].typ)
|
|
if n.len >= 2 and n[1].kind == nkIntLit:
|
|
statesCounter = getInt(n[1])
|
|
let prefix = if n.len == 3 and n[2].kind == nkStrLit: n[2].strVal.rope
|
|
else: rope"STATE"
|
|
for i in 0i64..toInt64(statesCounter):
|
|
p.s(cpsStmts).addSingleSwitchCase(cIntValue(i)):
|
|
p.s(cpsStmts).addGoto(prefix & $i)
|
|
|
|
proc genBreakState(p: BProc, n: PNode, d: var TLoc) =
|
|
var a: TLoc
|
|
d = initLoc(locExpr, n, OnUnknown)
|
|
|
|
if n[0].kind == nkClosure:
|
|
a = initLocExpr(p, n[0][1])
|
|
let ra = a.rdLoc
|
|
d.snippet = cOp(LessThan,
|
|
subscript(
|
|
cCast(ptrType(NimInt), ra),
|
|
cIntValue(1)),
|
|
cIntValue(0))
|
|
else:
|
|
a = initLocExpr(p, n[0])
|
|
let ra = a.rdLoc
|
|
# the environment is guaranteed to contain the 'state' field at offset 1:
|
|
d.snippet = cOp(LessThan,
|
|
subscript(
|
|
cCast(ptrType(NimInt), dotField(ra, "ClE_0")),
|
|
cIntValue(1)),
|
|
cIntValue(0))
|
|
|
|
proc genGotoVar(p: BProc; value: PNode) =
|
|
if value.kind notin {nkCharLit..nkUInt64Lit}:
|
|
localError(p.config, value.info, "'goto' target must be a literal value")
|
|
else:
|
|
p.s(cpsStmts).addGoto("NIMSTATE_" & $value.intVal)
|
|
|
|
proc genBracedInit(p: BProc, n: PNode; isConst: bool; optionalType: PType; result: var Builder)
|
|
|
|
proc potentialValueInit(p: BProc; v: PSym; value: PNode; result: var Builder) =
|
|
if lfDynamicLib in v.loc.flags or sfThread in v.flags or p.hcrOn:
|
|
discard "nothing to do"
|
|
elif sfGlobal in v.flags and value != nil and isDeepConstExpr(value, p.module.compileToCpp) and
|
|
p.withinLoop == 0 and not containsGarbageCollectedRef(v.typ):
|
|
#echo "New code produced for ", v.name.s, " ", p.config $ value.info
|
|
genBracedInit(p, value, isConst = false, v.typ, result)
|
|
|
|
proc genCppParamsForCtor(p: BProc; call: PNode; didGenTemp: var bool): Snippet =
|
|
var res = newBuilder("")
|
|
var argBuilder = default(CallBuilder) # not init, only building params
|
|
let typ = skipTypes(call[0].typ, abstractInst)
|
|
assert(typ.kind == tyProc)
|
|
for i in 1..<call.len:
|
|
#if it's a type we can just generate here another initializer as we are in an initializer context
|
|
if call[i].kind == nkCall and call[i][0].kind == nkSym and call[i][0].sym.kind == skType:
|
|
res.addArgument(argBuilder):
|
|
res.add genCppInitializer(p.module, p, call[i][0].sym.typ, didGenTemp)
|
|
else:
|
|
#We need to test for temp in globals, see: #23657
|
|
let param =
|
|
if typ[i].kind in {tyVar} and call[i].kind == nkHiddenAddr:
|
|
call[i][0]
|
|
else:
|
|
call[i]
|
|
if param.kind != nkBracketExpr or param.typ.kind in
|
|
{tyRef, tyPtr, tyUncheckedArray, tyArray, tyOpenArray,
|
|
tyVarargs, tySequence, tyString, tyCstring, tyTuple}:
|
|
let tempLoc = initLocExprSingleUse(p, param)
|
|
didGenTemp = didGenTemp or tempLoc.k == locTemp
|
|
genOtherArg(p, call, i, typ, res, argBuilder)
|
|
result = extract(res)
|
|
|
|
proc genSingleVar(p: BProc, v: PSym; vn, value: PNode) =
|
|
if sfGoto in v.flags:
|
|
# translate 'var state {.goto.} = X' into 'goto LX':
|
|
genGotoVar(p, value)
|
|
return
|
|
let imm = isAssignedImmediately(p.config, value)
|
|
let isCppCtorCall = p.module.compileToCpp and imm and
|
|
value.kind in nkCallKinds and value[0].kind == nkSym and
|
|
v.typ.kind != tyPtr and sfConstructor in value[0].sym.flags
|
|
var targetProc = p
|
|
var valueBuilder = newBuilder("")
|
|
potentialValueInit(p, v, value, valueBuilder)
|
|
let valueAsRope = extract(valueBuilder)
|
|
if sfGlobal in v.flags:
|
|
if v.flags * {sfImportc, sfExportc} == {sfImportc} and
|
|
value.kind == nkEmpty and
|
|
v.loc.flags * {lfHeader, lfNoDecl} != {}:
|
|
# IC XXX: this is bad, we should set v.loc regardless here
|
|
return
|
|
if sfPure in v.flags:
|
|
# v.owner.kind != skModule:
|
|
targetProc = p.module.preInitProc
|
|
if isCppCtorCall and not containsHiddenPointer(v.typ):
|
|
var didGenTemp = false
|
|
callGlobalVarCppCtor(targetProc, v, vn, value, didGenTemp)
|
|
if didGenTemp:
|
|
message(p.config, vn.info, warnGlobalVarConstructorTemporary, vn.sym.name.s)
|
|
#We fail to call the constructor in the global scope so we do the call inside the main proc
|
|
assignGlobalVar(targetProc, vn, valueAsRope)
|
|
var loc = initLocExprSingleUse(targetProc, value)
|
|
genAssignment(targetProc, v.loc, loc, {})
|
|
else:
|
|
assignGlobalVar(targetProc, vn, valueAsRope)
|
|
|
|
# XXX: be careful here.
|
|
# Global variables should not be zeromem-ed within loops
|
|
# (see bug #20).
|
|
# That's why we are doing the construction inside the preInitProc.
|
|
# genObjectInit relies on the C runtime's guarantees that
|
|
# global variables will be initialized to zero.
|
|
if valueAsRope.len == 0:
|
|
var loc = v.loc
|
|
# When the native TLS is unavailable, a global thread-local variable needs
|
|
# one more layer of indirection in order to access the TLS block.
|
|
# Only do this for complex types that may need a call to `objectInit`
|
|
if sfThread in v.flags and emulatedThreadVars(p.config) and
|
|
isComplexValueType(v.typ):
|
|
loc = initLocExprSingleUse(p.module.preInitProc, vn)
|
|
genObjectInit(p.module.preInitProc, cpsInit, v.typ, loc, constructObj)
|
|
# Alternative construction using default constructor (which may zeromem):
|
|
# if sfImportc notin v.flags: constructLoc(p.module.preInitProc, v.loc)
|
|
if sfExportc in v.flags and p.module.g.generatedHeader != nil:
|
|
genVarPrototype(p.module.g.generatedHeader, vn)
|
|
registerTraverseProc(p, v)
|
|
else:
|
|
if imm and p.module.compileToCpp and p.splitDecls == 0 and
|
|
not containsHiddenPointer(v.typ) and
|
|
nimErrorFlagAccessed notin p.flags:
|
|
# C++ really doesn't like things like 'Foo f; f = x' as that invokes a
|
|
# parameterless constructor followed by an assignment operator. So we
|
|
# generate better code here: 'Foo f = x;'
|
|
genLineDir(p, vn)
|
|
var initializer: Snippet = ""
|
|
var initializerKind: VarInitializerKind = Assignment
|
|
if isCppCtorCall:
|
|
var didGenTemp = false
|
|
initializer = genCppParamsForCtor(p, value, didGenTemp)
|
|
if initializer.len != 0:
|
|
initializer = "(" & initializer & ")"
|
|
initializerKind = CppConstructor
|
|
else:
|
|
var tmp = initLocExprSingleUse(p, value)
|
|
if value.kind != nkEmpty:
|
|
initializer = tmp.rdLoc
|
|
localVarDecl(p.s(cpsStmts), p, vn, initializer, initializerKind)
|
|
return
|
|
assignLocalVar(p, vn)
|
|
initLocalVar(p, v, imm)
|
|
|
|
let traverseProc = CNil
|
|
# If the var is in a block (control flow like if/while or a block) in global scope just
|
|
# register the so called "global" so it can be used later on. There is no need to close
|
|
# and reopen of if (nim_hcr_do_init_) blocks because we are in one already anyway.
|
|
var forHcr = treatGlobalDifferentlyForHCR(p.module, v)
|
|
if forHcr and targetProc.blocks.len > 3 and v.owner.kind == skModule:
|
|
# put it in the locals section - mainly because of loops which
|
|
# use the var in a call to resetLoc() in the statements section
|
|
let rv = rdLoc(v.loc)
|
|
p.s(cpsLocals).addCallStmt("hcrRegisterGlobal",
|
|
getModuleDllPath(p.module, v),
|
|
'"' & v.loc.snippet & '"',
|
|
cSizeof(rv),
|
|
traverseProc,
|
|
cCast(ptrType(CPointer), cAddr(v.loc.snippet)))
|
|
# nothing special left to do later on - let's avoid closing and reopening blocks
|
|
forHcr = false
|
|
|
|
# we close and reopen the global if (nim_hcr_do_init_) blocks in the main Init function
|
|
# for the module so we can have globals and top-level code be interleaved and still
|
|
# be able to re-run it but without the top level code - just the init of globals
|
|
var hcrInit = default(IfBuilder)
|
|
if forHcr:
|
|
startBlockWith(targetProc):
|
|
hcrInit = initIfStmt(p.s(cpsStmts))
|
|
initElifBranch(p.s(cpsStmts), hcrInit, cCall("hcrRegisterGlobal",
|
|
getModuleDllPath(p.module, v),
|
|
'"' & v.loc.snippet & '"',
|
|
cSizeof(rdLoc(v.loc)),
|
|
traverseProc,
|
|
cCast(ptrType(CPointer), cAddr(v.loc.snippet))))
|
|
if value.kind != nkEmpty and valueAsRope.len == 0:
|
|
genLineDir(targetProc, vn)
|
|
if not isCppCtorCall:
|
|
backendEnsureMutable v
|
|
loadInto(targetProc, vn, value, v.locImpl)
|
|
if forHcr:
|
|
endBlockWith(targetProc):
|
|
finishBranch(p.s(cpsStmts), hcrInit)
|
|
finishIfStmt(p.s(cpsStmts), hcrInit)
|
|
|
|
proc genSingleVar(p: BProc, a: PNode) =
|
|
let v = a[0].sym
|
|
if sfCompileTime in v.flags:
|
|
# fix issue #12640
|
|
# {.global, compileTime.} pragma in proc
|
|
if sfGlobal in v.flags and p.prc != nil and p.prc.kind == skProc:
|
|
discard
|
|
else:
|
|
return
|
|
genSingleVar(p, v, a[0], a[2])
|
|
|
|
proc genClosureVar(p: BProc, a: PNode) =
|
|
var immediateAsgn = a[2].kind != nkEmpty
|
|
var v: TLoc = initLocExpr(p, a[0])
|
|
genLineDir(p, a)
|
|
if immediateAsgn:
|
|
loadInto(p, a[0], a[2], v)
|
|
elif sfNoInit notin a[0][1].sym.flags:
|
|
constructLoc(p, v)
|
|
|
|
proc genVarStmt(p: BProc, n: PNode) =
|
|
for it in n:
|
|
case it.kind
|
|
of nkCommentStmt: discard
|
|
of nkIdentDefs:
|
|
# can be a lifted var nowadays ...
|
|
if it[0].kind == nkSym:
|
|
genSingleVar(p, it)
|
|
else:
|
|
genClosureVar(p, it)
|
|
of nkSym:
|
|
genSingleVar(p, it.sym, newSymNode(it.sym), it.sym.astdef)
|
|
else:
|
|
genVarTuple(p, it)
|
|
|
|
proc genIf(p: BProc, n: PNode, d: var TLoc) =
|
|
#
|
|
# { if (!expr1) goto L1;
|
|
# thenPart }
|
|
# goto LEnd
|
|
# L1:
|
|
# { if (!expr2) goto L2;
|
|
# thenPart2 }
|
|
# goto LEnd
|
|
# L2:
|
|
# { elsePart }
|
|
# Lend:
|
|
var
|
|
a: TLoc
|
|
lelse: TLabel
|
|
if not isEmptyType(n.typ) and d.k == locNone:
|
|
d = getTemp(p, n.typ)
|
|
genLineDir(p, n)
|
|
let lend = getLabel(p)
|
|
for it in n.sons:
|
|
# bug #4230: avoid false sharing between branches:
|
|
if d.k == locTemp and isEmptyType(n.typ): d.k = locNone
|
|
if it.len == 2:
|
|
var scope: ScopeBuilder
|
|
startSimpleBlock(p, scope)
|
|
a = initLocExprSingleUse(p, it[0])
|
|
lelse = getLabel(p)
|
|
inc(p.labels)
|
|
let ra = rdLoc(a)
|
|
p.s(cpsStmts).addSingleIfStmt(cOp(Not, ra)):
|
|
p.s(cpsStmts).addGoto(lelse)
|
|
if p.module.compileToCpp:
|
|
# avoid "jump to label crosses initialization" error:
|
|
p.s(cpsStmts).addScope():
|
|
expr(p, it[1], d)
|
|
else:
|
|
expr(p, it[1], d)
|
|
endSimpleBlock(p, scope)
|
|
if n.len > 1:
|
|
p.s(cpsStmts).addGoto(lend)
|
|
fixLabel(p, lelse)
|
|
elif it.len == 1:
|
|
var scope: ScopeBuilder
|
|
startSimpleBlock(p, scope)
|
|
expr(p, it[0], d)
|
|
endSimpleBlock(p, scope)
|
|
else: internalError(p.config, n.info, "genIf()")
|
|
if n.len > 1: fixLabel(p, lend)
|
|
|
|
proc genReturnStmt(p: BProc, t: PNode) =
|
|
if nfPreventCg in t.flags: return
|
|
p.flags.incl beforeRetNeeded
|
|
genLineDir(p, t)
|
|
if (t[0].kind != nkEmpty): genStmts(p, t[0])
|
|
blockLeaveActions(p,
|
|
howManyTrys = p.nestedTryStmts.len,
|
|
howManyExcepts = p.inExceptBlockLen,
|
|
isReturnStmt = true)
|
|
if (p.finallySafePoints.len > 0) and noSafePoints notin p.flags:
|
|
# If we're in a finally block, and we came here by exception
|
|
# consume it before we return.
|
|
var safePoint = p.finallySafePoints[^1]
|
|
p.s(cpsStmts).addSingleIfStmt(
|
|
cOp(NotEqual,
|
|
dotField(safePoint, "status"),
|
|
cIntValue(0))):
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "popCurrentException"))
|
|
p.s(cpsStmts).addGoto("BeforeRet_")
|
|
|
|
proc genGotoForCase(p: BProc; caseStmt: PNode) =
|
|
for i in 1..<caseStmt.len:
|
|
var scope: ScopeBuilder
|
|
startSimpleBlock(p, scope)
|
|
let it = caseStmt[i]
|
|
for j in 0..<it.len-1:
|
|
if it[j].kind == nkRange:
|
|
localError(p.config, it.info, "range notation not available for computed goto")
|
|
return
|
|
let val = getOrdValue(it[j])
|
|
p.s(cpsStmts).addLabel("NIMSTATE_" & $val)
|
|
genStmts(p, it.lastSon)
|
|
endSimpleBlock(p, scope)
|
|
|
|
|
|
iterator fieldValuePairs(n: PNode): tuple[memberSym, valueSym: PNode] =
|
|
assert(n.kind in {nkLetSection, nkVarSection})
|
|
for identDefs in n:
|
|
if identDefs.kind == nkIdentDefs:
|
|
let valueSym = identDefs[^1]
|
|
for i in 0..<identDefs.len-2:
|
|
let memberSym = identDefs[i]
|
|
yield((memberSym: memberSym, valueSym: valueSym))
|
|
|
|
proc genComputedGoto(p: BProc; n: PNode) =
|
|
# first pass: Generate array of computed labels:
|
|
|
|
# flatten the loop body because otherwise let and var sections
|
|
# wrapped inside stmt lists by inject destructors won't be recognised
|
|
let n = n.flattenStmts()
|
|
var casePos = -1
|
|
var arraySize: int = 0
|
|
for i in 0..<n.len:
|
|
let it = n[i]
|
|
if it.kind == nkCaseStmt:
|
|
if lastSon(it).kind != nkOfBranch:
|
|
localError(p.config, it.info,
|
|
"case statement must be exhaustive for computed goto"); return
|
|
casePos = i
|
|
if enumHasHoles(it[0].typ):
|
|
localError(p.config, it.info,
|
|
"case statement cannot work on enums with holes for computed goto"); return
|
|
let aSize = lengthOrd(p.config, it[0].typ)
|
|
if aSize > 10_000:
|
|
localError(p.config, it.info,
|
|
"case statement has too many cases for computed goto"); return
|
|
arraySize = toInt(aSize)
|
|
if firstOrd(p.config, it[0].typ) != 0:
|
|
localError(p.config, it.info,
|
|
"case statement has to start at 0 for computed goto"); return
|
|
if casePos < 0:
|
|
localError(p.config, n.info, "no case statement found for computed goto"); return
|
|
var id = p.labels+1
|
|
inc p.labels, arraySize+1
|
|
let tmp = "TMP$1_" % [id.rope]
|
|
p.s(cpsStmts).addArrayVarWithInitializer(kind = Global,
|
|
name = tmp,
|
|
elementType = CPointer,
|
|
len = arraySize):
|
|
var labelsInit: StructInitializer
|
|
p.s(cpsStmts).addStructInitializer(labelsInit, kind = siArray):
|
|
for i in 1..arraySize:
|
|
p.s(cpsStmts).addField(labelsInit, ""):
|
|
p.s(cpsStmts).add(cLabelAddr("TMP" & $(id+i) & "_"))
|
|
|
|
for j in 0..<casePos:
|
|
genStmts(p, n[j])
|
|
|
|
let caseStmt = n[casePos]
|
|
var a: TLoc = initLocExpr(p, caseStmt[0])
|
|
let ra = a.rdLoc
|
|
# first goto:
|
|
p.s(cpsStmts).addComputedGoto(subscript(tmp, ra))
|
|
|
|
for i in 1..<caseStmt.len:
|
|
var scope: ScopeBuilder
|
|
startSimpleBlock(p, scope)
|
|
let it = caseStmt[i]
|
|
for j in 0..<it.len-1:
|
|
if it[j].kind == nkRange:
|
|
localError(p.config, it.info, "range notation not available for computed goto")
|
|
return
|
|
|
|
let val = getOrdValue(it[j])
|
|
let lit = cIntLiteral(toInt64(val)+id+1)
|
|
p.s(cpsStmts).addLabel("TMP" & lit & "_")
|
|
|
|
genStmts(p, it.lastSon)
|
|
|
|
for j in casePos+1..<n.len:
|
|
genStmts(p, n[j])
|
|
|
|
for j in 0..<casePos:
|
|
# prevent new local declarations
|
|
# compile declarations as assignments
|
|
let it = n[j]
|
|
if it.kind in {nkLetSection, nkVarSection}:
|
|
let asgn = copyNode(it)
|
|
asgn.transitionSonsKind(nkAsgn)
|
|
asgn.sons.setLen 2
|
|
for sym, value in it.fieldValuePairs:
|
|
if value.kind != nkEmpty:
|
|
asgn[0] = sym
|
|
asgn[1] = value
|
|
genStmts(p, asgn)
|
|
else:
|
|
genStmts(p, it)
|
|
|
|
var a: TLoc = initLocExpr(p, caseStmt[0])
|
|
let ra = a.rdLoc
|
|
p.s(cpsStmts).addComputedGoto(subscript(tmp, ra))
|
|
endSimpleBlock(p, scope)
|
|
|
|
for j in casePos+1..<n.len:
|
|
genStmts(p, n[j])
|
|
|
|
|
|
proc genWhileStmt(p: BProc, t: PNode) =
|
|
# we don't generate labels here as for example GCC would produce
|
|
# significantly worse code
|
|
var
|
|
a: TLoc
|
|
assert(t.len == 2)
|
|
inc(p.withinLoop)
|
|
genLineDir(p, t)
|
|
|
|
preserveBreakIdx:
|
|
var loopBody = t[1]
|
|
if loopBody.stmtsContainPragma(wComputedGoto) and
|
|
hasComputedGoto in CC[p.config.cCompiler].props:
|
|
# for closure support weird loop bodies are generated:
|
|
if loopBody.len == 2 and loopBody[0].kind == nkEmpty:
|
|
loopBody = loopBody[1]
|
|
genComputedGoto(p, loopBody)
|
|
else:
|
|
var stmt: WhileBuilder
|
|
p.breakIdx = startBlockWith(p):
|
|
stmt = initWhileStmt(p.s(cpsStmts), cIntValue(1))
|
|
p.blocks[p.breakIdx].isLoop = true
|
|
a = initLocExpr(p, t[0])
|
|
if (t[0].kind != nkIntLit) or (t[0].intVal == 0):
|
|
let ra = a.rdLoc
|
|
var label: TLabel = ""
|
|
assignLabel(p.blocks[p.breakIdx], label)
|
|
p.s(cpsStmts).addSingleIfStmt(cOp(Not, ra)):
|
|
p.s(cpsStmts).addGoto(label)
|
|
genStmts(p, loopBody)
|
|
|
|
if optProfiler in p.options:
|
|
# invoke at loop body exit:
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimProfile"))
|
|
endBlockWith(p):
|
|
finishWhileStmt(p.s(cpsStmts), stmt)
|
|
|
|
dec(p.withinLoop)
|
|
|
|
proc genBlock(p: BProc, n: PNode, d: var TLoc) =
|
|
if not isEmptyType(n.typ):
|
|
# bug #4505: allocate the temp in the outer scope
|
|
# so that it can escape the generated {}:
|
|
if d.k == locNone:
|
|
d = getTemp(p, n.typ)
|
|
d.flags.incl(lfEnforceDeref)
|
|
preserveBreakIdx:
|
|
var scope: ScopeBuilder
|
|
p.breakIdx = startSimpleBlock(p, scope)
|
|
if n[0].kind != nkEmpty:
|
|
# named block?
|
|
assert(n[0].kind == nkSym)
|
|
var sym = n[0].sym
|
|
backendEnsureMutable sym
|
|
sym.locImpl.k = locOther
|
|
sym.positionImpl = p.breakIdx+1
|
|
# ^ IC: review this
|
|
expr(p, n[1], d)
|
|
endSimpleBlock(p, scope)
|
|
|
|
proc genParForStmt(p: BProc, t: PNode) =
|
|
assert(t.len == 3)
|
|
inc(p.withinLoop)
|
|
genLineDir(p, t)
|
|
|
|
preserveBreakIdx:
|
|
let forLoopVar = t[0].sym
|
|
assignLocalVar(p, t[0])
|
|
#initLoc(forLoopVar.loc, locLocalVar, forLoopVar.typ, onStack)
|
|
#discard mangleName(forLoopVar)
|
|
let call = t[1]
|
|
assert(call.len == 4 or call.len == 5)
|
|
var rangeA = initLocExpr(p, call[1])
|
|
var rangeB = initLocExpr(p, call[2])
|
|
|
|
var stepNode: PNode = nil
|
|
# $n at the beginning because of #9710
|
|
if call.len == 4: # procName(a, b, annotation)
|
|
if call[0].sym.name.s == "||": # `||`(a, b, annotation)
|
|
p.s(cpsStmts).addCPragma("omp " & call[3].getStr)
|
|
else:
|
|
p.s(cpsStmts).addCPragma(call[3].getStr)
|
|
else: # `||`(a, b, step, annotation)
|
|
stepNode = call[3]
|
|
p.s(cpsStmts).addCPragma("omp " & call[4].getStr)
|
|
|
|
p.breakIdx = startBlockWith(p):
|
|
if stepNode == nil:
|
|
initForRange(p.s(cpsStmts), forLoopVar.loc.rdLoc, rangeA.rdLoc, rangeB.rdLoc, true)
|
|
else:
|
|
var step: TLoc = initLocExpr(p, stepNode)
|
|
initForStep(p.s(cpsStmts), forLoopVar.loc.rdLoc, rangeA.rdLoc, rangeB.rdLoc, step.rdLoc, true)
|
|
p.blocks[p.breakIdx].isLoop = true
|
|
genStmts(p, t[2])
|
|
endBlockWith(p):
|
|
finishFor(p.s(cpsStmts))
|
|
|
|
dec(p.withinLoop)
|
|
|
|
proc genBreakStmt(p: BProc, t: PNode) =
|
|
var idx = p.breakIdx
|
|
if t[0].kind != nkEmpty:
|
|
# named break?
|
|
assert(t[0].kind == nkSym)
|
|
var sym = t[0].sym
|
|
doAssert(sym.loc.k == locOther)
|
|
idx = sym.position-1
|
|
else:
|
|
# an unnamed 'break' can only break a loop after 'transf' pass:
|
|
while idx >= 0 and not p.blocks[idx].isLoop: dec idx
|
|
if idx < 0 or not p.blocks[idx].isLoop:
|
|
internalError(p.config, t.info, "no loop to break")
|
|
p.blocks[idx].label = "LA" & p.blocks[idx].id.rope
|
|
blockLeaveActions(p,
|
|
p.nestedTryStmts.len - p.blocks[idx].nestedTryStmts,
|
|
p.inExceptBlockLen - p.blocks[idx].nestedExceptStmts)
|
|
genLineDir(p, t)
|
|
p.s(cpsStmts).addGoto(p.blocks[idx].label)
|
|
|
|
proc raiseExit(p: BProc) =
|
|
assert p.config.exc == excGoto
|
|
if nimErrorFlagDisabled notin p.flags:
|
|
p.flags.incl nimErrorFlagAccessed
|
|
p.s(cpsStmts).addSingleIfStmt(cUnlikely(cDeref("nimErr_"))):
|
|
if p.nestedTryStmts.len == 0:
|
|
p.flags.incl beforeRetNeeded
|
|
# easy case, simply goto 'ret':
|
|
p.s(cpsStmts).addGoto("BeforeRet_")
|
|
else:
|
|
p.s(cpsStmts).addGoto("LA" & $p.nestedTryStmts[^1].label & "_")
|
|
|
|
proc raiseExitCleanup(p: BProc, destroy: string) =
|
|
assert p.config.exc == excGoto
|
|
if nimErrorFlagDisabled notin p.flags:
|
|
p.flags.incl nimErrorFlagAccessed
|
|
p.s(cpsStmts).addSingleIfStmt(cUnlikely(cDeref("nimErr_"))):
|
|
p.s(cpsStmts).addStmt():
|
|
p.s(cpsStmts).add(destroy)
|
|
if p.nestedTryStmts.len == 0:
|
|
p.flags.incl beforeRetNeeded
|
|
# easy case, simply goto 'ret':
|
|
p.s(cpsStmts).addGoto("BeforeRet_")
|
|
else:
|
|
p.s(cpsStmts).addGoto("LA" & $p.nestedTryStmts[^1].label & "_")
|
|
|
|
proc finallyActions(p: BProc) =
|
|
if p.config.exc != excGoto:
|
|
# Walk past compiler-injected `nkHiddenTryStmt` wrappers (e.g. ARC's
|
|
# destructor try/finally that wraps `except T as e:` bodies) to reach
|
|
# the user's actual try. We must NOT walk past a real user try whose
|
|
# body we are currently in, because a raise from there will be caught
|
|
# by that try's own except branches rather than escaping outward.
|
|
#
|
|
# If after skipping wrappers the next entry is a user try in its
|
|
# except branch (inExcept=true), inline its finally body before the
|
|
# raise propagates — without this, the C++ sibling-catch rule would
|
|
# cause the user's catch(...)/finally pair to be bypassed and the
|
|
# finally would be silently dropped.
|
|
for i in countdown(p.nestedTryStmts.high, 0):
|
|
if p.nestedTryStmts[i].isHidden:
|
|
continue
|
|
if p.nestedTryStmts[i].inExcept:
|
|
let finallyBlock = p.nestedTryStmts[i].fin
|
|
if finallyBlock != nil:
|
|
genSimpleBlock(p, finallyBlock[0])
|
|
return
|
|
|
|
proc raiseInstr(p: BProc; result: var Builder) =
|
|
if p.config.exc == excGoto:
|
|
let L = p.nestedTryStmts.len
|
|
if L == 0:
|
|
p.flags.incl beforeRetNeeded
|
|
# easy case, simply goto 'ret':
|
|
result.addGoto("BeforeRet_")
|
|
else:
|
|
# raise inside an 'except' must go to the finally block,
|
|
# raise outside an 'except' block must go to the 'except' list.
|
|
result.addGoto("LA" & $p.nestedTryStmts[L-1].label & "_")
|
|
# + ord(p.nestedTryStmts[L-1].inExcept)])
|
|
|
|
proc genRaiseStmt(p: BProc, t: PNode) =
|
|
if t[0].kind != nkEmpty:
|
|
var a: TLoc = initLocExprSingleUse(p, t[0])
|
|
finallyActions(p)
|
|
var e = rdLoc(a)
|
|
discard getTypeDesc(p.module, t[0].typ)
|
|
var typ = skipTypes(t[0].typ, abstractPtrs)
|
|
case p.config.exc
|
|
of excCpp:
|
|
blockLeaveActions(p, howManyTrys = 0, howManyExcepts = p.inExceptBlockLen)
|
|
of excGoto:
|
|
blockLeaveActions(p, howManyTrys = 0,
|
|
howManyExcepts = (if p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept: 1 else: 0))
|
|
else:
|
|
discard
|
|
genLineDir(p, t)
|
|
if isImportedException(typ, p.config):
|
|
lineF(p, cpsStmts, "throw $1;$n", [e])
|
|
else:
|
|
let eName = makeCString(typ.sym.name.s)
|
|
let pName = makeCString(if p.prc != nil: p.prc.name.s else: p.module.module.name.s)
|
|
let fName = quotedFilename(p.config, t.info)
|
|
let ln = toLinenumber(t.info)
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseExceptionEx"),
|
|
cCast(ptrType(cgsymValue(p.module, "Exception")), e),
|
|
eName,
|
|
pName,
|
|
fName,
|
|
cIntValue(ln))
|
|
if optOwnedRefs in p.config.globalOptions:
|
|
p.s(cpsStmts).addAssignment(e, NimNil)
|
|
else:
|
|
finallyActions(p)
|
|
genLineDir(p, t)
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "reraiseException"))
|
|
raiseInstr(p, p.s(cpsStmts))
|
|
|
|
template genCaseGenericBranch(p: BProc, b: PNode, e: TLoc, labl: TLabel,
|
|
rangeFormat, eqFormat: untyped) =
|
|
var x, y: TLoc
|
|
for i in 0..<b.len - 1:
|
|
let rlabel {.inject.} = labl
|
|
if b[i].kind == nkRange:
|
|
x = initLocExpr(p, b[i][0])
|
|
y = initLocExpr(p, b[i][1])
|
|
let ra {.inject.} = rdCharLoc(e)
|
|
let rb {.inject.} = rdCharLoc(x)
|
|
let rc {.inject.} = rdCharLoc(y)
|
|
rangeFormat
|
|
else:
|
|
x = initLocExpr(p, b[i])
|
|
let ra {.inject.} = rdCharLoc(e)
|
|
let rb {.inject.} = rdCharLoc(x)
|
|
eqFormat
|
|
|
|
proc genCaseSecondPass(p: BProc, t: PNode, d: var TLoc,
|
|
labId, until: int): TLabel =
|
|
var lend = getLabel(p)
|
|
for i in 1..until:
|
|
# bug #4230: avoid false sharing between branches:
|
|
if d.k == locTemp and isEmptyType(t.typ): d.k = locNone
|
|
p.s(cpsStmts).addLabel("LA" & $(labId + i) & "_")
|
|
if t[i].kind == nkOfBranch:
|
|
exprBlock(p, t[i][^1], d)
|
|
p.s(cpsStmts).addGoto(lend)
|
|
else:
|
|
exprBlock(p, t[i][0], d)
|
|
result = lend
|
|
|
|
template genIfForCaseUntil(p: BProc, t: PNode, d: var TLoc,
|
|
until: int, a: TLoc,
|
|
rangeFormat, eqFormat: untyped): TLabel =
|
|
# generate a C-if statement for a Nim case statement
|
|
var res: TLabel
|
|
var labId = p.labels
|
|
for i in 1..until:
|
|
inc(p.labels)
|
|
let lab = "LA" & $p.labels & "_"
|
|
if t[i].kind == nkOfBranch: # else statement
|
|
genCaseGenericBranch(p, t[i], a, lab, rangeFormat, eqFormat)
|
|
else:
|
|
p.s(cpsStmts).addGoto(lab)
|
|
if until < t.len-1:
|
|
inc(p.labels)
|
|
var gotoTarget = "LA" & $p.labels & "_"
|
|
p.s(cpsStmts).addGoto(gotoTarget)
|
|
res = genCaseSecondPass(p, t, d, labId, until)
|
|
p.s(cpsStmts).addLabel(gotoTarget)
|
|
else:
|
|
res = genCaseSecondPass(p, t, d, labId, until)
|
|
res
|
|
|
|
template genCaseGeneric(p: BProc, t: PNode, d: var TLoc,
|
|
rangeFormat, eqFormat: untyped) =
|
|
var a: TLoc = initLocExpr(p, t[0])
|
|
var lend = genIfForCaseUntil(p, t, d, t.len-1, a, rangeFormat, eqFormat)
|
|
fixLabel(p, lend)
|
|
|
|
proc genCaseStringBranch(p: BProc, b: PNode, e: TLoc, labl: TLabel,
|
|
stringKind: TTypeKind,
|
|
branches: var openArray[Builder]) =
|
|
var x: TLoc
|
|
for i in 0..<b.len - 1:
|
|
assert(b[i].kind != nkRange)
|
|
x = initLocExpr(p, b[i])
|
|
var j: int = 0
|
|
case b[i].kind
|
|
of nkStrLit..nkTripleStrLit:
|
|
j = int(hashString(p.config, b[i].strVal) and high(branches))
|
|
of nkNilLit: j = 0
|
|
else:
|
|
assert false, "invalid string case branch node kind"
|
|
let re = rdLoc(e)
|
|
let rx = rdLoc(x)
|
|
branches[j].addSingleIfStmtWithCond():
|
|
let eqName = if stringKind == tyCstring: "eqCstrings" else: "eqStrings"
|
|
branches[j].addCall(cgsymValue(p.module, eqName), re, rx)
|
|
do:
|
|
branches[j].addGoto(labl)
|
|
|
|
proc genStringCase(p: BProc, t: PNode, stringKind: TTypeKind, d: var TLoc) =
|
|
# count how many constant strings there are in the case:
|
|
var strings = 0
|
|
for i in 1..<t.len:
|
|
if t[i].kind == nkOfBranch: inc(strings, t[i].len - 1)
|
|
if strings > stringCaseThreshold:
|
|
var bitMask = math.nextPowerOfTwo(strings) - 1
|
|
var branches: seq[Builder]
|
|
newSeq(branches, bitMask + 1)
|
|
var a: TLoc = initLocExpr(p, t[0]) # first pass: generate ifs+goto:
|
|
var labId = p.labels
|
|
for i in 1..<t.len:
|
|
inc(p.labels)
|
|
if t[i].kind == nkOfBranch:
|
|
genCaseStringBranch(p, t[i], a, "LA" & rope(p.labels) & "_",
|
|
stringKind, branches)
|
|
else:
|
|
# else statement: nothing to do yet
|
|
# but we reserved a label, which we use later
|
|
discard
|
|
let fnName = if stringKind == tyCstring: "hashCstring" else: "hashString"
|
|
let ra = rdLoc(a)
|
|
p.s(cpsStmts).addSwitchStmt(
|
|
cOp(BitAnd, NimInt,
|
|
cCall(cgsymValue(p.module, fnName), ra),
|
|
cIntValue(bitMask))):
|
|
for j in 0..high(branches):
|
|
if branches[j].buf.len != 0:
|
|
let lit = cIntLiteral(j)
|
|
p.s(cpsStmts).addSingleSwitchCase(lit):
|
|
p.s(cpsStmts).add(extract(branches[j]))
|
|
p.s(cpsStmts).addBreak()
|
|
# else statement:
|
|
if t[^1].kind != nkOfBranch:
|
|
p.s(cpsStmts).addGoto("LA" & rope(p.labels) & "_")
|
|
# third pass: generate statements
|
|
var lend = genCaseSecondPass(p, t, d, labId, t.len-1)
|
|
fixLabel(p, lend)
|
|
else:
|
|
let eqFn = cgsymValue(p.module,
|
|
if stringKind == tyCstring: "eqCstrings"
|
|
else: "eqStrings")
|
|
genCaseGeneric(p, t, d):
|
|
discard
|
|
do:
|
|
p.s(cpsStmts).addSingleIfStmt(
|
|
cCall(eqFn, ra, rb)):
|
|
p.s(cpsStmts).addGoto(rlabel)
|
|
|
|
proc branchHasTooBigRange(b: PNode): bool =
|
|
result = false
|
|
for it in b:
|
|
# last son is block
|
|
if (it.kind == nkRange) and
|
|
it[1].intVal - it[0].intVal > RangeExpandLimit:
|
|
return true
|
|
|
|
proc ifSwitchSplitPoint(p: BProc, n: PNode): int =
|
|
result = 0
|
|
for i in 1..<n.len:
|
|
var branch = n[i]
|
|
var stmtBlock = lastSon(branch)
|
|
if stmtBlock.stmtsContainPragma(wLinearScanEnd):
|
|
result = i
|
|
elif hasSwitchRange notin CC[p.config.cCompiler].props:
|
|
if branch.kind == nkOfBranch and branchHasTooBigRange(branch):
|
|
result = i
|
|
|
|
proc genCaseRange(p: BProc, branch: PNode, info: var SwitchCaseBuilder) =
|
|
for j in 0..<branch.len-1:
|
|
if branch[j].kind == nkRange:
|
|
if hasSwitchRange in CC[p.config.cCompiler].props:
|
|
var litA = newBuilder("")
|
|
var litB = newBuilder("")
|
|
genLiteral(p, branch[j][0], litA)
|
|
genLiteral(p, branch[j][1], litB)
|
|
p.s(cpsStmts).addCaseRange(info, extract(litA), extract(litB))
|
|
else:
|
|
var v = copyNode(branch[j][0])
|
|
while v.intVal <= branch[j][1].intVal:
|
|
var litA = newBuilder("")
|
|
genLiteral(p, v, litA)
|
|
p.s(cpsStmts).addCase(info, extract(litA))
|
|
inc(v.intVal)
|
|
else:
|
|
var litA = newBuilder("")
|
|
genLiteral(p, branch[j], litA)
|
|
p.s(cpsStmts).addCase(info, extract(litA))
|
|
|
|
proc genOrdinalCase(p: BProc, n: PNode, d: var TLoc) =
|
|
# analyse 'case' statement:
|
|
var splitPoint = ifSwitchSplitPoint(p, n)
|
|
|
|
# generate if part (might be empty):
|
|
var a: TLoc = initLocExpr(p, n[0])
|
|
var lend: TLabel = ""
|
|
if splitPoint > 0:
|
|
lend = genIfForCaseUntil(p, n, d, splitPoint, a):
|
|
p.s(cpsStmts).addSingleIfStmt(cOp(And,
|
|
cOp(GreaterEqual, ra, rb),
|
|
cOp(LessEqual, ra, rc))):
|
|
p.s(cpsStmts).addGoto(rlabel)
|
|
do:
|
|
p.s(cpsStmts).addSingleIfStmt(
|
|
removeSinglePar(cOp(Equal, ra, rb))):
|
|
p.s(cpsStmts).addGoto(rlabel)
|
|
|
|
# generate switch part (might be empty):
|
|
if splitPoint+1 < n.len:
|
|
let rca = rdCharLoc(a)
|
|
p.s(cpsStmts).addSwitchStmt(rca):
|
|
var hasDefault = false
|
|
for i in splitPoint+1..<n.len:
|
|
# bug #4230: avoid false sharing between branches:
|
|
if d.k == locTemp and isEmptyType(n.typ): d.k = locNone
|
|
var branch = n[i]
|
|
var caseBuilder: SwitchCaseBuilder
|
|
p.s(cpsStmts).addSwitchCase(caseBuilder):
|
|
if branch.kind == nkOfBranch:
|
|
genCaseRange(p, branch, caseBuilder)
|
|
else:
|
|
# else part of case statement:
|
|
hasDefault = true
|
|
p.s(cpsStmts).addCaseElse(caseBuilder)
|
|
do:
|
|
exprBlock(p, branch.lastSon, d)
|
|
p.s(cpsStmts).addBreak()
|
|
if not hasDefault:
|
|
if hasBuiltinUnreachable in CC[p.config.cCompiler].props:
|
|
p.s(cpsStmts).addSwitchElse():
|
|
p.s(cpsStmts).addCallStmt("__builtin_unreachable")
|
|
elif hasAssume in CC[p.config.cCompiler].props:
|
|
p.s(cpsStmts).addSwitchElse():
|
|
p.s(cpsStmts).addCallStmt("__assume", cIntValue(0))
|
|
if lend != "": fixLabel(p, lend)
|
|
|
|
proc genCase(p: BProc, t: PNode, d: var TLoc) =
|
|
genLineDir(p, t)
|
|
if not isEmptyType(t.typ) and d.k == locNone:
|
|
d = getTemp(p, t.typ)
|
|
case skipTypes(t[0].typ, abstractVarRange).kind
|
|
of tyString:
|
|
genStringCase(p, t, tyString, d)
|
|
of tyCstring:
|
|
genStringCase(p, t, tyCstring, d)
|
|
of tyFloat..tyFloat128:
|
|
genCaseGeneric(p, t, d):
|
|
p.s(cpsStmts).addSingleIfStmt(cOp(And,
|
|
cOp(GreaterEqual, ra, rb),
|
|
cOp(LessEqual, ra, rc))):
|
|
p.s(cpsStmts).addGoto(rlabel)
|
|
do:
|
|
p.s(cpsStmts).addSingleIfStmt(
|
|
removeSinglePar(cOp(Equal, ra, rb))):
|
|
p.s(cpsStmts).addGoto(rlabel)
|
|
else:
|
|
if t[0].kind == nkSym and sfGoto in t[0].sym.flags:
|
|
genGotoForCase(p, t)
|
|
else:
|
|
genOrdinalCase(p, t, d)
|
|
|
|
proc genRestoreFrameAfterException(p: BProc) =
|
|
if optStackTrace in p.module.config.options:
|
|
if hasCurFramePointer notin p.flags:
|
|
p.flags.incl hasCurFramePointer
|
|
p.procSec(cpsLocals).add('\t')
|
|
p.procSec(cpsLocals).addVar(kind = Local, name = "_nimCurFrame", typ = ptrType("TFrame"))
|
|
p.procSec(cpsInit).add('\t')
|
|
p.procSec(cpsInit).addAssignmentWithValue("_nimCurFrame"):
|
|
p.procSec(cpsInit).addCall(cgsymValue(p.module, "getFrame"))
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "setFrame"), "_nimCurFrame")
|
|
|
|
proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
|
|
#[ code to generate:
|
|
|
|
std::exception_ptr error;
|
|
try {
|
|
body;
|
|
} catch (Exception e) {
|
|
error = std::current_exception();
|
|
if (ofExpr(e, TypeHere)) {
|
|
|
|
error = nullptr; // handled
|
|
} else if (...) {
|
|
|
|
} else {
|
|
throw;
|
|
}
|
|
} catch(...) {
|
|
// C++ exception occurred, not under Nim's control.
|
|
}
|
|
{
|
|
/* finally: */
|
|
printf('fin!\n');
|
|
if (error) std::rethrow_exception(error); // re-raise the exception
|
|
}
|
|
]#
|
|
p.module.includeHeader("<exception>")
|
|
|
|
if not isEmptyType(t.typ) and d.k == locNone:
|
|
d = getTemp(p, t.typ)
|
|
genLineDir(p, t)
|
|
|
|
inc(p.labels, 2)
|
|
let etmp = p.labels
|
|
#init on locals, fixes #23306
|
|
lineCg(p, cpsLocals, "std::exception_ptr T$1_;$n", [etmp])
|
|
|
|
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
|
|
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural))
|
|
|
|
if t.kind == nkHiddenTryStmt:
|
|
lineCg(p, cpsStmts, "try {$n", [])
|
|
expr(p, t[0], d)
|
|
lineCg(p, cpsStmts, "}$n", [])
|
|
else:
|
|
startBlockWith(p):
|
|
p.s(cpsStmts).add("try {\n")
|
|
expr(p, t[0], d)
|
|
endBlockWith(p):
|
|
p.s(cpsStmts).add("}\n")
|
|
|
|
# First pass: handle Nim based exceptions:
|
|
lineCg(p, cpsStmts, "catch (#Exception* T$1_) {$n", [etmp+1])
|
|
genRestoreFrameAfterException(p)
|
|
# an unhandled exception happened!
|
|
lineCg(p, cpsStmts, "T$1_ = std::current_exception();$n", [etmp])
|
|
p.nestedTryStmts[^1].inExcept = true
|
|
var hasImportedCppExceptions = false
|
|
var i = 1
|
|
var ifStmt = default(IfBuilder)
|
|
var hasIf = false
|
|
var hasElse = false
|
|
while (i < t.len) and (t[i].kind == nkExceptBranch):
|
|
# bug #4230: avoid false sharing between branches:
|
|
if d.k == locTemp and isEmptyType(t.typ): d.k = locNone
|
|
if t[i].len == 1:
|
|
hasImportedCppExceptions = true
|
|
hasElse = true
|
|
# general except section:
|
|
var scope = default(ScopeBuilder)
|
|
startBlockWith(p):
|
|
if hasIf:
|
|
initElseBranch(p.s(cpsStmts), ifStmt)
|
|
else:
|
|
scope = initScope(p.s(cpsStmts))
|
|
# we handled the error:
|
|
expr(p, t[i][0], d)
|
|
linefmt(p, cpsStmts, "#popCurrentException();$n", [])
|
|
endBlockWith(p):
|
|
if hasIf:
|
|
finishBranch(p.s(cpsStmts), ifStmt)
|
|
else:
|
|
finishScope(p.s(cpsStmts), scope)
|
|
else:
|
|
var orExpr = newRopeAppender()
|
|
var exvar = PNode(nil)
|
|
for j in 0..<t[i].len - 1:
|
|
var typeNode = t[i][j]
|
|
if t[i][j].isInfixAs():
|
|
typeNode = t[i][j][1]
|
|
exvar = t[i][j][2] # ex1 in `except ExceptType as ex1:`
|
|
assert(typeNode.kind == nkType)
|
|
if isImportedException(typeNode.typ, p.config):
|
|
hasImportedCppExceptions = true
|
|
else:
|
|
if orExpr.len != 0: orExpr.add("||")
|
|
let memberName = if p.module.compileToCpp: "m_type" else: "Sup.m_type"
|
|
if optTinyRtti in p.config.globalOptions:
|
|
let checkFor = $getObjDepth(typeNode.typ)
|
|
appcg(p.module, orExpr, "#isObjDisplayCheck(#nimBorrowCurrentException()->$1, $2, $3)", [memberName, checkFor, $genDisplayElem(MD5Digest(hashType(typeNode.typ, p.config)))])
|
|
else:
|
|
let checkFor = genTypeInfoV1(p.module, typeNode.typ, typeNode.info)
|
|
appcg(p.module, orExpr, "#isObj(#nimBorrowCurrentException()->$1, $2)", [memberName, checkFor])
|
|
|
|
if orExpr.len != 0:
|
|
if not hasIf:
|
|
hasIf = true
|
|
ifStmt = initIfStmt(p.s(cpsStmts))
|
|
startBlockWith(p):
|
|
initElifBranch(p.s(cpsStmts), ifStmt, orExpr)
|
|
if exvar != nil:
|
|
fillLocalName(p, exvar.sym)
|
|
backendEnsureMutable exvar.sym
|
|
fillLoc(exvar.sym.locImpl, locTemp, exvar, OnStack)
|
|
linefmt(p, cpsStmts, "$1 $2 = T$3_;$n", [getTypeDesc(p.module, exvar.sym.typ),
|
|
rdLoc(exvar.sym.loc), rope(etmp+1)])
|
|
# we handled the error:
|
|
linefmt(p, cpsStmts, "T$1_ = nullptr;$n", [etmp])
|
|
expr(p, t[i][^1], d)
|
|
linefmt(p, cpsStmts, "#popCurrentException();$n", [])
|
|
endBlockWith(p):
|
|
finishBranch(p.s(cpsStmts), ifStmt)
|
|
inc(i)
|
|
if hasIf and not hasElse:
|
|
p.s(cpsStmts).addElseBranch(ifStmt):
|
|
p.s(cpsStmts).add("throw;\n")
|
|
if hasIf:
|
|
finishIfStmt(p.s(cpsStmts), ifStmt)
|
|
linefmt(p, cpsStmts, "}$n", [])
|
|
|
|
# Second pass: handle C++ based exceptions:
|
|
template genExceptBranchBody(body: PNode) {.dirty.} =
|
|
genRestoreFrameAfterException(p)
|
|
#linefmt(p, cpsStmts, "T$1_ = std::current_exception();$n", [etmp])
|
|
expr(p, body, d)
|
|
|
|
var catchAllPresent = false
|
|
incl p.flags, noSafePoints # mark as not needing 'popCurrentException'
|
|
if hasImportedCppExceptions:
|
|
for i in 1..<t.len:
|
|
if t[i].kind != nkExceptBranch: break
|
|
|
|
# bug #4230: avoid false sharing between branches:
|
|
if d.k == locTemp and isEmptyType(t.typ): d.k = locNone
|
|
|
|
if t[i].len == 1:
|
|
# general except section:
|
|
startBlockWith(p):
|
|
p.s(cpsStmts).add("catch (...) {\n")
|
|
genExceptBranchBody(t[i][0])
|
|
endBlockWith(p):
|
|
p.s(cpsStmts).add("}\n")
|
|
catchAllPresent = true
|
|
else:
|
|
for j in 0..<t[i].len-1:
|
|
var typeNode = t[i][j]
|
|
if t[i][j].isInfixAs():
|
|
typeNode = t[i][j][1]
|
|
if isImportedException(typeNode.typ, p.config):
|
|
let exvar = t[i][j][2] # ex1 in `except ExceptType as ex1:`
|
|
fillLocalName(p, exvar.sym)
|
|
backendEnsureMutable exvar.sym
|
|
fillLoc(exvar.sym.locImpl, locTemp, exvar, OnStack)
|
|
startBlockWith(p):
|
|
lineCg(p, cpsStmts, "catch ($1& $2) {$n", [getTypeDesc(p.module, typeNode.typ), rdLoc(exvar.sym.loc)])
|
|
genExceptBranchBody(t[i][^1]) # exception handler body will duplicated for every type
|
|
endBlockWith(p):
|
|
p.s(cpsStmts).add("}\n")
|
|
elif isImportedException(typeNode.typ, p.config):
|
|
startBlockWith(p):
|
|
lineCg(p, cpsStmts, "catch ($1&) {$n", [getTypeDesc(p.module, t[i][j].typ)])
|
|
genExceptBranchBody(t[i][^1]) # exception handler body will duplicated for every type
|
|
endBlockWith(p):
|
|
p.s(cpsStmts).add("}\n")
|
|
|
|
excl p.flags, noSafePoints
|
|
discard pop(p.nestedTryStmts)
|
|
# general finally block:
|
|
if t.len > 0 and t[^1].kind == nkFinally:
|
|
if not catchAllPresent:
|
|
startBlockWith(p):
|
|
p.s(cpsStmts).add("catch (...) {\n")
|
|
genRestoreFrameAfterException(p)
|
|
linefmt(p, cpsStmts, "T$1_ = std::current_exception();$n", [etmp])
|
|
endBlockWith(p):
|
|
p.s(cpsStmts).add("}\n")
|
|
|
|
var scope: ScopeBuilder
|
|
startSimpleBlock(p, scope)
|
|
genStmts(p, t[^1][0])
|
|
linefmt(p, cpsStmts, "if (T$1_) std::rethrow_exception(T$1_);$n", [etmp])
|
|
endSimpleBlock(p, scope)
|
|
|
|
proc genTryCppOld(p: BProc, t: PNode, d: var TLoc) =
|
|
# There are two versions we generate, depending on whether we
|
|
# catch C++ exceptions, imported via .importcpp or not. The
|
|
# code can be easier if there are no imported C++ exceptions
|
|
# to deal with.
|
|
|
|
# code to generate:
|
|
#
|
|
# try
|
|
# {
|
|
# myDiv(4, 9);
|
|
# } catch (NimExceptionType1&) {
|
|
# body
|
|
# } catch (NimExceptionType2&) {
|
|
# finallyPart()
|
|
# raise;
|
|
# }
|
|
# catch(...) {
|
|
# general_handler_body
|
|
# }
|
|
# finallyPart();
|
|
|
|
template genExceptBranchBody(body: PNode) {.dirty.} =
|
|
genRestoreFrameAfterException(p)
|
|
expr(p, body, d)
|
|
|
|
if not isEmptyType(t.typ) and d.k == locNone:
|
|
d = getTemp(p, t.typ)
|
|
genLineDir(p, t)
|
|
cgsym(p.module, "popCurrentExceptionEx")
|
|
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
|
|
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural))
|
|
startBlockWith(p):
|
|
p.s(cpsStmts).add("try {\n")
|
|
expr(p, t[0], d)
|
|
endBlockWith(p):
|
|
p.s(cpsStmts).add("}\n")
|
|
|
|
var catchAllPresent = false
|
|
|
|
p.nestedTryStmts[^1].inExcept = true
|
|
for i in 1..<t.len:
|
|
if t[i].kind != nkExceptBranch: break
|
|
|
|
# bug #4230: avoid false sharing between branches:
|
|
if d.k == locTemp and isEmptyType(t.typ): d.k = locNone
|
|
|
|
if t[i].len == 1:
|
|
# general except section:
|
|
catchAllPresent = true
|
|
startBlockWith(p):
|
|
p.s(cpsStmts).add("catch (...) {\n")
|
|
genExceptBranchBody(t[i][0])
|
|
endBlockWith(p):
|
|
p.s(cpsStmts).add("}\n")
|
|
else:
|
|
for j in 0..<t[i].len-1:
|
|
if t[i][j].isInfixAs():
|
|
let exvar = t[i][j][2] # ex1 in `except ExceptType as ex1:`
|
|
fillLocalName(p, exvar.sym)
|
|
backendEnsureMutable exvar.sym
|
|
fillLoc(exvar.sym.locImpl, locTemp, exvar, OnUnknown)
|
|
startBlockWith(p):
|
|
lineCg(p, cpsStmts, "catch ($1& $2) {$n", [getTypeDesc(p.module, t[i][j][1].typ), rdLoc(exvar.sym.loc)])
|
|
else:
|
|
startBlockWith(p):
|
|
lineCg(p, cpsStmts, "catch ($1&) {$n", [getTypeDesc(p.module, t[i][j].typ)])
|
|
genExceptBranchBody(t[i][^1]) # exception handler body will duplicated for every type
|
|
endBlockWith(p):
|
|
p.s(cpsStmts).add("}\n")
|
|
|
|
discard pop(p.nestedTryStmts)
|
|
|
|
if t[^1].kind == nkFinally:
|
|
# c++ does not have finally, therefore code needs to be generated twice
|
|
if not catchAllPresent:
|
|
# finally requires catch all presence
|
|
startBlockWith(p):
|
|
p.s(cpsStmts).add("catch (...) {\n")
|
|
genStmts(p, t[^1][0])
|
|
line(p, cpsStmts, "throw;\n")
|
|
endBlockWith(p):
|
|
p.s(cpsStmts).add("}\n")
|
|
|
|
genSimpleBlock(p, t[^1][0])
|
|
|
|
proc bodyCanRaise(p: BProc; n: PNode): bool =
|
|
case n.kind
|
|
of nkCallKinds:
|
|
result = canRaiseDisp(p, n[0])
|
|
if not result:
|
|
# also check the arguments:
|
|
for i in 1 ..< n.len:
|
|
if bodyCanRaise(p, n[i]): return true
|
|
of nkRaiseStmt:
|
|
result = true
|
|
of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef,
|
|
nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
|
|
result = false
|
|
else:
|
|
for i in 0 ..< safeLen(n):
|
|
if bodyCanRaise(p, n[i]): return true
|
|
result = false
|
|
|
|
proc genTryGoto(p: BProc; t: PNode; d: var TLoc) =
|
|
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
|
|
inc p.labels
|
|
let lab = p.labels
|
|
let hasExcept = t[1].kind == nkExceptBranch
|
|
if hasExcept: inc p.withinTryWithExcept
|
|
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, Natural lab))
|
|
|
|
p.flags.incl nimErrorFlagAccessed
|
|
|
|
if not isEmptyType(t.typ) and d.k == locNone:
|
|
d = getTemp(p, t.typ)
|
|
|
|
expr(p, t[0], d)
|
|
|
|
var ifStmt = default(IfBuilder)
|
|
var scope = default(ScopeBuilder)
|
|
var isIf = false
|
|
if 1 < t.len and t[1].kind == nkExceptBranch:
|
|
startBlockWith(p):
|
|
isIf = true
|
|
ifStmt = initIfStmt(p.s(cpsStmts))
|
|
initElifBranch(p.s(cpsStmts), ifStmt, cUnlikely(cDeref("nimErr_")))
|
|
else:
|
|
startBlockWith(p):
|
|
scope = initScope(p.s(cpsStmts))
|
|
p.s(cpsStmts).addLabel("LA" & $lab & "_")
|
|
|
|
p.nestedTryStmts[^1].inExcept = true
|
|
var i = 1
|
|
var innerIfStmt = default(IfBuilder)
|
|
var innerScope = default(ScopeBuilder)
|
|
var innerIsIf = false
|
|
while (i < t.len) and (t[i].kind == nkExceptBranch):
|
|
|
|
inc p.labels
|
|
let nextExcept = p.labels
|
|
p.nestedTryStmts[^1].label = nextExcept
|
|
|
|
var isScope = false
|
|
# bug #4230: avoid false sharing between branches:
|
|
if d.k == locTemp and isEmptyType(t.typ): d.k = locNone
|
|
if t[i].len == 1:
|
|
# general except section:
|
|
startBlockWith(p):
|
|
if innerIsIf:
|
|
initElseBranch(p.s(cpsStmts), innerIfStmt)
|
|
else:
|
|
isScope = true
|
|
innerScope = initScope(p.s(cpsStmts))
|
|
# we handled the exception, remember this:
|
|
p.s(cpsStmts).addAssignment(cDeref("nimErr_"), NimFalse)
|
|
expr(p, t[i][0], d)
|
|
else:
|
|
if not innerIsIf:
|
|
innerIsIf = true
|
|
innerIfStmt = initIfStmt(p.s(cpsStmts))
|
|
var orExpr: Snippet = ""
|
|
for j in 0..<t[i].len - 1:
|
|
assert(t[i][j].kind == nkType)
|
|
var excVal = cCall(cgsymValue(p.module, "nimBorrowCurrentException"))
|
|
let member =
|
|
if p.module.compileToCpp:
|
|
derefField(excVal, "m_type")
|
|
else:
|
|
dotField(derefField(excVal, "Sup"), "m_type")
|
|
var branch: Snippet = ""
|
|
if optTinyRtti in p.config.globalOptions:
|
|
let checkFor = $getObjDepth(t[i][j].typ)
|
|
branch = cCall(cgsymValue(p.module, "isObjDisplayCheck"),
|
|
member,
|
|
checkFor,
|
|
$genDisplayElem(MD5Digest(hashType(t[i][j].typ, p.config))))
|
|
else:
|
|
let checkFor = genTypeInfoV1(p.module, t[i][j].typ, t[i][j].info)
|
|
branch = cCall(cgsymValue(p.module, "isObj"),
|
|
member,
|
|
checkFor)
|
|
if orExpr.len == 0:
|
|
orExpr = branch
|
|
else:
|
|
orExpr = cOp(Or, orExpr, branch)
|
|
|
|
startBlockWith(p):
|
|
initElifBranch(p.s(cpsStmts), innerIfStmt, orExpr)
|
|
# we handled the exception, remember this:
|
|
p.s(cpsStmts).addAssignment(cDeref("nimErr_"), NimFalse)
|
|
expr(p, t[i][^1], d)
|
|
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "popCurrentException"))
|
|
p.s(cpsStmts).addLabel("LA" & $nextExcept & "_")
|
|
endBlockWith(p):
|
|
if isScope:
|
|
finishScope(p.s(cpsStmts), innerScope)
|
|
else:
|
|
finishBranch(p.s(cpsStmts), innerIfStmt)
|
|
|
|
inc(i)
|
|
if innerIsIf:
|
|
finishIfStmt(p.s(cpsStmts), innerIfStmt)
|
|
discard pop(p.nestedTryStmts)
|
|
endBlockWith(p):
|
|
if isIf:
|
|
finishBranch(p.s(cpsStmts), ifStmt)
|
|
finishIfStmt(p.s(cpsStmts), ifStmt)
|
|
else:
|
|
finishScope(p.s(cpsStmts), scope)
|
|
|
|
if i < t.len and t[i].kind == nkFinally:
|
|
var finallyScope: ScopeBuilder
|
|
startSimpleBlock(p, finallyScope)
|
|
if not bodyCanRaise(p, t[i][0]):
|
|
# this is an important optimization; most destroy blocks are detected not to raise an
|
|
# exception and so we help the C optimizer by not mutating nimErr_ pointlessly:
|
|
genStmts(p, t[i][0])
|
|
else:
|
|
# pretend we did handle the error for the safe execution of the 'finally' section:
|
|
p.procSec(cpsLocals).addVar(kind = Local, name = "oldNimErrFin" & $lab & "_", typ = NimBool)
|
|
p.s(cpsStmts).addAssignment("oldNimErrFin" & $lab & "_", cDeref("nimErr_"))
|
|
p.s(cpsStmts).addAssignment(cDeref("nimErr_"), NimFalse)
|
|
genStmts(p, t[i][0])
|
|
# this is correct for all these cases:
|
|
# 1. finally is run during ordinary control flow
|
|
# 2. finally is run after 'except' block handling: these however set the
|
|
# error back to nil.
|
|
# 3. finally is run for exception handling code without any 'except'
|
|
# handler present or only handlers that did not match.
|
|
p.s(cpsStmts).addAssignment(cDeref("nimErr_"), "oldNimErrFin" & $lab & "_")
|
|
endSimpleBlock(p, finallyScope)
|
|
raiseExit(p)
|
|
if hasExcept: inc p.withinTryWithExcept
|
|
|
|
proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) =
|
|
# code to generate:
|
|
#
|
|
# XXX: There should be a standard dispatch algorithm
|
|
# that's used both here and with multi-methods
|
|
#
|
|
# TSafePoint sp;
|
|
# pushSafePoint(&sp);
|
|
# sp.status = setjmp(sp.context);
|
|
# if (sp.status == 0) {
|
|
# myDiv(4, 9);
|
|
# popSafePoint();
|
|
# } else {
|
|
# popSafePoint();
|
|
# /* except DivisionByZero: */
|
|
# if (sp.status == DivisionByZero) {
|
|
# printf('Division by Zero\n');
|
|
# clearException();
|
|
# } else {
|
|
# clearException();
|
|
# }
|
|
# }
|
|
# {
|
|
# /* finally: */
|
|
# printf('fin!\n');
|
|
# }
|
|
# if (exception not cleared)
|
|
# propagateCurrentException();
|
|
#
|
|
if not isEmptyType(t.typ) and d.k == locNone:
|
|
d = getTemp(p, t.typ)
|
|
let quirkyExceptions = p.config.exc == excQuirky or
|
|
(t.kind == nkHiddenTryStmt and sfSystemModule in p.module.module.flags)
|
|
if not quirkyExceptions:
|
|
p.module.includeHeader("<setjmp.h>")
|
|
else:
|
|
p.flags.incl noSafePoints
|
|
genLineDir(p, t)
|
|
cgsym(p.module, "Exception")
|
|
var safePoint: Rope = ""
|
|
var nonQuirkyIf = default(IfBuilder)
|
|
if not quirkyExceptions:
|
|
safePoint = getTempName(p.module)
|
|
p.s(cpsLocals).addVar(name = safePoint, typ = cgsymValue(p.module, "TSafePoint"))
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "pushSafePoint"), cAddr(safePoint))
|
|
if isDefined(p.config, "nimStdSetjmp"):
|
|
p.s(cpsStmts).addFieldAssignmentWithValue(safePoint, "status"):
|
|
p.s(cpsStmts).addCall("setjmp", dotField(safePoint, "context"))
|
|
elif isDefined(p.config, "nimSigSetjmp"):
|
|
p.s(cpsStmts).addFieldAssignmentWithValue(safePoint, "status"):
|
|
p.s(cpsStmts).addCall("sigsetjmp", dotField(safePoint, "context"), cIntValue(0))
|
|
elif isDefined(p.config, "nimBuiltinSetjmp"):
|
|
p.s(cpsStmts).addFieldAssignmentWithValue(safePoint, "status"):
|
|
p.s(cpsStmts).addCall("__builtin_setjmp", dotField(safePoint, "context"))
|
|
elif isDefined(p.config, "nimRawSetjmp"):
|
|
if isDefined(p.config, "mswindows"):
|
|
if isDefined(p.config, "vcc") or isDefined(p.config, "clangcl"):
|
|
# For the vcc compiler, use `setjmp()` with one argument.
|
|
# See https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setjmp?view=msvc-170
|
|
p.s(cpsStmts).addFieldAssignmentWithValue(safePoint, "status"):
|
|
p.s(cpsStmts).addCall("setjmp", dotField(safePoint, "context"))
|
|
else:
|
|
# The Windows `_setjmp()` takes two arguments, with the second being an
|
|
# undocumented buffer used by the SEH mechanism for stack unwinding.
|
|
# Mingw-w64 has been trying to get it right for years, but it's still
|
|
# prone to stack corruption during unwinding, so we disable that by setting
|
|
# it to NULL.
|
|
# More details: https://github.com/status-im/nimbus-eth2/issues/3121
|
|
p.s(cpsStmts).addFieldAssignmentWithValue(safePoint, "status"):
|
|
p.s(cpsStmts).addCall("_setjmp", dotField(safePoint, "context"), cIntValue(0))
|
|
else:
|
|
p.s(cpsStmts).addFieldAssignmentWithValue(safePoint, "status"):
|
|
p.s(cpsStmts).addCall("_setjmp", dotField(safePoint, "context"))
|
|
else:
|
|
p.s(cpsStmts).addFieldAssignmentWithValue(safePoint, "status"):
|
|
p.s(cpsStmts).addCall("setjmp", dotField(safePoint, "context"))
|
|
nonQuirkyIf = initIfStmt(p.s(cpsStmts))
|
|
initElifBranch(p.s(cpsStmts), nonQuirkyIf, removeSinglePar(
|
|
cOp(Equal, dotField(safePoint, "status"), cIntValue(0))))
|
|
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
|
|
p.nestedTryStmts.add((fin, quirkyExceptions, t.kind == nkHiddenTryStmt, 0.Natural))
|
|
expr(p, t[0], d)
|
|
var quirkyIf = default(IfBuilder)
|
|
var quirkyScope = default(ScopeBuilder)
|
|
var isScope = false
|
|
if not quirkyExceptions:
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "popSafePoint"))
|
|
finishBranch(p.s(cpsStmts), nonQuirkyIf)
|
|
startBlockWith(p):
|
|
initElseBranch(p.s(cpsStmts), nonQuirkyIf)
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "popSafePoint"))
|
|
genRestoreFrameAfterException(p)
|
|
elif 1 < t.len and t[1].kind == nkExceptBranch:
|
|
startBlockWith(p):
|
|
quirkyIf = initIfStmt(p.s(cpsStmts))
|
|
initElifBranch(p.s(cpsStmts), quirkyIf,
|
|
cCall(cgsymValue(p.module, "nimBorrowCurrentException")))
|
|
else:
|
|
isScope = true
|
|
startBlockWith(p):
|
|
quirkyScope = initScope(p.s(cpsStmts))
|
|
p.nestedTryStmts[^1].inExcept = true
|
|
var i = 1
|
|
var exceptIf = default(IfBuilder)
|
|
var exceptIfInited = false
|
|
while (i < t.len) and (t[i].kind == nkExceptBranch):
|
|
# bug #4230: avoid false sharing between branches:
|
|
if d.k == locTemp and isEmptyType(t.typ): d.k = locNone
|
|
if t[i].len == 1:
|
|
# general except section:
|
|
var scope = default(ScopeBuilder)
|
|
startBlockWith(p):
|
|
if exceptIfInited:
|
|
initElseBranch(p.s(cpsStmts), exceptIf)
|
|
else:
|
|
scope = initScope(p.s(cpsStmts))
|
|
if not quirkyExceptions:
|
|
p.s(cpsStmts).addFieldAssignment(safePoint, "status", cIntValue(0))
|
|
expr(p, t[i][0], d)
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "popCurrentException"))
|
|
endBlockWith(p):
|
|
if exceptIfInited:
|
|
finishBranch(p.s(cpsStmts), exceptIf)
|
|
else:
|
|
finishScope(p.s(cpsStmts), scope)
|
|
else:
|
|
var orExpr: Snippet = ""
|
|
for j in 0..<t[i].len - 1:
|
|
assert(t[i][j].kind == nkType)
|
|
var excVal = cCall(cgsymValue(p.module, "nimBorrowCurrentException"))
|
|
let member =
|
|
if p.module.compileToCpp:
|
|
derefField(excVal, "m_type")
|
|
else:
|
|
dotField(derefField(excVal, "Sup"), "m_type")
|
|
var branch: Snippet = ""
|
|
if optTinyRtti in p.config.globalOptions:
|
|
let checkFor = $getObjDepth(t[i][j].typ)
|
|
branch = cCall(cgsymValue(p.module, "isObjDisplayCheck"),
|
|
member,
|
|
checkFor,
|
|
$genDisplayElem(MD5Digest(hashType(t[i][j].typ, p.config))))
|
|
else:
|
|
let checkFor = genTypeInfoV1(p.module, t[i][j].typ, t[i][j].info)
|
|
branch = cCall(cgsymValue(p.module, "isObj"),
|
|
member,
|
|
checkFor)
|
|
if orExpr.len == 0:
|
|
orExpr = branch
|
|
else:
|
|
orExpr = cOp(Or, orExpr, branch)
|
|
|
|
if not exceptIfInited:
|
|
exceptIf = initIfStmt(p.s(cpsStmts))
|
|
exceptIfInited = true
|
|
startBlockWith(p):
|
|
initElifBranch(p.s(cpsStmts), exceptIf, orExpr)
|
|
if not quirkyExceptions:
|
|
p.s(cpsStmts).addFieldAssignment(safePoint, "status", cIntValue(0))
|
|
expr(p, t[i][^1], d)
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "popCurrentException"))
|
|
endBlockWith(p):
|
|
finishBranch(p.s(cpsStmts), exceptIf)
|
|
inc(i)
|
|
if exceptIfInited:
|
|
finishIfStmt(p.s(cpsStmts), exceptIf)
|
|
discard pop(p.nestedTryStmts)
|
|
endBlockWith(p):
|
|
# end of else block
|
|
if not quirkyExceptions:
|
|
finishBranch(p.s(cpsStmts), nonQuirkyIf)
|
|
finishIfStmt(p.s(cpsStmts), nonQuirkyIf)
|
|
elif isScope:
|
|
finishScope(p.s(cpsStmts), quirkyScope)
|
|
else:
|
|
finishBranch(p.s(cpsStmts), quirkyIf)
|
|
finishIfStmt(p.s(cpsStmts), quirkyIf)
|
|
if i < t.len and t[i].kind == nkFinally:
|
|
p.finallySafePoints.add(safePoint)
|
|
var finallyScope: ScopeBuilder
|
|
startSimpleBlock(p, finallyScope)
|
|
genStmts(p, t[i][0])
|
|
# pretend we handled the exception in a 'finally' so that we don't
|
|
# re-raise the unhandled one but instead keep the old one (it was
|
|
# not popped either):
|
|
if not quirkyExceptions and getCompilerProc(p.module.g.graph, "nimLeaveFinally") != nil:
|
|
p.s(cpsStmts).addSingleIfStmt(
|
|
cOp(NotEqual,
|
|
dotField(safePoint, "status"),
|
|
cIntValue(0))):
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimLeaveFinally"))
|
|
endSimpleBlock(p, finallyScope)
|
|
discard pop(p.finallySafePoints)
|
|
if not quirkyExceptions:
|
|
p.s(cpsStmts).addSingleIfStmt(
|
|
cOp(NotEqual,
|
|
dotField(safePoint, "status"),
|
|
cIntValue(0))):
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "reraiseException"))
|
|
|
|
proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false; result: var Rope) =
|
|
var res = ""
|
|
let offset =
|
|
if isAsmStmt: 1 # first son is pragmas
|
|
else: 0
|
|
|
|
for i in offset..<t.len:
|
|
let it = t[i]
|
|
case it.kind
|
|
of nkStrLit..nkTripleStrLit:
|
|
res.add(it.strVal)
|
|
of nkSym:
|
|
var sym = it.sym
|
|
if sym.kind in {skProc, skFunc, skIterator, skMethod}:
|
|
var a: TLoc = initLocExpr(p, it)
|
|
res.add($rdLoc(a))
|
|
elif sym.kind == skType:
|
|
res.add($getTypeDesc(p.module, sym.typ))
|
|
else:
|
|
discard getTypeDesc(p.module, skipTypes(sym.typ, abstractPtrs))
|
|
fillBackendName(p.module, sym)
|
|
res.add($sym.loc.snippet)
|
|
of nkTypeOfExpr:
|
|
res.add($getTypeDesc(p.module, it.typ))
|
|
else:
|
|
discard getTypeDesc(p.module, skipTypes(it.typ, abstractPtrs))
|
|
var a: TLoc = initLocExpr(p, it)
|
|
res.add($a.rdLoc)
|
|
|
|
if isAsmStmt and hasGnuAsm in CC[p.config.cCompiler].props:
|
|
for x in splitLines(res):
|
|
var j = 0
|
|
while j < x.len and x[j] in {' ', '\t'}: inc(j)
|
|
if j < x.len:
|
|
if x[j] in {'"', ':'}:
|
|
# don't modify the line if already in quotes or
|
|
# some clobber register list:
|
|
result.add(x); result.add("\L")
|
|
else:
|
|
# ignore empty lines
|
|
result.add("\"")
|
|
result.add(x.replace("\"", "\\\""))
|
|
result.add("\\n\"\n")
|
|
else:
|
|
res.add("\L")
|
|
result.add res.rope
|
|
|
|
proc genAsmStmt(p: BProc, t: PNode) =
|
|
assert(t.kind == nkAsmStmt)
|
|
genLineDir(p, t)
|
|
var s = newRopeAppender()
|
|
|
|
var asmSyntax = ""
|
|
if (let p = t[0]; p.kind == nkPragma):
|
|
for i in p:
|
|
if whichPragma(i) == wAsmSyntax:
|
|
asmSyntax = i[1].strVal
|
|
|
|
if asmSyntax != "" and
|
|
not (
|
|
asmSyntax == "gcc" and hasGnuAsm in CC[p.config.cCompiler].props or
|
|
asmSyntax == "vcc" and hasGnuAsm notin CC[p.config.cCompiler].props):
|
|
localError(
|
|
p.config, t.info,
|
|
"Your compiler does not support the specified inline assembler")
|
|
|
|
genAsmOrEmitStmt(p, t, isAsmStmt=true, s)
|
|
# see bug #2362, "top level asm statements" seem to be a mis-feature
|
|
# but even if we don't do this, the example in #2362 cannot possibly
|
|
# work:
|
|
if p.prc == nil:
|
|
# top level asm statement?
|
|
p.module.s[cfsProcHeaders].add runtimeFormat(CC[p.config.cCompiler].asmStmtFrmt, [s])
|
|
else:
|
|
addIndent p, p.s(cpsStmts)
|
|
p.s(cpsStmts).add runtimeFormat(CC[p.config.cCompiler].asmStmtFrmt, [s])
|
|
|
|
proc determineSection(n: PNode): TCFileSection =
|
|
result = cfsProcHeaders
|
|
if n.len >= 1 and n[0].kind in {nkStrLit..nkTripleStrLit}:
|
|
let sec = n[0].strVal
|
|
if sec.startsWith("/*TYPESECTION*/"): result = cfsForwardTypes # TODO WORKAROUND
|
|
elif sec.startsWith("/*VARSECTION*/"): result = cfsVars
|
|
elif sec.startsWith("/*INCLUDESECTION*/"): result = cfsHeaders
|
|
|
|
proc genEmit(p: BProc, t: PNode) =
|
|
var s = newRopeAppender()
|
|
genAsmOrEmitStmt(p, t[1], false, s)
|
|
if p.prc == nil:
|
|
# top level emit pragma?
|
|
let section = determineSection(t[1])
|
|
genCLineDir(p.module.s[section], t.info, p.config)
|
|
p.module.s[section].add(s)
|
|
else:
|
|
genLineDir(p, t)
|
|
line(p, cpsStmts, s)
|
|
|
|
proc genPragma(p: BProc, n: PNode) =
|
|
for i in 0..<n.len:
|
|
let it = n[i]
|
|
case whichPragma(it)
|
|
of wEmit: genEmit(p, it)
|
|
of wPush:
|
|
processPushBackendOption(p.config, p.optionsStack, p.options, n, i+1)
|
|
of wPop:
|
|
processPopBackendOption(p.config, p.optionsStack, p.options)
|
|
else: discard
|
|
|
|
|
|
proc genDiscriminantCheck(p: BProc, a, tmp: TLoc, objtype: PType,
|
|
field: PSym) =
|
|
var t = skipTypes(objtype, abstractVar)
|
|
assert t.kind == tyObject
|
|
discard genTypeInfoV1(p.module, t, a.lode.info)
|
|
if not containsOrIncl(p.module.declaredThings, field.id):
|
|
p.module.s[cfsVars].addDeclWithVisibility(Extern):
|
|
discriminatorTableDecl(p.module, t, field, p.module.s[cfsVars])
|
|
let lit = cIntLiteral(toInt64(lengthOrd(p.config, field.typ))+1)
|
|
let ra = rdLoc(a)
|
|
let rtmp = rdLoc(tmp)
|
|
let dn = discriminatorTableName(p.module, t, field)
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "FieldDiscriminantCheck"),
|
|
cCast(NimInt, cCast(NimUint, ra)),
|
|
cCast(NimInt, cCast(NimUint, rtmp)),
|
|
dn,
|
|
lit)
|
|
if p.config.exc == excGoto:
|
|
raiseExit(p)
|
|
|
|
when false:
|
|
proc genCaseObjDiscMapping(p: BProc, e: PNode, t: PType, field: PSym; d: var TLoc) =
|
|
const ObjDiscMappingProcSlot = -5
|
|
var theProc: PSym = nil
|
|
for idx, p in items(t.methods):
|
|
if idx == ObjDiscMappingProcSlot:
|
|
theProc = p
|
|
break
|
|
if theProc == nil:
|
|
theProc = genCaseObjDiscMapping(t, field, e.info, p.module.g.graph, p.module.idgen)
|
|
t.methods.add((ObjDiscMappingProcSlot, theProc))
|
|
var call = newNodeIT(nkCall, e.info, getSysType(p.module.g.graph, e.info, tyUInt8))
|
|
call.add newSymNode(theProc)
|
|
call.add e
|
|
expr(p, call, d)
|
|
|
|
proc asgnFieldDiscriminant(p: BProc, e: PNode) =
|
|
var dotExpr = e[0]
|
|
if dotExpr.kind == nkCheckedFieldExpr: dotExpr = dotExpr[0]
|
|
var a = initLocExpr(p, e[0])
|
|
var tmp: TLoc = getTemp(p, a.t)
|
|
expr(p, e[1], tmp)
|
|
if p.inUncheckedAssignSection == 0:
|
|
let field = dotExpr[1].sym
|
|
genDiscriminantCheck(p, a, tmp, dotExpr[0].typ, field)
|
|
message(p.config, e.info, warnCaseTransition)
|
|
genAssignment(p, a, tmp, {})
|
|
|
|
proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) =
|
|
if e[0].kind == nkSym and sfGoto in e[0].sym.flags:
|
|
genLineDir(p, e)
|
|
genGotoVar(p, e[1])
|
|
elif optFieldCheck in p.options and isDiscriminantField(e[0]):
|
|
genLineDir(p, e)
|
|
asgnFieldDiscriminant(p, e)
|
|
elif p.config.usesSso() and e[0].kind == nkBracketExpr and
|
|
e[0][0].typ.skipTypes(abstractVar).kind == tyString:
|
|
# nimsso: s[i] = c → nimStrPutV3(&s, i, c) (handles COW internally)
|
|
genLineDir(p, e)
|
|
var base = initLocExpr(p, e[0][0])
|
|
var idx = initLocExpr(p, e[0][1])
|
|
var rhs = initLocExpr(p, e[1])
|
|
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimStrPutV3"),
|
|
byRefLoc(p, base), rdLoc(idx), rdCharLoc(rhs))
|
|
else:
|
|
let le = e[0]
|
|
let ri = e[1]
|
|
var a: TLoc = initLoc(locNone, le, OnUnknown)
|
|
discard getTypeDesc(p.module, le.typ.skipTypes(skipPtrs), dkVar)
|
|
a.flags.incl(lfEnforceDeref)
|
|
a.flags.incl(lfPrepareForMutation)
|
|
genLineDir(p, le) # it can be a nkBracketExpr, which may raise
|
|
expr(p, le, a)
|
|
a.flags.excl(lfPrepareForMutation)
|
|
if fastAsgn: incl(a.flags, lfNoDeepCopy)
|
|
assert(a.t != nil)
|
|
genLineDir(p, ri)
|
|
loadInto(p, le, ri, a)
|
|
|
|
proc genStmts(p: BProc, t: PNode) =
|
|
var a: TLoc = default(TLoc)
|
|
|
|
let isPush = p.config.hasHint(hintExtendedContext)
|
|
if isPush: pushInfoContext(p.config, t.info)
|
|
expr(p, t, a)
|
|
if isPush: popInfoContext(p.config)
|
|
internalAssert p.config, a.k in {locNone, locTemp, locLocalVar, locExpr}
|