Attempt to finish off araq cpp exceptions (#13695)

* config update
* disable a questionable test
* remove c++ exception handling IDs, new impl doesn't require it anymore
* C++ based exceptions finally work
* fixes bootstrapping problem in C++ mode
* teach GCC it's 2020 now
* more bugfixes for C++ based exception handling
* apply cooldome's patch
* another attempt to enable C++11
* bug fix

Co-authored-by: Araq <rumpf_a@web.de>
Co-authored-by: cooldome <ariabushenko@bk.ru>
This commit is contained in:
cooldome
2020-03-19 19:38:25 +00:00
committed by GitHub
parent 034dad8e32
commit b3176b8817
12 changed files with 233 additions and 74 deletions

View File

@@ -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
@@ -723,14 +722,16 @@ proc raiseInstr(p: BProc): Rope =
result = nil
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])
@@ -947,6 +948,170 @@ 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 {
throw;
}
} 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
var hasElse = 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:
hasElse = true
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)
if hasIf and not hasElse:
linefmt(p, cpsStmts, "else throw;$n", [etmp])
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

View File

@@ -609,13 +609,14 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope,
appcg(m, result, " : public $1 {$n",
[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:
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])
hasField = true
else:
appcg(m, result, " {$n $1 Sup;$n",

View File

@@ -343,11 +343,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:

View File

@@ -48,7 +48,8 @@ type
# used on some platforms
asmStmtFrmt: string, # format of ASM statement
structStmtFmt: string, # Format for struct statement
produceAsm: string, # Format how to produce assembler listings
produceAsm: string, # Format how to produce assembler listings
cppXsupport: string, # what to do to enable C++X support
props: TInfoCCProps] # properties of the C compiler
@@ -85,6 +86,7 @@ compiler gcc:
asmStmtFrmt: "asm($1);$n",
structStmtFmt: "$1 $3 $2 ", # struct|union [packed] $name
produceAsm: gnuAsmListing,
cppXsupport: "-std=gnu++14 -funsigned-char",
props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm,
hasAttribute})
@@ -111,6 +113,7 @@ compiler nintendoSwitchGCC:
asmStmtFrmt: "asm($1);$n",
structStmtFmt: "$1 $3 $2 ", # struct|union [packed] $name
produceAsm: gnuAsmListing,
cppXsupport: "-std=gnu++14 -funsigned-char",
props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm,
hasAttribute})
@@ -158,6 +161,7 @@ compiler vcc:
asmStmtFrmt: "__asm{$n$1$n}$n",
structStmtFmt: "$3$n$1 $2",
produceAsm: "/Fa$asmfile",
cppXsupport: "",
props: {hasCpp, hasAssume, hasDeclspec})
compiler clangcl:
@@ -204,6 +208,7 @@ compiler lcc:
asmStmtFrmt: "_asm{$n$1$n}$n",
structStmtFmt: "$1 $2",
produceAsm: "",
cppXsupport: "",
props: {})
# Borland C Compiler
@@ -229,6 +234,7 @@ compiler bcc:
asmStmtFrmt: "__asm{$n$1$n}$n",
structStmtFmt: "$1 $2",
produceAsm: "",
cppXsupport: "",
props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard,
hasAttribute})
@@ -255,6 +261,7 @@ compiler dmc:
asmStmtFrmt: "__asm{$n$1$n}$n",
structStmtFmt: "$3$n$1 $2",
produceAsm: "",
cppXsupport: "",
props: {hasCpp})
# Watcom C Compiler
@@ -280,6 +287,7 @@ compiler wcc:
asmStmtFrmt: "__asm{$n$1$n}$n",
structStmtFmt: "$1 $2",
produceAsm: "",
cppXsupport: "",
props: {hasCpp})
# Tiny C Compiler
@@ -305,6 +313,7 @@ compiler tcc:
asmStmtFrmt: "asm($1);$n",
structStmtFmt: "$1 $2",
produceAsm: gnuAsmListing,
cppXsupport: "",
props: {hasSwitchRange, hasComputedGoto, hasGnuAsm})
# Pelles C Compiler
@@ -331,6 +340,7 @@ compiler pcc:
asmStmtFrmt: "__asm{$n$1$n}$n",
structStmtFmt: "$1 $2",
produceAsm: "",
cppXsupport: "",
props: {})
# Your C Compiler
@@ -356,6 +366,7 @@ compiler ucc:
asmStmtFrmt: "__asm{$n$1$n}$n",
structStmtFmt: "$1 $2",
produceAsm: "",
cppXsupport: "",
props: {})
const
@@ -389,8 +400,10 @@ proc nameToCC*(name: string): TSystemCC =
return i
result = ccNone
proc listCCnames(): seq[string] =
proc listCCnames(): string =
result = ""
for i in succ(ccNone)..high(TSystemCC):
if i > succ(ccNone): result.add ", "
result.add CC[i].name
proc isVSCompatible*(conf: ConfigRef): bool =
@@ -426,8 +439,7 @@ proc getConfigVar(conf: ConfigRef; c: TSystemCC, suffix: string): string =
proc setCC*(conf: ConfigRef; ccname: string; info: TLineInfo) =
conf.cCompiler = nameToCC(ccname)
if conf.cCompiler == ccNone:
let ccList = listCCnames().join(", ")
localError(conf, info, "unknown C compiler: '$1'. Available options are: $2" % [ccname, ccList])
localError(conf, info, "unknown C compiler: '$1'. Available options are: $2" % [ccname, listCCnames()])
conf.compileOptions = getConfigVar(conf, conf.cCompiler, ".options.always")
conf.linkOptions = ""
conf.cCompilerPath = getConfigVar(conf, conf.cCompiler, ".path")
@@ -576,8 +588,11 @@ proc needsExeExt(conf: ConfigRef): bool {.inline.} =
result = (optGenScript in conf.globalOptions and conf.target.targetOS == osWindows) or
(conf.target.hostOS == osWindows)
proc useCpp(conf: ConfigRef; cfile: AbsoluteFile): bool =
conf.cmd == cmdCompileToCpp and not cfile.string.endsWith(".c")
proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: AbsoluteFile): string =
result = if conf.cmd == cmdCompileToCpp and not cfile.string.endsWith(".c"):
result = if useCpp(conf, cfile):
CC[compiler].cppCompiler
else:
CC[compiler].compilerExe
@@ -605,6 +620,9 @@ proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile,
ospNeedsPIC in platform.OS[conf.target.targetOS].props:
options.add(' ' & CC[c].pic)
if useCpp(conf, cfile.cname):
options.add(' ' & CC[c].cppXsupport)
var compilePattern: string
# compute include paths:
var includeCmd = CC[c].includeCmd & quoteShell(conf.libpath)

View File

@@ -69,7 +69,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)

View File

@@ -296,7 +296,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 = ""

View File

@@ -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 ## \

View File

@@ -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]
@@ -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
@@ -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:
@@ -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 & "]"
@@ -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

View File

@@ -68,4 +68,4 @@ except:
echo getCurrentExceptionMsg()
discard
doAssert: getCurrentException() == nil
doAssert: getCurrentException() == nil

View File

@@ -2,8 +2,9 @@ discard """
targets: "cpp"
outputsub: "Error: unhandled unknown cpp exception"
exitcode: 1
disabled: true
"""
type Crap {.importcpp: "int".} = object
var c: Crap
raise c
raise c

View File

@@ -14,7 +14,7 @@ cpp exception caught
'''
"""
type
type
std_exception* {.importcpp: "std::exception", header: "<exception>".} = object
std_runtime_error* {.importcpp: "std::runtime_error", header: "<stdexcept>".} = object
std_string* {.importcpp: "std::string", header: "<string>".} = object
@@ -25,7 +25,7 @@ proc constructRuntimeError(s: stdstring): std_runtime_error {.importcpp: "std::r
proc what(ex: std_runtime_error): cstring {.importcpp: "((char *)#.what())".}
proc myexception =
proc myexception =
raise constructRuntimeError(constructStdString("cpp_exception"))
try:
@@ -41,17 +41,17 @@ except std_exception:
doAssert(getCurrentException() == nil)
proc earlyReturn =
proc earlyReturn =
try:
try:
myexception()
myexception()
finally:
echo "finally1"
except:
return
finally:
echo "finally2"
earlyReturn()
doAssert(getCurrentException() == nil)
@@ -118,16 +118,15 @@ try:
echo "finally 2"
except:
echo "expected"
doAssert(getCurrentException() == nil)
try:
try:
myexception()
except std_runtime_error as ex:
echo "cpp exception caught"
raise newException(ValueError, "rewritten " & $ex.what())
try:
myexception()
except std_runtime_error as ex:
echo "cpp exception caught"
raise newException(ValueError, "rewritten " & $ex.what())
except:
doAssert(getCurrentExceptionMsg() == "rewritten cpp_exception")

View File

@@ -1,9 +1,11 @@
discard """
targets: "c cpp"
targets: "c"
output: "ok"
"""
var closureIterResult = newSeq[int]()
# XXX Investigate why this fails now for 'nim cpp'
proc checkpoint(arg: int) =
closureIterResult.add(arg)