--exception:goto switch for deterministic exception handling (#12977)

This implements "deterministic" exception handling for Nim based on goto instead of setjmp. This means raising an exception is much cheaper than in C++'s table based implementations. Supports hard realtime systems. Default for --gc:arc and the C target because it's generally a good idea and arc is all about deterministic behavior.

Note: This implies that fatal runtime traps are not catchable anymore! This needs to be documented.
This commit is contained in:
Andreas Rumpf 2020-01-01 10:01:49 +01:00 • committed by GitHub
commit c3344862b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 640 additions and 183 deletions

View file

@ -1763,7 +1763,7 @@ proc toObject*(typ: PType): PType =
proc isImportedException*(t: PType; conf: ConfigRef): bool = proc isImportedException*(t: PType; conf: ConfigRef): bool =
assert t != nil assert t != nil
if optNoCppExceptions in conf.globalOptions: if conf.exc != excCpp:
return false return false
let base = t.skipTypes({tyAlias, tyPtr, tyDistinct, tyGenericInst}) let base = t.skipTypes({tyAlias, tyPtr, tyDistinct, tyGenericInst})
@ -1834,7 +1834,7 @@ template assignment*(t: PType): PSym = t.attachedOps[attachedAsgn]
template asink*(t: PType): PSym = t.attachedOps[attachedSink] template asink*(t: PType): PSym = t.attachedOps[attachedSink]
const magicsThatCanRaise = { const magicsThatCanRaise = {
mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst} mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst, mEcho}
proc canRaiseConservative*(fn: PNode): bool = proc canRaiseConservative*(fn: PNode): bool =
if fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise: if fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise:
@ -1844,9 +1844,12 @@ proc canRaiseConservative*(fn: PNode): bool =
proc canRaise*(fn: PNode): bool = proc canRaise*(fn: PNode): bool =
if fn.kind == nkSym and (fn.sym.magic notin magicsThatCanRaise or if fn.kind == nkSym and (fn.sym.magic notin magicsThatCanRaise or
{sfImportc, sfInfixCall} * fn.sym.flags == {sfImportc}): {sfImportc, sfInfixCall} * fn.sym.flags == {sfImportc} or
sfGeneratedOp in fn.sym.flags):
result = false result = false
elif fn.kind == nkSym and fn.sym.magic == mEcho:
result = true
else: else:
result = fn.typ != nil and ((fn.typ.n[0].len < effectListLen) or result = fn.typ != nil and fn.typ.n != nil and ((fn.typ.n[0].len < effectListLen) or
(fn.typ.n[0][exceptionEffects] != nil and (fn.typ.n[0][exceptionEffects] != nil and
fn.typ.n[0][exceptionEffects].safeLen > 0)) fn.typ.n[0][exceptionEffects].safeLen > 0))

View file

@ -567,6 +567,8 @@ proc genCall(p: BProc, e: PNode, d: var TLoc) =
else: else:
genPrefixCall(p, nil, e, d) genPrefixCall(p, nil, e, d)
postStmtActions(p) postStmtActions(p)
if p.config.exc == excGoto and canRaise(e[0]):
raiseExit(p)
proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) = proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
if ri[0].typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure: if ri[0].typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure:
@ -578,3 +580,5 @@ proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
else: else:
genPrefixCall(p, le, ri, d) genPrefixCall(p, le, ri, d)
postStmtActions(p) postStmtActions(p)
if p.config.exc == excGoto and canRaise(ri[0]):
raiseExit(p)

View file

@ -2675,10 +2675,13 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
line(p, cpsStmts, "(void)(" & a.r & ");\L") line(p, cpsStmts, "(void)(" & a.r & ");\L")
of nkAsmStmt: genAsmStmt(p, n) of nkAsmStmt: genAsmStmt(p, n)
of nkTryStmt, nkHiddenTryStmt: of nkTryStmt, nkHiddenTryStmt:
if p.module.compileToCpp and optNoCppExceptions notin p.config.globalOptions: case p.config.exc
of excGoto:
genTryGoto(p, n, d)
of excCpp:
genTryCpp(p, n, d) genTryCpp(p, n, d)
else: else:
genTry(p, n, d) genTrySetjmp(p, n, d)
of nkRaiseStmt: genRaiseStmt(p, n) of nkRaiseStmt: genRaiseStmt(p, n)
of nkTypeSection: of nkTypeSection:
# we have to emit the type information for object types here to support # we have to emit the type information for object types here to support

View file

@ -196,13 +196,13 @@ proc genState(p: BProc, n: PNode) =
proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) = proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) =
# Called by return and break stmts. # Called by return and break stmts.
# Deals with issues faced when jumping out of try/except/finally stmts, # Deals with issues faced when jumping out of try/except/finally stmts.
var stack = newSeq[tuple[fin: PNode, inExcept: bool]](0) var stack = newSeq[tuple[fin: PNode, inExcept: bool, label: Natural]](0)
for i in 1..howManyTrys: for i in 1..howManyTrys:
let tryStmt = p.nestedTryStmts.pop let tryStmt = p.nestedTryStmts.pop
if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions: if p.config.exc == excSetjmp:
# Pop safe points generated by try # Pop safe points generated by try
if not tryStmt.inExcept: if not tryStmt.inExcept:
linefmt(p, cpsStmts, "#popSafePoint();$n", []) linefmt(p, cpsStmts, "#popSafePoint();$n", [])
@ -221,10 +221,10 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) =
for i in countdown(howManyTrys-1, 0): for i in countdown(howManyTrys-1, 0):
p.nestedTryStmts.add(stack[i]) p.nestedTryStmts.add(stack[i])
if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions: if p.config.exc != excCpp:
# Pop exceptions that was handled by the # Pop exceptions that was handled by the
# except-blocks we are in # except-blocks we are in
if not p.noSafePoints: if noSafePoints notin p.flags:
for i in countdown(howManyExcepts-1, 0): for i in countdown(howManyExcepts-1, 0):
linefmt(p, cpsStmts, "#popCurrentException();$n", []) linefmt(p, cpsStmts, "#popCurrentException();$n", [])
@ -237,7 +237,7 @@ proc genGotoState(p: BProc, n: PNode) =
var a: TLoc var a: TLoc
initLocExpr(p, n[0], a) initLocExpr(p, n[0], a)
lineF(p, cpsStmts, "switch ($1) {$n", [rdLoc(a)]) lineF(p, cpsStmts, "switch ($1) {$n", [rdLoc(a)])
p.beforeRetNeeded = true p.flags.incl beforeRetNeeded
lineF(p, cpsStmts, "case -1:$n", []) lineF(p, cpsStmts, "case -1:$n", [])
blockLeaveActions(p, blockLeaveActions(p,
howManyTrys = p.nestedTryStmts.len, howManyTrys = p.nestedTryStmts.len,
@ -454,13 +454,13 @@ proc genIf(p: BProc, n: PNode, d: var TLoc) =
proc genReturnStmt(p: BProc, t: PNode) = proc genReturnStmt(p: BProc, t: PNode) =
if nfPreventCg in t.flags: return if nfPreventCg in t.flags: return
p.beforeRetNeeded = true p.flags.incl beforeRetNeeded
genLineDir(p, t) genLineDir(p, t)
if (t[0].kind != nkEmpty): genStmts(p, t[0]) if (t[0].kind != nkEmpty): genStmts(p, t[0])
blockLeaveActions(p, blockLeaveActions(p,
howManyTrys = p.nestedTryStmts.len, howManyTrys = p.nestedTryStmts.len,
howManyExcepts = p.inExceptBlockLen) howManyExcepts = p.inExceptBlockLen)
if (p.finallySafePoints.len > 0) and not p.noSafePoints: if (p.finallySafePoints.len > 0) and noSafePoints notin p.flags:
# If we're in a finally block, and we came here by exception # If we're in a finally block, and we came here by exception
# consume it before we return. # consume it before we return.
var safePoint = p.finallySafePoints[^1] var safePoint = p.finallySafePoints[^1]
@ -687,15 +687,27 @@ proc genBreakStmt(p: BProc, t: PNode) =
genLineDir(p, t) genLineDir(p, t)
lineF(p, cpsStmts, "goto $1;$n", [label]) lineF(p, cpsStmts, "goto $1;$n", [label])
proc raiseExit(p: BProc) =
assert p.config.exc == excGoto
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 genRaiseStmt(p: BProc, t: PNode) = proc genRaiseStmt(p: BProc, t: PNode) =
if p.module.compileToCpp: if p.config.exc != excGoto:
discard cgsym(p.module, "popCurrentExceptionEx") if p.config.exc == excCpp:
if p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept: discard cgsym(p.module, "popCurrentExceptionEx")
# if the current try stmt have a finally block, if p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept:
# we must execute it before reraising # if the current try stmt have a finally block,
let finallyBlock = p.nestedTryStmts[^1].fin # we must execute it before reraising
if finallyBlock != nil: let finallyBlock = p.nestedTryStmts[^1].fin
genSimpleBlock(p, finallyBlock[0]) if finallyBlock != nil:
genSimpleBlock(p, finallyBlock[0])
if t[0].kind != nkEmpty: if t[0].kind != nkEmpty:
var a: TLoc var a: TLoc
initLocExprSingleUse(p, t[0], a) initLocExprSingleUse(p, t[0], a)
@ -714,10 +726,22 @@ proc genRaiseStmt(p: BProc, t: PNode) =
else: else:
genLineDir(p, t) genLineDir(p, t)
# reraise the last exception: # reraise the last exception:
if p.module.compileToCpp and optNoCppExceptions notin p.config.globalOptions: if p.config.exc == excCpp:
line(p, cpsStmts, ~"throw;$n") line(p, cpsStmts, ~"throw;$n")
else: else:
linefmt(p, cpsStmts, "#reraiseException();$n", []) linefmt(p, cpsStmts, "#reraiseException();$n", [])
if p.config.exc == excGoto:
let L = p.nestedTryStmts.len
if L == 0:
p.flags.incl beforeRetNeeded
# easy case, simply goto 'ret':
lineCg(p, cpsStmts, "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.
lineCg(p, cpsStmts, "goto LA$1_;$n",
[p.nestedTryStmts[L-1].label])
# + ord(p.nestedTryStmts[L-1].inExcept)])
template genCaseGenericBranch(p: BProc, b: PNode, e: TLoc, template genCaseGenericBranch(p: BProc, b: PNode, e: TLoc,
rangeFormat, eqFormat: FormatStr, labl: TLabel) = rangeFormat, eqFormat: FormatStr, labl: TLabel) =
@ -906,8 +930,8 @@ proc genCase(p: BProc, t: PNode, d: var TLoc) =
proc genRestoreFrameAfterException(p: BProc) = proc genRestoreFrameAfterException(p: BProc) =
if optStackTrace in p.module.config.options: if optStackTrace in p.module.config.options:
if not p.hasCurFramePointer: if hasCurFramePointer notin p.flags:
p.hasCurFramePointer = true p.flags.incl hasCurFramePointer
p.procSec(cpsLocals).add(ropecg(p.module, "\tTFrame* _nimCurFrame;$n", [])) p.procSec(cpsLocals).add(ropecg(p.module, "\tTFrame* _nimCurFrame;$n", []))
p.procSec(cpsInit).add(ropecg(p.module, "\t_nimCurFrame = #getFrame();$n", [])) p.procSec(cpsInit).add(ropecg(p.module, "\t_nimCurFrame = #getFrame();$n", []))
linefmt(p, cpsStmts, "#setFrame(_nimCurFrame);$n", []) linefmt(p, cpsStmts, "#setFrame(_nimCurFrame);$n", [])
@ -938,7 +962,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
genLineDir(p, t) genLineDir(p, t)
discard cgsym(p.module, "popCurrentExceptionEx") discard cgsym(p.module, "popCurrentExceptionEx")
let fin = if t[^1].kind == nkFinally: t[^1] else: nil let fin = if t[^1].kind == nkFinally: t[^1] else: nil
p.nestedTryStmts.add((fin, false)) p.nestedTryStmts.add((fin, false, 0.Natural))
startBlock(p, "try {$n") startBlock(p, "try {$n")
expr(p, t[0], d) expr(p, t[0], d)
endBlock(p) endBlock(p)
@ -982,7 +1006,116 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
genSimpleBlock(p, t[^1][0]) genSimpleBlock(p, t[^1][0])
proc genTry(p: BProc, t: PNode, d: var TLoc) = proc bodyCanRaise(n: PNode): bool =
case n.kind
of nkCallKinds:
result = canRaise(n[0])
if not result:
# also check the arguments:
for i in 1 ..< n.len:
if bodyCanRaise(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(n[i]): return true
result = false
proc genTryGoto(p: BProc; t: PNode; d: var TLoc) =
if not bodyCanRaise(t):
# optimize away the 'try' block:
expr(p, t[0], d)
if t.len > 1 and t[^1].kind == nkFinally:
genStmts(p, t[^1][0])
return
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
inc p.labels, 2
let lab = p.labels-1
p.nestedTryStmts.add((fin, false, Natural lab))
p.flags.incl nimErrorFlagAccessed
linefmt(p, cpsStmts, "NI oldNimErr$1_ = *nimErr_; *nimErr_ = 0;;$n", [lab])
expr(p, t[0], d)
if 1 < t.len and t[1].kind == nkExceptBranch:
startBlock(p, "if (NIM_UNLIKELY(*nimErr_)) {$n")
else:
startBlock(p)
# pretend we did handle the error for the safe execution of the sections:
linefmt(p, cpsStmts, "LA$1_: oldNimErr$1_ = *nimErr_; *nimErr_ = 0;$n", [lab])
p.nestedTryStmts[^1].inExcept = true
var i = 1
while (i < t.len) and (t[i].kind == nkExceptBranch):
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, "--oldNimErr$1_;$n", [lab])
expr(p, t[i][0], d)
else:
var orExpr: Rope = nil
for j in 0..<t[i].len - 1:
assert(t[i][j].kind == nkType)
if orExpr != nil: orExpr.add("||")
let checkFor = if optTinyRtti in p.config.globalOptions:
genTypeInfo2Name(p.module, t[i][j].typ)
else:
genTypeInfo(p.module, t[i][j].typ, t[i][j].info)
let memberName = if p.module.compileToCpp: "m_type" else: "Sup.m_type"
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, "--oldNimErr$1_;$n", [lab])
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)
#linefmt(p, cpsStmts, "LA$1_:;$n", [lab+1])
if i < t.len and t[i].kind == nkFinally:
startBlock(p)
if not bodyCanRaise(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:
linefmt(p, cpsStmts, "NI oldNimErrFin$1_ = *nimErr_; *nimErr_ = 0;$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_ += oldNimErr$1_ + (*nimErr_ - oldNimErrFin$1_); oldNimErr$1_ = 0;$n", [lab])
raiseExit(p)
endBlock(p)
# restore the real error value:
linefmt(p, cpsStmts, "*nimErr_ += oldNimErr$1_;$n", [lab])
proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) =
# code to generate: # code to generate:
# #
# XXX: There should be a standard dispatch algorithm # XXX: There should be a standard dispatch algorithm
@ -1013,12 +1146,12 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
# #
if not isEmptyType(t.typ) and d.k == locNone: if not isEmptyType(t.typ) and d.k == locNone:
getTemp(p, t.typ, d) getTemp(p, t.typ, d)
let quirkyExceptions = isDefined(p.config, "nimQuirky") or let quirkyExceptions = p.config.exc == excQuirky or
(t.kind == nkHiddenTryStmt and sfSystemModule in p.module.module.flags) (t.kind == nkHiddenTryStmt and sfSystemModule in p.module.module.flags)
if not quirkyExceptions: if not quirkyExceptions:
p.module.includeHeader("<setjmp.h>") p.module.includeHeader("<setjmp.h>")
else: else:
p.noSafePoints = true p.flags.incl noSafePoints
genLineDir(p, t) genLineDir(p, t)
discard cgsym(p.module, "Exception") discard cgsym(p.module, "Exception")
var safePoint: Rope var safePoint: Rope
@ -1036,7 +1169,7 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint]) linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint])
startBlock(p, "if ($1.status == 0) {$n", [safePoint]) startBlock(p, "if ($1.status == 0) {$n", [safePoint])
let fin = if t[^1].kind == nkFinally: t[^1] else: nil let fin = if t[^1].kind == nkFinally: t[^1] else: nil
p.nestedTryStmts.add((fin, quirkyExceptions)) p.nestedTryStmts.add((fin, quirkyExceptions, 0.Natural))
expr(p, t[0], d) expr(p, t[0], d)
if not quirkyExceptions: if not quirkyExceptions:
linefmt(p, cpsStmts, "#popSafePoint();$n", []) linefmt(p, cpsStmts, "#popSafePoint();$n", [])

View file

@ -16,8 +16,8 @@ proc emulatedThreadVars(conf: ConfigRef): bool =
result = {optThreads, optTlsEmulation} <= conf.globalOptions result = {optThreads, optTlsEmulation} <= conf.globalOptions
proc accessThreadLocalVar(p: BProc, s: PSym) = proc accessThreadLocalVar(p: BProc, s: PSym) =
if emulatedThreadVars(p.config) and not p.threadVarAccessed: if emulatedThreadVars(p.config) and threadVarAccessed notin p.flags:
p.threadVarAccessed = true p.flags.incl threadVarAccessed
incl p.module.flags, usesThreadVars incl p.module.flags, usesThreadVars
p.procSec(cpsLocals).addf("\tNimThreadVars* NimTV_;$n", []) p.procSec(cpsLocals).addf("\tNimThreadVars* NimTV_;$n", [])
p.procSec(cpsInit).add( p.procSec(cpsInit).add(

View file

@ -481,14 +481,6 @@ proc getIntTemp(p: BProc, result: var TLoc) =
result.lode = lodeTyp getSysType(p.module.g.graph, unknownLineInfo(), tyInt) result.lode = lodeTyp getSysType(p.module.g.graph, unknownLineInfo(), tyInt)
result.flags = {} result.flags = {}
proc initGCFrame(p: BProc): Rope =
if p.gcFrameId > 0: result = "struct {$1} GCFRAME_;$n" % [p.gcFrameType]
proc deinitGCFrame(p: BProc): Rope =
if p.gcFrameId > 0:
result = ropecg(p.module,
"if (((NU)&GCFRAME_) < 4096) #nimGCFrame(&GCFRAME_);$n", [])
proc localVarDecl(p: BProc; n: PNode): Rope = proc localVarDecl(p: BProc; n: PNode): Rope =
let s = n.sym let s = n.sym
if s.loc.k == locNone: if s.loc.k == locNone:
@ -599,6 +591,7 @@ proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc)
proc intLiteral(i: BiggestInt): Rope proc intLiteral(i: BiggestInt): Rope
proc genLiteral(p: BProc, n: PNode): Rope proc genLiteral(p: BProc, n: PNode): Rope
proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType): Rope proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType): Rope
proc raiseExit(p: BProc)
proc initLocExpr(p: BProc, e: PNode, result: var TLoc) = proc initLocExpr(p: BProc, e: PNode, result: var TLoc) =
initLoc(result, locNone, e, OnUnknown) initLoc(result, locNone, e, OnUnknown)
@ -636,13 +629,7 @@ proc initFrame(p: BProc, procname, filename: Rope): Rope =
appcg(p.module, p.module.s[cfsFrameDefines], frameDefines, ["#"]) appcg(p.module, p.module.s[cfsFrameDefines], frameDefines, ["#"])
discard cgsym(p.module, "nimFrame") discard cgsym(p.module, "nimFrame")
if p.maxFrameLen > 0: result = ropecg(p.module, "\tnimfr_($1, $2);$n", [procname, filename])
discard cgsym(p.module, "VarSlot")
result = ropecg(p.module, "\tnimfrs_($1, $2, $3, $4);$n",
[procname, filename, p.maxFrameLen,
p.blocks[0].frameLen])
else:
result = ropecg(p.module, "\tnimfr_($1, $2);$n", [procname, filename])
proc initFrameNoDebug(p: BProc; frame, procname, filename: Rope; line: int): Rope = proc initFrameNoDebug(p: BProc; frame, procname, filename: Rope; line: int): Rope =
discard cgsym(p.module, "nimFrame") discard cgsym(p.module, "nimFrame")
@ -979,6 +966,13 @@ proc getProcTypeCast(m: BModule, prc: PSym): Rope =
genProcParams(m, prc.typ, rettype, params, check) genProcParams(m, prc.typ, rettype, params, check)
result = "$1(*)$2" % [rettype, params] result = "$1(*)$2" % [rettype, params]
proc genProcBody(p: BProc; procBody: PNode) =
genStmts(p, procBody) # modifies p.locals, p.init, etc.
if {nimErrorFlagAccessed, nimErrorFlagDeclared} * p.flags == {nimErrorFlagAccessed}:
p.flags.incl nimErrorFlagDeclared
p.blocks[0].sections[cpsLocals].add(ropecg(p.module, "NI* nimErr_;$n", []))
p.blocks[0].sections[cpsInit].add(ropecg(p.module, "nimErr_ = #nimErrorFlag();$n", []))
proc genProcAux(m: BModule, prc: PSym) = proc genProcAux(m: BModule, prc: PSym) =
var p = newProc(prc, m) var p = newProc(prc, m)
var header = genProcHeader(m, prc) var header = genProcHeader(m, prc)
@ -1029,7 +1023,8 @@ proc genProcAux(m: BModule, prc: PSym) =
if param.typ.isCompileTimeOnly: continue if param.typ.isCompileTimeOnly: continue
assignParam(p, param, prc.typ[0]) assignParam(p, param, prc.typ[0])
closureSetup(p, prc) closureSetup(p, prc)
genStmts(p, procBody) # modifies p.locals, p.init, etc. genProcBody(p, procBody)
var generatedProc: Rope var generatedProc: Rope
generatedProc.genCLineDir prc.info, m.config generatedProc.genCLineDir prc.info, m.config
if sfNoReturn in prc.flags: if sfNoReturn in prc.flags:
@ -1046,8 +1041,7 @@ proc genProcAux(m: BModule, prc: PSym) =
# This fixes the use of methods and also the case when 2 functions within the same module # This fixes the use of methods and also the case when 2 functions within the same module
# call each other using directly the "_actual" versions (an optimization) - see issue #11608 # call each other using directly the "_actual" versions (an optimization) - see issue #11608
m.s[cfsProcHeaders].addf("$1;\n", [header]) m.s[cfsProcHeaders].addf("$1;\n", [header])
generatedProc.add ropecg(p.module, "$1 {", [header]) generatedProc.add ropecg(p.module, "$1 {$n", [header])
generatedProc.add(initGCFrame(p))
if optStackTrace in prc.options: if optStackTrace in prc.options:
generatedProc.add(p.s(cpsLocals)) generatedProc.add(p.s(cpsLocals))
var procname = makeCString(prc.name.s) var procname = makeCString(prc.name.s)
@ -1057,11 +1051,12 @@ proc genProcAux(m: BModule, prc: PSym) =
if optProfiler in prc.options: if optProfiler in prc.options:
# invoke at proc entry for recursion: # invoke at proc entry for recursion:
appcg(p, cpsInit, "\t#nimProfile();$n", []) appcg(p, cpsInit, "\t#nimProfile();$n", [])
if p.beforeRetNeeded: generatedProc.add("{") # this pair of {} is required for C++ (C++ is weird with its
# control flow integrity checks):
if beforeRetNeeded in p.flags: generatedProc.add("{")
generatedProc.add(p.s(cpsInit)) generatedProc.add(p.s(cpsInit))
generatedProc.add(p.s(cpsStmts)) generatedProc.add(p.s(cpsStmts))
if p.beforeRetNeeded: generatedProc.add(~"\t}BeforeRet_: ;$n") if beforeRetNeeded in p.flags: generatedProc.add(~"\t}BeforeRet_: ;$n")
generatedProc.add(deinitGCFrame(p))
if optStackTrace in prc.options: generatedProc.add(deinitFrame(p)) if optStackTrace in prc.options: generatedProc.add(deinitFrame(p))
generatedProc.add(returnStmt) generatedProc.add(returnStmt)
generatedProc.add(~"}$N") generatedProc.add(~"}$N")
@ -1645,10 +1640,6 @@ proc genInitCode(m: BModule) =
# add new scope for following code, because old vcc compiler need variable # add new scope for following code, because old vcc compiler need variable
# be defined at the top of the block # be defined at the top of the block
prc.addf("{$N", []) prc.addf("{$N", [])
if m.initProc.gcFrameId > 0:
moduleInitRequired = true
prc.add(initGCFrame(m.initProc))
writeSection(initProc, cpsLocals) writeSection(initProc, cpsLocals)
if m.initProc.s(cpsInit).len > 0 or m.initProc.s(cpsStmts).len > 0: if m.initProc.s(cpsInit).len > 0 or m.initProc.s(cpsStmts).len > 0:
@ -1666,12 +1657,11 @@ proc genInitCode(m: BModule) =
writeSection(initProc, cpsInit, m.hcrOn) writeSection(initProc, cpsInit, m.hcrOn)
writeSection(initProc, cpsStmts) writeSection(initProc, cpsStmts)
if beforeRetNeeded in m.initProc.flags:
prc.add(~"\tBeforeRet_: ;$n")
if optStackTrace in m.initProc.options and preventStackTrace notin m.flags: if optStackTrace in m.initProc.options and preventStackTrace notin m.flags:
prc.add(deinitFrame(m.initProc)) prc.add(deinitFrame(m.initProc))
if m.initProc.gcFrameId > 0:
moduleInitRequired = true
prc.add(deinitGCFrame(m.initProc))
prc.addf("}$N", []) prc.addf("}$N", [])
prc.addf("}$N$N", []) prc.addf("}$N$N", [])
@ -1894,7 +1884,7 @@ proc myProcess(b: PPassContext, n: PNode): PNode =
if m.hcrOn: if m.hcrOn:
addHcrInitGuards(m.initProc, transformedN, m.inHcrInitGuard) addHcrInitGuards(m.initProc, transformedN, m.inHcrInitGuard)
else: else:
genStmts(m.initProc, transformedN) genProcBody(m.initProc, transformedN)
proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool = proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool =
if optForceFullMake notin m.config.globalOptions: if optForceFullMake notin m.config.globalOptions:
@ -1996,6 +1986,10 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode =
if b == nil: return if b == nil: return
var m = BModule(b) var m = BModule(b)
if sfMainModule in m.module.flags: if sfMainModule in m.module.flags:
let testForError = getCompilerProc(graph, "nimTestErrorFlag")
if testForError != nil and graph.config.exc == excGoto:
n.add newTree(nkCall, testForError.newSymNode)
for i in countdown(high(graph.globalDestructors), 0): for i in countdown(high(graph.globalDestructors), 0):
n.add graph.globalDestructors[i] n.add graph.globalDestructors[i]
if passes.skipCodegen(m.config, n): return if passes.skipCodegen(m.config, n): return
@ -2005,7 +1999,7 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode =
# XXX emit the dispatchers into its own .c file? # XXX emit the dispatchers into its own .c file?
if n != nil: if n != nil:
m.initProc.options = initProcOptions(m) m.initProc.options = initProcOptions(m)
genStmts(m.initProc, n) genProcBody(m.initProc, n)
if m.hcrOn: if m.hcrOn:
# make sure this is pulled in (meaning hcrGetGlobal() is called for it during init) # make sure this is pulled in (meaning hcrGetGlobal() is called for it during init)

View file

@ -64,16 +64,20 @@ type
nestedExceptStmts*: int16 # how many except statements is it nested into nestedExceptStmts*: int16 # how many except statements is it nested into
frameLen*: int16 frameLen*: int16
TCProcFlag* = enum
beforeRetNeeded,
threadVarAccessed,
hasCurFramePointer,
noSafePoints,
nimErrorFlagAccessed,
nimErrorFlagDeclared
TCProc = object # represents C proc that is currently generated TCProc = object # represents C proc that is currently generated
prc*: PSym # the Nim proc that this C proc belongs to prc*: PSym # the Nim proc that this C proc belongs to
beforeRetNeeded*: bool # true iff 'BeforeRet' label for proc is needed flags*: set[TCProcFlag]
threadVarAccessed*: bool # true if the proc already accessed some threadvar
hasCurFramePointer*: bool # true if _nimCurFrame var needed to recover after
# exception is generated
noSafePoints*: bool # the proc doesn't use safe points in exception handling
lastLineInfo*: TLineInfo # to avoid generating excessive 'nimln' statements lastLineInfo*: TLineInfo # to avoid generating excessive 'nimln' statements
currLineInfo*: TLineInfo # AST codegen will make this superfluous currLineInfo*: TLineInfo # AST codegen will make this superfluous
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool]] nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, label: Natural]]
# in how many nested try statements we are # in how many nested try statements we are
# (the vars must be volatile then) # (the vars must be volatile then)
# bool is true when are in the except part of a try block # bool is true when are in the except part of a try block
@ -86,14 +90,11 @@ type
options*: TOptions # options that should be used for code options*: TOptions # options that should be used for code
# generation; this is the same as prc.options # generation; this is the same as prc.options
# unless prc == nil # unless prc == nil
maxFrameLen*: int # max length of frame descriptor
module*: BModule # used to prevent excessive parameter passing module*: BModule # used to prevent excessive parameter passing
withinLoop*: int # > 0 if we are within a loop withinLoop*: int # > 0 if we are within a loop
splitDecls*: int # > 0 if we are in some context for C++ that splitDecls*: int # > 0 if we are in some context for C++ that
# requires 'T x = T()' to become 'T x; x = T()' # requires 'T x = T()' to become 'T x; x = T()'
# (yes, C++ is weird like that) # (yes, C++ is weird like that)
gcFrameId*: Natural # for the GC stack marking
gcFrameType*: Rope # the struct {} we put the GC markers into
sigConflicts*: CountTable[string] sigConflicts*: CountTable[string]
TTypeSeq* = seq[PType] TTypeSeq* = seq[PType]

View file

@ -219,6 +219,7 @@ const
errNoneBoehmRefcExpectedButXFound = "'none', 'boehm' or 'refc' expected, but '$1' found" errNoneBoehmRefcExpectedButXFound = "'none', 'boehm' or 'refc' expected, but '$1' found"
errNoneSpeedOrSizeExpectedButXFound = "'none', 'speed' or 'size' expected, but '$1' found" errNoneSpeedOrSizeExpectedButXFound = "'none', 'speed' or 'size' expected, but '$1' found"
errGuiConsoleOrLibExpectedButXFound = "'gui', 'console' or 'lib' expected, but '$1' found" errGuiConsoleOrLibExpectedButXFound = "'gui', 'console' or 'lib' expected, but '$1' found"
errInvalidExceptionSystem = "'goto', 'setjump', 'cpp' or 'quirky' expected, but '$1' found"
proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo): bool = proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo): bool =
case switch.normalize case switch.normalize
@ -254,6 +255,13 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
else: localError(conf, info, errGuiConsoleOrLibExpectedButXFound % arg) else: localError(conf, info, errGuiConsoleOrLibExpectedButXFound % arg)
of "dynliboverride": of "dynliboverride":
result = isDynlibOverride(conf, arg) result = isDynlibOverride(conf, arg)
of "exceptions":
case arg.normalize
of "cpp": result = conf.exc == excCpp
of "setjmp": result = conf.exc == excSetjmp
of "quirky": result = conf.exc == excQuirky
of "goto": result = conf.exc == excGoto
else: localError(conf, info, errInvalidExceptionSystem % arg)
else: invalidCmdLineOption(conf, passCmd1, switch, info) else: invalidCmdLineOption(conf, passCmd1, switch, info)
proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool = proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool =
@ -411,8 +419,12 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
expectArg(conf, switch, arg, pass, info) expectArg(conf, switch, arg, pass, info)
if {':', '='} in arg: if {':', '='} in arg:
splitSwitch(conf, arg, key, val, pass, info) splitSwitch(conf, arg, key, val, pass, info)
if cmpIgnoreStyle(key, "nimQuirky") == 0:
conf.exc = excQuirky
defineSymbol(conf.symbols, key, val) defineSymbol(conf.symbols, key, val)
else: else:
if cmpIgnoreStyle(arg, "nimQuirky") == 0:
conf.exc = excQuirky
defineSymbol(conf.symbols, arg) defineSymbol(conf.symbols, arg)
of "undef", "u": of "undef", "u":
expectArg(conf, switch, arg, pass, info) expectArg(conf, switch, arg, pass, info)
@ -457,6 +469,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
defineSymbol(conf.symbols, "gcmarkandsweep") defineSymbol(conf.symbols, "gcmarkandsweep")
of "destructors", "arc": of "destructors", "arc":
conf.selectedGC = gcArc conf.selectedGC = gcArc
when true:
if conf.cmd != cmdCompileToCpp:
conf.exc = excGoto
defineSymbol(conf.symbols, "gcdestructors") defineSymbol(conf.symbols, "gcdestructors")
defineSymbol(conf.symbols, "gcarc") defineSymbol(conf.symbols, "gcarc")
incl conf.globalOptions, optSeqDestructors incl conf.globalOptions, optSeqDestructors
@ -466,6 +481,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
defineSymbol(conf.symbols, "nimV2") defineSymbol(conf.symbols, "nimV2")
of "orc": of "orc":
conf.selectedGC = gcOrc conf.selectedGC = gcOrc
when true:
if conf.cmd != cmdCompileToCpp:
conf.exc = excGoto
defineSymbol(conf.symbols, "gcdestructors") defineSymbol(conf.symbols, "gcdestructors")
defineSymbol(conf.symbols, "gcorc") defineSymbol(conf.symbols, "gcorc")
incl conf.globalOptions, optSeqDestructors incl conf.globalOptions, optSeqDestructors
@ -775,8 +793,15 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
localError(conf, info, "unknown obsolete feature") localError(conf, info, "unknown obsolete feature")
of "nocppexceptions": of "nocppexceptions":
expectNoArg(conf, switch, arg, pass, info) expectNoArg(conf, switch, arg, pass, info)
incl(conf.globalOptions, optNoCppExceptions) conf.exc = low(ExceptionSystem)
defineSymbol(conf.symbols, "noCppExceptions") defineSymbol(conf.symbols, "noCppExceptions")
of "exceptions":
case arg.normalize
of "cpp": conf.exc = excCpp
of "setjmp": conf.exc = excSetjmp
of "quirky": conf.exc = excQuirky
of "goto": conf.exc = excGoto
else: localError(conf, info, errInvalidExceptionSystem % arg)
of "cppdefine": of "cppdefine":
expectArg(conf, switch, arg, pass, info) expectArg(conf, switch, arg, pass, info)
if conf != nil: if conf != nil:

View file

@ -102,3 +102,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimnomagic64") defineSymbol("nimnomagic64")
defineSymbol("nimNewShiftOps") defineSymbol("nimNewShiftOps")
defineSymbol("nimHasCursor") defineSymbol("nimHasCursor")
defineSymbol("nimHasExceptionsQuery")

View file

@ -108,6 +108,7 @@ proc commandJsonScript(graph: ModuleGraph) =
when not defined(leanCompiler): when not defined(leanCompiler):
proc commandCompileToJS(graph: ModuleGraph) = proc commandCompileToJS(graph: ModuleGraph) =
let conf = graph.config let conf = graph.config
conf.exc = excCpp
if conf.outDir.isEmpty: if conf.outDir.isEmpty:
conf.outDir = conf.projectPath conf.outDir = conf.projectPath
@ -188,6 +189,7 @@ proc mainCommand*(graph: ModuleGraph) =
commandCompileToC(graph) commandCompileToC(graph)
of "cpp", "compiletocpp": of "cpp", "compiletocpp":
conf.cmd = cmdCompileToCpp conf.cmd = cmdCompileToCpp
conf.exc = excCpp
defineSymbol(graph.config.symbols, "cpp") defineSymbol(graph.config.symbols, "cpp")
commandCompileToC(graph) commandCompileToC(graph)
of "objc", "compiletooc": of "objc", "compiletooc":

View file

@ -71,7 +71,6 @@ type # please make sure we have under 32 options
# also: generate header file # also: generate header file
optIdeDebug # idetools: debug mode optIdeDebug # idetools: debug mode
optIdeTerse # idetools: use terse descriptions optIdeTerse # idetools: use terse descriptions
optNoCppExceptions # use C exception handling even with CPP
optExcessiveStackTrace # fully qualified module filenames optExcessiveStackTrace # fully qualified module filenames
optShowAllMismatches # show all overloading resolution candidates optShowAllMismatches # show all overloading resolution candidates
optWholeProject # for 'doc2': output any dependency optWholeProject # for 'doc2': output any dependency
@ -159,6 +158,12 @@ type
ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc, ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc,
ccTcc, ccPcc, ccUcc, ccIcl, ccIcc, ccClangCl ccTcc, ccPcc, ccUcc, ccIcl, ccIcc, ccClangCl
ExceptionSystem* = enum
excSetjmp, # setjmp based exception handling
excCpp, # use C++'s native exception handling
excGoto, # exception handling based on goto (should become the new default for C)
excQuirky # quirky exception handling
CfileFlag* {.pure.} = enum CfileFlag* {.pure.} = enum
Cached, ## no need to recompile this time Cached, ## no need to recompile this time
External ## file was introduced via .compile pragma External ## file was introduced via .compile pragma
@ -204,6 +209,7 @@ type
exitcode*: int8 exitcode*: int8
cmd*: TCommands # the command cmd*: TCommands # the command
selectedGC*: TGCMode # the selected GC (+) selectedGC*: TGCMode # the selected GC (+)
exc*: ExceptionSystem
verbosity*: int # how verbose the compiler is verbosity*: int # how verbose the compiler is
numberOfProcessors*: int # number of processors numberOfProcessors*: int # number of processors
evalExpr*: string # expression for idetools --eval evalExpr*: string # expression for idetools --eval

View file

@ -880,7 +880,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
of wNoreturn: of wNoreturn:
noVal(c, it) noVal(c, it)
# Disable the 'noreturn' annotation when in the "Quirky Exceptions" mode! # Disable the 'noreturn' annotation when in the "Quirky Exceptions" mode!
if not isDefined(c.config, "nimQuirky"): if c.config.exc notin {excQuirky, excGoto}:
incl(sym.flags, sfNoReturn) incl(sym.flags, sfNoReturn)
if sym.typ[0] != nil: if sym.typ[0] != nil:
localError(c.config, sym.ast[paramsPos][0].info, localError(c.config, sym.ast[paramsPos][0].info,

View file

@ -167,9 +167,7 @@ proc guardDotAccess(a: PEffects; n: PNode) =
guardGlobal(a, n, g) guardGlobal(a, n, g)
proc makeVolatile(a: PEffects; s: PSym) {.inline.} = proc makeVolatile(a: PEffects; s: PSym) {.inline.} =
template compileToCpp(a): untyped = if a.inTryStmt > 0 and a.config.exc == excSetjmp:
a.config.cmd == cmdCompileToCpp or sfCompileToCpp in getModule(a.owner).flags
if a.inTryStmt > 0 and not compileToCpp(a):
incl(s.flags, sfVolatile) incl(s.flags, sfVolatile)
proc initVar(a: PEffects, n: PNode; volatileCheck: bool) = proc initVar(a: PEffects, n: PNode; volatileCheck: bool) =

View file

@ -98,12 +98,13 @@ Advanced options:
--skipProjCfg:on|off do not read the project's configuration file --skipProjCfg:on|off do not read the project's configuration file
--gc:refc|markAndSweep|boehm|go|none|regions --gc:refc|markAndSweep|boehm|go|none|regions
select the GC to use; default is 'refc' select the GC to use; default is 'refc'
--exceptions:setjmp|cpp|goto|quirky
select the exception handling implementation
--index:on|off turn index file generation on|off --index:on|off turn index file generation on|off
--putenv:key=value set an environment variable --putenv:key=value set an environment variable
--NimblePath:PATH add a path for Nimble support --NimblePath:PATH add a path for Nimble support
--noNimblePath deactivate the Nimble path --noNimblePath deactivate the Nimble path
--clearNimblePath empty the list of Nimble package search paths --clearNimblePath empty the list of Nimble package search paths
--noCppExceptions use default exception handling with C++ backend
--cppCompileToNamespace:namespace --cppCompileToNamespace:namespace
use the provided namespace for the generated C++ code, use the provided namespace for the generated C++ code,
if no namespace is provided "Nim" will be used if no namespace is provided "Nim" will be used

View file

@ -3105,7 +3105,7 @@ when not defined(js):
when defined(nimV2): when defined(nimV2):
type type
TNimNode {.compilerproc.} = object # to keep the code generator simple TNimNode {.compilerproc.} = object # to keep the code generator simple
DestructorProc = proc (p: pointer) {.nimcall, benign.} DestructorProc = proc (p: pointer) {.nimcall, benign, raises: [].}
TNimType {.compilerproc.} = object TNimType {.compilerproc.} = object
destructor: pointer destructor: pointer
size: int size: int
@ -3120,6 +3120,12 @@ when not defined(js):
{.pop.} {.pop.}
when not defined(js) and not defined(nimscript):
proc writeStackTrace*() {.tags: [], gcsafe, raises: [].}
## Writes the current stack trace to ``stderr``. This is only works
## for debug builds. Since it's usually used for debugging, this
## is proclaimed to have no IO effect!
when not declared(sysFatal): when not declared(sysFatal):
include "system/fatal" include "system/fatal"
@ -3693,10 +3699,6 @@ when not defined(JS): #and not defined(nimscript):
proc unsetControlCHook*() proc unsetControlCHook*()
## Reverts a call to setControlCHook. ## Reverts a call to setControlCHook.
proc writeStackTrace*() {.tags: [], gcsafe.}
## Writes the current stack trace to ``stderr``. This is only works
## for debug builds. Since it's usually used for debugging, this
## is proclaimed to have no IO effect!
when hostOS != "standalone": when hostOS != "standalone":
proc getStackTrace*(): string {.gcsafe.} proc getStackTrace*(): string {.gcsafe.}
## Gets the current stack trace. This only works for debug builds. ## Gets the current stack trace. This only works for debug builds.

View file

@ -27,19 +27,21 @@ else:
proc writeToStdErr(msg: cstring) = proc writeToStdErr(msg: cstring) =
discard MessageBoxA(nil, msg, nil, 0) discard MessageBoxA(nil, msg, nil, 0)
proc showErrorMessage(data: cstring) {.gcsafe.} = proc showErrorMessage(data: cstring) {.gcsafe, raises: [].} =
var toWrite = true
if errorMessageWriter != nil: if errorMessageWriter != nil:
errorMessageWriter($data) try:
else: errorMessageWriter($data)
toWrite = false
except:
discard
if toWrite:
when defined(genode): when defined(genode):
# stderr not available by default, use the LOG session # stderr not available by default, use the LOG session
echo data echo data
else: else:
writeToStdErr(data) writeToStdErr(data)
proc quitOrDebug() {.inline.} =
quit(1)
proc chckIndx(i, a, b: int): int {.inline, compilerproc, benign.} proc chckIndx(i, a, b: int): int {.inline, compilerproc, benign.}
proc chckRange(i, a, b: int): int {.inline, compilerproc, benign.} proc chckRange(i, a, b: int): int {.inline, compilerproc, benign.}
proc chckRangeF(x, a, b: float): float {.inline, compilerproc, benign.} proc chckRangeF(x, a, b: float): float {.inline, compilerproc, benign.}
@ -57,10 +59,12 @@ var
# list of exception handlers # list of exception handlers
# a global variable for the root of all try blocks # a global variable for the root of all try blocks
currException {.threadvar.}: ref Exception currException {.threadvar.}: ref Exception
raiseCounter {.threadvar.}: uint
gcFramePtr {.threadvar.}: GcFrame gcFramePtr {.threadvar.}: GcFrame
when defined(cpp) and not defined(noCppExceptions):
var
raiseCounter {.threadvar.}: uint
type type
FrameState = tuple[gcFramePtr: GcFrame, framePtr: PFrame, FrameState = tuple[gcFramePtr: GcFrame, framePtr: PFrame,
excHandler: PSafePoint, currException: ref Exception] excHandler: PSafePoint, currException: ref Exception]
@ -130,7 +134,7 @@ proc popCurrentExceptionEx(id: uint) {.compilerRtl.} =
cur = cur.up cur = cur.up
if cur == nil: if cur == nil:
showErrorMessage("popCurrentExceptionEx() exception was not found in the exception stack. Aborting...") showErrorMessage("popCurrentExceptionEx() exception was not found in the exception stack. Aborting...")
quitOrDebug() quit(1)
prev.up = cur.up prev.up = cur.up
proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} = proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} =
@ -347,57 +351,87 @@ var onUnhandledException*: (proc (errorMsg: string) {.
## The default is to write a stacktrace to ``stderr`` and then call ``quit(1)``. ## The default is to write a stacktrace to ``stderr`` and then call ``quit(1)``.
## Unstable API. ## Unstable API.
template unhandled(buf, body) = proc reportUnhandledError(e: ref Exception) {.nodestroy.} =
if onUnhandledException != nil: when hasSomeStackTrace:
onUnhandledException($buf) var buf = newStringOfCap(2000)
if e.trace.len == 0:
rawWriteStackTrace(buf)
else:
var trace = $e.trace
add(buf, trace)
`=destroy`(trace)
add(buf, "Error: unhandled exception: ")
add(buf, e.msg)
add(buf, " [")
add(buf, $e.name)
add(buf, "]\n")
if onUnhandledException != nil:
onUnhandledException(buf)
else:
showErrorMessage(buf)
`=destroy`(buf)
else: else:
body # ugly, but avoids heap allocations :-)
template xadd(buf, s, slen) =
if L + slen < high(buf):
copyMem(addr(buf[L]), cstring(s), slen)
inc L, slen
template add(buf, s) =
xadd(buf, s, s.len)
var buf: array[0..2000, char]
var L = 0
if e.trace.len != 0:
var trace = $e.trace
add(buf, trace)
`=destroy`(trace)
add(buf, "Error: unhandled exception: ")
add(buf, e.msg)
add(buf, " [")
xadd(buf, e.name, e.name.len)
add(buf, "]\n")
when defined(nimNoArrayToCstringConversion):
template tbuf(): untyped = addr buf
else:
template tbuf(): untyped = buf
if onUnhandledException != nil:
onUnhandledException($tbuf())
else:
showErrorMessage(tbuf())
proc nimLeaveFinally() {.compilerRtl.} = proc nimLeaveFinally() {.compilerRtl.} =
when defined(cpp) and not defined(noCppExceptions): when defined(cpp) and not defined(noCppExceptions):
{.emit: "throw;".} {.emit: "throw;".}
else: else:
template e: untyped = currException
if excHandler != nil: if excHandler != nil:
c_longjmp(excHandler.context, 1) c_longjmp(excHandler.context, 1)
else: else:
when hasSomeStackTrace: reportUnhandledError(currException)
var buf = newStringOfCap(2000) quit(1)
if e.trace.len == 0: rawWriteStackTrace(buf)
else: add(buf, $e.trace) when gotoBasedExceptions:
add(buf, "Error: unhandled exception: ") var nimInErrorMode {.threadvar.}: int
add(buf, e.msg)
add(buf, " [") proc nimErrorFlag(): ptr int {.compilerRtl, inl.} =
add(buf, $e.name) result = addr(nimInErrorMode)
add(buf, "]\n")
unhandled(buf): proc nimTestErrorFlag() {.compilerRtl.} =
showErrorMessage(buf) ## This proc must be called before ``currException`` is destroyed.
quitOrDebug() ## It also must be called at the end of every thread to ensure no
`=destroy`(buf) ## error is swallowed.
else: if currException != nil:
# ugly, but avoids heap allocations :-) reportUnhandledError(currException)
template xadd(buf, s, slen) = currException = nil
if L + slen < high(buf): quit(1)
copyMem(addr(buf[L]), cstring(s), slen)
inc L, slen addQuitProc(proc () {.noconv.} =
template add(buf, s) = if currException != nil:
xadd(buf, s, s.len) reportUnhandledError(currException)
var buf: array[0..2000, char] # emulate: ``programResult = 1`` via abort() and a nop signal handler.
var L = 0 c_signal(SIGABRT, (proc (sign: cint) {.noconv, benign.} = discard))
if e.trace.len != 0: c_abort()
add(buf, $e.trace) # gc allocation )
add(buf, "Error: unhandled exception: ")
add(buf, e.msg)
add(buf, " [")
xadd(buf, e.name, e.name.len)
add(buf, "]\n")
when defined(nimNoArrayToCstringConversion):
template tbuf(): untyped = addr buf
else:
template tbuf(): untyped = buf
unhandled(tbuf()):
showErrorMessage(tbuf())
quitOrDebug()
proc raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} = proc raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} =
if localRaiseHook != nil: if localRaiseHook != nil:
@ -414,50 +448,19 @@ proc raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} =
raiseCounter.inc # skip zero at overflow raiseCounter.inc # skip zero at overflow
e.raiseId = raiseCounter e.raiseId = raiseCounter
{.emit: "`e`->raise();".} {.emit: "`e`->raise();".}
elif defined(nimQuirky): elif defined(nimQuirky) or gotoBasedExceptions:
pushCurrentException(e) # XXX This check should likely also be done in the setjmp case below.
if e != currException:
pushCurrentException(e)
when gotoBasedExceptions:
inc nimInErrorMode
else: else:
if excHandler != nil: if excHandler != nil:
pushCurrentException(e) pushCurrentException(e)
c_longjmp(excHandler.context, 1) c_longjmp(excHandler.context, 1)
else: else:
when hasSomeStackTrace: reportUnhandledError(e)
var buf = newStringOfCap(2000) quit(1)
if e.trace.len == 0: rawWriteStackTrace(buf)
else: add(buf, $e.trace)
add(buf, "Error: unhandled exception: ")
add(buf, e.msg)
add(buf, " [")
add(buf, $e.name)
add(buf, "]\n")
unhandled(buf):
showErrorMessage(buf)
quitOrDebug()
`=destroy`(buf)
else:
# ugly, but avoids heap allocations :-)
template xadd(buf, s, slen) =
if L + slen < high(buf):
copyMem(addr(buf[L]), cstring(s), slen)
inc L, slen
template add(buf, s) =
xadd(buf, s, s.len)
var buf: array[0..2000, char]
var L = 0
if e.trace.len != 0:
add(buf, $e.trace) # gc allocation
add(buf, "Error: unhandled exception: ")
add(buf, e.msg)
add(buf, " [")
xadd(buf, e.name, e.name.len)
add(buf, "]\n")
when defined(nimNoArrayToCstringConversion):
template tbuf(): untyped = addr buf
else:
template tbuf(): untyped = buf
unhandled(tbuf()):
showErrorMessage(tbuf())
quitOrDebug()
proc raiseExceptionEx(e: sink(ref Exception), ename, procname, filename: cstring, proc raiseExceptionEx(e: sink(ref Exception), ename, procname, filename: cstring,
line: int) {.compilerRtl, nodestroy.} = line: int) {.compilerRtl, nodestroy.} =
@ -484,15 +487,18 @@ proc reraiseException() {.compilerRtl.} =
if currException == nil: if currException == nil:
sysFatal(ReraiseError, "no exception to reraise") sysFatal(ReraiseError, "no exception to reraise")
else: else:
raiseExceptionAux(currException) when gotoBasedExceptions:
inc nimInErrorMode
else:
raiseExceptionAux(currException)
proc writeStackTrace() = proc writeStackTrace() =
when hasSomeStackTrace: when hasSomeStackTrace:
var s = "" var s = ""
rawWriteStackTrace(s) rawWriteStackTrace(s)
cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall.}](showErrorMessage)(s) cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall, raises: [].}](showErrorMessage)(s)
else: else:
cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall.}](showErrorMessage)("No stack traceback available\n") cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall, raises: [].}](showErrorMessage)("No stack traceback available\n")
proc getStackTrace(): string = proc getStackTrace(): string =
when hasSomeStackTrace: when hasSomeStackTrace:
@ -529,9 +535,9 @@ proc callDepthLimitReached() {.noinline.} =
$nimCallDepthLimit & " function calls). You can change it with " & $nimCallDepthLimit & " function calls). You can change it with " &
"-d:nimCallDepthLimit=<int> but really try to avoid deep " & "-d:nimCallDepthLimit=<int> but really try to avoid deep " &
"recursions instead.\n") "recursions instead.\n")
quitOrDebug() quit(1)
proc nimFrame(s: PFrame) {.compilerRtl, inl.} = proc nimFrame(s: PFrame) {.compilerRtl, inl, raises: [].} =
s.calldepth = if framePtr == nil: 0 else: framePtr.calldepth+1 s.calldepth = if framePtr == nil: 0 else: framePtr.calldepth+1
s.prev = framePtr s.prev = framePtr
framePtr = s framePtr = s

View file

@ -1,4 +1,19 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2019 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
{.push profiler: off.} {.push profiler: off.}
when defined(nimHasExceptionsQuery):
const gotoBasedExceptions = compileOption("exceptions", "goto")
else:
const gotoBasedExceptions = false
when hostOS == "standalone": when hostOS == "standalone":
include "$projectpath/panicoverride" include "$projectpath/panicoverride"
@ -9,19 +24,20 @@ when hostOS == "standalone":
rawoutput(message) rawoutput(message)
panic(arg) panic(arg)
elif defined(nimQuirky) and not defined(nimscript): elif (defined(nimQuirky) or gotoBasedExceptions) and not defined(nimscript):
import ansi_c import ansi_c
proc name(t: typedesc): string {.magic: "TypeTrait".} proc name(t: typedesc): string {.magic: "TypeTrait".}
proc sysFatal(exceptn: typedesc, message, arg: string) {.inline, noreturn.} = proc sysFatal(exceptn: typedesc, message, arg: string) {.inline, noreturn.} =
writeStackTrace()
var buf = newStringOfCap(200) var buf = newStringOfCap(200)
add(buf, "Error: unhandled exception: ") add(buf, "Error: unhandled exception: ")
add(buf, message) add(buf, message)
add(buf, arg) add(buf, arg)
add(buf, " [") add(buf, " [")
add(buf, name exceptn) add(buf, name exceptn)
add(buf, "]") add(buf, "]\n")
cstderr.rawWrite buf cstderr.rawWrite buf
quit 1 quit 1

View file

@ -111,7 +111,7 @@ proc nimRawDispose(p: pointer) {.compilerRtl.} =
template dispose*[T](x: owned(ref T)) = nimRawDispose(cast[pointer](x)) template dispose*[T](x: owned(ref T)) = nimRawDispose(cast[pointer](x))
#proc dispose*(x: pointer) = nimRawDispose(x) #proc dispose*(x: pointer) = nimRawDispose(x)
proc nimDestroyAndDispose(p: pointer) {.compilerRtl.} = proc nimDestroyAndDispose(p: pointer) {.compilerRtl, raises: [].} =
let d = cast[ptr PNimType](p)[].destructor let d = cast[ptr PNimType](p)[].destructor
if d != nil: cast[DestructorProc](d)(p) if d != nil: cast[DestructorProc](d)(p)
when false: when false:

View file

@ -8,7 +8,7 @@ tassert_c.nim(35) tassert_c
tassert_c.nim(34) foo tassert_c.nim(34) foo
assertions.nim(27) failedAssertImpl assertions.nim(27) failedAssertImpl
assertions.nim(20) raiseAssert assertions.nim(20) raiseAssert
fatal.nim(39) sysFatal""" fatal.nim(55) sysFatal"""
proc tmatch(x, p: string): bool = proc tmatch(x, p: string): bool =
var i = 0 var i = 0

View file

@ -0,0 +1,117 @@
discard """
output: '''
msg1
msg2
finally2
finally1
begin
one iteration!
caught!
except1
finally1
caught! 2
BEFORE
FINALLY
BEFORE
EXCEPT
FINALLY
RECOVER
BEFORE
EXCEPT: IOError: hi
FINALLY
'''
cmd: "nim c --gc:arc --exceptions:goto $file"
"""
#bug 7204
proc nested_finally =
try:
raise newException(KeyError, "msg1")
except KeyError as ex:
echo ex.msg
try:
raise newException(ValueError, "msg2")
except:
echo getCurrentExceptionMsg()
finally:
echo "finally2"
finally:
echo "finally1"
nested_finally()
proc doraise =
raise newException(ValueError, "gah")
proc main =
while true:
try:
echo "begin"
doraise()
finally:
echo "one ", "iteration!"
try:
main()
except:
echo "caught!"
when true:
proc p =
try:
raise newException(Exception, "Hello")
except:
echo "except1"
raise
finally:
echo "finally1"
try:
p()
except:
echo "caught! 2"
proc noException =
try:
echo "BEFORE"
except:
echo "EXCEPT"
raise
finally:
echo "FINALLY"
try: noException()
except: echo "RECOVER"
proc reraise_in_except =
try:
echo "BEFORE"
raise newException(IOError, "")
except IOError:
echo "EXCEPT"
raise
finally:
echo "FINALLY"
try: reraise_in_except()
except: echo "RECOVER"
proc return_in_except =
try:
echo "BEFORE"
raise newException(IOError, "hi")
except:
echo "EXCEPT: ", getCurrentException().name, ": ", getCurrentExceptionMsg()
return
finally:
echo "FINALLY"
try: return_in_except()
except: echo "RECOVER"

View file

@ -0,0 +1,104 @@
discard """
cmd: "nim c --gc:arc --exceptions:goto $file"
output: '''
B1
B2
catch
A1
1
B1
B2
catch
A1
A2
0
B1
B2
A1
1
B1
B2
A1
A2
3
A
B
C
'''
"""
# More thorough test of return-in-finaly
var raiseEx = true
var returnA = true
var returnB = false
proc main: int =
try: #A
try: #B
if raiseEx:
raise newException(OSError, "")
return 3
finally: #B
echo "B1"
if returnB:
return 2
echo "B2"
except OSError: #A
echo "catch"
finally: #A
echo "A1"
if returnA:
return 1
echo "A2"
for x in [true, false]:
for y in [true, false]:
# echo "raiseEx: " & $x
# echo "returnA: " & $y
# echo "returnB: " & $z
# in the original test returnB was set to true too and
# this leads to swallowing the OSError exception. This is
# somewhat compatible with Python but it's non-sense, 'finally'
# should not be allowed to swallow exceptions. The goto based
# implementation does something sane so we don't "correct" its
# behavior just to be compatible with v1.
raiseEx = x
returnA = y
echo main()
# Various tests of return nested in double try/except statements
proc test1() =
defer: echo "A"
try:
raise newException(OSError, "Problem")
except OSError:
return
test1()
proc test2() =
defer: echo "B"
try:
return
except OSError:
discard
test2()
proc test3() =
try:
try:
raise newException(OSError, "Problem")
except OSError:
return
finally:
echo "C"
test3()

View file

@ -0,0 +1,7 @@
discard """
cmd: "nim c --gc:arc --exceptions:goto $file"
outputsub: "Error: unhandled exception: Problem [OSError]"
exitcode: "1"
"""
raise newException(OSError, "Problem")

View file

@ -1,5 +1,39 @@
discard """ discard """
output: "B1\nA1\n1\nB1\nB2\ncatch\nA1\n1\nB1\nA1\nA2\n2\nB1\nB2\ncatch\nA1\nA2\n0\nB1\nA1\n1\nB1\nB2\nA1\n1\nB1\nA1\nA2\n2\nB1\nB2\nA1\nA2\n3" output: '''
B1
A1
1
B1
B2
catch
A1
1
B1
A1
A2
2
B1
B2
catch
A1
A2
0
B1
A1
1
B1
B2
A1
1
B1
A1
A2
2
B1
B2
A1
A2
3'''
""" """
# More thorough test of return-in-finaly # More thorough test of return-in-finaly