--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:
parent
8a63caca07
commit
c3344862b0
23 changed files with 640 additions and 183 deletions
|
|
@ -196,13 +196,13 @@ proc genState(p: BProc, n: PNode) =
|
|||
|
||||
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,
|
||||
# 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:
|
||||
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
|
||||
if not tryStmt.inExcept:
|
||||
linefmt(p, cpsStmts, "#popSafePoint();$n", [])
|
||||
|
|
@ -221,10 +221,10 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) =
|
|||
for i in countdown(howManyTrys-1, 0):
|
||||
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
|
||||
# except-blocks we are in
|
||||
if not p.noSafePoints:
|
||||
if noSafePoints notin p.flags:
|
||||
for i in countdown(howManyExcepts-1, 0):
|
||||
linefmt(p, cpsStmts, "#popCurrentException();$n", [])
|
||||
|
||||
|
|
@ -237,7 +237,7 @@ proc genGotoState(p: BProc, n: PNode) =
|
|||
var a: TLoc
|
||||
initLocExpr(p, n[0], a)
|
||||
lineF(p, cpsStmts, "switch ($1) {$n", [rdLoc(a)])
|
||||
p.beforeRetNeeded = true
|
||||
p.flags.incl beforeRetNeeded
|
||||
lineF(p, cpsStmts, "case -1:$n", [])
|
||||
blockLeaveActions(p,
|
||||
howManyTrys = p.nestedTryStmts.len,
|
||||
|
|
@ -454,13 +454,13 @@ proc genIf(p: BProc, n: PNode, d: var TLoc) =
|
|||
|
||||
proc genReturnStmt(p: BProc, t: PNode) =
|
||||
if nfPreventCg in t.flags: return
|
||||
p.beforeRetNeeded = true
|
||||
p.flags.incl beforeRetNeeded
|
||||
genLineDir(p, t)
|
||||
if (t[0].kind != nkEmpty): genStmts(p, t[0])
|
||||
blockLeaveActions(p,
|
||||
howManyTrys = p.nestedTryStmts.len,
|
||||
howManyExcepts = p.inExceptBlockLen)
|
||||
if (p.finallySafePoints.len > 0) and 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
|
||||
# consume it before we return.
|
||||
var safePoint = p.finallySafePoints[^1]
|
||||
|
|
@ -687,15 +687,27 @@ proc genBreakStmt(p: BProc, t: PNode) =
|
|||
genLineDir(p, t)
|
||||
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) =
|
||||
if p.module.compileToCpp:
|
||||
discard cgsym(p.module, "popCurrentExceptionEx")
|
||||
if p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept:
|
||||
# if the current try stmt have a finally block,
|
||||
# we must execute it before reraising
|
||||
let finallyBlock = p.nestedTryStmts[^1].fin
|
||||
if finallyBlock != nil:
|
||||
genSimpleBlock(p, finallyBlock[0])
|
||||
if p.config.exc != excGoto:
|
||||
if p.config.exc == excCpp:
|
||||
discard cgsym(p.module, "popCurrentExceptionEx")
|
||||
if p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept:
|
||||
# if the current try stmt have a finally block,
|
||||
# we must execute it before reraising
|
||||
let finallyBlock = p.nestedTryStmts[^1].fin
|
||||
if finallyBlock != nil:
|
||||
genSimpleBlock(p, finallyBlock[0])
|
||||
if t[0].kind != nkEmpty:
|
||||
var a: TLoc
|
||||
initLocExprSingleUse(p, t[0], a)
|
||||
|
|
@ -714,10 +726,22 @@ proc genRaiseStmt(p: BProc, t: PNode) =
|
|||
else:
|
||||
genLineDir(p, t)
|
||||
# reraise the last exception:
|
||||
if p.module.compileToCpp and optNoCppExceptions notin p.config.globalOptions:
|
||||
if p.config.exc == excCpp:
|
||||
line(p, cpsStmts, ~"throw;$n")
|
||||
else:
|
||||
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,
|
||||
rangeFormat, eqFormat: FormatStr, labl: TLabel) =
|
||||
|
|
@ -906,8 +930,8 @@ proc genCase(p: BProc, t: PNode, d: var TLoc) =
|
|||
|
||||
proc genRestoreFrameAfterException(p: BProc) =
|
||||
if optStackTrace in p.module.config.options:
|
||||
if not p.hasCurFramePointer:
|
||||
p.hasCurFramePointer = true
|
||||
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", [])
|
||||
|
|
@ -938,7 +962,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
|
|||
genLineDir(p, t)
|
||||
discard cgsym(p.module, "popCurrentExceptionEx")
|
||||
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")
|
||||
expr(p, t[0], d)
|
||||
endBlock(p)
|
||||
|
|
@ -982,7 +1006,116 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
|
|||
|
||||
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:
|
||||
#
|
||||
# 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:
|
||||
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)
|
||||
if not quirkyExceptions:
|
||||
p.module.includeHeader("<setjmp.h>")
|
||||
else:
|
||||
p.noSafePoints = true
|
||||
p.flags.incl noSafePoints
|
||||
genLineDir(p, t)
|
||||
discard cgsym(p.module, "Exception")
|
||||
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])
|
||||
startBlock(p, "if ($1.status == 0) {$n", [safePoint])
|
||||
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)
|
||||
if not quirkyExceptions:
|
||||
linefmt(p, cpsStmts, "#popSafePoint();$n", [])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue