Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better C++ based exception handling #13065

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 172 additions & 12 deletions compiler/ccgstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,11 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) =
for i in countdown(howManyTrys-1, 0):
p.nestedTryStmts.add(stack[i])

if p.config.exc != excCpp:
# Pop exceptions that was handled by the
# except-blocks we are in
if noSafePoints notin p.flags:
for i in countdown(howManyExcepts-1, 0):
linefmt(p, cpsStmts, "#popCurrentException();$n", [])
# Pop exceptions that was handled by the
# except-blocks we are in
if noSafePoints notin p.flags:
for i in countdown(howManyExcepts-1, 0):
linefmt(p, cpsStmts, "#popCurrentException();$n", [])

proc genGotoState(p: BProc, n: PNode) =
# we resist the temptation to translate it into duff's device as it later
Expand Down Expand Up @@ -707,14 +706,16 @@ proc finallyActions(p: BProc) =
genSimpleBlock(p, finallyBlock[0])

proc genRaiseStmt(p: BProc, t: PNode) =
if p.config.exc == excCpp:
discard cgsym(p.module, "popCurrentExceptionEx")
if t[0].kind != nkEmpty:
var a: TLoc
initLocExprSingleUse(p, t[0], a)
finallyActions(p)
var e = rdLoc(a)
var typ = skipTypes(t[0].typ, abstractPtrs)
# XXX For reasons that currently escape me, this is only required by the new
# C++ based exception handling:
if p.config.exc == excCpp:
blockLeaveActions(p, howManyTrys = 0, howManyExcepts = p.inExceptBlockLen)
genLineDir(p, t)
if isImportedException(typ, p.config):
lineF(p, cpsStmts, "throw $1;$n", [e])
Expand All @@ -727,12 +728,11 @@ proc genRaiseStmt(p: BProc, t: PNode) =
lineCg(p, cpsStmts, "$1 = NIM_NIL;$n", [e])
else:
finallyActions(p)
if p.config.exc == excCpp:
blockLeaveActions(p, howManyTrys = 0, howManyExcepts = p.inExceptBlockLen)
genLineDir(p, t)
# reraise the last exception:
if p.config.exc == excCpp:
line(p, cpsStmts, ~"throw;$n")
else:
linefmt(p, cpsStmts, "#reraiseException();$n", [])
linefmt(p, cpsStmts, "#reraiseException();$n", [])
if p.config.exc == excGoto:
let L = p.nestedTryStmts.len
if L == 0:
Expand Down Expand Up @@ -940,6 +940,166 @@ proc genRestoreFrameAfterException(p: BProc) =
linefmt(p, cpsStmts, "#setFrame(_nimCurFrame);$n", [])

proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
#[ code to generate:

std::exception_ptr error = nullptr;
try {
body;
} catch (Exception e) {
error = std::current_exception();
if (ofExpr(e, TypeHere)) {

error = nullptr; // handled
} else if (...) {

} else {
}
} catch(...) {
// C++ exception occured, not under Nim's control.
}
{
/* finally: */
printf('fin!\n');
if (error) std::rethrow_exception(error); // re-raise the exception
}
]#
p.module.includeHeader("<exception>")

if not isEmptyType(t.typ) and d.k == locNone:
getTemp(p, t.typ, d)
genLineDir(p, t)

inc(p.labels, 2)
let etmp = p.labels

lineCg(p, cpsStmts, "std::exception_ptr T$1_ = nullptr;", [etmp])

let fin = if t[^1].kind == nkFinally: t[^1] else: nil
p.nestedTryStmts.add((fin, false, 0.Natural))

startBlock(p, "try {$n")
expr(p, t[0], d)
endBlock(p)

# First pass: handle Nim based exceptions:
lineCg(p, cpsStmts, "catch (#Exception* T$1_) {$n", [etmp+1])
genRestoreFrameAfterException(p)
# an unhandled exception happened!
lineCg(p, cpsStmts, "T$1_ = std::current_exception();$n", [etmp])
p.nestedTryStmts[^1].inExcept = true
var hasImportedCppExceptions = false
var i = 1
var hasIf = false
while (i < t.len) and (t[i].kind == nkExceptBranch):
# bug #4230: avoid false sharing between branches:
if d.k == locTemp and isEmptyType(t.typ): d.k = locNone
if t[i].len == 1:
hasImportedCppExceptions = true
# general except section:
if hasIf: lineF(p, cpsStmts, "else ", [])
startBlock(p)
# we handled the error:
linefmt(p, cpsStmts, "T$1_ = nullptr;$n", [etmp])
expr(p, t[i][0], d)
linefmt(p, cpsStmts, "#popCurrentException();$n", [])
endBlock(p)
else:
var orExpr = Rope(nil)
var exvar = PNode(nil)
for j in 0..<t[i].len - 1:
var typeNode = t[i][j]
if t[i][j].isInfixAs():
typeNode = t[i][j][1]
exvar = t[i][j][2] # ex1 in `except ExceptType as ex1:`
assert(typeNode.kind == nkType)
if isImportedException(typeNode.typ, p.config):
hasImportedCppExceptions = true
else:
if orExpr != nil: orExpr.add("||")
let checkFor = if optTinyRtti in p.config.globalOptions:
genTypeInfo2Name(p.module, typeNode.typ)
else:
genTypeInfo(p.module, typeNode.typ, typeNode.info)
let memberName = if p.module.compileToCpp: "m_type" else: "Sup.m_type"
appcg(p.module, orExpr, "#isObj(#nimBorrowCurrentException()->$1, $2)", [memberName, checkFor])

if orExpr != nil:
if hasIf:
startBlock(p, "else if ($1) {$n", [orExpr])
else:
startBlock(p, "if ($1) {$n", [orExpr])
hasIf = true
if exvar != nil:
fillLoc(exvar.sym.loc, locTemp, exvar, mangleLocalName(p, exvar.sym), OnStack)
linefmt(p, cpsStmts, "$1 $2 = T$3_;$n", [getTypeDesc(p.module, exvar.sym.typ),
rdLoc(exvar.sym.loc), rope(etmp+1)])
# we handled the error:
linefmt(p, cpsStmts, "T$1_ = nullptr;$n", [etmp])
expr(p, t[i][^1], d)
linefmt(p, cpsStmts, "#popCurrentException();$n", [])
endBlock(p)
inc(i)

linefmt(p, cpsStmts, "}$n", [])

# Second pass: handle C++ based exceptions:
template genExceptBranchBody(body: PNode) {.dirty.} =
genRestoreFrameAfterException(p)
#linefmt(p, cpsStmts, "T$1_ = std::current_exception();$n", [etmp])
expr(p, body, d)

var catchAllPresent = false
incl p.flags, noSafePoints # mark as not needing 'popCurrentException'
if hasImportedCppExceptions:
for i in 1..<t.len:
if t[i].kind != nkExceptBranch: break

# 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:
startBlock(p, "catch (...) {", [])
genExceptBranchBody(t[i][0])
endBlock(p)
catchAllPresent = true
else:
for j in 0..<t[i].len-1:
var typeNode = t[i][j]
if t[i][j].isInfixAs():
typeNode = t[i][j][1]
if isImportedException(typeNode.typ, p.config):
let exvar = t[i][j][2] # ex1 in `except ExceptType as ex1:`
fillLoc(exvar.sym.loc, locTemp, exvar, mangleLocalName(p, exvar.sym), OnStack)
startBlock(p, "catch ($1& $2) {$n", getTypeDesc(p.module, typeNode.typ), rdLoc(exvar.sym.loc))
genExceptBranchBody(t[i][^1]) # exception handler body will duplicated for every type
endBlock(p)
elif isImportedException(typeNode.typ, p.config):
startBlock(p, "catch ($1&) {$n", getTypeDesc(p.module, t[i][j].typ))
genExceptBranchBody(t[i][^1]) # exception handler body will duplicated for every type
endBlock(p)

excl p.flags, noSafePoints
discard pop(p.nestedTryStmts)
# general finally block:
if t.len > 0 and t[^1].kind == nkFinally:
if not catchAllPresent:
startBlock(p, "catch (...) {", [])
genRestoreFrameAfterException(p)
linefmt(p, cpsStmts, "T$1_ = std::current_exception();$n", [etmp])
endBlock(p)

startBlock(p)
genStmts(p, t[^1][0])
linefmt(p, cpsStmts, "if (T$1_) std::rethrow_exception(T$1_);$n", [etmp])
endBlock(p)

proc genTryCppOld(p: BProc, t: PNode, d: var TLoc) =
# There are two versions we generate, depending on whether we
# catch C++ exceptions, imported via .importcpp or not. The
# code can be easier if there are no imported C++ exceptions
# to deal with.

# code to generate:
#
# try
Expand Down
13 changes: 7 additions & 6 deletions compiler/ccgtypes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -610,12 +610,13 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope,
[getTypeDescAux(m, typ[0].skipTypes(skipPtrs), check)])
if typ.isException and m.config.exc == excCpp:
appcg(m, result, "virtual void raise() { throw *this; }$n", []) # required for polymorphic exceptions
if typ.sym.magic == mException:
# Add cleanup destructor to Exception base class
appcg(m, result, "~$1();$n", [name])
# define it out of the class body and into the procs section so we don't have to
# artificially forward-declare popCurrentExceptionEx (very VERY troublesome for HCR)
appcg(m, cfsProcs, "inline $1::~$1() {if(this->raiseId) #popCurrentExceptionEx(this->raiseId);}$n", [name])
when false:
if typ.sym.magic == mException:
Araq marked this conversation as resolved.
Show resolved Hide resolved
# Add cleanup destructor to Exception base class
appcg(m, result, "~$1();$n", [name])
# define it out of the class body and into the procs section so we don't have to
# artificially forward-declare popCurrentExceptionEx (very VERY troublesome for HCR)
appcg(m, cfsProcs, "inline $1::~$1() {if(this->raiseId) #popCurrentExceptionEx(this->raiseId);}$n", [name])
hasField = true
else:
appcg(m, result, " {$n $1 Sup;$n",
Expand Down
5 changes: 0 additions & 5 deletions compiler/cgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -342,11 +342,6 @@ type

proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: var TLoc,
mode: ObjConstrMode) =
if p.module.compileToCpp and t.isException and p.config.exc == excCpp:
# init vtable in Exception object for polymorphic exceptions
includeHeader(p.module, "<new>")
linefmt(p, section, "new ($1) $2;$n", [rdLoc(a), getTypeDesc(p.module, t)])

#if optNimV2 in p.config.globalOptions: return
case analyseObjectWithTypeField(t)
of frNone:
Expand Down
6 changes: 3 additions & 3 deletions compiler/extccomp.nim
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ compiler gcc:
optSize: " -Os -fno-ident",
compilerExe: "gcc",
cppCompiler: "g++",
compileTmpl: "-c $options $include -o $objfile $file",
compileTmpl: "-c -std=gnu++11 $options $include -o $objfile $file",
buildGui: " -mwindows",
buildDll: " -shared",
buildLib: "ar rcs $libfile $objfiles",
Expand All @@ -97,7 +97,7 @@ compiler nintendoSwitchGCC:
optSize: " -Os ",
compilerExe: "aarch64-none-elf-gcc",
cppCompiler: "aarch64-none-elf-g++",
compileTmpl: "-w -MMD -MP -MF $dfile -c $options $include -o $objfile $file",
compileTmpl: "-std=gnu++11 -w -MMD -MP -MF $dfile -c $options $include -o $objfile $file",
buildGui: " -mwindows",
buildDll: " -shared",
buildLib: "aarch64-none-elf-gcc-ar rcs $libfile $objfiles",
Expand Down Expand Up @@ -555,7 +555,7 @@ proc getCompileOptions(conf: ConfigRef): string =
proc vccplatform(conf: ConfigRef): string =
# VCC specific but preferable over the config hacks people
# had to do before, see #11306
if conf.cCompiler == ccVcc:
if conf.cCompiler == ccVcc:
let exe = getConfigVar(conf, conf.cCompiler, ".exe")
if "vccexe.exe" == extractFilename(exe):
result = case conf.target.targetCPU
Expand Down
8 changes: 4 additions & 4 deletions compiler/llstream.nim
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
import
pathutils

template imp(x) = import x
const hasRstdin = compiles(imp(rdstdin))
when hasRstdin: import rdstdin
# support '-d:useGnuReadline' for backwards compatibility:
when not defined(windows) and (defined(useGnuReadline) or defined(useLinenoise)):
import rdstdin

type
TLLRepl* = proc (s: PLLStream, buf: pointer, bufLen: int): int
Expand Down Expand Up @@ -67,7 +67,7 @@ proc llStreamClose*(s: PLLStream) =
of llsFile:
close(s.f)

when not hasRstdin:
when not declared(readLineFromStdin):
# fallback implementation:
proc readLineFromStdin(prompt: string, line: var string): bool =
stderr.write(prompt)
Expand Down
6 changes: 5 additions & 1 deletion koch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,11 @@ proc boot(args: string) =

let nimStart = findStartNim().quoteShell()
for i in 0..2:
let defaultCommand = if useCpp: "cpp" else: "c"
# Nim versions < (1, 1) expect Nim's exception type to have a 'raiseId' field for
# C++ interop. Later Nim versions do this differently and removed the 'raiseId' field.
# Thus we always bootstrap the first iteration with "c" and not with "cpp" as
# a workaround.
let defaultCommand = if useCpp and i > 0: "cpp" else: "c"
let bootOptions = if args.len == 0 or args.startsWith("-"): defaultCommand else: ""
echo "iteration: ", i+1
var extraOption = ""
Expand Down
6 changes: 0 additions & 6 deletions lib/system/exceptions.nim
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ type
trace: string
else:
trace: seq[StackTraceEntry]
when defined(nimBoostrapCsources0_19_0):
# see #10315, bootstrap with `nim cpp` from csources gave error:
# error: no member named 'raise_id' in 'Exception'
raise_id: uint # set when exception is raised
else:
raiseId: uint # set when exception is raised
up: ref Exception # used for stacking exceptions. Not exported!

Defect* = object of Exception ## \
Expand Down
32 changes: 6 additions & 26 deletions lib/system/excpt.nim
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,6 @@ var
currException {.threadvar.}: ref Exception
gcFramePtr {.threadvar.}: GcFrame

when defined(cpp) and not defined(noCppExceptions) and not gotoBasedExceptions:
var
raiseCounter {.threadvar.}: uint

type
FrameState = tuple[gcFramePtr: GcFrame, framePtr: PFrame,
excHandler: PSafePoint, currException: ref Exception]
Expand Down Expand Up @@ -123,19 +119,7 @@ proc popCurrentException {.compilerRtl, inl.} =
#showErrorMessage "B"

proc popCurrentExceptionEx(id: uint) {.compilerRtl.} =
# in cpp backend exceptions can pop-up in the different order they were raised, example #5628
if currException.raiseId == id:
currException = currException.up
else:
var cur = currException.up
var prev = currException
while cur != nil and cur.raiseId != id:
prev = cur
cur = cur.up
if cur == nil:
showErrorMessage("popCurrentExceptionEx() exception was not found in the exception stack. Aborting...")
quit(1)
prev.up = cur.up
discard "only for bootstrapping compatbility"

proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} =
currException = e
Expand Down Expand Up @@ -444,11 +428,7 @@ proc raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} =
{.emit: "throw;".}
else:
pushCurrentException(e)
raiseCounter.inc
if raiseCounter == 0:
raiseCounter.inc # skip zero at overflow
e.raiseId = raiseCounter
{.emit: "`e`->raise();".}
{.emit: "throw e;".}
elif defined(nimQuirky) or gotoBasedExceptions:
# XXX This check should likely also be done in the setjmp case below.
if e != currException:
Expand Down Expand Up @@ -562,9 +542,9 @@ when defined(cpp) and appType != "lib" and not gotoBasedExceptions and

var msg = "Unknown error in unexpected exception handler"
try:
{.emit"#if !defined(_MSC_VER) || (_MSC_VER >= 1923)".}
{.emit: "#if !defined(_MSC_VER) || (_MSC_VER >= 1923)".}
raise
{.emit"#endif".}
{.emit: "#endif".}
except Exception:
msg = currException.getStackTrace() & "Error: unhandled exception: " &
currException.msg & " [" & $currException.name & "]"
Expand All @@ -573,9 +553,9 @@ when defined(cpp) and appType != "lib" and not gotoBasedExceptions and
except:
msg = "Error: unhandled unknown cpp exception"

{.emit"#if defined(_MSC_VER) && (_MSC_VER < 1923)".}
{.emit: "#if defined(_MSC_VER) && (_MSC_VER < 1923)".}
msg = "Error: unhandled unknown cpp exception"
{.emit"#endif".}
{.emit: "#endif".}

when defined(genode):
# stderr not available by default, use the LOG session
Expand Down
Loading