Skip to content

Commit 69aabda

Browse files
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++
1 parent 32d4bf3 commit 69aabda

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
@@ -1356,8 +1356,19 @@ proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) =
13561356
linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint])
13571357
elif isDefined(p.config, "nimSigSetjmp"):
13581358
linefmt(p, cpsStmts, "$1.status = sigsetjmp($1.context, 0);$n", [safePoint])
1359+
elif isDefined(p.config, "nimBuiltinSetjmp"):
1360+
linefmt(p, cpsStmts, "$1.status = __builtin_setjmp($1.context);$n", [safePoint])
13591361
elif isDefined(p.config, "nimRawSetjmp"):
1360-
linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", [safePoint])
1362+
if isDefined(p.config, "mswindows"):
1363+
# The Windows `_setjmp()` takes two arguments, with the second being an
1364+
# undocumented buffer used by the SEH mechanism for stack unwinding.
1365+
# Mingw-w64 has been trying to get it right for years, but it's still
1366+
# prone to stack corruption during unwinding, so we disable that by setting
1367+
# it to NULL.
1368+
# More details: https://github.com/status-im/nimbus-eth2/issues/3121
1369+
linefmt(p, cpsStmts, "$1.status = _setjmp($1.context, 0);$n", [safePoint])
1370+
else:
1371+
linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", [safePoint])
13611372
else:
13621373
linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint])
13631374
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
@@ -165,6 +165,20 @@ ignored too. `--define:FOO`:option: and `--define:foo`:option: are identical.
165165
Compile-time symbols starting with the `nim` prefix are reserved for the
166166
implementation and should not be used elsewhere.
167167

168+
========================== ============================================
169+
Name Description
170+
========================== ============================================
171+
nimStdSetjmp Use the standard `setjmp()/longjmp()` library
172+
functions for setjmp-based exceptions. This is
173+
the default on most platforms.
174+
nimSigSetjmp Use `sigsetjmp()/siglongjmp()` for setjmp-based exceptions.
175+
nimRawSetjmp Use `_setjmp()/_longjmp()` on POSIX and `_setjmp()/longjmp()`
176+
on Windows, for setjmp-based exceptions. It's the default on
177+
BSDs and BSD-like platforms, where it's significantly faster
178+
than the standard functions.
179+
nimBuiltinSetjmp Use `__builtin_setjmp()/__builtin_longjmp()` for setjmp-based
180+
exceptions. This will not work if an exception is being thrown
181+
and caught inside the same procedure. Useful for benchmarking.
168182

169183
Configuration files
170184
-------------------

lib/system/ansi_c.nim

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

3333

34-
when defined(linux) and defined(amd64):
34+
when defined(nimBuiltinSetjmp):
35+
type
36+
C_JmpBuf* = array[5, pointer]
37+
elif defined(linux) and defined(amd64):
3538
type
3639
C_JmpBuf* {.importc: "jmp_buf", header: "<setjmp.h>", bycopy.} = object
3740
abi: array[200 div sizeof(clong), clong]
@@ -92,18 +95,47 @@ when defined(macosx):
9295
elif defined(haiku):
9396
const SIGBUS* = cint(30)
9497

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

1820
echo ""
1921

20-
proc no_expcetion =
22+
proc no_exception =
2123
try:
2224
echo "BEFORE"
2325

@@ -28,7 +30,7 @@ proc no_expcetion =
2830
finally:
2931
echo "FINALLY"
3032

31-
try: no_expcetion()
33+
try: no_exception()
3234
except: echo "RECOVER"
3335

3436
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)