diff options
Diffstat (limited to 'compiler/ccgstmts.nim')
-rw-r--r-- | compiler/ccgstmts.nim | 1776 |
1 files changed, 1161 insertions, 615 deletions
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 8fb096c70..883108f2c 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -8,71 +8,134 @@ # # 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 registerGcRoot(p: BProc, v: PSym) = - if gSelectedGC in {gcMarkAndSweep, gcGenerational, gcV2, gcRefc} and +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 :-) - let prc = genTraverseProcForGlobal(p.module, v) - appcg(p.module, p.module.initProc.procSec(cpsInit), - "#nimRegisterGlobalMarker($1);$n", [prc]) - -proc isAssignedImmediately(n: PNode): bool {.inline.} = - if n.kind == nkEmpty: return false - if isInvalidReturnType(n.typ): - # var v = f() - # is transformed into: var v; f(addr v) - # where 'f' **does not** initialize the result! - return false - result = true + 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) = - var tup, field: TLoc - if n.kind != nkVarTuple: internalError(n.info, "genVarTuple") - var L = sonsLen(n) + 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 countup(0, L-3): + for i in 0..<n.len-2: if n[i].kind != nkSym: - genStmts(p, lowerTupleUnpacking(n, p.prc)) + 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 + + 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) - initLocExpr(p, n.sons[L-1], tup) + var tup = initLocExpr(p, n[^1]) var t = tup.t.skipTypes(abstractInst) - for i in countup(0, L-3): - let vn = n.sons[i] + for i in 0..<n.len-2: + let vn = n[i] let v = vn.sym if sfCompileTime in v.flags: continue if sfGlobal in v.flags: - assignGlobalVar(p, vn) - genObjectInit(p, cpsInit, v.typ, v.loc, true) - registerGcRoot(p, v) + assignGlobalVar(p, vn, "") + genObjectInit(p, cpsInit, v.typ, v.loc, constructObj) + registerTraverseProc(p, v) else: assignLocalVar(p, vn) - initLocalVar(p, v, immediateAsgn=isAssignedImmediately(n[L-1])) - initLoc(field, locExpr, vn, tup.storage) + initLocalVar(p, v, immediateAsgn=isAssignedImmediately(p.config, n[^1])) + var field = initLoc(locExpr, vn, tup.storage) if t.kind == tyTuple: - field.r = "$1.Field$2" % [rdLoc(tup), rope(i)] + field.snippet = "$1.Field$2" % [rdLoc(tup), rope(i)] else: - if t.n.sons[i].kind != nkSym: internalError(n.info, "genVarTuple") - field.r = "$1.$2" % [rdLoc(tup), mangleRecFieldName(p.module, t.n.sons[i].sym, t)] + if t.n[i].kind != nkSym: internalError(p.config, n.info, "genVarTuple") + field.snippet = "$1.$2" % [rdLoc(tup), mangleRecFieldName(p.module, t.n[i].sym)] putLocIntoDest(p, v.loc, field) + if forHcr or isGlobalInBlock: + hcrGlobals.add((loc: v.loc, tp: "NULL")) + + if forHcr: + # end the block where the tuple gets initialized + endBlock(p) + 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 + lineCg(p, cpsLocals, "NIM_BOOL $1 = NIM_FALSE;$n", [hcrCond]) + for curr in hcrGlobals: + lineCg(p, cpsLocals, "$1 |= hcrRegisterGlobal($4, \"$2\", sizeof($3), $5, (void**)&$2);$N", + [hcrCond, curr.loc.snippet, rdLoc(curr.loc), getModuleDllPath(p.module, n[0].sym), curr.tp]) -proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false) proc loadInto(p: BProc, le, ri: PNode, a: var TLoc) {.inline.} = - if ri.kind in nkCallKinds and (ri.sons[0].kind != nkSym or - ri.sons[0].sym.magic == mNone): + if ri.kind in nkCallKinds and (ri[0].kind != nkSym or + ri[0].sym.magic == mNone): genAsgnCall(p, le, ri, a) - elif ri.kind in {nkDerefExpr, nkHiddenDeref}: + else: # this is a hacky way to fix #1181 (tmissingderef):: # # var arr1 = cast[ptr array[4, int8]](addr foo)[] @@ -80,26 +143,15 @@ proc loadInto(p: BProc, le, ri: PNode, a: var TLoc) {.inline.} = # 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. - genDeref(p, ri, a, enforceDeref=true) - else: + a.flags.incl(lfEnforceDeref) expr(p, ri, a) -proc startBlock(p: BProc, start: FormatStr = "{$n", - args: varargs[Rope]): int {.discardable.} = - lineCg(p, cpsStmts, start, args) - inc(p.labels) - result = len(p.blocks) - setLen(p.blocks, result + 1) - p.blocks[result].id = p.labels - p.blocks[result].nestedTryStmts = p.nestedTryStmts.len.int16 - p.blocks[result].nestedExceptStmts = p.inExceptBlock.int16 - -proc assignLabel(b: var TBlock): Rope {.inline.} = +proc assignLabel(b: var TBlock; result: var Rope) {.inline.} = b.label = "LA" & b.id.rope - result = b.label + result.add b.label -proc blockBody(b: var TBlock): Rope = - result = b.sections[cpsLocals] +proc blockBody(b: var TBlock; result: var Rope) = + result.add b.sections[cpsLocals] if b.frameLen > 0: result.addf("FR_.len+=$1;$n", [b.frameLen.rope]) result.add(b.sections[cpsInit]) @@ -108,7 +160,7 @@ proc blockBody(b: var TBlock): Rope = proc endBlock(p: BProc, blockEnd: Rope) = let topBlock = p.blocks.len-1 # the block is merged into the parent block - add(p.blocks[topBlock-1].sections[cpsStmts], p.blocks[topBlock].blockBody) + 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 @@ -116,13 +168,14 @@ proc endBlock(p: BProc, blockEnd: Rope) = proc endBlock(p: BProc) = let topBlock = p.blocks.len - 1 - var blockEnd = if p.blocks[topBlock].label != nil: - rfmt(nil, "} $1: ;$n", p.blocks[topBlock].label) - else: - ~"}$n" 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.} = @@ -141,13 +194,49 @@ template preserveBreakIdx(body: untyped): untyped = p.breakIdx = oldBreakIdx proc genState(p: BProc, n: PNode) = - internalAssert n.len == 1 + internalAssert p.config, n.len == 1 let n0 = n[0] if n0.kind == nkIntLit: - let idx = n.sons[0].intVal - linefmt(p, cpsStmts, "STATE$1: ;$n", idx.rope) + let idx = n[0].intVal + linefmt(p, cpsStmts, "STATE$1: ;$n", [idx]) elif n0.kind == nkStrLit: - linefmt(p, cpsStmts, "$1: ;$n", n0.strVal.rope) + 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 @@ -155,133 +244,216 @@ proc genGotoState(p: BProc, n: PNode) = # switch (x.state) { # case 0: goto STATE0; # ... - var a: TLoc - initLocExpr(p, n.sons[0], a) + var a: TLoc = initLocExpr(p, n[0]) lineF(p, cpsStmts, "switch ($1) {$n", [rdLoc(a)]) - p.beforeRetNeeded = true - lineF(p, cpsStmts, "case -1: goto BeforeRet_;$n", []) - var statesCounter = lastOrd(n.sons[0].typ) + 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 = n[1].intVal + 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 0 .. statesCounter: + 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) = +proc genBreakState(p: BProc, n: PNode, d: var TLoc) = var a: TLoc - if n.sons[0].kind == nkClosure: - # XXX this produces quite inefficient code! - initLocExpr(p, n.sons[0].sons[1], a) - lineF(p, cpsStmts, "if (((NI*) $1)[1] < 0) break;$n", [rdLoc(a)]) + 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: - initLocExpr(p, n.sons[0], a) + a = initLocExpr(p, n[0]) # the environment is guaranteed to contain the 'state' field at offset 1: - lineF(p, cpsStmts, "if ((((NI*) $1.ClE_0)[1]) < 0) break;$n", [rdLoc(a)]) - # lineF(p, cpsStmts, "if (($1) < 0) break;$n", [rdLoc(a)]) - -proc genVarPrototypeAux(m: BModule, n: PNode) + d.snippet = "((((NI*) $1.ClE_0)[1]) < 0)" % [rdLoc(a)] proc genGotoVar(p: BProc; value: PNode) = if value.kind notin {nkCharLit..nkUInt64Lit}: - localError(value.info, "'goto' target must be a literal value") + localError(p.config, value.info, "'goto' target must be a literal value") else: lineF(p, cpsStmts, "goto NIMSTATE_$#;$n", [value.intVal.rope]) -proc genSingleVar(p: BProc, a: PNode) = - let vn = a.sons[0] - let v = vn.sym - if sfCompileTime in v.flags: return +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..<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: + if argsCounter > 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, a.sons[2]) + 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 - a.sons[2].kind == nkEmpty and + value.kind == nkEmpty and v.loc.flags * {lfHeader, lfNoDecl} != {}: return if sfPure in v.flags: # v.owner.kind != skModule: targetProc = p.module.preInitProc - assignGlobalVar(targetProc, vn) + 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. - genObjectInit(p.module.preInitProc, cpsInit, v.typ, v.loc, true) + 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: - genVarPrototypeAux(p.module.g.generatedHeader, vn) - registerGcRoot(p, v) + genVarPrototype(p.module.g.generatedHeader, vn) + registerTraverseProc(p, v) else: - let value = a.sons[2] - let imm = isAssignedImmediately(value) if imm and p.module.compileToCpp and p.splitDecls == 0 and - not containsHiddenPointer(v.typ): + 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: - genLineDir(p, a) - let decl = localVarDecl(p, vn) + # generate better code here: 'Foo f = x;' + genLineDir(p, vn) + var decl = localVarDecl(p, vn) var tmp: TLoc - if value.kind in nkCallKinds and value[0].kind == nkSym and - sfConstructor in value[0].sym.flags: - var params: Rope - let typ = skipTypes(value.sons[0].typ, abstractInst) - assert(typ.kind == tyProc) - for i in 1.. <value.len: - if params != nil: params.add(~", ") - assert(sonsLen(typ) == sonsLen(typ.n)) - add(params, genOtherArg(p, value, i, typ)) - lineF(p, cpsStmts, "$#($#);$n", [decl, params]) + if isCppCtorCall: + var didGenTemp = false + genCppVarForCtor(p, value, decl, didGenTemp) + line(p, cpsStmts, decl) else: - initLocExprSingleUse(p, value, tmp) - lineF(p, cpsStmts, "$# = $#;$n", [decl, tmp.rdLoc]) + 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) - if a.sons[2].kind != nkEmpty: - genLineDir(targetProc, a) - loadInto(targetProc, a.sons[0], a.sons[2], v.loc) + 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.sons[2].kind != nkEmpty + var immediateAsgn = a[2].kind != nkEmpty + var v: TLoc = initLocExpr(p, a[0]) + genLineDir(p, a) if immediateAsgn: - var v: TLoc - initLocExpr(p, a.sons[0], v) - genLineDir(p, a) - loadInto(p, a.sons[0], a.sons[2], v) + 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 i in countup(0, sonsLen(n) - 1): - var a = n.sons[i] - if a.kind == nkCommentStmt: continue - if a.kind == nkIdentDefs: + for it in n.sons: + if it.kind == nkCommentStmt: continue + if it.kind == nkIdentDefs: # can be a lifted var nowadays ... - if a.sons[0].kind == nkSym: - genSingleVar(p, a) + if it[0].kind == nkSym: + genSingleVar(p, it) else: - genClosureVar(p, a) + genClosureVar(p, it) else: - genVarTuple(p, a) - -proc genConstStmt(p: BProc, t: PNode) = - for i in countup(0, sonsLen(t) - 1): - var it = t.sons[i] - if it.kind == nkCommentStmt: continue - if it.kind != nkConstDef: internalError(t.info, "genConstStmt") - var c = it.sons[0].sym - if c.typ.containsCompileTimeOnly: continue - elif c.typ.kind in ConstantDataTypes and lfNoDecl notin c.loc.flags and - c.ast.len != 0: - if not emitLazily(c): requestConstImpl(p, c) + genVarTuple(p, it) proc genIf(p: BProc, n: PNode, d: var TLoc) = # @@ -299,309 +471,366 @@ proc genIf(p: BProc, n: PNode, d: var TLoc) = a: TLoc lelse: TLabel if not isEmptyType(n.typ) and d.k == locNone: - getTemp(p, n.typ, d) + d = getTemp(p, n.typ) genLineDir(p, n) let lend = getLabel(p) - for i in countup(0, sonsLen(n) - 1): + for it in n.sons: # bug #4230: avoid false sharing between branches: if d.k == locTemp and isEmptyType(n.typ): d.k = locNone - let it = n.sons[i] if it.len == 2: - when newScopeForIf: startBlock(p) - initLocExprSingleUse(p, it.sons[0], a) + startBlock(p) + a = initLocExprSingleUse(p, it[0]) lelse = getLabel(p) inc(p.labels) lineF(p, cpsStmts, "if (!$1) goto $2;$n", [rdLoc(a), lelse]) - when not newScopeForIf: startBlock(p) if p.module.compileToCpp: # avoid "jump to label crosses initialization" error: - add(p.s(cpsStmts), "{") - expr(p, it.sons[1], d) - add(p.s(cpsStmts), "}") + p.s(cpsStmts).add "{" + expr(p, it[1], d) + p.s(cpsStmts).add "}" else: - expr(p, it.sons[1], d) + expr(p, it[1], d) endBlock(p) - if sonsLen(n) > 1: + if n.len > 1: lineF(p, cpsStmts, "goto $1;$n", [lend]) fixLabel(p, lelse) elif it.len == 1: startBlock(p) - expr(p, it.sons[0], d) + expr(p, it[0], d) endBlock(p) - else: internalError(n.info, "genIf()") - if sonsLen(n) > 1: fixLabel(p, lend) - - -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: seq[PNode] - newSeq(stack, 0) - - var alreadyPoppedCnt = p.inExceptBlock - for i in countup(1, howManyTrys): - if not p.module.compileToCpp or optNoCppExceptions in gGlobalOptions: - # Pop safe points generated by try - if alreadyPoppedCnt > 0: - dec alreadyPoppedCnt - else: - 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. - var tryStmt = p.nestedTryStmts.pop - stack.add(tryStmt) - - # Find finally-stmt for this try-stmt - # and generate a copy of its sons - var finallyStmt = lastSon(tryStmt) - if finallyStmt.kind == nkFinally: - genStmts(p, finallyStmt.sons[0]) - - # push old elements again: - for i in countdown(howManyTrys-1, 0): - p.nestedTryStmts.add(stack[i]) - - if not p.module.compileToCpp or optNoCppExceptions in gGlobalOptions: - # Pop exceptions that was handled by the - # except-blocks we are in - for i in countdown(howManyExcepts-1, 0): - linefmt(p, cpsStmts, "#popCurrentException();$n") + 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.beforeRetNeeded = true + p.flags.incl beforeRetNeeded genLineDir(p, t) - if (t.sons[0].kind != nkEmpty): genStmts(p, t.sons[0]) + if (t[0].kind != nkEmpty): genStmts(p, t[0]) blockLeaveActions(p, howManyTrys = p.nestedTryStmts.len, - howManyExcepts = p.inExceptBlock) - if (p.finallySafePoints.len > 0): + 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[p.finallySafePoints.len-1] - linefmt(p, cpsStmts, "if ($1.status != 0) #popCurrentException();$n", safePoint) + 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 .. <caseStmt.len: + for i in 1..<caseStmt.len: startBlock(p) - let it = caseStmt.sons[i] - for j in 0 .. it.len-2: - if it.sons[j].kind == nkRange: - localError(it.info, "range notation not available for computed goto") + 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.sons[j]) + let val = getOrdValue(it[j]) lineF(p, cpsStmts, "NIMSTATE_$#:$n", [val.rope]) genStmts(p, it.lastSon) endBlock(p) + +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 - for i in 0 .. <n.len: - let it = n.sons[i] + var arraySize: int = 0 + for i in 0..<n.len: + let it = n[i] if it.kind == nkCaseStmt: if lastSon(it).kind != nkOfBranch: - localError(it.info, + localError(p.config, it.info, "case statement must be exhaustive for computed goto"); return casePos = i - let aSize = lengthOrd(it.sons[0].typ) + 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(it.info, + localError(p.config, it.info, "case statement has too many cases for computed goto"); return - arraySize = aSize.int - if firstOrd(it.sons[0].typ) != 0: - localError(it.info, + 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(n.info, "no case statement found for computed goto"); return + 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$#_, ", [(id+i).rope]) - gotoArray.addf("&&TMP$#_};$n", [(id+arraySize).rope]) + gotoArray.addf("&&TMP$#_, ", [rope(id+i)]) + gotoArray.addf("&&TMP$#_};$n", [rope(id+arraySize)]) line(p, cpsLocals, gotoArray) - let topBlock = p.blocks.len-1 - let oldBody = p.blocks[topBlock].sections[cpsStmts] - p.blocks[topBlock].sections[cpsStmts] = nil - - for j in casePos+1 .. <n.len: genStmts(p, n.sons[j]) - let tailB = p.blocks[topBlock].sections[cpsStmts] - - p.blocks[topBlock].sections[cpsStmts] = nil - for j in 0 .. casePos-1: genStmts(p, n.sons[j]) - let tailA = p.blocks[topBlock].sections[cpsStmts] + for j in 0..<casePos: + genStmts(p, n[j]) - p.blocks[topBlock].sections[cpsStmts] = oldBody & tailA - - let caseStmt = n.sons[casePos] - var a: TLoc - initLocExpr(p, caseStmt.sons[0], a) + let caseStmt = n[casePos] + var a: TLoc = initLocExpr(p, caseStmt[0]) # first goto: lineF(p, cpsStmts, "goto *$#[$#];$n", [tmp, a.rdLoc]) - for i in 1 .. <caseStmt.len: + for i in 1..<caseStmt.len: startBlock(p) - let it = caseStmt.sons[i] - for j in 0 .. it.len-2: - if it.sons[j].kind == nkRange: - localError(it.info, "range notation not available for computed goto") + 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.sons[j]) - lineF(p, cpsStmts, "TMP$#_:$n", [intLiteral(val+id+1)]) + + let val = getOrdValue(it[j]) + var lit = newRopeAppender() + intLiteral(toInt64(val)+id+1, lit) + lineF(p, cpsStmts, "TMP$#_:$n", [lit]) + genStmts(p, it.lastSon) - #for j in casePos+1 .. <n.len: genStmts(p, n.sons[j]) # tailB - #for j in 0 .. casePos-1: genStmts(p, n.sons[j]) # tailA - add(p.s(cpsStmts), tailB) - add(p.s(cpsStmts), tailA) - var a: TLoc - initLocExpr(p, caseStmt.sons[0], a) + 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]) lineF(p, cpsStmts, "goto *$#[$#];$n", [tmp, a.rdLoc]) endBlock(p) + 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(sonsLen(t) == 2) + assert(t.len == 2) inc(p.withinLoop) genLineDir(p, t) preserveBreakIdx: - p.breakIdx = startBlock(p, "while (1) {$n") - p.blocks[p.breakIdx].isLoop = true - initLocExpr(p, t.sons[0], a) - if (t.sons[0].kind != nkIntLit) or (t.sons[0].intVal == 0): - let label = assignLabel(p.blocks[p.breakIdx]) - lineF(p, cpsStmts, "if (!$1) goto $2;$n", [rdLoc(a), label]) - var loopBody = t.sons[1] + var loopBody = t[1] if loopBody.stmtsContainPragma(wComputedGoto) and - hasComputedGoto in CC[cCompiler].props: - # for closure support weird loop bodies are generated: - if loopBody.len == 2 and loopBody.sons[0].kind == nkEmpty: - loopBody = loopBody.sons[1] + 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: + p.breakIdx = startBlock(p, "while (1) {$n") + p.blocks[p.breakIdx].isLoop = true + a = initLocExpr(p, t[0]) + if (t[0].kind != nkIntLit) or (t[0].intVal == 0): + lineF(p, cpsStmts, "if (!$1) goto ", [rdLoc(a)]) + assignLabel(p.blocks[p.breakIdx], p.s(cpsStmts)) + appcg(p, cpsStmts, ";$n", []) genStmts(p, loopBody) - if optProfiler in p.options: - # invoke at loop body exit: - linefmt(p, cpsStmts, "#nimProfile();$n") - endBlock(p) + if optProfiler in p.options: + # invoke at loop body exit: + linefmt(p, cpsStmts, "#nimProfile();$n", []) + endBlock(p) dec(p.withinLoop) proc genBlock(p: BProc, n: PNode, d: var TLoc) = - # bug #4505: allocate the temp in the outer scope - # so that it can escape the generated {}: - if not isEmptyType(n.typ) and d.k == locNone: - getTemp(p, n.typ, d) + 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: p.breakIdx = startBlock(p) - if n.sons[0].kind != nkEmpty: + if n[0].kind != nkEmpty: # named block? - assert(n.sons[0].kind == nkSym) - var sym = n.sons[0].sym + assert(n[0].kind == nkSym) + var sym = n[0].sym sym.loc.k = locOther sym.position = p.breakIdx+1 - expr(p, n.sons[1], d) + expr(p, n[1], d) endBlock(p) proc genParForStmt(p: BProc, t: PNode) = - assert(sonsLen(t) == 3) + assert(t.len == 3) inc(p.withinLoop) genLineDir(p, t) preserveBreakIdx: - let forLoopVar = t.sons[0].sym - var rangeA, rangeB: TLoc - assignLocalVar(p, t.sons[0]) + let forLoopVar = t[0].sym + assignLocalVar(p, t[0]) #initLoc(forLoopVar.loc, locLocalVar, forLoopVar.typ, onStack) #discard mangleName(forLoopVar) - let call = t.sons[1] - initLocExpr(p, call.sons[1], rangeA) - initLocExpr(p, call.sons[2], rangeB) - - lineF(p, cpsStmts, "#pragma omp parallel for $4$n" & - "for ($1 = $2; $1 <= $3; ++$1)", - [forLoopVar.loc.rdLoc, - rangeA.rdLoc, rangeB.rdLoc, - call.sons[3].getStr.rope]) + let call = t[1] + assert(call.len == 4 or call.len == 5) + var rangeA = initLocExpr(p, call[1]) + var rangeB = initLocExpr(p, call[2]) + + # $n at the beginning because of #9710 + if call.len == 4: # procName(a, b, annotation) + if call[0].sym.name.s == "||": # `||`(a, b, annotation) + lineF(p, cpsStmts, "$n#pragma omp $4$n" & + "for ($1 = $2; $1 <= $3; ++$1)", + [forLoopVar.loc.rdLoc, + rangeA.rdLoc, rangeB.rdLoc, + call[3].getStr.rope]) + else: + lineF(p, cpsStmts, "$n#pragma $4$n" & + "for ($1 = $2; $1 <= $3; ++$1)", + [forLoopVar.loc.rdLoc, + rangeA.rdLoc, rangeB.rdLoc, + call[3].getStr.rope]) + else: # `||`(a, b, step, annotation) + var step: TLoc = initLocExpr(p, call[3]) + lineF(p, cpsStmts, "$n#pragma omp $5$n" & + "for ($1 = $2; $1 <= $3; $1 += $4)", + [forLoopVar.loc.rdLoc, + rangeA.rdLoc, rangeB.rdLoc, step.rdLoc, + call[4].getStr.rope]) p.breakIdx = startBlock(p) p.blocks[p.breakIdx].isLoop = true - genStmts(p, t.sons[2]) + genStmts(p, t[2]) endBlock(p) dec(p.withinLoop) proc genBreakStmt(p: BProc, t: PNode) = var idx = p.breakIdx - if t.sons[0].kind != nkEmpty: + if t[0].kind != nkEmpty: # named break? - assert(t.sons[0].kind == nkSym) - var sym = t.sons[0].sym + 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(t.info, "no loop to break") - let label = assignLabel(p.blocks[idx]) + 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.inExceptBlock - p.blocks[idx].nestedExceptStmts) + p.inExceptBlockLen - p.blocks[idx].nestedExceptStmts) genLineDir(p, t) - lineF(p, cpsStmts, "goto $1;$n", [label]) - -proc getRaiseFrmt(p: BProc): string = - result = "#raiseException((#Exception*)$1, $2);$n" + 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 genRaiseStmt(p: BProc, t: PNode) = - if p.inExceptBlock > 0: +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 - var finallyBlock = p.nestedTryStmts[p.nestedTryStmts.len - 1].lastSon - if finallyBlock.kind == nkFinally: - genSimpleBlock(p, finallyBlock.sons[0]) - if t.sons[0].kind != nkEmpty: - var a: TLoc - initLocExpr(p, t.sons[0], a) + 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) - var typ = skipTypes(t.sons[0].typ, abstractPtrs) + 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) - lineCg(p, cpsStmts, getRaiseFrmt(p), [e, makeCString(typ.sym.name.s)]) + 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) - # reraise the last exception: - if p.module.compileToCpp and optNoCppExceptions notin gGlobalOptions: - line(p, cpsStmts, ~"throw;$n") - else: - linefmt(p, cpsStmts, "#reraiseException();$n") + linefmt(p, cpsStmts, "#reraiseException();$n", []) + raiseInstr(p, p.s(cpsStmts)) -proc genCaseGenericBranch(p: BProc, b: PNode, e: TLoc, +template genCaseGenericBranch(p: BProc, b: PNode, e: TLoc, rangeFormat, eqFormat: FormatStr, labl: TLabel) = - var - x, y: TLoc - var length = sonsLen(b) - for i in countup(0, length - 2): - if b.sons[i].kind == nkRange: - initLocExpr(p, b.sons[i].sons[0], x) - initLocExpr(p, b.sons[i].sons[1], y) + var x, y: TLoc + for i in 0..<b.len - 1: + if b[i].kind == nkRange: + x = initLocExpr(p, b[i][0]) + y = initLocExpr(p, b[i][1]) lineCg(p, cpsStmts, rangeFormat, [rdCharLoc(e), rdCharLoc(x), rdCharLoc(y), labl]) else: - initLocExpr(p, b.sons[i], x) + x = initLocExpr(p, b[i]) lineCg(p, cpsStmts, eqFormat, [rdCharLoc(e), rdCharLoc(x), labl]) proc genCaseSecondPass(p: BProc, t: PNode, d: var TLoc, @@ -611,23 +840,23 @@ proc genCaseSecondPass(p: BProc, t: PNode, d: var TLoc, # bug #4230: avoid false sharing between branches: if d.k == locTemp and isEmptyType(t.typ): d.k = locNone lineF(p, cpsStmts, "LA$1_: ;$n", [rope(labId + i)]) - if t.sons[i].kind == nkOfBranch: - var length = sonsLen(t.sons[i]) - exprBlock(p, t.sons[i].sons[length - 1], d) + if t[i].kind == nkOfBranch: + exprBlock(p, t[i][^1], d) lineF(p, cpsStmts, "goto $1;$n", [lend]) else: - exprBlock(p, t.sons[i].sons[0], d) + exprBlock(p, t[i][0], d) result = lend -proc genIfForCaseUntil(p: BProc, t: PNode, d: var TLoc, +template genIfForCaseUntil(p: BProc, t: PNode, d: var TLoc, rangeFormat, eqFormat: FormatStr, until: int, a: TLoc): 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) - if t.sons[i].kind == nkOfBranch: # else statement - genCaseGenericBranch(p, t.sons[i], a, rangeFormat, eqFormat, + if t[i].kind == nkOfBranch: # else statement + genCaseGenericBranch(p, t[i], a, rangeFormat, eqFormat, "LA" & rope(p.labels) & "_") else: lineF(p, cpsStmts, "goto LA$1_;$n", [rope(p.labels)]) @@ -635,116 +864,139 @@ proc genIfForCaseUntil(p: BProc, t: PNode, d: var TLoc, inc(p.labels) var gotoTarget = p.labels lineF(p, cpsStmts, "goto LA$1_;$n", [rope(gotoTarget)]) - result = genCaseSecondPass(p, t, d, labId, until) + res = genCaseSecondPass(p, t, d, labId, until) lineF(p, cpsStmts, "LA$1_: ;$n", [rope(gotoTarget)]) else: - result = genCaseSecondPass(p, t, d, labId, until) + res = genCaseSecondPass(p, t, d, labId, until) + res -proc genCaseGeneric(p: BProc, t: PNode, d: var TLoc, +template genCaseGeneric(p: BProc, t: PNode, d: var TLoc, rangeFormat, eqFormat: FormatStr) = - var a: TLoc - initLocExpr(p, t.sons[0], a) - var lend = genIfForCaseUntil(p, t, d, rangeFormat, eqFormat, sonsLen(t)-1, a) + var a: TLoc = initLocExpr(p, t[0]) + var lend = genIfForCaseUntil(p, t, d, rangeFormat, eqFormat, t.len-1, a) fixLabel(p, lend) proc genCaseStringBranch(p: BProc, b: PNode, e: TLoc, labl: TLabel, + stringKind: TTypeKind, branches: var openArray[Rope]) = var x: TLoc - var length = sonsLen(b) - for i in countup(0, length - 2): - assert(b.sons[i].kind != nkRange) - initLocExpr(p, b.sons[i], x) - assert(b.sons[i].kind in {nkStrLit..nkTripleStrLit}) - var j = int(hashString(b.sons[i].strVal) and high(branches)) - appcg(p.module, branches[j], "if (#eqStrings($1, $2)) goto $3;$n", + 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" + if stringKind == tyCstring: + appcg(p.module, branches[j], "if (#eqCstrings($1, $2)) goto $3;$n", + [rdLoc(e), rdLoc(x), labl]) + else: + appcg(p.module, branches[j], "if (#eqStrings($1, $2)) goto $3;$n", [rdLoc(e), rdLoc(x), labl]) -proc genStringCase(p: BProc, t: PNode, d: var TLoc) = +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 countup(1, sonsLen(t) - 1): - if t.sons[i].kind == nkOfBranch: inc(strings, sonsLen(t.sons[i]) - 1) + 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[Rope] newSeq(branches, bitMask + 1) - var a: TLoc - initLocExpr(p, t.sons[0], a) # fist pass: gnerate ifs+goto: + var a: TLoc = initLocExpr(p, t[0]) # first pass: generate ifs+goto: var labId = p.labels - for i in countup(1, sonsLen(t) - 1): + for i in 1..<t.len: inc(p.labels) - if t.sons[i].kind == nkOfBranch: - genCaseStringBranch(p, t.sons[i], a, "LA" & rope(p.labels) & "_", - branches) + 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 - linefmt(p, cpsStmts, "switch (#hashString($1) & $2) {$n", - rdLoc(a), rope(bitMask)) - for j in countup(0, high(branches)): - if branches[j] != nil: + if stringKind == tyCstring: + linefmt(p, cpsStmts, "switch (#hashCstring($1) & $2) {$n", + [rdLoc(a), bitMask]) + else: + linefmt(p, cpsStmts, "switch (#hashString($1) & $2) {$n", + [rdLoc(a), bitMask]) + for j in 0..high(branches): + if branches[j] != "": + var lit = newRopeAppender() + intLiteral(j, lit) lineF(p, cpsStmts, "case $1: $n$2break;$n", - [intLiteral(j), branches[j]]) + [lit, branches[j]]) lineF(p, cpsStmts, "}$n", []) # else statement: - if t.sons[sonsLen(t)-1].kind != nkOfBranch: + if t[^1].kind != nkOfBranch: lineF(p, cpsStmts, "goto LA$1_;$n", [rope(p.labels)]) # third pass: generate statements - var lend = genCaseSecondPass(p, t, d, labId, sonsLen(t)-1) + var lend = genCaseSecondPass(p, t, d, labId, t.len-1) fixLabel(p, lend) else: - genCaseGeneric(p, t, d, "", "if (#eqStrings($1, $2)) goto $3;$n") + if stringKind == tyCstring: + genCaseGeneric(p, t, d, "", "if (#eqCstrings($1, $2)) goto $3;$n") + else: + genCaseGeneric(p, t, d, "", "if (#eqStrings($1, $2)) goto $3;$n") proc branchHasTooBigRange(b: PNode): bool = - for i in countup(0, sonsLen(b)-2): + result = false + for it in b: # last son is block - if (b.sons[i].kind == nkRange) and - b.sons[i].sons[1].intVal - b.sons[i].sons[0].intVal > RangeExpandLimit: + if (it.kind == nkRange) and + it[1].intVal - it[0].intVal > RangeExpandLimit: return true proc ifSwitchSplitPoint(p: BProc, n: PNode): int = - for i in 1..n.len-1: + 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[cCompiler].props: + elif hasSwitchRange notin CC[p.config.cCompiler].props: if branch.kind == nkOfBranch and branchHasTooBigRange(branch): result = i proc genCaseRange(p: BProc, branch: PNode) = - var length = branch.len - for j in 0 .. length-2: + for j in 0..<branch.len-1: if branch[j].kind == nkRange: - if hasSwitchRange in CC[cCompiler].props: - lineF(p, cpsStmts, "case $1 ... $2:$n", [ - genLiteral(p, branch[j][0]), - genLiteral(p, branch[j][1])]) + if hasSwitchRange in CC[p.config.cCompiler].props: + var litA = newRopeAppender() + var litB = newRopeAppender() + genLiteral(p, branch[j][0], litA) + genLiteral(p, branch[j][1], litB) + lineF(p, cpsStmts, "case $1 ... $2:$n", [litA, litB]) else: var v = copyNode(branch[j][0]) while v.intVal <= branch[j][1].intVal: - lineF(p, cpsStmts, "case $1:$n", [genLiteral(p, v)]) + var litA = newRopeAppender() + genLiteral(p, v, litA) + lineF(p, cpsStmts, "case $1:$n", [litA]) inc(v.intVal) else: - lineF(p, cpsStmts, "case $1:$n", [genLiteral(p, branch[j])]) + var litA = newRopeAppender() + genLiteral(p, branch[j], litA) + lineF(p, cpsStmts, "case $1:$n", [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.sons[0], a) + var a: TLoc = initLocExpr(p, n[0]) var lend = if splitPoint > 0: genIfForCaseUntil(p, n, d, rangeFormat = "if ($1 >= $2 && $1 <= $3) goto $4;$n", eqFormat = "if ($1 == $2) goto $3;$n", - splitPoint, a) else: nil + 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 .. < n.len: + 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] @@ -756,114 +1008,389 @@ proc genOrdinalCase(p: BProc, n: PNode, d: var TLoc) = hasDefault = true exprBlock(p, branch.lastSon, d) lineF(p, cpsStmts, "break;$n", []) - if (hasAssume in CC[cCompiler].props) and not hasDefault: - lineF(p, cpsStmts, "default: __assume(0);$n", []) + if not hasDefault: + if hasBuiltinUnreachable in CC[p.config.cCompiler].props: + lineF(p, cpsStmts, "default: __builtin_unreachable();$n", []) + elif hasAssume in CC[p.config.cCompiler].props: + lineF(p, cpsStmts, "default: __assume(0);$n", []) lineF(p, cpsStmts, "}$n", []) - if lend != nil: fixLabel(p, lend) + 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: - getTemp(p, t.typ, d) - case skipTypes(t.sons[0].typ, abstractVarRange).kind + d = getTemp(p, t.typ) + case skipTypes(t[0].typ, abstractVarRange).kind of tyString: - genStringCase(p, t, d) + genStringCase(p, t, tyString, d) + of tyCstring: + genStringCase(p, t, tyCstring, d) of tyFloat..tyFloat128: genCaseGeneric(p, t, d, "if ($1 >= $2 && $1 <= $3) goto $4;$n", "if ($1 == $2) goto $3;$n") else: - if t.sons[0].kind == nkSym and sfGoto in t.sons[0].sym.flags: + 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("<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, 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..<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 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..<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: + startBlock(p, "catch (...) {$n", []) + genExceptBranchBody(t[i][0]) + endBlock(p) + 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) + fillLoc(exvar.sym.loc, locTemp, exvar, OnStack) + startBlock(p, "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 + endBlock(p) + elif isImportedException(typeNode.typ, p.config): + startBlock(p, "catch ($1&) {$n", getTypeDesc(p.module, t[i][j].typ)) + genExceptBranchBody(t[i][^1]) # exception handler body will duplicated for every type + endBlock(p) + + excl p.flags, noSafePoints + discard pop(p.nestedTryStmts) + # general finally block: + if t.len > 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: # - # XXX: There should be a standard dispatch algorithm - # that's used both here and with multi-methods - # # try # { # myDiv(4, 9); - # } catch (NimException& exp) { - # if (isObj(exp, EIO) { - # ... - # } else if (isObj(exp, ESystem) { - # ... - # finallyPart() - # raise; - # } else { - # // general handler - # } - # } - # finallyPart(); + # } 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: - getTemp(p, t.typ, d) + d = getTemp(p, t.typ) genLineDir(p, t) - let exc = getTempName(p.module) - if getCompilerProc("Exception") != nil: - discard cgsym(p.module, "Exception") - else: - discard cgsym(p.module, "E_Base") - add(p.nestedTryStmts, 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.sons[0], d) - let length = sonsLen(t) - endBlock(p, ropecg(p.module, "} catch (NimException& $1) {$n", [exc])) - if optStackTrace in p.options: - linefmt(p, cpsStmts, "#setFrame((TFrame*)&FR_);$n") - inc p.inExceptBlock - var i = 1 + expr(p, t[0], d) + endBlock(p) + var catchAllPresent = false - while (i < length) and (t.sons[i].kind == nkExceptBranch): + + 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 - let blen = sonsLen(t.sons[i]) - if i > 1: addf(p.s(cpsStmts), "else ", []) - if blen == 1: + + if t[i].len == 1: # general except section: catchAllPresent = true - startBlock(p) - expr(p, t.sons[i].sons[0], d) - linefmt(p, cpsStmts, "#popCurrentException();$n") + startBlock(p, "catch (...) {$n") + genExceptBranchBody(t[i][0]) endBlock(p) else: - var orExpr: Rope = nil - for j in countup(0, blen - 2): - assert(t.sons[i].sons[j].kind == nkType) - if orExpr != nil: add(orExpr, "||") - appcg(p.module, orExpr, - "#isObj($1.exp->m_type, $2)", - [exc, genTypeInfo(p.module, t.sons[i].sons[j].typ)]) - lineF(p, cpsStmts, "if ($1) ", [orExpr]) - startBlock(p) - expr(p, t.sons[i].sons[blen-1], d) - linefmt(p, cpsStmts, "#popCurrentException();$n") + 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) + fillLoc(exvar.sym.loc, locTemp, exvar, OnUnknown) + startBlock(p, "catch ($1& $2) {$n", getTypeDesc(p.module, t[i][j][1].typ), rdLoc(exvar.sym.loc)) + else: + startBlock(p, "catch ($1&) {$n", getTypeDesc(p.module, t[i][j].typ)) + genExceptBranchBody(t[i][^1]) # exception handler body will duplicated for every type + endBlock(p) + + 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 + startBlock(p, "catch (...) {$n") + genStmts(p, t[^1][0]) + line(p, cpsStmts, "throw;\n") endBlock(p) - inc(i) - # reraise the exception if there was no catch all - # and none of the handlers matched - if not catchAllPresent: - if i > 1: lineF(p, cpsStmts, "else ", []) + 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, 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) + + if 1 < t.len and t[1].kind == nkExceptBranch: + startBlock(p, "if (NIM_UNLIKELY(*nimErr_)) {$n") + else: startBlock(p) - var finallyBlock = t.lastSon - if finallyBlock.kind == nkFinally: - #expr(p, finallyBlock.sons[0], d) - genStmts(p, finallyBlock.sons[0]) + linefmt(p, cpsStmts, "LA$1_:;$n", [lab]) - line(p, cpsStmts, ~"throw;$n") - endBlock(p) + p.nestedTryStmts[^1].inExcept = true + var i = 1 + while (i < t.len) and (t[i].kind == nkExceptBranch): - lineF(p, cpsStmts, "}$n", []) # end of catch block - dec p.inExceptBlock + inc p.labels + let nextExcept = p.labels + p.nestedTryStmts[^1].label = nextExcept + # 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) + # 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..<t[i].len - 1: + assert(t[i][j].kind == nkType) + 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(t[i][j].typ) + appcg(p.module, orExpr, "#isObjDisplayCheck(#nimBorrowCurrentException()->$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) - if (i < length) and (t.sons[i].kind == nkFinally): - genSimpleBlock(p, t.sons[i].sons[0]) + 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 genTry(p: BProc, t: PNode, d: var TLoc) = +proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) = # code to generate: # # XXX: There should be a standard dispatch algorithm @@ -893,272 +1420,291 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = # propagateCurrentException(); # if not isEmptyType(t.typ) and d.k == locNone: - getTemp(p, t.typ, d) - p.module.includeHeader("<setjmp.h>") - genLineDir(p, t) - var safePoint = getTempName(p.module) - if getCompilerProc("Exception") != nil: - discard cgsym(p.module, "Exception") + 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: - discard cgsym(p.module, "E_Base") - linefmt(p, cpsLocals, "#TSafePoint $1;$n", safePoint) - linefmt(p, cpsStmts, "#pushSafePoint(&$1);$n", safePoint) - if isDefined("nimStdSetjmp"): - linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", safePoint) - elif isDefined("nimSigSetjmp"): - linefmt(p, cpsStmts, "$1.status = sigsetjmp($1.context, 0);$n", safePoint) - elif isDefined("nimRawSetjmp"): - linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", safePoint) + 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: - linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", safePoint) - startBlock(p, "if ($1.status == 0) {$n", [safePoint]) - var length = sonsLen(t) - add(p.nestedTryStmts, t) - expr(p, t.sons[0], d) - linefmt(p, cpsStmts, "#popSafePoint();$n") - endBlock(p) - startBlock(p, "else {$n") - linefmt(p, cpsStmts, "#popSafePoint();$n") - if optStackTrace in p.options: - linefmt(p, cpsStmts, "#setFrame((TFrame*)&FR_);$n") - inc p.inExceptBlock + startBlock(p) + p.nestedTryStmts[^1].inExcept = true var i = 1 - while (i < length) and (t.sons[i].kind == nkExceptBranch): + 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 - var blen = sonsLen(t.sons[i]) - if blen == 1: + if t[i].len == 1: # general except section: if i > 1: lineF(p, cpsStmts, "else", []) startBlock(p) - linefmt(p, cpsStmts, "$1.status = 0;$n", safePoint) - expr(p, t.sons[i].sons[0], d) - linefmt(p, cpsStmts, "#popCurrentException();$n") + 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: Rope = nil - for j in countup(0, blen - 2): - assert(t.sons[i].sons[j].kind == nkType) - if orExpr != nil: add(orExpr, "||") - let isObjFormat = if not p.module.compileToCpp: - "#isObj(#getCurrentException()->Sup.m_type, $1)" - else: "#isObj(#getCurrentException()->m_type, $1)" - appcg(p.module, orExpr, isObjFormat, - [genTypeInfo(p.module, t.sons[i].sons[j].typ)]) + var orExpr = newRopeAppender() + for j in 0..<t[i].len - 1: + assert(t[i][j].kind == nkType) + 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(t[i][j].typ) + appcg(p.module, orExpr, "#isObjDisplayCheck(#nimBorrowCurrentException()->$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]) - linefmt(p, cpsStmts, "$1.status = 0;$n", safePoint) - expr(p, t.sons[i].sons[blen-1], d) - linefmt(p, cpsStmts, "#popCurrentException();$n") + 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) - dec p.inExceptBlock discard pop(p.nestedTryStmts) endBlock(p) # end of else block - if i < length and t.sons[i].kind == nkFinally: + if i < t.len and t[i].kind == nkFinally: p.finallySafePoints.add(safePoint) - genSimpleBlock(p, t.sons[i].sons[0]) + 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) - linefmt(p, cpsStmts, "if ($1.status != 0) #reraiseException();$n", safePoint) + if not quirkyExceptions: + linefmt(p, cpsStmts, "if ($1.status != 0) #reraiseException();$n", [safePoint]) -proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): Rope = +proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false; result: var Rope) = var res = "" - for i in countup(0, sonsLen(t) - 1): - case t.sons[i].kind + 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(t.sons[i].strVal) + res.add(it.strVal) of nkSym: - var sym = t.sons[i].sym + var sym = it.sym if sym.kind in {skProc, skFunc, skIterator, skMethod}: - var a: TLoc - initLocExpr(p, t.sons[i], a) + var a: TLoc = initLocExpr(p, it) res.add($rdLoc(a)) elif sym.kind == skType: res.add($getTypeDesc(p.module, sym.typ)) else: - var r = sym.loc.r - if r == nil: - # if no name has already been given, - # it doesn't matter much: - r = mangleName(p.module, sym) - sym.loc.r = r # but be consequent! - res.add($r) + discard getTypeDesc(p.module, skipTypes(sym.typ, abstractPtrs)) + fillBackendName(p.module, sym) + res.add($sym.loc.snippet) of nkTypeOfExpr: - res.add($getTypeDesc(p.module, t.sons[i].typ)) + res.add($getTypeDesc(p.module, it.typ)) else: - var a: TLoc - initLocExpr(p, t.sons[i], a) + discard getTypeDesc(p.module, skipTypes(it.typ, abstractPtrs)) + var a: TLoc = initLocExpr(p, it) res.add($a.rdLoc) - #internalError(t.sons[i].info, "genAsmOrEmitStmt()") - if isAsmStmt and hasGnuAsm in CC[cCompiler].props: + if isAsmStmt and hasGnuAsm in CC[p.config.cCompiler].props: for x in splitLines(res): var j = 0 - while x[j] in {' ', '\t'}: inc(j) - if x[j] in {'"', ':'}: - # don't modify the line if already in quotes or - # some clobber register list: - add(result, x); add(result, tnl) - elif x[j] != '\0': - # ignore empty lines - add(result, "\"") - add(result, x) - add(result, "\\n\"\n") + 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(tnl) - result = res.rope + res.add("\L") + result.add res.rope proc genAsmStmt(p: BProc, t: PNode) = assert(t.kind == nkAsmStmt) genLineDir(p, t) - var s = genAsmOrEmitStmt(p, t, isAsmStmt=true) + 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? - addf(p.module.s[cfsProcHeaders], CC[cCompiler].asmStmtFrmt, [s]) + p.module.s[cfsProcHeaders].add runtimeFormat(CC[p.config.cCompiler].asmStmtFrmt, [s]) else: - lineF(p, cpsStmts, CC[cCompiler].asmStmtFrmt, [s]) + 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.sons[0].kind in {nkStrLit..nkTripleStrLit}: - let sec = n.sons[0].strVal - if sec.startsWith("/*TYPESECTION*/"): result = cfsTypes + 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 = genAsmOrEmitStmt(p, t.sons[1]) + 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) - add(p.module.s[section], s) + genCLineDir(p.module.s[section], t.info, p.config) + p.module.s[section].add(s) else: genLineDir(p, t) line(p, cpsStmts, s) -proc genBreakPoint(p: BProc, t: PNode) = - var name: string - if optEndb in p.options: - if t.kind == nkExprColonExpr: - assert(t.sons[1].kind in {nkStrLit..nkTripleStrLit}) - name = normalize(t.sons[1].strVal) - else: - inc(p.module.g.breakPointId) - name = "bp" & $p.module.g.breakPointId - genLineDir(p, t) # BUGFIX - appcg(p.module, p.module.g.breakpoints, - "#dbgRegisterBreakpoint($1, (NCSTRING)$2, (NCSTRING)$3);$n", [ - rope(toLinenumber(t.info)), makeCString(toFilename(t.info)), - makeCString(name)]) - -proc genWatchpoint(p: BProc, n: PNode) = - if optEndb notin p.options: return - var a: TLoc - initLocExpr(p, n.sons[1], a) - let typ = skipTypes(n.sons[1].typ, abstractVarRange) - lineCg(p, cpsStmts, "#dbgRegisterWatchpoint($1, (NCSTRING)$2, $3);$n", - [a.addrLoc, makeCString(renderTree(n.sons[1])), - genTypeInfo(p.module, typ)]) - proc genPragma(p: BProc, n: PNode) = - for i in countup(0, sonsLen(n) - 1): - var it = n.sons[i] + for i in 0..<n.len: + let it = n[i] case whichPragma(it) of wEmit: genEmit(p, it) - of wBreakpoint: genBreakPoint(p, it) - of wWatchPoint: genWatchpoint(p, it) - of wInjectStmt: - var p = newProc(nil, p.module) - p.options = p.options - {optLineTrace, optStackTrace} - genStmts(p, it.sons[1]) - p.module.injectStmt = p.s(cpsStmts) + of wPush: + processPushBackendOption(p.config, p.optionsStack, p.options, n, i+1) + of wPop: + processPopBackendOption(p.config, p.optionsStack, p.options) else: discard -proc fieldDiscriminantCheckNeeded(p: BProc, asgn: PNode): bool = - if optFieldCheck in p.options: - var le = asgn.sons[0] - if le.kind == nkCheckedFieldExpr: - var field = le.sons[0].sons[1].sym - result = sfDiscriminant in field.flags - elif le.kind == nkDotExpr: - var field = le.sons[1].sym - result = sfDiscriminant in field.flags proc genDiscriminantCheck(p: BProc, a, tmp: TLoc, objtype: PType, field: PSym) = var t = skipTypes(objtype, abstractVar) assert t.kind == tyObject - discard genTypeInfo(p.module, t) - var L = lengthOrd(field.typ) + discard genTypeInfoV1(p.module, t, a.lode.info) if not containsOrIncl(p.module.declaredThings, field.id): appcg(p.module, cfsVars, "extern $1", - discriminatorTableDecl(p.module, t, field)) + [discriminatorTableDecl(p.module, t, field)]) + var lit = newRopeAppender() + intLiteral(toInt64(lengthOrd(p.config, field.typ))+1, lit) lineCg(p, cpsStmts, "#FieldDiscriminantCheck((NI)(NU)($1), (NI)(NU)($2), $3, $4);$n", [rdLoc(a), rdLoc(tmp), discriminatorTableName(p.module, t, field), - intLiteral(L+1)]) + 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 a, tmp: TLoc - var dotExpr = e.sons[0] - if dotExpr.kind == nkCheckedFieldExpr: dotExpr = dotExpr.sons[0] - initLocExpr(p, e.sons[0], a) - getTemp(p, a.t, tmp) - expr(p, e.sons[1], tmp) - genDiscriminantCheck(p, a, tmp, dotExpr.sons[0].typ, dotExpr.sons[1].sym) + 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 patchAsgnStmtListExpr(father, orig, n: PNode) = - case n.kind - of nkDerefExpr, nkHiddenDeref: - let asgn = copyNode(orig) - asgn.add orig[0] - asgn.add n - father.add asgn - of nkStmtList, nkStmtListExpr: - for x in n: - patchAsgnStmtListExpr(father, orig, x) - else: - father.add n - proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) = - if e.sons[0].kind == nkSym and sfGoto in e.sons[0].sym.flags: + 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) - genGotoVar(p, e.sons[1]) - elif not fieldDiscriminantCheckNeeded(p, e): - # this fixes bug #6422 but we really need to change the representation of - # arrays in the backend... + asgnFieldDiscriminant(p, e) + else: let le = e[0] let ri = e[1] - var needsRepair = false - var it = ri - while it.kind in {nkStmtList, nkStmtListExpr}: - it = it.lastSon - needsRepair = true - if it.kind in {nkDerefExpr, nkHiddenDeref} and needsRepair: - var patchedTree = newNodeI(nkStmtList, e.info) - patchAsgnStmtListExpr(patchedTree, e, ri) - genStmts(p, patchedTree) - return - - var a: TLoc - if le.kind in {nkDerefExpr, nkHiddenDeref}: - genDeref(p, le, a, enforceDeref=true) - else: - initLocExpr(p, le, a) + 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, e.sons[0], ri, a) - else: - genLineDir(p, e) - asgnFieldDiscriminant(p, e) + loadInto(p, le, ri, a) proc genStmts(p: BProc, t: PNode) = - var a: TLoc + var a: TLoc = default(TLoc) + + let isPush = p.config.hasHint(hintExtendedContext) + if isPush: pushInfoContext(p.config, t.info) expr(p, t, a) - internalAssert a.k in {locNone, locTemp, locLocalVar} + if isPush: popInfoContext(p.config) + internalAssert p.config, a.k in {locNone, locTemp, locLocalVar, locExpr} |