compiler can emulate thread local variables

This commit is contained in:
Araq 2011-06-15 02:09:02 +02:00
commit 4fa80956b8
10 changed files with 255 additions and 250 deletions

View file

@ -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