summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2020-01-01 10:01:49 +0100
committerGitHub <noreply@github.com>2020-01-01 10:01:49 +0100
commitc3344862b0d6061cc1581f29c81b29b75c78615a (patch)
tree75661179ec450bb4e2783603c09f4304dfe42a45
parent8a63caca07349742d071dcd3a7d3e3055fe617cf (diff)
downloadNim-c3344862b0d6061cc1581f29c81b29b75c78615a.tar.gz
--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.
-rw-r--r--compiler/ast.nim11
-rw-r--r--compiler/ccgcalls.nim4
-rw-r--r--compiler/ccgexprs.nim7
-rw-r--r--compiler/ccgstmts.nim181
-rw-r--r--compiler/ccgthreadvars.nim4
-rw-r--r--compiler/cgen.nim54
-rw-r--r--compiler/cgendata.nim19
-rw-r--r--compiler/commands.nim27
-rw-r--r--compiler/condsyms.nim1
-rw-r--r--compiler/main.nim2
-rw-r--r--compiler/options.nim8
-rw-r--r--compiler/pragmas.nim2
-rw-r--r--compiler/sempass2.nim4
-rw-r--r--doc/advopt.txt3
-rw-r--r--lib/system.nim12
-rw-r--r--lib/system/excpt.nim196
-rw-r--r--lib/system/fatal.nim20
-rw-r--r--lib/system/refs_v2.nim2
-rw-r--r--tests/assert/tassert_c.nim2
-rwxr-xr-xtests/destructor/tgotoexceptions.nim117
-rw-r--r--tests/destructor/tgotoexceptions2.nim104
-rw-r--r--tests/destructor/tgotoexceptions3.nim7
-rw-r--r--tests/exception/tfinally4.nim36
23 files changed, 640 insertions, 183 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 7bcb7570d..0daa1f11f 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -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})
@@ -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:
@@ -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))
diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim
index b5fffaddb..da5dd9b76 100644
--- a/compiler/ccgcalls.nim
+++ b/compiler/ccgcalls.nim
@@ -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:
@@ -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)
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index e1f4d180d..8c41fb370 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -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
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim
index 86125af66..4c342aaf6 100644
--- a/compiler/ccgstmts.nim
+++ b/compiler/ccgstmts.nim
@@ -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", [])
diff --git a/compiler/ccgthreadvars.nim b/compiler/ccgthreadvars.nim
index 7586aa865..cc72e3d37 100644
--- a/compiler/ccgthreadvars.nim
+++ b/compiler/ccgthreadvars.nim
@@ -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(
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index 1e454f25b..f91f66933 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -481,14 +481,6 @@ proc getIntTemp(p: BProc, result: var TLoc) =
   result.lode = lodeTyp getSysType(p.module.g.graph, unknownLineInfo(), tyInt)
   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 =
   let s = n.sym
   if s.loc.k == locNone:
@@ -599,6 +591,7 @@ proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc)
 proc intLiteral(i: BiggestInt): Rope
 proc genLiteral(p: BProc, n: PNode): 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) =
   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, ["#"])
 
   discard cgsym(p.module, "nimFrame")
-  if p.maxFrameLen > 0:
-    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])
+  result = ropecg(p.module, "\tnimfr_($1, $2);$n", [procname, filename])
 
 proc initFrameNoDebug(p: BProc; frame, procname, filename: Rope; line: int): Rope =
   discard cgsym(p.module, "nimFrame")
@@ -979,6 +966,13 @@ proc getProcTypeCast(m: BModule, prc: PSym): Rope =
     genProcParams(m, prc.typ, rettype, params, check)
     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) =
   var p = newProc(prc, m)
   var header = genProcHeader(m, prc)
@@ -1029,7 +1023,8 @@ proc genProcAux(m: BModule, prc: PSym) =
     if param.typ.isCompileTimeOnly: continue
     assignParam(p, param, prc.typ[0])
   closureSetup(p, prc)
-  genStmts(p, procBody) # modifies p.locals, p.init, etc.
+  genProcBody(p, procBody)
+
   var generatedProc: Rope
   generatedProc.genCLineDir prc.info, m.config
   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
       # call each other using directly the "_actual" versions (an optimization) - see issue #11608
       m.s[cfsProcHeaders].addf("$1;\n", [header])
-    generatedProc.add ropecg(p.module, "$1 {", [header])
-    generatedProc.add(initGCFrame(p))
+    generatedProc.add ropecg(p.module, "$1 {$n", [header])
     if optStackTrace in prc.options:
       generatedProc.add(p.s(cpsLocals))
       var procname = makeCString(prc.name.s)
@@ -1057,11 +1051,12 @@ proc genProcAux(m: BModule, prc: PSym) =
     if optProfiler in prc.options:
       # invoke at proc entry for recursion:
       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(cpsStmts))
-    if p.beforeRetNeeded: generatedProc.add(~"\t}BeforeRet_: ;$n")
-    generatedProc.add(deinitGCFrame(p))
+    if beforeRetNeeded in p.flags: generatedProc.add(~"\t}BeforeRet_: ;$n")
     if optStackTrace in prc.options: generatedProc.add(deinitFrame(p))
     generatedProc.add(returnStmt)
     generatedProc.add(~"}$N")
@@ -1645,10 +1640,6 @@ proc genInitCode(m: BModule) =
   # add new scope for following code, because old vcc compiler need variable
   # be defined at the top of the block
   prc.addf("{$N", [])
-  if m.initProc.gcFrameId > 0:
-    moduleInitRequired = true
-    prc.add(initGCFrame(m.initProc))
-
   writeSection(initProc, cpsLocals)
 
   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, cpsStmts)
 
+    if beforeRetNeeded in m.initProc.flags:
+      prc.add(~"\tBeforeRet_: ;$n")
     if optStackTrace in m.initProc.options and preventStackTrace notin m.flags:
       prc.add(deinitFrame(m.initProc))
 
-  if m.initProc.gcFrameId > 0:
-    moduleInitRequired = true
-    prc.add(deinitGCFrame(m.initProc))
   prc.addf("}$N", [])
 
   prc.addf("}$N$N", [])
@@ -1894,7 +1884,7 @@ proc myProcess(b: PPassContext, n: PNode): PNode =
   if m.hcrOn:
     addHcrInitGuards(m.initProc, transformedN, m.inHcrInitGuard)
   else:
-    genStmts(m.initProc, transformedN)
+    genProcBody(m.initProc, transformedN)
 
 proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool =
   if optForceFullMake notin m.config.globalOptions:
@@ -1996,6 +1986,10 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode =
   if b == nil: return
   var m = BModule(b)
   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):
       n.add graph.globalDestructors[i]
   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?
     if n != nil:
       m.initProc.options = initProcOptions(m)
-      genStmts(m.initProc, n)
+      genProcBody(m.initProc, n)
 
     if m.hcrOn:
       # make sure this is pulled in (meaning hcrGetGlobal() is called for it during init)
diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim
index 9255e82a5..326bdeb45 100644
--- a/compiler/cgendata.nim
+++ b/compiler/cgendata.nim
@@ -64,16 +64,20 @@ type
     nestedExceptStmts*: int16 # how many except statements is it nested into
     frameLen*: int16
 
+  TCProcFlag* = enum
+    beforeRetNeeded,
+    threadVarAccessed,
+    hasCurFramePointer,
+    noSafePoints,
+    nimErrorFlagAccessed,
+    nimErrorFlagDeclared
+
   TCProc = object             # represents C proc that is currently generated
     prc*: PSym                # the Nim proc that this C proc belongs to
-    beforeRetNeeded*: bool    # true iff 'BeforeRet' label for proc is needed
-    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
+    flags*: set[TCProcFlag]
     lastLineInfo*: TLineInfo  # to avoid generating excessive 'nimln' statements
     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
                               # (the vars must be volatile then)
                               # 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
                               # generation; this is the same as prc.options
                               # unless prc == nil
-    maxFrameLen*: int         # max length of frame descriptor
     module*: BModule          # used to prevent excessive parameter passing
     withinLoop*: int          # > 0 if we are within a loop
     splitDecls*: int          # > 0 if we are in some context for C++ that
                               # requires 'T x = T()' to become 'T x; x = T()'
                               # (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]
 
   TTypeSeq* = seq[PType]
diff --git a/compiler/commands.nim b/compiler/commands.nim
index fa94d36e3..4e258f810 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -219,6 +219,7 @@ const
   errNoneBoehmRefcExpectedButXFound = "'none', 'boehm' or 'refc' expected, but '$1' found"
   errNoneSpeedOrSizeExpectedButXFound = "'none', 'speed' or 'size' 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 =
   case switch.normalize
@@ -254,6 +255,13 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
     else: localError(conf, info, errGuiConsoleOrLibExpectedButXFound % arg)
   of "dynliboverride":
     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)
 
 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)
     if {':', '='} in arg:
       splitSwitch(conf, arg, key, val, pass, info)
+      if cmpIgnoreStyle(key, "nimQuirky") == 0:
+        conf.exc = excQuirky
       defineSymbol(conf.symbols, key, val)
     else:
+      if cmpIgnoreStyle(arg, "nimQuirky") == 0:
+        conf.exc = excQuirky
       defineSymbol(conf.symbols, arg)
   of "undef", "u":
     expectArg(conf, switch, arg, pass, info)
@@ -457,6 +469,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
         defineSymbol(conf.symbols, "gcmarkandsweep")
       of "destructors", "arc":
         conf.selectedGC = gcArc
+        when true:
+          if conf.cmd != cmdCompileToCpp:
+            conf.exc = excGoto
         defineSymbol(conf.symbols, "gcdestructors")
         defineSymbol(conf.symbols, "gcarc")
         incl conf.globalOptions, optSeqDestructors
@@ -466,6 +481,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
           defineSymbol(conf.symbols, "nimV2")
       of "orc":
         conf.selectedGC = gcOrc
+        when true:
+          if conf.cmd != cmdCompileToCpp:
+            conf.exc = excGoto
         defineSymbol(conf.symbols, "gcdestructors")
         defineSymbol(conf.symbols, "gcorc")
         incl conf.globalOptions, optSeqDestructors
@@ -775,8 +793,15 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
       localError(conf, info, "unknown obsolete feature")
   of "nocppexceptions":
     expectNoArg(conf, switch, arg, pass, info)
-    incl(conf.globalOptions, optNoCppExceptions)
+    conf.exc = low(ExceptionSystem)
     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":
     expectArg(conf, switch, arg, pass, info)
     if conf != nil:
diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim
index ae23fec3f..ec9b4a9f4 100644
--- a/compiler/condsyms.nim
+++ b/compiler/condsyms.nim
@@ -102,3 +102,4 @@ proc initDefines*(symbols: StringTableRef) =
   defineSymbol("nimnomagic64")
   defineSymbol("nimNewShiftOps")
   defineSymbol("nimHasCursor")
+  defineSymbol("nimHasExceptionsQuery")
diff --git a/compiler/main.nim b/compiler/main.nim
index 877b82dd9..7a78c66ba 100644
--- a/compiler/main.nim
+++ b/compiler/main.nim
@@ -108,6 +108,7 @@ proc commandJsonScript(graph: ModuleGraph) =
 when not defined(leanCompiler):
   proc commandCompileToJS(graph: ModuleGraph) =
     let conf = graph.config
+    conf.exc = excCpp
 
     if conf.outDir.isEmpty:
       conf.outDir = conf.projectPath
@@ -188,6 +189,7 @@ proc mainCommand*(graph: ModuleGraph) =
     commandCompileToC(graph)
   of "cpp", "compiletocpp":
     conf.cmd = cmdCompileToCpp
+    conf.exc = excCpp
     defineSymbol(graph.config.symbols, "cpp")
     commandCompileToC(graph)
   of "objc", "compiletooc":
diff --git a/compiler/options.nim b/compiler/options.nim
index 443b2a1b6..9c17ea1e6 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -71,7 +71,6 @@ type                          # please make sure we have under 32 options
                               # also: generate header file
     optIdeDebug               # idetools: debug mode
     optIdeTerse               # idetools: use terse descriptions
-    optNoCppExceptions        # use C exception handling even with CPP
     optExcessiveStackTrace    # fully qualified module filenames
     optShowAllMismatches      # show all overloading resolution candidates
     optWholeProject           # for 'doc2': output any dependency
@@ -159,6 +158,12 @@ type
     ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc,
     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
     Cached,    ## no need to recompile this time
     External   ## file was introduced via .compile pragma
@@ -204,6 +209,7 @@ type
     exitcode*: int8
     cmd*: TCommands  # the command
     selectedGC*: TGCMode       # the selected GC (+)
+    exc*: ExceptionSystem
     verbosity*: int            # how verbose the compiler is
     numberOfProcessors*: int   # number of processors
     evalExpr*: string          # expression for idetools --eval
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 6596cfba6..c8b13ab6a 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -880,7 +880,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
       of wNoreturn:
         noVal(c, it)
         # 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)
         if sym.typ[0] != nil:
           localError(c.config, sym.ast[paramsPos][0].info,
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index 6a6195600..6f83fac05 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -167,9 +167,7 @@ proc guardDotAccess(a: PEffects; n: PNode) =
     guardGlobal(a, n, g)
 
 proc makeVolatile(a: PEffects; s: PSym) {.inline.} =
-  template compileToCpp(a): untyped =
-    a.config.cmd == cmdCompileToCpp or sfCompileToCpp in getModule(a.owner).flags
-  if a.inTryStmt > 0 and not compileToCpp(a):
+  if a.inTryStmt > 0 and a.config.exc == excSetjmp:
     incl(s.flags, sfVolatile)
 
 proc initVar(a: PEffects, n: PNode; volatileCheck: bool) =
diff --git a/doc/advopt.txt b/doc/advopt.txt
index fef9f6895..e7626bd89 100644
--- a/doc/advopt.txt
+++ b/doc/advopt.txt
@@ -98,12 +98,13 @@ Advanced options:
   --skipProjCfg:on|off      do not read the project's configuration file
   --gc:refc|markAndSweep|boehm|go|none|regions
                             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
   --putenv:key=value        set an environment variable
   --NimblePath:PATH         add a path for Nimble support
   --noNimblePath            deactivate the Nimble path
   --clearNimblePath         empty the list of Nimble package search paths
-  --noCppExceptions         use default exception handling with C++ backend
   --cppCompileToNamespace:namespace
                             use the provided namespace for the generated C++ code,
                             if no namespace is provided "Nim" will be used
diff --git a/lib/system.nim b/lib/system.nim
index 58250db3f..838205c57 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -3105,7 +3105,7 @@ when not defined(js):
   when defined(nimV2):
     type
       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
         destructor: pointer
         size: int
@@ -3120,6 +3120,12 @@ when not defined(js):
 
   {.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):
   include "system/fatal"
 
@@ -3693,10 +3699,6 @@ when not defined(JS): #and not defined(nimscript):
       proc unsetControlCHook*()
         ## 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":
       proc getStackTrace*(): string {.gcsafe.}
         ## Gets the current stack trace. This only works for debug builds.
diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim
index 140cd00b8..e241879c2 100644
--- a/lib/system/excpt.nim
+++ b/lib/system/excpt.nim
@@ -27,19 +27,21 @@ else:
   proc writeToStdErr(msg: cstring) =
     discard MessageBoxA(nil, msg, nil, 0)
 
-proc showErrorMessage(data: cstring) {.gcsafe.} =
+proc showErrorMessage(data: cstring) {.gcsafe, raises: [].} =
+  var toWrite = true
   if errorMessageWriter != nil:
-    errorMessageWriter($data)
-  else:
+    try:
+      errorMessageWriter($data)
+      toWrite = false
+    except:
+      discard
+  if toWrite:
     when defined(genode):
       # stderr not available by default, use the LOG session
       echo data
     else:
       writeToStdErr(data)
 
-proc quitOrDebug() {.inline.} =
-  quit(1)
-
 proc chckIndx(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.}
@@ -57,10 +59,12 @@ var
     # list of exception handlers
     # a global variable for the root of all try blocks
   currException {.threadvar.}: ref Exception
-  raiseCounter {.threadvar.}: uint
-
   gcFramePtr {.threadvar.}: GcFrame
 
+when defined(cpp) and not defined(noCppExceptions):
+  var
+    raiseCounter {.threadvar.}: uint
+
 type
   FrameState = tuple[gcFramePtr: GcFrame, framePtr: PFrame,
                      excHandler: PSafePoint, currException: ref Exception]
@@ -130,7 +134,7 @@ proc popCurrentExceptionEx(id: uint) {.compilerRtl.} =
       cur = cur.up
     if cur == nil:
       showErrorMessage("popCurrentExceptionEx() exception was not found in the exception stack. Aborting...")
-      quitOrDebug()
+      quit(1)
     prev.up = cur.up
 
 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)``.
   ## Unstable API.
 
-template unhandled(buf, body) =
-  if onUnhandledException != nil:
-    onUnhandledException($buf)
+proc reportUnhandledError(e: ref Exception) {.nodestroy.} =
+  when hasSomeStackTrace:
+    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:
-    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.} =
   when defined(cpp) and not defined(noCppExceptions):
     {.emit: "throw;".}
   else:
-    template e: untyped = currException
     if excHandler != nil:
       c_longjmp(excHandler.context, 1)
     else:
-      when hasSomeStackTrace:
-        var buf = newStringOfCap(2000)
-        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()
+      reportUnhandledError(currException)
+      quit(1)
+
+when gotoBasedExceptions:
+  var nimInErrorMode {.threadvar.}: int
+
+  proc nimErrorFlag(): ptr int {.compilerRtl, inl.} =
+    result = addr(nimInErrorMode)
+
+  proc nimTestErrorFlag() {.compilerRtl.} =
+    ## This proc must be called before ``currException`` is destroyed.
+    ## It also must be called at the end of every thread to ensure no
+    ## error is swallowed.
+    if currException != nil:
+      reportUnhandledError(currException)
+      currException = nil
+      quit(1)
+
+  addQuitProc(proc () {.noconv.} =
+    if currException != nil:
+      reportUnhandledError(currException)
+      # emulate: ``programResult = 1`` via abort() and a nop signal handler.
+      c_signal(SIGABRT, (proc (sign: cint) {.noconv, benign.} = discard))
+      c_abort()
+  )
 
 proc raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} =
   if localRaiseHook != nil:
@@ -414,50 +448,19 @@ proc raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} =
         raiseCounter.inc # skip zero at overflow
       e.raiseId = raiseCounter
       {.emit: "`e`->raise();".}
-  elif defined(nimQuirky):
-    pushCurrentException(e)
+  elif defined(nimQuirky) or gotoBasedExceptions:
+    # XXX This check should likely also be done in the setjmp case below.
+    if e != currException:
+      pushCurrentException(e)
+      when gotoBasedExceptions:
+        inc nimInErrorMode
   else:
     if excHandler != nil:
       pushCurrentException(e)
       c_longjmp(excHandler.context, 1)
     else:
-      when hasSomeStackTrace:
-        var buf = newStringOfCap(2000)
-        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()
+      reportUnhandledError(e)
+      quit(1)
 
 proc raiseExceptionEx(e: sink(ref Exception), ename, procname, filename: cstring,
                       line: int) {.compilerRtl, nodestroy.} =
@@ -484,15 +487,18 @@ proc reraiseException() {.compilerRtl.} =
   if currException == nil:
     sysFatal(ReraiseError, "no exception to reraise")
   else:
-    raiseExceptionAux(currException)
+    when gotoBasedExceptions:
+      inc nimInErrorMode
+    else:
+      raiseExceptionAux(currException)
 
 proc writeStackTrace() =
   when hasSomeStackTrace:
     var s = ""
     rawWriteStackTrace(s)
-    cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall.}](showErrorMessage)(s)
+    cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall, raises: [].}](showErrorMessage)(s)
   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 =
   when hasSomeStackTrace:
@@ -529,9 +535,9 @@ proc callDepthLimitReached() {.noinline.} =
       $nimCallDepthLimit & " function calls). You can change it with " &
       "-d:nimCallDepthLimit=<int> but really try to avoid deep " &
       "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.prev = framePtr
   framePtr = s
diff --git a/lib/system/fatal.nim b/lib/system/fatal.nim
index 087753d3d..d68d06712 100644
--- a/lib/system/fatal.nim
+++ b/lib/system/fatal.nim
@@ -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.}
+
+when defined(nimHasExceptionsQuery):
+  const gotoBasedExceptions = compileOption("exceptions", "goto")
+else:
+  const gotoBasedExceptions = false
+
 when hostOS == "standalone":
   include "$projectpath/panicoverride"
 
@@ -9,19 +24,20 @@ when hostOS == "standalone":
     rawoutput(message)
     panic(arg)
 
-elif defined(nimQuirky) and not defined(nimscript):
+elif (defined(nimQuirky) or gotoBasedExceptions) and not defined(nimscript):
   import ansi_c
 
   proc name(t: typedesc): string {.magic: "TypeTrait".}
 
   proc sysFatal(exceptn: typedesc, message, arg: string) {.inline, noreturn.} =
+    writeStackTrace()
     var buf = newStringOfCap(200)
     add(buf, "Error: unhandled exception: ")
     add(buf, message)
     add(buf, arg)
     add(buf, " [")
     add(buf, name exceptn)
-    add(buf, "]")
+    add(buf, "]\n")
     cstderr.rawWrite buf
     quit 1
 
diff --git a/lib/system/refs_v2.nim b/lib/system/refs_v2.nim
index 6fd34fca6..e07c33086 100644
--- a/lib/system/refs_v2.nim
+++ b/lib/system/refs_v2.nim
@@ -111,7 +111,7 @@ proc nimRawDispose(p: pointer) {.compilerRtl.} =
 template dispose*[T](x: owned(ref T)) = nimRawDispose(cast[pointer](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
   if d != nil: cast[DestructorProc](d)(p)
   when false:
diff --git a/tests/assert/tassert_c.nim b/tests/assert/tassert_c.nim
index 877b3aead..84ccea823 100644
--- a/tests/assert/tassert_c.nim
+++ b/tests/assert/tassert_c.nim
@@ -8,7 +8,7 @@ tassert_c.nim(35)        tassert_c
 tassert_c.nim(34)        foo
 assertions.nim(27)       failedAssertImpl
 assertions.nim(20)       raiseAssert
-fatal.nim(39)            sysFatal"""
+fatal.nim(55)            sysFatal"""
 
 proc tmatch(x, p: string): bool =
   var i = 0
diff --git a/tests/destructor/tgotoexceptions.nim b/tests/destructor/tgotoexceptions.nim
new file mode 100755
index 000000000..f76592270
--- /dev/null
+++ b/tests/destructor/tgotoexceptions.nim
@@ -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"
diff --git a/tests/destructor/tgotoexceptions2.nim b/tests/destructor/tgotoexceptions2.nim
new file mode 100644
index 000000000..057caf7b7
--- /dev/null
+++ b/tests/destructor/tgotoexceptions2.nim
@@ -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()
diff --git a/tests/destructor/tgotoexceptions3.nim b/tests/destructor/tgotoexceptions3.nim
new file mode 100644
index 000000000..308d288b2
--- /dev/null
+++ b/tests/destructor/tgotoexceptions3.nim
@@ -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")
diff --git a/tests/exception/tfinally4.nim b/tests/exception/tfinally4.nim
index feaf1bc96..a7dbbffef 100644
--- a/tests/exception/tfinally4.nim
+++ b/tests/exception/tfinally4.nim
@@ -1,5 +1,39 @@
 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