mirror of
https://github.com/nim-lang/Nim.git
synced 2026-07-16 22:11:18 +00:00
compiler can emulate thread local variables
This commit is contained in:
@@ -10,6 +10,9 @@
|
||||
# Exception handling code. This is difficult because it has
|
||||
# to work if there is no more memory (but it doesn't yet!).
|
||||
|
||||
# XXX assertions are unnecessarily complex; system should use their own
|
||||
# assertion mechanism instead!
|
||||
|
||||
var
|
||||
stackTraceNewLine* = "\n" ## undocumented feature; it is replaced by ``<br>``
|
||||
## for CGI applications
|
||||
@@ -32,64 +35,51 @@ proc chckRange(i, a, b: int): int {.inline, compilerproc.}
|
||||
proc chckRangeF(x, a, b: float): float {.inline, compilerproc.}
|
||||
proc chckNil(p: pointer) {.inline, compilerproc.}
|
||||
|
||||
when hasThreadSupport:
|
||||
template ThreadGlobals =
|
||||
var currentThread = ThisThread()
|
||||
template `||`(varname: expr): expr = currentThread.g.varname
|
||||
|
||||
else:
|
||||
template ThreadGlobals = nil # nothing
|
||||
template `||`(varname: expr): expr = varname
|
||||
var
|
||||
framePtr {.rtlThreadVar.}: PFrame
|
||||
excHandler {.rtlThreadVar.}: PSafePoint
|
||||
# list of exception handlers
|
||||
# a global variable for the root of all try blocks
|
||||
currException {.rtlThreadVar.}: ref E_Base
|
||||
|
||||
var
|
||||
framePtr: PFrame
|
||||
excHandler: PSafePoint = nil
|
||||
# list of exception handlers
|
||||
# a global variable for the root of all try blocks
|
||||
currException: ref E_Base
|
||||
buf {.rtlThreadVar.}: string # cannot be allocated on the stack!
|
||||
assertBuf {.rtlThreadVar.}: string
|
||||
# we need a different buffer for
|
||||
# assert, as it raises an exception and
|
||||
# exception handler needs the buffer too
|
||||
gAssertionFailed {.rtlThreadVar.}: ref EAssertionFailed
|
||||
|
||||
buf: string # cannot be allocated on the stack!
|
||||
assertBuf: string # we need a different buffer for
|
||||
# assert, as it raises an exception and
|
||||
# exception handler needs the buffer too
|
||||
tempFrames: array [0..127, PFrame] # cannot be allocated on the stack!
|
||||
gAssertionFailed: ref EAssertionFailed
|
||||
|
||||
new(||gAssertionFailed)
|
||||
||buf = newStringOfCap(2000)
|
||||
||assertBuf = newStringOfCap(2000)
|
||||
proc initGlobals() =
|
||||
new(gAssertionFailed)
|
||||
buf = newStringOfCap(2000)
|
||||
assertBuf = newStringOfCap(2000)
|
||||
|
||||
when not hasThreadSupport:
|
||||
initGlobals()
|
||||
|
||||
proc pushFrame(s: PFrame) {.compilerRtl, inl.} =
|
||||
ThreadGlobals()
|
||||
s.prev = ||framePtr
|
||||
||framePtr = s
|
||||
s.prev = framePtr
|
||||
framePtr = s
|
||||
|
||||
proc popFrame {.compilerRtl, inl.} =
|
||||
ThreadGlobals()
|
||||
||framePtr = (||framePtr).prev
|
||||
framePtr = framePtr.prev
|
||||
|
||||
proc setFrame(s: PFrame) {.compilerRtl, inl.} =
|
||||
ThreadGlobals()
|
||||
||framePtr = s
|
||||
framePtr = s
|
||||
|
||||
proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} =
|
||||
ThreadGlobals()
|
||||
s.prev = ||excHandler
|
||||
||excHandler = s
|
||||
s.prev = excHandler
|
||||
excHandler = s
|
||||
|
||||
proc popSafePoint {.compilerRtl, inl.} =
|
||||
ThreadGlobals()
|
||||
||excHandler = (||excHandler).prev
|
||||
excHandler = excHandler.prev
|
||||
|
||||
proc pushCurrentException(e: ref E_Base) {.compilerRtl, inl.} =
|
||||
ThreadGlobals()
|
||||
e.parent = ||currException
|
||||
||currException = e
|
||||
e.parent = currException
|
||||
currException = e
|
||||
|
||||
proc popCurrentException {.compilerRtl, inl.} =
|
||||
ThreadGlobals()
|
||||
||currException = (||currException).parent
|
||||
currException = currException.parent
|
||||
|
||||
# some platforms have native support for stack traces:
|
||||
const
|
||||
@@ -143,18 +133,24 @@ when defined(nativeStacktrace) and nativeStackTraceSupported:
|
||||
# Once we're past signalHandler, we're at what the user is
|
||||
# interested in
|
||||
enabled = true
|
||||
|
||||
when not hasThreadSupport:
|
||||
var
|
||||
tempFrames: array [0..127, PFrame] # should not be alloc'd on stack
|
||||
|
||||
proc auxWriteStackTrace(f: PFrame, s: var string) =
|
||||
const
|
||||
when hasThreadSupport:
|
||||
var
|
||||
tempFrames: array [0..127, PFrame] # but better than a threadvar
|
||||
const
|
||||
firstCalls = 32
|
||||
ThreadGlobals()
|
||||
var
|
||||
it = f
|
||||
i = 0
|
||||
total = 0
|
||||
while it != nil and i <= high(||tempFrames)-(firstCalls-1):
|
||||
while it != nil and i <= high(tempFrames)-(firstCalls-1):
|
||||
# the (-1) is for a nil entry that marks where the '...' should occur
|
||||
(||tempFrames)[i] = it
|
||||
tempFrames[i] = it
|
||||
inc(i)
|
||||
inc(total)
|
||||
it = it.prev
|
||||
@@ -165,38 +161,37 @@ proc auxWriteStackTrace(f: PFrame, s: var string) =
|
||||
for j in 1..total-i-(firstCalls-1):
|
||||
if b != nil: b = b.prev
|
||||
if total != i:
|
||||
(||tempFrames)[i] = nil
|
||||
tempFrames[i] = nil
|
||||
inc(i)
|
||||
while b != nil and i <= high(||tempFrames):
|
||||
(||tempFrames)[i] = b
|
||||
while b != nil and i <= high(tempFrames):
|
||||
tempFrames[i] = b
|
||||
inc(i)
|
||||
b = b.prev
|
||||
for j in countdown(i-1, 0):
|
||||
if (||tempFrames)[j] == nil:
|
||||
if tempFrames[j] == nil:
|
||||
add(s, "(")
|
||||
add(s, $(total-i-1))
|
||||
add(s, " calls omitted) ...")
|
||||
else:
|
||||
var oldLen = s.len
|
||||
add(s, (||tempFrames)[j].filename)
|
||||
if (||tempFrames)[j].line > 0:
|
||||
add(s, tempFrames[j].filename)
|
||||
if tempFrames[j].line > 0:
|
||||
add(s, '(')
|
||||
add(s, $(||tempFrames)[j].line)
|
||||
add(s, $tempFrames[j].line)
|
||||
add(s, ')')
|
||||
for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
|
||||
add(s, (||tempFrames)[j].procname)
|
||||
add(s, tempFrames[j].procname)
|
||||
add(s, stackTraceNewLine)
|
||||
|
||||
proc rawWriteStackTrace(s: var string) =
|
||||
when nimrodStackTrace:
|
||||
ThreadGlobals()
|
||||
if ||framePtr == nil:
|
||||
if framePtr == nil:
|
||||
add(s, "No stack traceback available")
|
||||
add(s, stackTraceNewLine)
|
||||
else:
|
||||
add(s, "Traceback (most recent call last)")
|
||||
add(s, stackTraceNewLine)
|
||||
auxWriteStackTrace(||framePtr, s)
|
||||
auxWriteStackTrace(framePtr, s)
|
||||
elif defined(nativeStackTrace) and nativeStackTraceSupported:
|
||||
add(s, "Traceback from system (most recent call last)")
|
||||
add(s, stackTraceNewLine)
|
||||
@@ -216,53 +211,50 @@ proc raiseException(e: ref E_Base, ename: CString) {.compilerRtl.} =
|
||||
if raiseHook != nil:
|
||||
if not raiseHook(e): return
|
||||
GC_disable() # a bad thing is an error in the GC while raising an exception
|
||||
ThreadGlobals()
|
||||
if ||excHandler != nil:
|
||||
if excHandler != nil:
|
||||
pushCurrentException(e)
|
||||
c_longjmp((||excHandler).context, 1)
|
||||
c_longjmp(excHandler.context, 1)
|
||||
else:
|
||||
if not isNil(||buf):
|
||||
setLen(||buf, 0)
|
||||
rawWriteStackTrace(||buf)
|
||||
if not isNil(buf):
|
||||
setLen(buf, 0)
|
||||
rawWriteStackTrace(buf)
|
||||
if e.msg != nil and e.msg[0] != '\0':
|
||||
add(||buf, "Error: unhandled exception: ")
|
||||
add(||buf, $e.msg)
|
||||
add(buf, "Error: unhandled exception: ")
|
||||
add(buf, $e.msg)
|
||||
else:
|
||||
add(||buf, "Error: unhandled exception")
|
||||
add(||buf, " [")
|
||||
add(||buf, $ename)
|
||||
add(||buf, "]\n")
|
||||
writeToStdErr(||buf)
|
||||
add(buf, "Error: unhandled exception")
|
||||
add(buf, " [")
|
||||
add(buf, $ename)
|
||||
add(buf, "]\n")
|
||||
writeToStdErr(buf)
|
||||
else:
|
||||
writeToStdErr(ename)
|
||||
quitOrDebug()
|
||||
GC_enable()
|
||||
|
||||
proc reraiseException() {.compilerRtl.} =
|
||||
ThreadGlobals()
|
||||
if ||currException == nil:
|
||||
if currException == nil:
|
||||
raise newException(ENoExceptionToReraise, "no exception to reraise")
|
||||
else:
|
||||
raiseException(||currException, (||currException).name)
|
||||
raiseException(currException, currException.name)
|
||||
|
||||
proc internalAssert(file: cstring, line: int, cond: bool) {.compilerproc.} =
|
||||
if not cond:
|
||||
ThreadGlobals()
|
||||
#c_fprintf(c_stdout, "Assertion failure: file %s line %ld\n", file, line)
|
||||
#quit(1)
|
||||
GC_disable() # BUGFIX: `$` allocates a new string object!
|
||||
if not isNil(||assertBuf):
|
||||
if not isNil(assertBuf):
|
||||
# BUGFIX: when debugging the GC, assertBuf may be nil
|
||||
setLen(||assertBuf, 0)
|
||||
add(||assertBuf, "[Assertion failure] file: ")
|
||||
add(||assertBuf, file)
|
||||
add(||assertBuf, " line: ")
|
||||
add(||assertBuf, $line)
|
||||
add(||assertBuf, "\n")
|
||||
(||gAssertionFailed).msg = ||assertBuf
|
||||
setLen(assertBuf, 0)
|
||||
add(assertBuf, "[Assertion failure] file: ")
|
||||
add(assertBuf, file)
|
||||
add(assertBuf, " line: ")
|
||||
add(assertBuf, $line)
|
||||
add(assertBuf, "\n")
|
||||
gAssertionFailed.msg = assertBuf
|
||||
GC_enable()
|
||||
if ||gAssertionFailed != nil:
|
||||
raise ||gAssertionFailed
|
||||
if gAssertionFailed != nil:
|
||||
raise gAssertionFailed
|
||||
else:
|
||||
c_fprintf(c_stdout, "Assertion failure: file %s line %ld\n", file, line)
|
||||
quit(1)
|
||||
@@ -277,24 +269,23 @@ var
|
||||
|
||||
proc signalHandler(sig: cint) {.exportc: "signalHandler", noconv.} =
|
||||
# print stack trace and quit
|
||||
ThreadGlobals()
|
||||
var s = sig
|
||||
GC_disable()
|
||||
setLen(||buf, 0)
|
||||
rawWriteStackTrace(||buf)
|
||||
setLen(buf, 0)
|
||||
rawWriteStackTrace(buf)
|
||||
|
||||
if s == SIGINT: add(||buf, "SIGINT: Interrupted by Ctrl-C.\n")
|
||||
if s == SIGINT: add(buf, "SIGINT: Interrupted by Ctrl-C.\n")
|
||||
elif s == SIGSEGV:
|
||||
add(||buf, "SIGSEGV: Illegal storage access. (Attempt to read from nil?)\n")
|
||||
add(buf, "SIGSEGV: Illegal storage access. (Attempt to read from nil?)\n")
|
||||
elif s == SIGABRT:
|
||||
if dbgAborting: return # the debugger wants to abort
|
||||
add(||buf, "SIGABRT: Abnormal termination.\n")
|
||||
elif s == SIGFPE: add(||buf, "SIGFPE: Arithmetic error.\n")
|
||||
elif s == SIGILL: add(||buf, "SIGILL: Illegal operation.\n")
|
||||
add(buf, "SIGABRT: Abnormal termination.\n")
|
||||
elif s == SIGFPE: add(buf, "SIGFPE: Arithmetic error.\n")
|
||||
elif s == SIGILL: add(buf, "SIGILL: Illegal operation.\n")
|
||||
elif s == SIGBUS:
|
||||
add(||buf, "SIGBUS: Illegal storage access. (Attempt to read from nil?)\n")
|
||||
else: add(||buf, "unknown signal\n")
|
||||
writeToStdErr(||buf)
|
||||
add(buf, "SIGBUS: Illegal storage access. (Attempt to read from nil?)\n")
|
||||
else: add(buf, "unknown signal\n")
|
||||
writeToStdErr(buf)
|
||||
dbgAborting = True # play safe here...
|
||||
GC_enable()
|
||||
quit(1) # always quit when SIGABRT
|
||||
|
||||
@@ -63,7 +63,7 @@ type
|
||||
var
|
||||
stackBottom {.rtlThreadVar.}: pointer
|
||||
gch {.rtlThreadVar.}: TGcHeap
|
||||
cycleThreshold {.rtlThreadVar.}: int = InitialCycleThreshold
|
||||
cycleThreshold {.rtlThreadVar.}: int
|
||||
|
||||
proc acquire(gch: var TGcHeap) {.inline.} =
|
||||
when hasThreadSupport and hasSharedHeap:
|
||||
@@ -267,6 +267,7 @@ proc initGC() =
|
||||
when not defined(useNimRtl):
|
||||
when traceGC:
|
||||
for i in low(TCellState)..high(TCellState): Init(states[i])
|
||||
cycleThreshold = InitialCycleThreshold
|
||||
gch.stat.stackScans = 0
|
||||
gch.stat.cycleCollections = 0
|
||||
gch.stat.maxThreshold = 0
|
||||
|
||||
@@ -52,22 +52,22 @@ when defined(Windows):
|
||||
LockSemaphore: int
|
||||
Reserved: int32
|
||||
|
||||
proc InitSysLock(L: var TSysLock) {.stdcall,
|
||||
proc InitSysLock(L: var TSysLock) {.stdcall, noSideEffect,
|
||||
dynlib: "kernel32", importc: "InitializeCriticalSection".}
|
||||
## Initializes the lock `L`.
|
||||
|
||||
proc TryAcquireSysAux(L: var TSysLock): int32 {.stdcall,
|
||||
proc TryAcquireSysAux(L: var TSysLock): int32 {.stdcall, noSideEffect,
|
||||
dynlib: "kernel32", importc: "TryEnterCriticalSection".}
|
||||
## Tries to acquire the lock `L`.
|
||||
|
||||
proc TryAcquireSys(L: var TSysLock): bool {.inline.} =
|
||||
result = TryAcquireSysAux(L) != 0'i32
|
||||
|
||||
proc AcquireSys(L: var TSysLock) {.stdcall,
|
||||
proc AcquireSys(L: var TSysLock) {.stdcall, noSideEffect,
|
||||
dynlib: "kernel32", importc: "EnterCriticalSection".}
|
||||
## Acquires the lock `L`.
|
||||
|
||||
proc ReleaseSys(L: var TSysLock) {.stdcall,
|
||||
proc ReleaseSys(L: var TSysLock) {.stdcall, noSideEffect,
|
||||
dynlib: "kernel32", importc: "LeaveCriticalSection".}
|
||||
## Releases the lock `L`.
|
||||
|
||||
@@ -120,17 +120,17 @@ else:
|
||||
header: "<sys/types.h>".} = object
|
||||
|
||||
proc InitSysLock(L: var TSysLock, attr: pointer = nil) {.
|
||||
importc: "pthread_mutex_init", header: "<pthread.h>".}
|
||||
importc: "pthread_mutex_init", header: "<pthread.h>", noSideEffect.}
|
||||
|
||||
proc AcquireSys(L: var TSysLock) {.
|
||||
proc AcquireSys(L: var TSysLock) {.noSideEffect,
|
||||
importc: "pthread_mutex_lock", header: "<pthread.h>".}
|
||||
proc TryAcquireSysAux(L: var TSysLock): cint {.
|
||||
proc TryAcquireSysAux(L: var TSysLock): cint {.noSideEffect,
|
||||
importc: "pthread_mutex_trylock", header: "<pthread.h>".}
|
||||
|
||||
proc TryAcquireSys(L: var TSysLock): bool {.inline.} =
|
||||
result = TryAcquireSysAux(L) == 0'i32
|
||||
|
||||
proc ReleaseSys(L: var TSysLock) {.
|
||||
proc ReleaseSys(L: var TSysLock) {.noSideEffect,
|
||||
importc: "pthread_mutex_unlock", header: "<pthread.h>".}
|
||||
|
||||
type
|
||||
@@ -184,32 +184,25 @@ else:
|
||||
proc pthread_setspecific(a1: TThreadVarSlot, a2: pointer): int32 {.
|
||||
importc: "pthread_setspecific", header: "<pthread.h>".}
|
||||
|
||||
proc ThreadVarAlloc(): TThreadVarSlot {.compilerproc, inline.} =
|
||||
proc ThreadVarAlloc(): TThreadVarSlot {.inline.} =
|
||||
discard pthread_key_create(addr(result), nil)
|
||||
proc ThreadVarSetValue(s: TThreadVarSlot, value: pointer) {.
|
||||
compilerproc, inline.} =
|
||||
proc ThreadVarSetValue(s: TThreadVarSlot, value: pointer) {.inline.} =
|
||||
discard pthread_setspecific(s, value)
|
||||
proc ThreadVarGetValue(s: TThreadVarSlot): pointer {.compilerproc, inline.} =
|
||||
proc ThreadVarGetValue(s: TThreadVarSlot): pointer {.inline.} =
|
||||
result = pthread_getspecific(s)
|
||||
|
||||
type
|
||||
TGlobals {.final, pure.} = object
|
||||
excHandler: PSafePoint
|
||||
currException: ref E_Base
|
||||
framePtr: PFrame
|
||||
buf: string # cannot be allocated on the stack!
|
||||
assertBuf: string # we need a different buffer for
|
||||
# assert, as it raises an exception and
|
||||
# exception handler needs the buffer too
|
||||
gAssertionFailed: ref EAssertionFailed
|
||||
tempFrames: array [0..127, PFrame] # cannot be allocated on the stack!
|
||||
data: float # compiler should add thread local variables here!
|
||||
const emulatedThreadVars = defined(macosx)
|
||||
|
||||
proc initGlobals(g: var TGlobals) =
|
||||
new(g.gAssertionFailed)
|
||||
g.buf = newStringOfCap(2000)
|
||||
g.assertBuf = newStringOfCap(2000)
|
||||
when emulatedThreadVars:
|
||||
# the compiler generates this proc for us, so that we can get the size of
|
||||
# the thread local var block:
|
||||
proc NimThreadVarsSize(): int {.noconv, importc: "NimThreadVarsSize".}
|
||||
|
||||
proc ThreadVarsAlloc(size: int): pointer =
|
||||
result = c_malloc(size)
|
||||
zeroMem(result, size)
|
||||
proc ThreadVarsDealloc(p: pointer) {.importc: "free", nodecl.}
|
||||
proc initGlobals()
|
||||
|
||||
type
|
||||
PGcThread = ptr TGcThread
|
||||
@@ -218,7 +211,6 @@ type
|
||||
next, prev: PGcThread
|
||||
stackBottom, stackTop, threadLocalStorage: pointer
|
||||
stackSize: int
|
||||
g: TGlobals
|
||||
locksLen: int
|
||||
locks: array [0..MaxLocksPerThread-1, pointer]
|
||||
registers: array[0..maxRegisters-1, pointer] # register contents for GC
|
||||
@@ -240,8 +232,14 @@ proc GetThreadLocalVars(): pointer {.compilerRtl, inl.} =
|
||||
# of all threads; it's not to be stopped etc.
|
||||
when not defined(useNimRtl):
|
||||
var mainThread: TGcThread
|
||||
initGlobals(mainThread.g)
|
||||
|
||||
ThreadVarSetValue(globalsSlot, addr(mainThread))
|
||||
when emulatedThreadVars:
|
||||
mainThread.threadLocalStorage = ThreadVarsAlloc(NimThreadVarsSize())
|
||||
|
||||
initStackBottom()
|
||||
initGC()
|
||||
initGlobals()
|
||||
|
||||
var heapLock: TSysLock
|
||||
InitSysLock(HeapLock)
|
||||
@@ -296,15 +294,21 @@ when not defined(boehmgc) and not hasSharedHeap:
|
||||
template ThreadProcWrapperBody(closure: expr) =
|
||||
ThreadVarSetValue(globalsSlot, closure)
|
||||
var t = cast[ptr TThread[TParam]](closure)
|
||||
when emulatedThreadVars:
|
||||
t.threadLocalStorage = ThreadVarsAlloc(NimThreadVarsSize())
|
||||
when not defined(boehmgc) and not hasSharedHeap:
|
||||
# init the GC for this thread:
|
||||
setStackBottom(addr(t))
|
||||
initGC()
|
||||
t.stackBottom = addr(t)
|
||||
initGlobals()
|
||||
registerThread(t)
|
||||
try:
|
||||
t.fn(t.data)
|
||||
finally:
|
||||
# XXX shut-down is not executed when the thread is forced down!
|
||||
when emulatedThreadVars:
|
||||
ThreadVarsDealloc(t.threadLocalStorage)
|
||||
unregisterThread(t)
|
||||
when defined(deallocOsPages): deallocOsPages()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user