diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2020-01-01 10:01:49 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-01-01 10:01:49 +0100 |
commit | c3344862b0d6061cc1581f29c81b29b75c78615a (patch) | |
tree | 75661179ec450bb4e2783603c09f4304dfe42a45 /lib/system | |
parent | 8a63caca07349742d071dcd3a7d3e3055fe617cf (diff) | |
download | Nim-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.
Diffstat (limited to 'lib/system')
-rw-r--r-- | lib/system/excpt.nim | 196 | ||||
-rw-r--r-- | lib/system/fatal.nim | 20 | ||||
-rw-r--r-- | lib/system/refs_v2.nim | 2 |
3 files changed, 120 insertions, 98 deletions
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: |