Skip to content

Commit 8c356fd

Browse files
stefantalpalarunarimiran
authored andcommitted
nimRawSetjmp: support Windows (#19197)
* nimRawSetjmp: support Windows Using `_setjmp()` directly is required to avoid some rare (but very annoying) exception-related stack corruption leading to segfaults on Windows, with Mingw-w64 and SEH. More details: status-im/nimbus-eth2#3121 Also add "nimBuiltinSetjmp" - mostly for benchmarking. * fix for Apple's Clang++ (cherry picked from commit 69aabda)
1 parent 17522d6 commit 8c356fd

File tree

5 files changed

+199
-10
lines changed

5 files changed

+199
-10
lines changed

compiler/ccgstmts.nim

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1349,8 +1349,19 @@ proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) =
13491349
linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint])
13501350
elif isDefined(p.config, "nimSigSetjmp"):
13511351
linefmt(p, cpsStmts, "$1.status = sigsetjmp($1.context, 0);$n", [safePoint])
1352+
elif isDefined(p.config, "nimBuiltinSetjmp"):
1353+
linefmt(p, cpsStmts, "$1.status = __builtin_setjmp($1.context);$n", [safePoint])
13521354
elif isDefined(p.config, "nimRawSetjmp"):
1353-
linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", [safePoint])
1355+
if isDefined(p.config, "mswindows"):
1356+
# The Windows `_setjmp()` takes two arguments, with the second being an
1357+
# undocumented buffer used by the SEH mechanism for stack unwinding.
1358+
# Mingw-w64 has been trying to get it right for years, but it's still
1359+
# prone to stack corruption during unwinding, so we disable that by setting
1360+
# it to NULL.
1361+
# More details: https://github.com/status-im/nimbus-eth2/issues/3121
1362+
linefmt(p, cpsStmts, "$1.status = _setjmp($1.context, 0);$n", [safePoint])
1363+
else:
1364+
linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", [safePoint])
13541365
else:
13551366
linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint])
13561367
lineCg(p, cpsStmts, "if ($1.status == 0) {$n", [safePoint])

doc/nimc.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,20 @@ ignored too. ``--define:FOO`` and ``--define:foo`` are identical.
149149
Compile-time symbols starting with the ``nim`` prefix are reserved for the
150150
implementation and should not be used elsewhere.
151151

152+
========================== ============================================
153+
Name Description
154+
========================== ============================================
155+
nimStdSetjmp Use the standard `setjmp()/longjmp()` library
156+
functions for setjmp-based exceptions. This is
157+
the default on most platforms.
158+
nimSigSetjmp Use `sigsetjmp()/siglongjmp()` for setjmp-based exceptions.
159+
nimRawSetjmp Use `_setjmp()/_longjmp()` on POSIX and `_setjmp()/longjmp()`
160+
on Windows, for setjmp-based exceptions. It's the default on
161+
BSDs and BSD-like platforms, where it's significantly faster
162+
than the standard functions.
163+
nimBuiltinSetjmp Use `__builtin_setjmp()/__builtin_longjmp()` for setjmp-based
164+
exceptions. This will not work if an exception is being thrown
165+
and caught inside the same procedure. Useful for benchmarking.
152166

153167
Configuration files
154168
-------------------

lib/system/ansi_c.nim

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ proc c_abort*() {.
3333
importc: "abort", header: "<stdlib.h>", noSideEffect, noreturn.}
3434

3535

36-
when defined(linux) and defined(amd64):
36+
when defined(nimBuiltinSetjmp):
37+
type
38+
C_JmpBuf* = array[5, pointer]
39+
elif defined(linux) and defined(amd64):
3740
type
3841
C_JmpBuf* {.importc: "jmp_buf", header: "<setjmp.h>", bycopy.} = object
3942
abi: array[200 div sizeof(clong), clong]
@@ -89,18 +92,47 @@ when defined(macosx):
8992
elif defined(haiku):
9093
const SIGBUS* = cint(30)
9194

92-
when defined(nimSigSetjmp) and not defined(nimStdSetjmp):
95+
# "nimRawSetjmp" is defined by default for certain platforms, so we need the
96+
# "nimStdSetjmp" escape hatch with it.
97+
when defined(nimSigSetjmp):
9398
proc c_longjmp*(jmpb: C_JmpBuf, retval: cint) {.
9499
header: "<setjmp.h>", importc: "siglongjmp".}
95-
template c_setjmp*(jmpb: C_JmpBuf): cint =
100+
proc c_setjmp*(jmpb: C_JmpBuf): cint =
96101
proc c_sigsetjmp(jmpb: C_JmpBuf, savemask: cint): cint {.
97102
header: "<setjmp.h>", importc: "sigsetjmp".}
98103
c_sigsetjmp(jmpb, 0)
104+
elif defined(nimBuiltinSetjmp):
105+
proc c_longjmp*(jmpb: C_JmpBuf, retval: cint) =
106+
# Apple's Clang++ has trouble converting array names to pointers, so we need
107+
# to be very explicit here.
108+
proc c_builtin_longjmp(jmpb: ptr pointer, retval: cint) {.
109+
importc: "__builtin_longjmp", nodecl.}
110+
# The second parameter needs to be 1 and sometimes the C/C++ compiler checks it.
111+
c_builtin_longjmp(unsafeAddr jmpb[0], 1)
112+
proc c_setjmp*(jmpb: C_JmpBuf): cint =
113+
proc c_builtin_setjmp(jmpb: ptr pointer): cint {.
114+
importc: "__builtin_setjmp", nodecl.}
115+
c_builtin_setjmp(unsafeAddr jmpb[0])
99116
elif defined(nimRawSetjmp) and not defined(nimStdSetjmp):
100-
proc c_longjmp*(jmpb: C_JmpBuf, retval: cint) {.
101-
header: "<setjmp.h>", importc: "_longjmp".}
102-
proc c_setjmp*(jmpb: C_JmpBuf): cint {.
103-
header: "<setjmp.h>", importc: "_setjmp".}
117+
when defined(windows):
118+
# No `_longjmp()` on Windows.
119+
proc c_longjmp*(jmpb: C_JmpBuf, retval: cint) {.
120+
header: "<setjmp.h>", importc: "longjmp".}
121+
# The Windows `_setjmp()` takes two arguments, with the second being an
122+
# undocumented buffer used by the SEH mechanism for stack unwinding.
123+
# Mingw-w64 has been trying to get it right for years, but it's still
124+
# prone to stack corruption during unwinding, so we disable that by setting
125+
# it to NULL.
126+
# More details: https://github.com/status-im/nimbus-eth2/issues/3121
127+
proc c_setjmp*(jmpb: C_JmpBuf): cint =
128+
proc c_setjmp_win(jmpb: C_JmpBuf, ctx: pointer): cint {.
129+
header: "<setjmp.h>", importc: "_setjmp".}
130+
c_setjmp_win(jmpb, nil)
131+
else:
132+
proc c_longjmp*(jmpb: C_JmpBuf, retval: cint) {.
133+
header: "<setjmp.h>", importc: "_longjmp".}
134+
proc c_setjmp*(jmpb: C_JmpBuf): cint {.
135+
header: "<setjmp.h>", importc: "_setjmp".}
104136
else:
105137
proc c_longjmp*(jmpb: C_JmpBuf, retval: cint) {.
106138
header: "<setjmp.h>", importc: "longjmp".}

tests/exception/texceptions.nim

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
discard """
2+
disabled: "windows" # no sigsetjmp() there
3+
matrix: "-d:nimStdSetjmp; -d:nimSigSetjmp; -d:nimRawSetjmp; -d:nimBuiltinSetjmp"
24
output: '''
35
BEFORE
46
FINALLY
@@ -16,7 +18,7 @@ FINALLY
1618

1719
echo ""
1820

19-
proc no_expcetion =
21+
proc no_exception =
2022
try:
2123
echo "BEFORE"
2224

@@ -27,7 +29,7 @@ proc no_expcetion =
2729
finally:
2830
echo "FINALLY"
2931

30-
try: no_expcetion()
32+
try: no_exception()
3133
except: echo "RECOVER"
3234

3335
echo ""

tests/exception/texceptions2.nim

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
discard """
2+
disabled: "posix" # already covered by texceptions.nim
3+
matrix: "-d:nimStdSetjmp; -d:nimRawSetjmp; -d:nimBuiltinSetjmp"
4+
output: '''
5+
6+
BEFORE
7+
FINALLY
8+
9+
BEFORE
10+
EXCEPT
11+
FINALLY
12+
RECOVER
13+
14+
BEFORE
15+
EXCEPT: IOError: hi
16+
FINALLY
17+
'''
18+
"""
19+
20+
echo ""
21+
22+
proc no_exception =
23+
try:
24+
echo "BEFORE"
25+
26+
except:
27+
echo "EXCEPT"
28+
raise
29+
30+
finally:
31+
echo "FINALLY"
32+
33+
try: no_exception()
34+
except: echo "RECOVER"
35+
36+
echo ""
37+
38+
proc reraise_in_except =
39+
try:
40+
echo "BEFORE"
41+
raise newException(IOError, "")
42+
43+
except IOError:
44+
echo "EXCEPT"
45+
raise
46+
47+
finally:
48+
echo "FINALLY"
49+
50+
try: reraise_in_except()
51+
except: echo "RECOVER"
52+
53+
echo ""
54+
55+
proc return_in_except =
56+
try:
57+
echo "BEFORE"
58+
raise newException(IOError, "hi")
59+
60+
except:
61+
echo "EXCEPT: ", getCurrentException().name, ": ", getCurrentExceptionMsg()
62+
return
63+
64+
finally:
65+
echo "FINALLY"
66+
67+
try: return_in_except()
68+
except: echo "RECOVER"
69+
70+
block: #10417
71+
proc moo() {.noreturn.} = discard
72+
73+
let bar =
74+
try:
75+
1
76+
except:
77+
moo()
78+
79+
doAssert(bar == 1)
80+
81+
# Make sure the VM handles the exceptions correctly
82+
block:
83+
proc fun1(): seq[int] =
84+
try:
85+
try:
86+
raise newException(ValueError, "xx")
87+
except:
88+
doAssert("xx" == getCurrentExceptionMsg())
89+
raise newException(KeyError, "yy")
90+
except:
91+
doAssert("yy" == getCurrentExceptionMsg())
92+
result.add(1212)
93+
try:
94+
try:
95+
raise newException(AssertionDefect, "a")
96+
finally:
97+
result.add(42)
98+
except AssertionDefect:
99+
result.add(99)
100+
finally:
101+
result.add(10)
102+
result.add(4)
103+
result.add(0)
104+
try:
105+
result.add(1)
106+
except KeyError:
107+
result.add(-1)
108+
except ValueError:
109+
result.add(-1)
110+
except IndexDefect:
111+
result.add(2)
112+
except:
113+
result.add(3)
114+
115+
try:
116+
try:
117+
result.add(1)
118+
return
119+
except:
120+
result.add(-1)
121+
finally:
122+
result.add(2)
123+
except KeyError:
124+
doAssert(false)
125+
finally:
126+
result.add(3)
127+
128+
let x1 = fun1()
129+
const x2 = fun1()
130+
doAssert(x1 == x2)

0 commit comments

Comments
 (0)