# # # 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 containsGarbageCollectedRef(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: if sfThread in v.flags: appcg(p.module, p.module.preInitProc.procSec(cpsInit), "$n\t#nimRegisterThreadLocalMarker($1);$n$n", [traverseProc]) else: appcg(p.module, p.module.preInitProc.procSec(cpsInit), "$n\t#nimRegisterGlobalMarker($1);$n$n", [traverseProc]) 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 startBlockInternal(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 startBlock(p: BProc, start: FormatStr = "{$n", args: varargs[Rope]): int = lineCg(p, cpsStmts, start, args) startBlockInternal(p) proc endBlock(p: BProc) 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.. 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 if forHcr: # check with the boolean if the initializing code for the tuple should be ran lineCg(p, cpsStmts, "if ($1)$n", [hcrCond]) startBlock(p) genLineDir(p, n) var tup = initLocExpr(p, n[^1]) var t = tup.t.skipTypes(abstractInst) for i in 0.. 0: result.addf("FR_.len+=$1;$n", [b.frameLen.rope]) result.add(b.sections[cpsInit]) result.add(b.sections[cpsStmts]) proc endBlock(p: BProc, blockEnd: Rope) = 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) # this is done after the block is popped so $n is # properly indented when pretty printing is enabled line(p, cpsStmts, blockEnd) proc endBlock(p: BProc) = let topBlock = p.blocks.len - 1 let frameLen = p.blocks[topBlock].frameLen var blockEnd: Rope = "" if frameLen > 0: blockEnd.addf("FR_.len-=$1;$n", [frameLen.rope]) if p.blocks[topBlock].label.len != 0: blockEnd.addf("} $1: ;$n", [p.blocks[topBlock].label]) else: blockEnd.addf("}$n", []) endBlock(p, blockEnd) proc genSimpleBlock(p: BProc, stmts: PNode) {.inline.} = startBlock(p) genStmts(p, stmts) endBlock(p) proc exprBlock(p: BProc, n: PNode, d: var TLoc) = startBlock(p) expr(p, n, d) endBlock(p) 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 linefmt(p, cpsStmts, "STATE$1: ;$n", [idx]) elif n0.kind == nkStrLit: linefmt(p, cpsStmts, "$1: ;$n", [n0.strVal]) proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) = # 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, 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: linefmt(p, cpsStmts, "#popSafePoint();$n", []) # 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: for i in countdown(howManyExcepts-1, 0): linefmt(p, cpsStmts, "#popCurrentException();$n", []) 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]) lineF(p, cpsStmts, "switch ($1) {$n", [rdLoc(a)]) p.flags.incl beforeRetNeeded lineF(p, cpsStmts, "case -1:$n", []) blockLeaveActions(p, howManyTrys = p.nestedTryStmts.len, howManyExcepts = p.inExceptBlockLen) lineF(p, cpsStmts, " goto BeforeRet_;$n", []) 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): lineF(p, cpsStmts, "case $2: goto $1$2;$n", [prefix, rope(i)]) lineF(p, cpsStmts, "}$n", []) 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]) d.snippet = "(((NI*) $1)[1] < 0)" % [rdLoc(a)] else: a = initLocExpr(p, n[0]) # the environment is guaranteed to contain the 'state' field at offset 1: d.snippet = "((((NI*) $1.ClE_0)[1]) < 0)" % [rdLoc(a)] 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: lineF(p, cpsStmts, "goto NIMSTATE_$#;$n", [value.intVal.rope]) proc genBracedInit(p: BProc, n: PNode; isConst: bool; optionalType: PType; result: var Rope) proc potentialValueInit(p: BProc; v: PSym; value: PNode; result: var Rope) = 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): string = result = "" var argsCounter = 0 let typ = skipTypes(call[0].typ, abstractInst) assert(typ.kind == tyProc) for i in 1.. 0: result.add "," result.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, result, argsCounter) proc genCppVarForCtor(p: BProc; call: PNode; decl: var Rope, didGenTemp: var bool) = let params = genCppParamsForCtor(p, call, didGenTemp) if params.len == 0: decl = runtimeFormat("$#;\n", [decl]) else: decl = runtimeFormat("$#($#);\n", [decl, params]) 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 valueAsRope = "" potentialValueInit(p, v, value, valueAsRope) if sfGlobal in v.flags: if v.flags * {sfImportc, sfExportc} == {sfImportc} and value.kind == nkEmpty and v.loc.flags * {lfHeader, lfNoDecl} != {}: 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 decl = localVarDecl(p, vn) var tmp: TLoc if isCppCtorCall: var didGenTemp = false genCppVarForCtor(p, value, decl, didGenTemp) line(p, cpsStmts, decl) else: tmp = initLocExprSingleUse(p, value) if value.kind == nkEmpty: lineF(p, cpsStmts, "$#;\n", [decl]) else: lineF(p, cpsStmts, "$# = $#;\n", [decl, tmp.rdLoc]) return assignLocalVar(p, vn) initLocalVar(p, v, imm) let traverseProc = "NULL" # 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 lineCg(targetProc, cpsLocals, "hcrRegisterGlobal($3, \"$1\", sizeof($2), $4, (void**)&$1);$n", [v.loc.snippet, rdLoc(v.loc), getModuleDllPath(p.module, v), traverseProc]) # 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 if forHcr: lineCg(targetProc, cpsStmts, "if (hcrRegisterGlobal($3, \"$1\", sizeof($2), $4, (void**)&$1))$N", [v.loc.snippet, rdLoc(v.loc), getModuleDllPath(p.module, v), traverseProc]) startBlock(targetProc) if value.kind != nkEmpty and valueAsRope.len == 0: genLineDir(targetProc, vn) if not isCppCtorCall: loadInto(targetProc, vn, value, v.loc) if forHcr: endBlock(targetProc) 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.sons: if it.kind == nkCommentStmt: continue if it.kind == nkIdentDefs: # can be a lifted var nowadays ... if it[0].kind == nkSym: genSingleVar(p, it) else: genClosureVar(p, it) 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: startBlock(p) a = initLocExprSingleUse(p, it[0]) lelse = getLabel(p) inc(p.labels) lineF(p, cpsStmts, "if (!$1) goto $2;$n", [rdLoc(a), lelse]) if p.module.compileToCpp: # avoid "jump to label crosses initialization" error: p.s(cpsStmts).add "{" expr(p, it[1], d) p.s(cpsStmts).add "}" else: expr(p, it[1], d) endBlock(p) if n.len > 1: lineF(p, cpsStmts, "goto $1;$n", [lend]) fixLabel(p, lelse) elif it.len == 1: startBlock(p) expr(p, it[0], d) endBlock(p) 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) 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] linefmt(p, cpsStmts, "if ($1.status != 0) #popCurrentException();$n", [safePoint]) lineF(p, cpsStmts, "goto BeforeRet_;$n", []) proc genGotoForCase(p: BProc; caseStmt: PNode) = for i in 1.. 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] var gotoArray = "static void* $#[$#] = {" % [tmp, arraySize.rope] for i in 1..arraySize-1: gotoArray.addf("&&TMP$#_, ", [rope(id+i)]) gotoArray.addf("&&TMP$#_};$n", [rope(id+arraySize)]) line(p, cpsLocals, gotoArray) for j in 0..= 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) lineF(p, cpsStmts, "goto $1;$n", [p.blocks[idx].label]) proc raiseExit(p: BProc) = assert p.config.exc == excGoto if nimErrorFlagDisabled notin p.flags: p.flags.incl nimErrorFlagAccessed if p.nestedTryStmts.len == 0: p.flags.incl beforeRetNeeded # easy case, simply goto 'ret': lineCg(p, cpsStmts, "if (NIM_UNLIKELY(*nimErr_)) goto BeforeRet_;$n", []) else: lineCg(p, cpsStmts, "if (NIM_UNLIKELY(*nimErr_)) goto LA$1_;$n", [p.nestedTryStmts[^1].label]) proc raiseExitCleanup(p: BProc, destroy: string) = assert p.config.exc == excGoto if nimErrorFlagDisabled notin p.flags: p.flags.incl nimErrorFlagAccessed if p.nestedTryStmts.len == 0: p.flags.incl beforeRetNeeded # easy case, simply goto 'ret': lineCg(p, cpsStmts, "if (NIM_UNLIKELY(*nimErr_)) {$1; goto BeforeRet_;}$n", [destroy]) else: lineCg(p, cpsStmts, "if (NIM_UNLIKELY(*nimErr_)) {$2; goto LA$1_;}$n", [p.nestedTryStmts[^1].label, destroy]) proc finallyActions(p: BProc) = if p.config.exc != excGoto and p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept: # if the current try stmt have a finally block, # we must execute it before reraising let finallyBlock = p.nestedTryStmts[^1].fin if finallyBlock != nil: genSimpleBlock(p, finallyBlock[0]) proc raiseInstr(p: BProc; result: var Rope) = if p.config.exc == excGoto: let L = p.nestedTryStmts.len if L == 0: p.flags.incl beforeRetNeeded # easy case, simply goto 'ret': result.add ropecg(p.module, "goto BeforeRet_;$n", []) else: # raise inside an 'except' must go to the finally block, # raise outside an 'except' block must go to the 'except' list. result.add ropecg(p.module, "goto LA$1_;$n", [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: lineCg(p, cpsStmts, "#raiseExceptionEx((#Exception*)$1, $2, $3, $4, $5);$n", [e, makeCString(typ.sym.name.s), makeCString(if p.prc != nil: p.prc.name.s else: p.module.module.name.s), quotedFilename(p.config, t.info), toLinenumber(t.info)]) if optOwnedRefs in p.config.globalOptions: lineCg(p, cpsStmts, "$1 = NIM_NIL;$n", [e]) else: finallyActions(p) genLineDir(p, t) linefmt(p, cpsStmts, "#reraiseException();$n", []) raiseInstr(p, p.s(cpsStmts)) template genCaseGenericBranch(p: BProc, b: PNode, e: TLoc, rangeFormat, eqFormat: FormatStr, labl: TLabel) = var x, y: TLoc for i in 0.. stringCaseThreshold: var bitMask = math.nextPowerOfTwo(strings) - 1 var branches: seq[Rope] newSeq(branches, bitMask + 1) var a: TLoc = initLocExpr(p, t[0]) # first pass: generate ifs+goto: var labId = p.labels for i in 1.. RangeExpandLimit: return true proc ifSwitchSplitPoint(p: BProc, n: PNode): int = result = 0 for i in 1.. 0: genIfForCaseUntil(p, n, d, rangeFormat = "if ($1 >= $2 && $1 <= $3) goto $4;$n", eqFormat = "if ($1 == $2) goto $3;$n", splitPoint, a) else: "" # generate switch part (might be empty): if splitPoint+1 < n.len: lineF(p, cpsStmts, "switch ($1) {$n", [rdCharLoc(a)]) var hasDefault = false for i in splitPoint+1..= $2 && $1 <= $3) goto $4;$n", "if ($1 == $2) goto $3;$n") 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(ropecg(p.module, "\tTFrame* _nimCurFrame;$n", [])) p.procSec(cpsInit).add(ropecg(p.module, "\t_nimCurFrame = #getFrame();$n", [])) linefmt(p, cpsStmts, "#setFrame(_nimCurFrame);$n", []) 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 occured, not under Nim's control. } { /* finally: */ printf('fin!\n'); if (error) std::rethrow_exception(error); // re-raise the exception } ]# p.module.includeHeader("") 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, 0.Natural)) if t.kind == nkHiddenTryStmt: lineCg(p, cpsStmts, "try {$n", []) expr(p, t[0], d) lineCg(p, cpsStmts, "}$n", []) else: startBlock(p, "try {$n") expr(p, t[0], d) endBlock(p) # 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 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 # general except section: hasElse = true if hasIf: lineF(p, cpsStmts, "else ", []) startBlock(p) # we handled the error: expr(p, t[i][0], d) linefmt(p, cpsStmts, "#popCurrentException();$n", []) endBlock(p) else: var orExpr = newRopeAppender() var exvar = PNode(nil) for j in 0..$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 hasIf: startBlock(p, "else if ($1) {$n", [orExpr]) else: startBlock(p, "if ($1) {$n", [orExpr]) hasIf = true if exvar != nil: fillLocalName(p, exvar.sym) fillLoc(exvar.sym.loc, 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", []) endBlock(p) inc(i) if hasIf and not hasElse: linefmt(p, cpsStmts, "else throw;$n", [etmp]) 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.. 0 and t[^1].kind == nkFinally: if not catchAllPresent: startBlock(p, "catch (...) {$n", []) genRestoreFrameAfterException(p) linefmt(p, cpsStmts, "T$1_ = std::current_exception();$n", [etmp]) endBlock(p) startBlock(p) genStmts(p, t[^1][0]) linefmt(p, cpsStmts, "if (T$1_) std::rethrow_exception(T$1_);$n", [etmp]) endBlock(p) 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, 0.Natural)) startBlock(p, "try {$n") expr(p, t[0], d) endBlock(p) var catchAllPresent = false p.nestedTryStmts[^1].inExcept = true for i in 1.. 1: lineF(p, cpsStmts, "else", []) startBlock(p) # we handled the exception, remember this: linefmt(p, cpsStmts, "*nimErr_ = NIM_FALSE;$n", []) expr(p, t[i][0], d) else: var orExpr = newRopeAppender() for j in 0..$1, $2, $3)", [memberName, checkFor, $genDisplayElem(MD5Digest(hashType(t[i][j].typ, p.config)))]) else: let checkFor = genTypeInfoV1(p.module, t[i][j].typ, t[i][j].info) appcg(p.module, orExpr, "#isObj(#nimBorrowCurrentException()->$1, $2)", [memberName, checkFor]) if i > 1: line(p, cpsStmts, "else ") startBlock(p, "if ($1) {$n", [orExpr]) # we handled the exception, remember this: linefmt(p, cpsStmts, "*nimErr_ = NIM_FALSE;$n", []) expr(p, t[i][^1], d) linefmt(p, cpsStmts, "#popCurrentException();$n", []) linefmt(p, cpsStmts, "LA$1_:;$n", [nextExcept]) endBlock(p) inc(i) discard pop(p.nestedTryStmts) endBlock(p) if i < t.len and t[i].kind == nkFinally: startBlock(p) 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).add(ropecg(p.module, "NIM_BOOL oldNimErrFin$1_;$n", [lab])) linefmt(p, cpsStmts, "oldNimErrFin$1_ = *nimErr_; *nimErr_ = NIM_FALSE;$n", [lab]) 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. linefmt(p, cpsStmts, "*nimErr_ = oldNimErrFin$1_;$n", [lab]) endBlock(p) 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("") else: p.flags.incl noSafePoints genLineDir(p, t) cgsym(p.module, "Exception") var safePoint: Rope = "" if not quirkyExceptions: safePoint = getTempName(p.module) linefmt(p, cpsLocals, "#TSafePoint $1;$n", [safePoint]) linefmt(p, cpsStmts, "#pushSafePoint(&$1);$n", [safePoint]) if isDefined(p.config, "nimStdSetjmp"): linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint]) elif isDefined(p.config, "nimSigSetjmp"): linefmt(p, cpsStmts, "$1.status = sigsetjmp($1.context, 0);$n", [safePoint]) elif isDefined(p.config, "nimBuiltinSetjmp"): linefmt(p, cpsStmts, "$1.status = __builtin_setjmp($1.context);$n", [safePoint]) 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 linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint]) 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 linefmt(p, cpsStmts, "$1.status = _setjmp($1.context, 0);$n", [safePoint]) else: linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", [safePoint]) else: linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint]) lineCg(p, cpsStmts, "if ($1.status == 0) {$n", [safePoint]) let fin = if t[^1].kind == nkFinally: t[^1] else: nil p.nestedTryStmts.add((fin, quirkyExceptions, 0.Natural)) expr(p, t[0], d) if not quirkyExceptions: linefmt(p, cpsStmts, "#popSafePoint();$n", []) lineCg(p, cpsStmts, "}$n", []) startBlock(p, "else {$n") linefmt(p, cpsStmts, "#popSafePoint();$n", []) genRestoreFrameAfterException(p) elif 1 < t.len and t[1].kind == nkExceptBranch: startBlock(p, "if (#nimBorrowCurrentException()) {$n") else: startBlock(p) p.nestedTryStmts[^1].inExcept = true var i = 1 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: if i > 1: lineF(p, cpsStmts, "else", []) startBlock(p) if not quirkyExceptions: linefmt(p, cpsStmts, "$1.status = 0;$n", [safePoint]) expr(p, t[i][0], d) linefmt(p, cpsStmts, "#popCurrentException();$n", []) endBlock(p) else: var orExpr = newRopeAppender() for j in 0..$1, $2, $3)", [memberName, checkFor, $genDisplayElem(MD5Digest(hashType(t[i][j].typ, p.config)))]) else: let checkFor = genTypeInfoV1(p.module, t[i][j].typ, t[i][j].info) appcg(p.module, orExpr, "#isObj(#nimBorrowCurrentException()->$1, $2)", [memberName, checkFor]) if i > 1: line(p, cpsStmts, "else ") startBlock(p, "if ($1) {$n", [orExpr]) if not quirkyExceptions: linefmt(p, cpsStmts, "$1.status = 0;$n", [safePoint]) expr(p, t[i][^1], d) linefmt(p, cpsStmts, "#popCurrentException();$n", []) endBlock(p) inc(i) discard pop(p.nestedTryStmts) endBlock(p) # end of else block if i < t.len and t[i].kind == nkFinally: p.finallySafePoints.add(safePoint) startBlock(p) 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: linefmt(p, cpsStmts, "if ($1.status != 0) #nimLeaveFinally();$n", [safePoint]) endBlock(p) discard pop(p.finallySafePoints) if not quirkyExceptions: linefmt(p, cpsStmts, "if ($1.status != 0) #reraiseException();$n", [safePoint]) 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..= 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..