Skip to content

Commit

Permalink
Merge pull request #12 from nim-lang/devel
Browse files Browse the repository at this point in the history
--exception:goto switch for deterministic exception handling (nim-lang#12977)
  • Loading branch information
sthagen authored Jan 1, 2020
2 parents dc4f897 + c334486 commit d765783
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 d765783

Please sign in to comment.