Skip to content

Commit

Permalink
--exception:goto switch for deterministic exception handling (#12977)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Araq authored Jan 1, 2020
1 parent 8a63cac commit c334486
Show file tree
Hide file tree
Showing 23 changed files with 640 additions and 183 deletions.
11 changes: 7 additions & 4 deletions compiler/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1763,7 +1763,7 @@ proc toObject*(typ: PType): PType =
proc isImportedException*(t: PType; conf: ConfigRef): bool =
assert t != nil

if optNoCppExceptions in conf.globalOptions:
if conf.exc != excCpp:
return false

let base = t.skipTypes({tyAlias, tyPtr, tyDistinct, tyGenericInst})
Expand Down Expand Up @@ -1834,7 +1834,7 @@ template assignment*(t: PType): PSym = t.attachedOps[attachedAsgn]
template asink*(t: PType): PSym = t.attachedOps[attachedSink]

const magicsThatCanRaise = {
mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst}
mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst, mEcho}

proc canRaiseConservative*(fn: PNode): bool =
if fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise:
Expand All @@ -1844,9 +1844,12 @@ proc canRaiseConservative*(fn: PNode): bool =

proc canRaise*(fn: PNode): bool =
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
elif fn.kind == nkSym and fn.sym.magic == mEcho:
result = true
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].safeLen > 0))
4 changes: 4 additions & 0 deletions compiler/ccgcalls.nim
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,8 @@ proc genCall(p: BProc, e: PNode, d: var TLoc) =
else:
genPrefixCall(p, nil, e, d)
postStmtActions(p)
if p.config.exc == excGoto and canRaise(e[0]):
raiseExit(p)

proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
if ri[0].typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure:
Expand All @@ -578,3 +580,5 @@ proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
else:
genPrefixCall(p, le, ri, d)
postStmtActions(p)
if p.config.exc == excGoto and canRaise(ri[0]):
raiseExit(p)
7 changes: 5 additions & 2 deletions compiler/ccgexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2675,10 +2675,13 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
line(p, cpsStmts, "(void)(" & a.r & ");\L")
of nkAsmStmt: genAsmStmt(p, n)
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)
else:
genTry(p, n, d)
genTrySetjmp(p, n, d)
of nkRaiseStmt: genRaiseStmt(p, n)
of nkTypeSection:
# we have to emit the type information for object types here to support
Expand Down
181 changes: 157 additions & 24 deletions compiler/ccgstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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", [])
Expand All @@ -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", [])

Expand All @@ -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,
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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)
Expand All @@ -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) =
Expand Down Expand Up @@ -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", [])
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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", [])
Expand Down
4 changes: 2 additions & 2 deletions compiler/ccgthreadvars.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ proc emulatedThreadVars(conf: ConfigRef): bool =
result = {optThreads, optTlsEmulation} <= conf.globalOptions

proc accessThreadLocalVar(p: BProc, s: PSym) =
if emulatedThreadVars(p.config) and not p.threadVarAccessed:
p.threadVarAccessed = true
if emulatedThreadVars(p.config) and threadVarAccessed notin p.flags:
p.flags.incl threadVarAccessed
incl p.module.flags, usesThreadVars
p.procSec(cpsLocals).addf("\tNimThreadVars* NimTV_;$n", [])
p.procSec(cpsInit).add(
Expand Down
Loading

0 comments on commit c334486

Please sign in to comment.