summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ccgstmts.nim13
-rw-r--r--doc/nimc.rst14
-rw-r--r--lib/system/ansi_c.nim46
-rw-r--r--tests/exception/texceptions.nim6
-rw-r--r--tests/exception/texceptions2.nim130
5 files changed, 199 insertions, 10 deletions
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim
index 559849f0d..7fecc1475 100644
--- a/compiler/ccgstmts.nim
+++ b/compiler/ccgstmts.nim
@@ -1356,8 +1356,19 @@ proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) =
       linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint])
     elif isDefined(p.config, "nimSigSetjmp"):
       linefmt(p, cpsStmts, "$1.status = sigsetjmp($1.context, 0);$n", [safePoint])
+    elif isDefined(p.config, "nimBuiltinSetjmp"):
+      linefmt(p, cpsStmts, "$1.status = __builtin_setjmp($1.context);$n", [safePoint])
     elif isDefined(p.config, "nimRawSetjmp"):
-      linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", [safePoint])
+      if isDefined(p.config, "mswindows"):
+        # The Windows `_setjmp()` takes two arguments, with the second being an
+        # undocumented buffer used by the SEH mechanism for stack unwinding.
+        # Mingw-w64 has been trying to get it right for years, but it's still
+        # prone to stack corruption during unwinding, so we disable that by setting
+        # it to NULL.
+        # More details: https://github.com/status-im/nimbus-eth2/issues/3121
+        linefmt(p, cpsStmts, "$1.status = _setjmp($1.context, 0);$n", [safePoint])
+      else:
+        linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", [safePoint])
     else:
       linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint])
     lineCg(p, cpsStmts, "if ($1.status == 0) {$n", [safePoint])
diff --git a/doc/nimc.rst b/doc/nimc.rst
index eb066e748..0ab1501ff 100644
--- a/doc/nimc.rst
+++ b/doc/nimc.rst
@@ -165,6 +165,20 @@ ignored too. `--define:FOO`:option: and `--define:foo`:option: are identical.
 Compile-time symbols starting with the `nim` prefix are reserved for the
 implementation and should not be used elsewhere.
 
+==========================       ============================================
+Name                             Description
+==========================       ============================================
+nimStdSetjmp                     Use the standard `setjmp()/longjmp()` library
+                                 functions for setjmp-based exceptions. This is
+                                 the default on most platforms.
+nimSigSetjmp                     Use `sigsetjmp()/siglongjmp()` for setjmp-based exceptions.
+nimRawSetjmp                     Use `_setjmp()/_longjmp()` on POSIX and `_setjmp()/longjmp()`
+                                 on Windows, for setjmp-based exceptions. It's the default on
+                                 BSDs and BSD-like platforms, where it's significantly faster
+                                 than the standard functions.
+nimBuiltinSetjmp                 Use `__builtin_setjmp()/__builtin_longjmp()` for setjmp-based
+                                 exceptions. This will not work if an exception is being thrown
+                                 and caught inside the same procedure. Useful for benchmarking.
 
 Configuration files
 -------------------
diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim
index 259d36633..23fb9fdef 100644
--- a/lib/system/ansi_c.nim
+++ b/lib/system/ansi_c.nim
@@ -31,7 +31,10 @@ proc c_abort*() {.
   importc: "abort", header: "<stdlib.h>", noSideEffect, noreturn.}
 
 
-when defined(linux) and defined(amd64):
+when defined(nimBuiltinSetjmp):
+  type
+    C_JmpBuf* = array[5, pointer]
+elif defined(linux) and defined(amd64):
   type
     C_JmpBuf* {.importc: "jmp_buf", header: "<setjmp.h>", bycopy.} = object
         abi: array[200 div sizeof(clong), clong]
@@ -92,18 +95,47 @@ when defined(macosx):
 elif defined(haiku):
   const SIGBUS* = cint(30)
 
-when defined(nimSigSetjmp) and not defined(nimStdSetjmp):
+# "nimRawSetjmp" is defined by default for certain platforms, so we need the
+# "nimStdSetjmp" escape hatch with it.
+when defined(nimSigSetjmp):
   proc c_longjmp*(jmpb: C_JmpBuf, retval: cint) {.
     header: "<setjmp.h>", importc: "siglongjmp".}
-  template c_setjmp*(jmpb: C_JmpBuf): cint =
+  proc c_setjmp*(jmpb: C_JmpBuf): cint =
     proc c_sigsetjmp(jmpb: C_JmpBuf, savemask: cint): cint {.
       header: "<setjmp.h>", importc: "sigsetjmp".}
     c_sigsetjmp(jmpb, 0)
+elif defined(nimBuiltinSetjmp):
+  proc c_longjmp*(jmpb: C_JmpBuf, retval: cint) =
+    # Apple's Clang++ has trouble converting array names to pointers, so we need
+    # to be very explicit here.
+    proc c_builtin_longjmp(jmpb: ptr pointer, retval: cint) {.
+      importc: "__builtin_longjmp", nodecl.}
+    # The second parameter needs to be 1 and sometimes the C/C++ compiler checks it.
+    c_builtin_longjmp(unsafeAddr jmpb[0], 1)
+  proc c_setjmp*(jmpb: C_JmpBuf): cint =
+    proc c_builtin_setjmp(jmpb: ptr pointer): cint {.
+      importc: "__builtin_setjmp", nodecl.}
+    c_builtin_setjmp(unsafeAddr jmpb[0])
 elif defined(nimRawSetjmp) and not defined(nimStdSetjmp):
-  proc c_longjmp*(jmpb: C_JmpBuf, retval: cint) {.
-    header: "<setjmp.h>", importc: "_longjmp".}
-  proc c_setjmp*(jmpb: C_JmpBuf): cint {.
-    header: "<setjmp.h>", importc: "_setjmp".}
+  when defined(windows):
+    # No `_longjmp()` on Windows.
+    proc c_longjmp*(jmpb: C_JmpBuf, retval: cint) {.
+      header: "<setjmp.h>", importc: "longjmp".}
+    # The Windows `_setjmp()` takes two arguments, with the second being an
+    # undocumented buffer used by the SEH mechanism for stack unwinding.
+    # Mingw-w64 has been trying to get it right for years, but it's still
+    # prone to stack corruption during unwinding, so we disable that by setting
+    # it to NULL.
+    # More details: https://github.com/status-im/nimbus-eth2/issues/3121
+    proc c_setjmp*(jmpb: C_JmpBuf): cint =
+      proc c_setjmp_win(jmpb: C_JmpBuf, ctx: pointer): cint {.
+        header: "<setjmp.h>", importc: "_setjmp".}
+      c_setjmp_win(jmpb, nil)
+  else:
+    proc c_longjmp*(jmpb: C_JmpBuf, retval: cint) {.
+      header: "<setjmp.h>", importc: "_longjmp".}
+    proc c_setjmp*(jmpb: C_JmpBuf): cint {.
+      header: "<setjmp.h>", importc: "_setjmp".}
 else:
   proc c_longjmp*(jmpb: C_JmpBuf, retval: cint) {.
     header: "<setjmp.h>", importc: "longjmp".}
diff --git a/tests/exception/texceptions.nim b/tests/exception/texceptions.nim
index adee5d1d5..62d24c934 100644
--- a/tests/exception/texceptions.nim
+++ b/tests/exception/texceptions.nim
@@ -1,4 +1,6 @@
 discard """
+  disabled: "windows" # no sigsetjmp() there
+  matrix: "-d:nimStdSetjmp; -d:nimSigSetjmp; -d:nimRawSetjmp; -d:nimBuiltinSetjmp"
   output: '''
 
 BEFORE
@@ -17,7 +19,7 @@ FINALLY
 
 echo ""
 
-proc no_expcetion =
+proc no_exception =
   try:
     echo "BEFORE"
 
@@ -28,7 +30,7 @@ proc no_expcetion =
   finally:
     echo "FINALLY"
 
-try: no_expcetion()
+try: no_exception()
 except: echo "RECOVER"
 
 echo ""
diff --git a/tests/exception/texceptions2.nim b/tests/exception/texceptions2.nim
new file mode 100644
index 000000000..97fd856a0
--- /dev/null
+++ b/tests/exception/texceptions2.nim
@@ -0,0 +1,130 @@
+discard """
+  disabled: "posix" # already covered by texceptions.nim
+  matrix: "-d:nimStdSetjmp; -d:nimRawSetjmp; -d:nimBuiltinSetjmp"
+  output: '''
+
+BEFORE
+FINALLY
+
+BEFORE
+EXCEPT
+FINALLY
+RECOVER
+
+BEFORE
+EXCEPT: IOError: hi
+FINALLY
+'''
+"""
+
+echo ""
+
+proc no_exception =
+  try:
+    echo "BEFORE"
+
+  except:
+    echo "EXCEPT"
+    raise
+
+  finally:
+    echo "FINALLY"
+
+try: no_exception()
+except: echo "RECOVER"
+
+echo ""
+
+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"
+
+echo ""
+
+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"
+
+block: #10417
+  proc moo() {.noreturn.} = discard
+
+  let bar =
+    try:
+      1
+    except:
+      moo()
+
+  doAssert(bar == 1)
+
+# Make sure the VM handles the exceptions correctly
+block:
+  proc fun1(): seq[int] =
+    try:
+      try:
+        raise newException(ValueError, "xx")
+      except:
+        doAssert("xx" == getCurrentExceptionMsg())
+        raise newException(KeyError, "yy")
+    except:
+      doAssert("yy" == getCurrentExceptionMsg())
+      result.add(1212)
+    try:
+      try:
+        raise newException(AssertionDefect, "a")
+      finally:
+        result.add(42)
+    except AssertionDefect:
+      result.add(99)
+    finally:
+      result.add(10)
+    result.add(4)
+    result.add(0)
+    try:
+      result.add(1)
+    except KeyError:
+      result.add(-1)
+    except ValueError:
+      result.add(-1)
+    except IndexDefect:
+      result.add(2)
+    except:
+      result.add(3)
+
+    try:
+      try:
+        result.add(1)
+        return
+      except:
+        result.add(-1)
+      finally:
+        result.add(2)
+    except KeyError:
+      doAssert(false)
+    finally:
+      result.add(3)
+
+  let x1 = fun1()
+  const x2 = fun1()
+  doAssert(x1 == x2)