summary refs log tree commit diff stats
path: root/lib/system
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 /lib/system
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.
Diffstat (limited to 'lib/system')
-rw-r--r--lib/system/excpt.nim196
-rw-r--r--lib/system/fatal.nim20
-rw-r--r--lib/system/refs_v2.nim2
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: