diff options
-rw-r--r-- | compiler/ccgstmts.nim | 13 | ||||
-rw-r--r-- | doc/nimc.rst | 14 | ||||
-rw-r--r-- | lib/system/ansi_c.nim | 46 | ||||
-rw-r--r-- | tests/exception/texceptions.nim | 6 | ||||
-rw-r--r-- | tests/exception/texceptions2.nim | 130 |
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) |