summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2011-06-15 02:09:02 +0200
committerAraq <rumpf_a@web.de>2011-06-15 02:09:02 +0200
commit4fa80956b89c2ee06d8940018101240efec7dc02 (patch)
tree5e3d03d41122f0ad8def8e7d9be8b2eff2a2ea81
parentadbb48fbcecd82e60c3a25ee006baa0aa5dd7b10 (diff)
downloadNim-4fa80956b89c2ee06d8940018101240efec7dc02.tar.gz
compiler can emulate thread local variables
-rwxr-xr-xcompiler/ccgexprs.nim15
-rw-r--r--compiler/ccgthreadvars.nim54
-rwxr-xr-xcompiler/cgen.nim106
-rwxr-xr-xcompiler/msgs.nim13
-rw-r--r--compiler/semthreads.nim13
-rwxr-xr-xlib/system.nim9
-rwxr-xr-xlib/system/excpt.nim185
-rwxr-xr-xlib/system/gc.nim3
-rwxr-xr-xlib/system/threads.nim66
-rwxr-xr-xtodo.txt49
10 files changed, 259 insertions, 254 deletions
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index faf37247e..d054e5bac 100755
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -1482,7 +1482,6 @@ proc genSetConstr(p: BProc, e: PNode, d: var TLoc) =
   # incl(tmp, d); incl(tmp, e); inclRange(tmp, f, g);
   var
     a, b, idx: TLoc
-    ts: string
   if nfAllConst in e.flags:
     putIntoDest(p, d, e.typ, genSetNode(p, e))
   else:
@@ -1504,7 +1503,7 @@ proc genSetConstr(p: BProc, e: PNode, d: var TLoc) =
                [rdLoc(d), rdSetElemLoc(a, e.typ)])
     else:
       # small set
-      ts = "NI" & $(getSize(e.typ) * 8)
+      var ts = "NI" & $(getSize(e.typ) * 8)
       appf(p.s[cpsStmts], "$1 = 0;$n", [rdLoc(d)])
       for i in countup(0, sonsLen(e) - 1):
         if e.sons[i].kind == nkRange:
@@ -1645,10 +1644,17 @@ proc expr(p: BProc, e: PNode, d: var TLoc) =
     of skEnumField:
       putIntoDest(p, d, e.typ, toRope(sym.position))
     of skVar:
-      if (sfGlobal in sym.flags): genVarPrototype(p.module, sym)
+      if sfGlobal in sym.flags: genVarPrototype(p.module, sym)
       if ((sym.loc.r == nil) or (sym.loc.t == nil)):
         InternalError(e.info, "expr: var not init " & sym.name.s)
-      putLocIntoDest(p, d, sym.loc)
+      if sfThreadVar in sym.flags:
+        AccessThreadLocalVar(p, sym)
+        if emulatedThreadVars(): 
+          putIntoDest(p, d, sym.loc.t, con("NimTV->", sym.loc.r))
+        else:
+          putLocIntoDest(p, d, sym.loc)
+      else:
+        putLocIntoDest(p, d, sym.loc)
     of skForVar, skTemp:
       if ((sym.loc.r == nil) or (sym.loc.t == nil)):
         InternalError(e.info, "expr: temp not init " & sym.name.s)
@@ -1727,7 +1733,6 @@ proc genConstExpr(p: BProc, n: PNode): PRope =
     # XXX: tySequence!
     result = genConstSimpleList(p, n)
   else:
-    #  result := genLiteral(p, n)
     var d: TLoc
     initLocExpr(p, n, d)
     result = rdLoc(d)
diff --git a/compiler/ccgthreadvars.nim b/compiler/ccgthreadvars.nim
new file mode 100644
index 000000000..e92e955c5
--- /dev/null
+++ b/compiler/ccgthreadvars.nim
@@ -0,0 +1,54 @@
+#
+#
+#           The Nimrod Compiler
+#        (c) Copyright 2011 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Thread var support for crappy architectures that lack native support for 
+## thread local storage.
+
+proc AccessThreadLocalVar(p: BProc, s: PSym) =
+  if optThreads in gGlobalOptions:
+    if platform.OS[targetOS].props.contains(ospLacksThreadVars):
+      if not p.ThreadVarAccessed:
+        p.ThreadVarAccessed = true
+        p.module.usesThreadVars = true
+        appf(p.s[cpsLocals], "NimThreadVars* NimTV;$n")
+        appcg(p, cpsInit, "NimTV=(NimThreadVars*)#GetThreadLocalVars();$n")
+
+var
+  nimtv: PRope # nimrod thread vars
+  nimtvDeps: seq[PType] = @[]
+  nimtvDeclared = initIntSet()
+
+proc emulatedThreadVars(): bool {.inline.} =
+  result = optThreads in gGlobalOptions and
+    platform.OS[targetOS].props.contains(ospLacksThreadVars)
+
+proc declareThreadVar(m: BModule, s: PSym, isExtern: bool) =
+  if emulatedThreadVars():
+    # we gather all thread locals var into a struct; we need to allocate
+    # storage for that somehow, can't use the thread local storage
+    # allocator for it :-(
+    if not containsOrIncl(nimtvDeclared, s.id):
+      nimtvDeps.add(s.loc.t)
+      appf(nimtv, "$1 $2;$n", [getTypeDesc(m, s.loc.t), s.loc.r])
+  else:
+    if isExtern: app(m.s[cfsVars], "extern ")
+    if optThreads in gGlobalOptions: app(m.s[cfsVars], "NIM_THREADVAR ")
+    app(m.s[cfsVars], getTypeDesc(m, s.loc.t))
+    appf(m.s[cfsVars], " $1;$n", [s.loc.r])
+  
+proc generateThreadLocalStorage(m: BModule) =
+  if nimtv != nil and (m.usesThreadVars or sfMainModule in m.module.flags):
+    for t in items(nimtvDeps): discard getTypeDesc(m, t)
+    appf(m.s[cfsSeqTypes], "typedef struct {$1} NimThreadVars;$n", [nimtv])
+
+proc GenerateThreadVarsSize(m: BModule) =
+  if nimtv != nil:
+    app(m.s[cfsProcs], 
+      "NI NimThreadVarsSize(){return (NI)sizeof(NimThreadVars);}" & tnl)
+
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index 449814d01..1ecadbec4 100755
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -66,6 +66,7 @@ type
     s: TCProcSections        # the procs sections; short name for readability
     prc: PSym                # the Nimrod proc that this C proc belongs to
     BeforeRetNeeded: bool    # true iff 'BeforeRet' label for proc is needed
+    ThreadVarAccessed: bool  # true if the proc already accessed some threadvar
     nestedTryStmts: seq[PNode] # in how many nested try statements we are
                                # (the vars must be volatile then)
     labels: Natural          # for generating unique labels in the C proc
@@ -85,6 +86,7 @@ type
     filename*: string
     s*: TCFileSections        # sections of the C file
     PreventStackTrace: bool   # true if stack traces need to be prevented
+    usesThreadVars: bool      # true if the module uses a thread var
     cfilename*: string        # filename of the module (including path,
                               # without extension)
     typeCache*: TIdTable      # cache the generated types
@@ -386,29 +388,12 @@ proc localDebugInfo(p: BProc, s: PSym) =
   if {optStackTrace, optEndb} * p.options != {optStackTrace, optEndb}: return 
   # XXX work around a bug: No type information for open arrays possible:
   if skipTypes(s.typ, abstractVar).kind == tyOpenArray: return
-  if gCmd == cmdCompileToLLVM: 
-    # "address" is the 0th field
-    # "typ" is the 1rst field
-    # "name" is the 2nd field
-    var name = cstringLit(p, p.s[cpsInit], normalize(s.name.s))
-    if (s.kind == skParam) and not ccgIntroducedPtr(s): allocParam(p, s)
-    inc(p.labels, 3)
-    appf(p.s[cpsInit], "%LOC$6 = getelementptr %TF* %F, %NI 0, $1, %NI 0$n" &
-        "%LOC$7 = getelementptr %TF* %F, %NI 0, $1, %NI 1$n" &
-        "%LOC$8 = getelementptr %TF* %F, %NI 0, $1, %NI 2$n" &
-        "store i8* $2, i8** %LOC$6$n" & "store $3* $4, $3** %LOC$7$n" &
-        "store i8* $5, i8** %LOC$8$n", [toRope(p.frameLen), s.loc.r, 
-                                        getTypeDesc(p.module, "TNimType"), 
-                                        genTypeInfo(p.module, s.loc.t), name, 
-                                        toRope(p.labels), toRope(p.labels - 1), 
-                                        toRope(p.labels - 2)])
-  else: 
-    var a = con("&", s.loc.r)
-    if (s.kind == skParam) and ccgIntroducedPtr(s): a = s.loc.r
-    appf(p.s[cpsInit], 
-         "F.s[$1].address = (void*)$3; F.s[$1].typ = $4; F.s[$1].name = $2;$n",
-         [toRope(p.frameLen), makeCString(normalize(s.name.s)), a, 
-          genTypeInfo(p.module, s.loc.t)])
+  var a = con("&", s.loc.r)
+  if (s.kind == skParam) and ccgIntroducedPtr(s): a = s.loc.r
+  appf(p.s[cpsInit], 
+       "F.s[$1].address = (void*)$3; F.s[$1].typ = $4; F.s[$1].name = $2;$n",
+       [toRope(p.frameLen), makeCString(normalize(s.name.s)), a, 
+        genTypeInfo(p.module, s.loc.t)])
   inc(p.frameLen)
 
 proc assignLocalVar(p: BProc, s: PSym) = 
@@ -417,55 +402,35 @@ proc assignLocalVar(p: BProc, s: PSym) =
   # for each module that uses them!
   if s.loc.k == locNone: 
     fillLoc(s.loc, locLocalVar, s.typ, mangleName(s), OnStack)
-  if gCmd == cmdCompileToLLVM: 
-    appf(p.s[cpsLocals], "$1 = alloca $2$n", 
-         [s.loc.r, getTypeDesc(p.module, s.loc.t)])
-    incl(s.loc.flags, lfIndirect)
-  else: 
-    app(p.s[cpsLocals], getTypeDesc(p.module, s.loc.t))
-    if sfRegister in s.flags: app(p.s[cpsLocals], " register")
-    if (sfVolatile in s.flags) or (p.nestedTryStmts.len > 0): 
-      app(p.s[cpsLocals], " volatile")
-    appf(p.s[cpsLocals], " $1;$n", [s.loc.r])
+  app(p.s[cpsLocals], getTypeDesc(p.module, s.loc.t))
+  if sfRegister in s.flags: app(p.s[cpsLocals], " register")
+  if (sfVolatile in s.flags) or (p.nestedTryStmts.len > 0): 
+    app(p.s[cpsLocals], " volatile")
+  appf(p.s[cpsLocals], " $1;$n", [s.loc.r])
   localDebugInfo(p, s)
 
-proc declareThreadVar(m: BModule, s: PSym) =
-  if optThreads in gGlobalOptions:
-    if platform.OS[targetOS].props.contains(ospLacksThreadVars):
-      # we gather all thread locals var into a struct and put that into
-      # nim__dat.c; we need to allocate storage for that somehow, can't use
-      # the thread local storage allocator for it :-(
-      # XXX we need to adapt expr() too, every reference to a thread local var
-      # generates quite some code ...
-      InternalError("no workaround for lack of thread local vars implemented")
-    else:
-      app(m.s[cfsVars], "NIM_THREADVAR ")
-      app(m.s[cfsVars], getTypeDesc(m, s.loc.t))
-  else:
-    app(m.s[cfsVars], getTypeDesc(m, s.loc.t))
+include ccgthreadvars
 
 proc assignGlobalVar(p: BProc, s: PSym) = 
   if s.loc.k == locNone: 
     fillLoc(s.loc, locGlobalVar, s.typ, mangleName(s), OnHeap)
   useHeader(p.module, s)
-  if lfNoDecl in s.loc.flags: return 
-  if sfImportc in s.flags: app(p.module.s[cfsVars], "extern ")
-  if sfThreadVar in s.flags: declareThreadVar(p.module, s)
-  else: app(p.module.s[cfsVars], getTypeDesc(p.module, s.loc.t))
-  if sfRegister in s.flags: app(p.module.s[cfsVars], " register")
-  if sfVolatile in s.flags: app(p.module.s[cfsVars], " volatile")
-  appf(p.module.s[cfsVars], " $1;$n", [s.loc.r])
-  if {optStackTrace, optEndb} * p.module.module.options ==
-      {optStackTrace, optEndb}: 
+  if lfNoDecl in s.loc.flags: return
+  if sfThreadVar in s.flags: 
+    declareThreadVar(p.module, s, sfImportc in s.flags)
+  else: 
+    if sfImportc in s.flags: app(p.module.s[cfsVars], "extern ")
+    app(p.module.s[cfsVars], getTypeDesc(p.module, s.loc.t))
+    if sfRegister in s.flags: app(p.module.s[cfsVars], " register")
+    if sfVolatile in s.flags: app(p.module.s[cfsVars], " volatile")
+    appf(p.module.s[cfsVars], " $1;$n", [s.loc.r])
+  if p.module.module.options * {optStackTrace, optEndb} ==
+                               {optStackTrace, optEndb}: 
     appcg(p.module, p.module.s[cfsDebugInit], 
           "#dbgRegisterGlobal($1, &$2, $3);$n", 
          [cstringLit(p, p.module.s[cfsDebugInit], 
           normalize(s.owner.name.s & '.' & s.name.s)), 
           s.loc.r, genTypeInfo(p.module, s.typ)])
-
-proc iff(cond: bool, the, els: PRope): PRope = 
-  if cond: result = the
-  else: result = els
   
 proc assignParam(p: BProc, s: PSym) = 
   assert(s.loc.r != nil)
@@ -691,9 +656,8 @@ proc genProcPrototype(m: BModule, sym: PSym) =
   if lfDynamicLib in sym.loc.Flags: 
     if sym.owner.id != m.module.id and
         not ContainsOrIncl(m.declaredThings, sym.id): 
-      appff(m.s[cfsVars], "extern $1 $2;$n", 
-            "@$2 = linkonce global $1 zeroinitializer$n", 
-            [getTypeDesc(m, sym.loc.t), mangleDynLibProc(sym)])
+      appf(m.s[cfsVars], "extern $1 $2;$n", 
+           [getTypeDesc(m, sym.loc.t), mangleDynLibProc(sym)])
       if gCmd == cmdCompileToLLVM: incl(sym.loc.flags, lfIndirect)
   elif not ContainsOrIncl(m.declaredProtos, sym.id): 
     appf(m.s[cfsProcHeaders], "$1;$n", [genProcHeader(m, sym)])
@@ -725,20 +689,16 @@ proc genVarPrototype(m: BModule, sym: PSym) =
   assert(sfGlobal in sym.flags)
   useHeader(m, sym)
   fillLoc(sym.loc, locGlobalVar, sym.typ, mangleName(sym), OnHeap)
-  if (lfNoDecl in sym.loc.Flags) or
-      ContainsOrIncl(m.declaredThings, sym.id): 
+  if (lfNoDecl in sym.loc.Flags) or ContainsOrIncl(m.declaredThings, sym.id): 
     return 
   if sym.owner.id != m.module.id: 
     # else we already have the symbol generated!
     assert(sym.loc.r != nil)
-    if gCmd == cmdCompileToLLVM: 
-      incl(sym.loc.flags, lfIndirect)
-      appf(m.s[cfsVars], "$1 = linkonce global $2 zeroinitializer$n", 
-           [sym.loc.r, getTypeDesc(m, sym.loc.t)])
-    else: 
+    if sfThreadVar in sym.flags: 
+      declareThreadVar(m, sym, true)
+    else:
       app(m.s[cfsVars], "extern ")
-      if sfThreadVar in sym.flags: declareThreadVar(m, sym)
-      else: app(m.s[cfsVars], getTypeDesc(m, sym.loc.t))
+      app(m.s[cfsVars], getTypeDesc(m, sym.loc.t))
       if sfRegister in sym.flags: app(m.s[cfsVars], " register")
       if sfVolatile in sym.flags: app(m.s[cfsVars], " volatile")
       appf(m.s[cfsVars], " $1;$n", [sym.loc.r])
@@ -898,6 +858,7 @@ proc genInitCode(m: BModule) =
 proc genModule(m: BModule, cfilenoext: string): PRope = 
   result = getFileHeader(cfilenoext)
   generateHeaders(m)
+  generateThreadLocalStorage(m)
   for i in countup(low(TCFileSection), cfsProcs): app(result, m.s[i])
   
 proc rawNewModule(module: PSym, filename: string): BModule = 
@@ -990,6 +951,7 @@ proc writeModule(m: BModule) =
   if sfMainModule in m.module.flags: 
     # generate main file:
     app(m.s[cfsProcHeaders], mainModProcs)
+    GenerateThreadVarsSize(m)
   var code = genModule(m, cfilenoext)
   
   when hasTinyCBackend:
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index ebc646483..ed88ff551 100755
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -86,14 +86,15 @@ type
     errCannotInterpretNodeX, errFieldXNotFound, errInvalidConversionFromTypeX, 
     errAssertionFailed, errCannotGenerateCodeForX, errXRequiresOneArgument, 
     errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate, 
-    errXhasSideEffects, errIteratorExpected, errDifferentHeaps, 
-    errUser, 
+    errXhasSideEffects, errIteratorExpected,
+    errUser,
     warnCannotOpenFile, 
     warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, 
     warnCannotWriteMO2, warnCannotReadMO2, warnDeprecated, 
     warnSmallLshouldNotBeUsed, warnUnknownMagic, warnRedefinitionOfLabel, 
     warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored, 
     warnXisPassedToProcVar, warnDerefDeprecated, warnAnalysisLoophole,
+    warnDifferentHeaps,
     warnUser, 
     hintSuccess, hintSuccessX, 
     hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, 
@@ -310,7 +311,6 @@ const
     errXisNoMacroOrTemplate: "\'$1\' is no macro or template",
     errXhasSideEffects: "\'$1\' can have side effects", 
     errIteratorExpected: "iterator within for loop context expected",
-    errDifferentHeaps: "possible inconsistency of thread local heaps", 
     errUser: "$1", 
     warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]",
     warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]", 
@@ -327,7 +327,8 @@ const
     warnCommentXIgnored: "comment \'$1\' ignored [CommentXIgnored]", 
     warnXisPassedToProcVar: "\'$1\' is passed to a procvar; deprecated [XisPassedToProcVar]", 
     warnDerefDeprecated: "p^ is deprecated; use p[] instead [DerefDeprecated]",
-    warnAnalysisLoophole: "thread analysis incomplete due to indirect call '$1' [AnalysisLoophole]",
+    warnAnalysisLoophole: "thread analysis incomplete due to unkown call '$1' [AnalysisLoophole]",
+    warnDifferentHeaps: "possible inconsistency of thread local heaps",
     warnUser: "$1 [User]", 
     hintSuccess: "operation successful [Success]", 
     hintSuccessX: "operation successful ($1 lines compiled; $2 sec total) [SuccessX]", 
@@ -345,12 +346,12 @@ const
     hintUser: "$1 [User]"]
 
 const 
-  WarningsToStr*: array[0..16, string] = ["CannotOpenFile", "OctalEscape", 
+  WarningsToStr*: array[0..17, string] = ["CannotOpenFile", "OctalEscape", 
     "XIsNeverRead", "XmightNotBeenInit", "CannotWriteMO2", "CannotReadMO2", 
     "Deprecated", "SmallLshouldNotBeUsed", "UnknownMagic", 
     "RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported", 
     "CommentXIgnored", "XisPassedToProcVar", "DerefDeprecated",
-    "AnalysisLoophole", "User"]
+    "AnalysisLoophole", "DifferentHeaps", "User"]
 
   HintsToStr*: array[0..13, string] = ["Success", "SuccessX", "LineTooLong", 
     "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", 
diff --git a/compiler/semthreads.nim b/compiler/semthreads.nim
index 70ae3d3d3..3ca52fc30 100644
--- a/compiler/semthreads.nim
+++ b/compiler/semthreads.nim
@@ -138,7 +138,7 @@ proc writeAccess(c: PProcCtx, n: PNode, owner: TThreadOwner) =
     of toVoid, toUndefined: InternalError(n.info, "writeAccess")
     of toTheirs, toMine:
       if lastOwner != owner and owner != toNil:
-        LocalError(n.info, errDifferentHeaps)
+        Message(n.info, warnDifferentHeaps)
   else:
     # we could not backtrack to a concrete symbol, but that's fine:
     var lastOwner = analyseSym(c, n)
@@ -147,7 +147,7 @@ proc writeAccess(c: PProcCtx, n: PNode, owner: TThreadOwner) =
     of toVoid, toUndefined: InternalError(n.info, "writeAccess")
     of toTheirs, toMine:
       if lastOwner != owner and owner != toNil:
-        LocalError(n.info, errDifferentHeaps)
+        Message(n.info, warnDifferentHeaps)
 
 proc analyseAssign(c: PProcCtx, le, ri: PNode) =
   var y = analyse(c, ri) # read access; ok
@@ -171,7 +171,7 @@ proc analyseCall(c: PProcCtx, n: PNode): TThreadOwner =
       newCtx.mapping[formal.id] = call.args[i-1]
     pushInfoContext(n.info)
     result = analyse(newCtx, prc.ast.sons[codePos])
-    if prc.ast.sons[codePos].kind == nkEmpty:
+    if prc.ast.sons[codePos].kind == nkEmpty and sfNoSideEffect notin prc.flags:
       Message(n.info, warnAnalysisLoophole, renderTree(n))
     if prc.typ.sons[0] != nil:
       if prc.ast.len > resultPos:
@@ -221,14 +221,15 @@ template aggregateOwner(result, ana: expr) =
   var a = ana # eval once
   if result != a:
     if result == toNil: result = a
-    else: localError(n.info, errDifferentHeaps)
+    else: Message(n.info, warnDifferentHeaps)
 
 proc analyseArgs(c: PProcCtx, n: PNode, start = 1) =
   for i in start..n.len-1: discard analyse(c, n[i])
 
 proc analyseOp(c: PProcCtx, n: PNode): TThreadOwner =
   if n[0].kind != nkSym or n[0].sym.kind != skProc:
-    Message(n.info, warnAnalysisLoophole, renderTree(n))
+    if tfNoSideEffect notin n[0].typ.flags:
+      Message(n.info, warnAnalysisLoophole, renderTree(n))
     result = toNil
   else:
     var prc = n[0].sym
@@ -316,7 +317,7 @@ proc analyse(c: PProcCtx, n: PNode): TThreadOwner =
     result = analyse(c, n.sons[0])
   of nkRaiseStmt:
     var a = analyse(c, n.sons[0])
-    if a != toMine: LocalError(n.info, errDifferentHeaps)
+    if a != toMine: Message(n.info, warnDifferentHeaps)
     result = toVoid
   of nkVarSection: result = analyseVarSection(c, n)
   of nkConstSection: result = analyseConstSection(c, n)
diff --git a/lib/system.nim b/lib/system.nim
index 1c8bf3ae9..7670288fc 100755
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -1479,8 +1479,6 @@ when not defined(EcmaScript) and not defined(NimrodVM):
   strDesc.size = sizeof(string)
   strDesc.kind = tyString
   strDesc.flags = {ntfAcyclic}
-  initStackBottom()
-  initGC() # BUGFIX: need to be called here!
 
   include "system/ansi_c"
 
@@ -1692,6 +1690,10 @@ when not defined(EcmaScript) and not defined(NimrodVM):
 
   when hasThreadSupport:
     include "system/threads"
+  else:
+    initStackBottom()
+    initGC()
+    
   include "system/excpt"
   # we cannot compile this with stack tracing on
   # as it would recurse endlessly!
@@ -1755,8 +1757,7 @@ when not defined(EcmaScript) and not defined(NimrodVM):
 
   proc getCurrentException*(): ref E_Base {.compilerRtl, inl.} =
     ## retrieves the current exception; if there is none, nil is returned.
-    ThreadGlobals()
-    result = ||currException
+    result = currException
 
   proc getCurrentExceptionMsg*(): string {.inline.} =
     ## retrieves the error message that was attached to the current
diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim
index 75cac97ba..ac4ec2f0b 100755
--- a/lib/system/excpt.nim
+++ b/lib/system/excpt.nim
@@ -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: PFrame
-    excHandler: PSafePoint = nil
-      # list of exception handlers
-      # a global variable for the root of all try blocks
-    currException: ref E_Base
-
-    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)
-
+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
+
+  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
+
+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
diff --git a/lib/system/gc.nim b/lib/system/gc.nim
index d2a6b4b94..033a7bdbe 100755
--- a/lib/system/gc.nim
+++ b/lib/system/gc.nim
@@ -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
diff --git a/lib/system/threads.nim b/lib/system/threads.nim
index 7b035e819..db16502ff 100755
--- a/lib/system/threads.nim
+++ b/lib/system/threads.nim
@@ -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!
-
-proc initGlobals(g: var TGlobals) = 
-  new(g.gAssertionFailed)
-  g.buf = newStringOfCap(2000)
-  g.assertBuf = newStringOfCap(2000)
+const emulatedThreadVars = defined(macosx)
+
+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()
   
diff --git a/todo.txt b/todo.txt
index 873cf8bf9..5d928ab88 100755
--- a/todo.txt
+++ b/todo.txt
@@ -1,14 +1,17 @@
-* codegen for threadvars
+High priority (version 0.8.12)
+==============================
 * implement message passing built-ins
 
 * add --deadlock_prevention:on|off switch? timeout for locks?
-* test the sort implementation again
+* iterators should not always be destructive!
+* real types for template results
+* built-in serialization
 
 
-High priority (version 0.9.0)
-=============================
+version 0.9.0
+=============
 
-- iterators should not always be destructive!
+- test the sort implementation again
 - warning for implicit openArray -> varargs convention
 - implement explicit varargs
 - tests: run modules that contain "#RUN_ME", compile the other
@@ -17,6 +20,7 @@ High priority (version 0.9.0)
 - fix overloading resolution
 - wrong co-/contravariance
 - make ^ available as operator
+- implement closures for the C code generator
 
 Bugs
 ----
@@ -33,26 +37,13 @@ Bugs
   --> system.swap or genericAssign is broken! And indeed, if reference counts
       are not modified and the GC is triggered in between a swap, bad things
       may happen!
-  
-  proc sort*[A](t: var TCountTable[A]) =
-    for i in 0 .. high(t.data)-1:
-      var maxIdx = i
-      for j in i+1 .. high(t.data):
-        if t.data[j].val > t.data[maxIdx].val: maxIdx = j
-      swap(t.data[maxIdx], t.data[i])
-
-
-To implement
-------------
 
-* distinct types for array/seq indexes
-* implement closures for the C code generator
-* GC: marker procs for native Nimrod GC and Boehm GC
-* built-in serialization
 
+version 0.9.XX
+==============
 
-Low priority
-------------
+- distinct types for array/seq indexes
+- GC: marker procs for native Nimrod GC and Boehm GC
 - implicit ref/ptr->var conversion; the compiler may store an object
   implicitly on the heap for write barrier efficiency
 - resizing of strings/sequences could take into account the memory that
@@ -61,9 +52,8 @@ Low priority
 - find a way to reintroduce the cleanup() pass for C code generation: this
   is hard because of partial evaluation --> symbol files will fix this as
   a side effect
-- floating point checks for EcmaScript
+- EcmaScript needs a new and better code gen: simply adapt the C code gen to it
 - prefer proc in current module over other procs with same overloading result?
-- real types for template results
 - generalized case statement (requires better transf)
 - tlastmod returns wrong results on BSD (Linux, MacOS X: works)
 - nested tuple unpacking
@@ -96,7 +86,7 @@ Library
 
 
 Version 2
----------
+=========
 
 - language change: inheritance should only work with reference types, so that
   the ``type`` field is not needed for objects! --> zero overhead aggregation
@@ -123,6 +113,8 @@ Version 2
     var x = myProc() # checks myProc() initializes every pointer explicitely
     
 - the two other parsers
+- rethink the syntax: distinction between expr and stmt is unfortunate; 
+  indentation handling is quite complex too
 
 
 Low priority
@@ -135,10 +127,3 @@ Low priority
   important than constructors)
 - code generated for type information is wasteful
 
-
-Other ideas
------------
-- startsWith `=^`
-- endsWith `=$`
-- ignore case `=?` --> `=$?` too?
-