summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ast.nim20
-rw-r--r--compiler/astalgo.nim4
-rw-r--r--compiler/lowerings.nim4
-rw-r--r--compiler/msgs.nim10
-rw-r--r--compiler/pragmas.nim35
-rw-r--r--compiler/sempass2.nim112
-rw-r--r--compiler/sigmatch.nim2
-rw-r--r--compiler/wordrecg.nim11
-rw-r--r--doc/manual.txt104
-rw-r--r--lib/packages/docutils/rst.nim4
-rw-r--r--lib/posix/epoll.nim2
-rw-r--r--lib/posix/inotify.nim2
-rw-r--r--lib/posix/linux.nim5
-rw-r--r--lib/posix/posix.nim2
-rw-r--r--lib/system.nim114
-rw-r--r--lib/system/avltree.nim4
-rw-r--r--lib/system/cgprocs.nim4
-rw-r--r--lib/system/excpt.nim12
-rw-r--r--lib/system/gc.nim21
-rw-r--r--lib/system/hti.nim2
-rw-r--r--lib/system/syslocks.nim12
-rw-r--r--lib/system/sysspawn.nim172
-rw-r--r--lib/windows/winlean.nim2
23 files changed, 460 insertions, 200 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index e720d2bfa..e575f317d 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -13,7 +13,7 @@ import
   msgs, hashes, nversion, options, strutils, crc, ropes, idents, lists, 
   intsets, idgen
 
-type 
+type
   TCallingConvention* = enum 
     ccDefault,                # proc has no explicit calling convention
     ccStdCall,                # procedure is stdcall
@@ -299,7 +299,7 @@ const
   nkEffectList* = nkArgList 
   # hacks ahead: an nkEffectList is a node with 4 children:
   exceptionEffects* = 0 # exceptions at position 0
-  readEffects* = 1      # read effects at position 1
+  usesEffects* = 1      # read effects at position 1
   writeEffects* = 2     # write effects at position 2
   tagEffects* = 3       # user defined tag ('gc', 'time' etc.)
   effectListLen* = 4    # list of effects list
@@ -432,7 +432,7 @@ type
     tfAcyclic,        # type is acyclic (for GC optimization)
     tfEnumHasHoles,   # enum cannot be mapped into a range
     tfShallow,        # type can be shallow copied on assignment
-    tfThread,         # proc type is marked as ``thread``
+    tfThread,         # proc type is marked as ``thread``; alias for ``gcsafe``
     tfFromGeneric,    # type is an instantiation of a generic; this is needed
                       # because for instantiations of objects, structural
                       # type equality has to be used
@@ -509,6 +509,7 @@ const
   tfIncompleteStruct* = tfVarargs
   tfUncheckedArray* = tfVarargs
   tfUnion* = tfNoSideEffect
+  tfGcSafe* = tfThread
   skError* = skUnknown
   
   # type flags that are essential for type equality:
@@ -978,6 +979,8 @@ template `{}=`*(n: PNode, i: int, s: PNode): stmt =
 var emptyNode* = newNode(nkEmpty)
 # There is a single empty node that is shared! Do not overwrite it!
 
+var anyGlobal* = newSym(skVar, getIdent("*"), nil, unknownLineInfo())
+
 proc isMetaType*(t: PType): bool =
   return t.kind in tyMetaTypes or
          (t.kind == tyStatic and t.n == nil) or
@@ -1310,8 +1313,7 @@ proc skipTypes*(t: PType, kinds: TTypeKinds): PType =
 
 proc propagateToOwner*(owner, elem: PType) =
   const HaveTheirOwnEmpty = {tySequence, tySet}
-  owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta,
-                                             tfHasGCedMem})
+  owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta})
   if tfNotNil in elem.flags:
     if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvokation}:
       owner.flags.incl tfNotNil
@@ -1328,9 +1330,11 @@ proc propagateToOwner*(owner, elem: PType) =
   if elem.isMetaType:
     owner.flags.incl tfHasMeta
 
-  if elem.kind in {tyString, tyRef, tySequence} or
-      elem.kind == tyProc and elem.callConv == ccClosure:
-    owner.flags.incl tfHasGCedMem
+  if owner.kind != tyProc:
+    if elem.kind in {tyString, tyRef, tySequence} or
+        elem.kind == tyProc and elem.callConv == ccClosure or
+        tfHasGCedMem in elem.flags:
+      owner.flags.incl tfHasGCedMem
 
 proc rawAddSon*(father, son: PType) =
   if isNil(father.sons): father.sons = @[]
diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim
index 36dd7f562..dbf13f764 100644
--- a/compiler/astalgo.nim
+++ b/compiler/astalgo.nim
@@ -448,9 +448,9 @@ proc debug(n: PSym) =
     writeln(stdout, "skUnknown")
   else:
     #writeln(stdout, ropeToStr(symToYaml(n, 0, 1)))
-    writeln(stdout, ropeToStr(ropef("$1_$2: $3, $4", [
+    writeln(stdout, ropeToStr(ropef("$1_$2: $3, $4, $5", [
       toRope(n.name.s), toRope(n.id), flagsToStr(n.flags), 
-      flagsToStr(n.loc.flags)])))
+      flagsToStr(n.loc.flags), lineInfoToStr(n.info)])))
 
 proc debug(n: PType) = 
   writeln(stdout, ropeToStr(debugType(n)))
diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim
index bee3427f4..0ca07e828 100644
--- a/compiler/lowerings.nim
+++ b/compiler/lowerings.nim
@@ -151,6 +151,8 @@ proc wrapProcForSpawn*(owner: PSym; n: PNode): PNode =
   if n.kind notin nkCallKinds or not n.typ.isEmptyType:
     localError(n.info, "'spawn' takes a call expression of type void")
     return
+  if {tfThread, tfNoSideEffect} * n[0].typ.flags == {}:
+    localError(n.info, "'spawn' takes a GC safe call expression")
   var
     threadParam = newSym(skParam, getIdent"thread", owner, n.info)
     argsParam = newSym(skParam, getIdent"args", owner, n.info)
@@ -196,7 +198,7 @@ proc wrapProcForSpawn*(owner: PSym; n: PNode): PNode =
     # we pick n's type here, which hopefully is 'tyArray' and not
     # 'tyOpenArray':
     var argType = n[i].typ.skipTypes(abstractInst)
-    if argType.kind == tyVar:
+    if i < formals.len and formals[i].typ.kind == tyVar:
       localError(n[i].info, "'spawn'ed function cannot have a 'var' parameter")
     elif containsTyRef(argType):
       localError(n[i].info, "'spawn'ed function cannot refer to 'ref'/closure")
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index a63fbca7f..7ad393b4d 100644
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -118,7 +118,7 @@ type
     warnNilStatement, warnAnalysisLoophole,
     warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure,
     warnEachIdentIsTuple, warnShadowIdent, 
-    warnProveInit, warnProveField, warnProveIndex,
+    warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe,
     warnUninit, warnGcMem, warnUser,
     hintSuccess, hintSuccessX,
     hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
@@ -386,6 +386,7 @@ const
     warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future. [ProveInit]",
     warnProveField: "cannot prove that field '$1' is accessible [ProveField]",
     warnProveIndex: "cannot prove index '$1' is valid [ProveIndex]",
+    warnGcUnsafe: "not GC-safe: '$1' [GcUnsafe]",
     warnUninit: "'$1' might not have been initialized [Uninit]",
     warnGcMem: "'$1' uses GC'ed memory [GcMem]",
     warnUser: "$1 [User]", 
@@ -407,7 +408,7 @@ const
     hintUser: "$1 [User]"]
 
 const
-  WarningsToStr*: array[0..24, string] = ["CannotOpenFile", "OctalEscape", 
+  WarningsToStr*: array[0..25, string] = ["CannotOpenFile", "OctalEscape", 
     "XIsNeverRead", "XmightNotBeenInit",
     "Deprecated", "ConfigDeprecated",
     "SmallLshouldNotBeUsed", "UnknownMagic", 
@@ -415,7 +416,8 @@ const
     "CommentXIgnored", "NilStmt",
     "AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap",
     "ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", 
-    "ProveInit", "ProveField", "ProveIndex", "Uninit", "GcMem", "User"]
+    "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "Uninit",
+    "GcMem", "User"]
 
   HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong", 
     "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", 
@@ -557,7 +559,7 @@ proc sourceLine*(i: TLineInfo): PRope
 var
   gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} - 
                         {warnShadowIdent, warnUninit,
-                         warnProveField, warnProveIndex}
+                         warnProveField, warnProveIndex, warnGcUnsafe}
   gErrorCounter*: int = 0     # counts the number of errors
   gHintCounter*: int = 0
   gWarnCounter*: int = 0
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 14d155539..88fa516bf 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -24,7 +24,7 @@ const
     wCompilerproc, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge, 
     wBorrow, wExtern, wImportCompilerProc, wThread, wImportCpp, wImportObjC,
     wAsmNoStackFrame, wError, wDiscardable, wNoInit, wDestructor, wCodegenDecl,
-    wGensym, wInject, wRaises, wTags, wOperator, wDelegator}
+    wGensym, wInject, wRaises, wTags, wUses, wOperator, wDelegator, wGcSafe}
   converterPragmas* = procPragmas
   methodPragmas* = procPragmas
   templatePragmas* = {wImmediate, wDeprecated, wError, wGensym, wInject, wDirty,
@@ -35,7 +35,7 @@ const
   iteratorPragmas* = {FirstCallConv..LastCallConv, wNosideeffect, wSideeffect, 
     wImportc, wExportc, wNodecl, wMagic, wDeprecated, wBorrow, wExtern,
     wImportCpp, wImportObjC, wError, wDiscardable, wGensym, wInject, wRaises,
-    wTags, wOperator}
+    wTags, wUses, wOperator, wGcSafe}
   exprPragmas* = {wLine}
   stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks,
     wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints,
@@ -48,7 +48,7 @@ const
   lambdaPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl, 
     wNosideeffect, wSideeffect, wNoreturn, wDynlib, wHeader, 
     wDeprecated, wExtern, wThread, wImportCpp, wImportObjC, wAsmNoStackFrame,
-    wRaises, wTags}
+    wRaises, wUses, wTags, wGcSafe}
   typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl, 
     wPure, wHeader, wCompilerproc, wFinal, wSize, wExtern, wShallow,
     wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef,
@@ -64,7 +64,7 @@ const
     wExtern, wImportCpp, wImportObjC, wError, wGensym, wInject}
   letPragmas* = varPragmas
   procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideeffect,
-                      wThread, wRaises, wTags}
+                      wThread, wRaises, wUses, wTags, wGcSafe}
   allRoutinePragmas* = procPragmas + iteratorPragmas + lambdaPragmas
 
 proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords)
@@ -513,6 +513,27 @@ proc pragmaRaisesOrTags(c: PContext, n: PNode) =
   else:
     invalidPragma(n)
 
+proc pragmaUses(c: PContext, n: PNode) =
+  proc processExc(c: PContext, x: PNode): PNode =
+    if x.kind in {nkAccQuoted, nkIdent, nkSym,
+                  nkOpenSymChoice, nkClosedSymChoice}:
+      if considerAcc(x).s == "*":
+        return newSymNode(ast.anyGlobal)
+    result = c.semExpr(c, x)
+    if result.kind != nkSym or sfGlobal notin result.sym.flags:
+      localError(x.info, "'$1' is not a global variable" % result.renderTree)
+      result = newSymNode(ast.anyGlobal)
+    
+  if n.kind == nkExprColonExpr:
+    let it = n.sons[1]
+    if it.kind notin {nkCurly, nkBracket}:
+      n.sons[1] = processExc(c, it)
+    else:
+      for i in 0 .. <it.len:
+        it.sons[i] = processExc(c, it.sons[i])
+  else:
+    invalidPragma(n)
+
 proc typeBorrow(sym: PSym, n: PNode) =
   if n.kind == nkExprColonExpr:
     let it = n.sons[1]
@@ -667,6 +688,11 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
           incl(sym.flags, sfThread)
           incl(sym.flags, sfProcvar)
           if sym.typ != nil: incl(sym.typ.flags, tfThread)
+        of wGcSafe:
+          noVal(it)
+          incl(sym.flags, sfThread)
+          if sym.typ != nil: incl(sym.typ.flags, tfGcSafe)
+          else: invalidPragma(it)
         of wPacked:
           noVal(it)
           if sym.typ == nil: invalidPragma(it)
@@ -759,6 +785,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
           if sym == nil: invalidPragma(it)
         of wLine: pragmaLine(c, it)
         of wRaises, wTags: pragmaRaisesOrTags(c, it)
+        of wUses: pragmaUses(c, it)
         of wOperator:
           if sym == nil: invalidPragma(it)
           else: sym.position = expectIntLit(c, it)
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index a00325277..b2b91490c 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -59,15 +59,19 @@ discard """
  --> we need a stack of scopes for this analysis
 """
 
+const trackGlobals = false ## we don't need it for now
+
 type
   TEffects = object
     exc: PNode  # stack of exceptions
     tags: PNode # list of tags
+    uses: PNode # list of used global variables
     bottom: int
     owner: PSym
     init: seq[int] # list of initialized variables
     guards: TModel # nested guards
     locked: seq[PNode] # locked locations
+    gcUnsafe: bool
   PEffects = var TEffects
 
 proc isLocalVar(a: PEffects, s: PSym): bool =
@@ -89,20 +93,29 @@ proc initVarViaNew(a: PEffects, n: PNode) =
     # are initialized:
     initVar(a, n)
 
+when trackGlobals: 
+  proc addUse(a: PEffects, e: PNode) =
+    var aa = a.uses
+    for i in 0 .. <aa.len:
+      if aa[i].sym.id == e.sym.id: return
+    a.uses.add(e)
+
 proc useVar(a: PEffects, n: PNode) =
   let s = n.sym
   if isLocalVar(a, s):
     if s.id notin a.init:
       if {tfNeedsInit, tfNotNil} * s.typ.flags != {}:
-        when true:
-          message(n.info, warnProveInit, s.name.s)
-        else:
-          Message(n.info, errGenerated,
-            "'$1' might not have been initialized" % s.name.s)
+        message(n.info, warnProveInit, s.name.s)
       else:
         message(n.info, warnUninit, s.name.s)
       # prevent superfluous warnings about the same variable:
       a.init.add s.id
+  if {sfGlobal, sfThread} * s.flags == {sfGlobal} and s.kind == skVar:
+    when trackGlobals:
+      a.addUse(copyNode(n))
+    if tfHasGCedMem in s.typ.flags: 
+      message(n.info, warnGcUnsafe, renderTree(n))
+      a.gcUnsafe = true
 
 type
   TIntersection = seq[tuple[id, count: int]] # a simple count table
@@ -132,6 +145,10 @@ proc createTag(n: PNode): PNode =
   result.typ = sysTypeFromName"TEffect"
   if not n.isNil: result.info = n.info
 
+proc createAnyGlobal(n: PNode): PNode =
+  result = newSymNode(anyGlobal)
+  result.info = n.info
+
 proc addEffect(a: PEffects, e: PNode, useLineInfo=true) =
   assert e.kind != nkRaiseStmt
   var aa = a.exc
@@ -161,9 +178,17 @@ proc mergeTags(a: PEffects, b, comesFrom: PNode) =
   else:
     for effect in items(b): addTag(a, effect, useLineInfo=comesFrom != nil)
 
+when trackGlobals:
+  proc mergeUses(a: PEffects, b, comesFrom: PNode) =
+    if b.isNil:
+      addUse(a, createAnyGlobal(comesFrom))
+    else:
+      for effect in items(b): addUse(a, effect)
+
 proc listEffects(a: PEffects) =
   for e in items(a.exc):  message(e.info, hintUser, typeToString(e.typ))
   for e in items(a.tags): message(e.info, hintUser, typeToString(e.typ))
+  for e in items(a.uses): message(e.info, hintUser, e.sym.name.s)
 
 proc catches(tracked: PEffects, e: PType) =
   let e = skipTypes(e, skipPtrs)
@@ -289,6 +314,13 @@ proc documentRaises*(n: PNode) =
   if n.sons[namePos].kind != nkSym: return
   documentEffect(n, n.sons[pragmasPos], wRaises, exceptionEffects)
   documentEffect(n, n.sons[pragmasPos], wTags, tagEffects)
+  documentEffect(n, n.sons[pragmasPos], wUses, usesEffects)
+
+template notGcSafe(t): expr = {tfGcSafe, tfNoSideEffect} * t.flags == {}
+
+proc importedFromC(n: PNode): bool =
+  # when imported from C, we assume GC-safety.
+  result = n.kind == nkSym and sfImportc in n.sym.flags
 
 proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
   let pragma = s.ast.sons[pragmasPos]
@@ -298,6 +330,14 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
   let tagSpec = effectSpec(pragma, wTags)
   mergeTags(tracked, tagSpec, n)
 
+  if notGcSafe(s.typ) and sfImportc notin s.flags:
+    message(n.info, warnGcUnsafe, renderTree(n))
+    tracked.gcUnsafe = true
+
+  when trackGlobals:
+    let usesSpec = effectSpec(pragma, wUses)
+    mergeUses(tracked, usesSpec, n)
+
 proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
   let n = n.skipConv
   if paramType != nil and tfNotNil in paramType.flags and 
@@ -330,9 +370,18 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
       else:
         addEffect(tracked, createRaise(n))
         addTag(tracked, createTag(n))
+        when trackGlobals: addUse(tracked, createAnyGlobal(n))
+      # assume GcUnsafe unless in its type:
+      if notGcSafe(op): 
+        message(n.info, warnGcUnsafe, renderTree(n))
+        tracked.gcUnsafe = true
     else:
       mergeEffects(tracked, effectList.sons[exceptionEffects], n)
       mergeTags(tracked, effectList.sons[tagEffects], n)
+      when trackGlobals: mergeUses(tracked, effectList.sons[usesEffects], n)
+      if notGcSafe(op):
+        message(n.info, warnGcUnsafe, renderTree(n))
+        tracked.gcUnsafe = true
   notNilCheck(tracked, n, paramType)
 
 proc breaksBlock(n: PNode): bool =
@@ -451,8 +500,11 @@ proc track(tracked: PEffects, n: PNode) =
     # XXX: in rare situations, templates and macros will reach here after
     # calling getAst(templateOrMacro()). Currently, templates and macros
     # are indistinguishable from normal procs (both have tyProc type) and
-    # we can detect them only by cheking for attached nkEffectList.
+    # we can detect them only by checking for attached nkEffectList.
     if op != nil and op.kind == tyProc and op.n.sons[0].kind == nkEffectList:
+      if notGcSafe(op) and not importedFromC(a):
+        message(n.info, warnGcUnsafe, renderTree(n))
+        tracked.gcUnsafe = true
       var effectList = op.n.sons[0]
       if a.kind == nkSym and a.sym.kind == skMethod:
         propagateEffects(tracked, n, a.sym)
@@ -462,9 +514,11 @@ proc track(tracked: PEffects, n: PNode) =
         elif isIndirectCall(a, tracked.owner):
           addEffect(tracked, createRaise(n))
           addTag(tracked, createTag(n))
+          when trackGlobals: addUse(tracked, createAnyGlobal(n))
       else:
         mergeEffects(tracked, effectList.sons[exceptionEffects], n)
         mergeTags(tracked, effectList.sons[tagEffects], n)
+        when trackGlobals: mergeUses(tracked, effectList.sons[usesEffects], n)
     for i in 1 .. <len(n): trackOperand(tracked, n.sons[i], paramType(op, i))
     if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
       # may not look like an assignment, but it is:
@@ -531,14 +585,21 @@ proc track(tracked: PEffects, n: PNode) =
   else:
     for i in 0 .. <safeLen(n): track(tracked, n.sons[i])
 
-proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool) =
+proc subtypeRelation(spec, real: PNode): bool =
+  result = safeInheritanceDiff(real.excType, spec.typ) <= 0
+
+proc symbolPredicate(spec, real: PNode): bool =
+  result = real.sym.id == spec.sym.id
+
+proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool;
+                     effectPredicate: proc (a, b: PNode): bool {.nimcall.}) =
   # check that any real exception is listed in 'spec'; mark those as used;
   # report any unused exception
   var used = initIntSet()
   for r in items(real):
     block search:
       for s in 0 .. <spec.len:
-        if safeInheritanceDiff(r.excType, spec[s].typ) <= 0:
+        if effectPredicate(spec[s], r):
           used.incl(s)
           break search
       # XXX call graph analysis would be nice here!
@@ -560,11 +621,18 @@ proc checkMethodEffects*(disp, branch: PSym) =
   let raisesSpec = effectSpec(p, wRaises)
   if not isNil(raisesSpec):
     checkRaisesSpec(raisesSpec, actual.sons[exceptionEffects],
-      "can raise an unlisted exception: ", hints=off)
+      "can raise an unlisted exception: ", hints=off, subtypeRelation)
   let tagsSpec = effectSpec(p, wTags)
   if not isNil(tagsSpec):
     checkRaisesSpec(tagsSpec, actual.sons[tagEffects],
-      "can have an unlisted effect: ", hints=off)
+      "can have an unlisted effect: ", hints=off, subtypeRelation)
+  let usesSpec = effectSpec(p, wUses)
+  if not isNil(usesSpec):
+    checkRaisesSpec(usesSpec, actual.sons[usesEffects],
+      "may use an unlisted global variable: ", hints=off, symbolPredicate)
+  if sfThread in disp.flags and notGcSafe(branch.typ):
+    localError(branch.info, "base method is GC-safe, but '$1' is not" % 
+                                branch.name.s)
 
 proc setEffectsForProcType*(t: PType, n: PNode) =
   var effects = t.n.sons[0]
@@ -573,21 +641,26 @@ proc setEffectsForProcType*(t: PType, n: PNode) =
   let
     raisesSpec = effectSpec(n, wRaises)
     tagsSpec = effectSpec(n, wTags)
-  if not isNil(raisesSpec) or not isNil(tagsSpec):
+    usesSpec = effectSpec(n, wUses)
+  if not isNil(raisesSpec) or not isNil(tagsSpec) or not isNil(usesSpec):
     internalAssert effects.len == 0
     newSeq(effects.sons, effectListLen)
     if not isNil(raisesSpec):
       effects.sons[exceptionEffects] = raisesSpec
     if not isNil(tagsSpec):
       effects.sons[tagEffects] = tagsSpec
+    if not isNil(usesSpec):
+      effects.sons[usesEffects] = usesSpec
 
 proc initEffects(effects: PNode; s: PSym; t: var TEffects) =
   newSeq(effects.sons, effectListLen)
   effects.sons[exceptionEffects] = newNodeI(nkArgList, s.info)
   effects.sons[tagEffects] = newNodeI(nkArgList, s.info)
+  effects.sons[usesEffects] = newNodeI(nkArgList, s.info)
   
   t.exc = effects.sons[exceptionEffects]
   t.tags = effects.sons[tagEffects]
+  t.uses = effects.sons[usesEffects]
   t.owner = s
   t.init = @[]
   t.guards = @[]
@@ -602,7 +675,6 @@ proc trackProc*(s: PSym, body: PNode) =
   var t: TEffects
   initEffects(effects, s, t)
   track(t, body)
-  
   if not isEmptyType(s.typ.sons[0]) and tfNeedsInit in s.typ.sons[0].flags and
       s.kind in {skProc, skConverter, skMethod}:
     var res = s.ast.sons[resultPos].sym # get result symbol
@@ -612,17 +684,27 @@ proc trackProc*(s: PSym, body: PNode) =
   let raisesSpec = effectSpec(p, wRaises)
   if not isNil(raisesSpec):
     checkRaisesSpec(raisesSpec, t.exc, "can raise an unlisted exception: ",
-                    hints=on)
+                    hints=on, subtypeRelation)
     # after the check, use the formal spec:
     effects.sons[exceptionEffects] = raisesSpec
 
   let tagsSpec = effectSpec(p, wTags)
   if not isNil(tagsSpec):
     checkRaisesSpec(tagsSpec, t.tags, "can have an unlisted effect: ",
-                    hints=off)
+                    hints=off, subtypeRelation)
     # after the check, use the formal spec:
     effects.sons[tagEffects] = tagsSpec
-    
+
+  when trackGlobals:
+    let usesSpec = effectSpec(p, wUses)
+    if not isNil(usesSpec):
+      checkRaisesSpec(usesSpec, t.uses,
+        "uses an unlisted global variable: ", hints=on, symbolPredicate)
+      effects.sons[usesEffects] = usesSpec
+  if sfThread in s.flags and t.gcUnsafe:
+    localError(s.info, "'$1' is not GC-safe" % s.name.s)
+  if not t.gcUnsafe: s.typ.flags.incl tfGcSafe
+  
 proc trackTopLevelStmt*(module: PSym; n: PNode) =
   if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef,
                 nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}:
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index 6b85542d3..5d3ed05f7 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -402,7 +402,7 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
     if tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags:
       return isNone
     elif tfThread in f.flags and a.flags * {tfThread, tfNoSideEffect} == {}:
-      # noSideEffect implies ``tfThread``! XXX really?
+      # noSideEffect implies ``tfThread``!
       return isNone
     elif f.flags * {tfIterator} != a.flags * {tfIterator}:
       return isNone
diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim
index 9a74f53fc..fbe005031 100644
--- a/compiler/wordrecg.nim
+++ b/compiler/wordrecg.nim
@@ -1,7 +1,7 @@
 #
 #
 #           The Nimrod Compiler
-#        (c) Copyright 2012 Andreas Rumpf
+#        (c) Copyright 2014 Andreas Rumpf
 #
 #    See the file "copying.txt", included in this
 #    distribution, for details about the copyright.
@@ -44,7 +44,8 @@ type
     wImportCompilerProc,
     wImportc, wExportc, wIncompleteStruct, wRequiresInit,
     wAlign, wNodecl, wPure, wSideeffect, wHeader,
-    wNosideeffect, wNoreturn, wMerge, wLib, wDynlib, wCompilerproc, wProcVar, 
+    wNosideeffect, wGcSafe, wNoreturn, wMerge, wLib, wDynlib, 
+    wCompilerproc, wProcVar, 
     wFatal, wError, wWarning, wHint, wLine, wPush, wPop, wDefine, wUndef, 
     wLinedir, wStacktrace, wLinetrace, wLink, wCompile, 
     wLinksys, wDeprecated, wVarargs, wCallconv, wBreakpoint, wDebugger, 
@@ -63,7 +64,7 @@ type
     wAcyclic, wShallow, wUnroll, wLinearScanEnd, wComputedGoto, wInjectStmt,
     wWrite, wGensym, wInject, wDirty, wInheritable, wThreadVar, wEmit, 
     wAsmNoStackFrame,
-    wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard,
+    wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard, wUses,
 
     wAuto, wBool, wCatch, wChar, wClass,
     wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast,
@@ -125,7 +126,7 @@ const
     "importcpp", "importobjc",
     "importcompilerproc", "importc", "exportc", "incompletestruct",
     "requiresinit", "align", "nodecl", "pure", "sideeffect",
-    "header", "nosideeffect", "noreturn", "merge", "lib", "dynlib", 
+    "header", "nosideeffect", "gcsafe", "noreturn", "merge", "lib", "dynlib", 
     "compilerproc", "procvar", "fatal", "error", "warning", "hint", "line", 
     "push", "pop", "define", "undef", "linedir", "stacktrace", "linetrace", 
     "link", "compile", "linksys", "deprecated", "varargs", 
@@ -146,7 +147,7 @@ const
     "computedgoto", "injectstmt",
     "write", "gensym", "inject", "dirty", "inheritable", "threadvar", "emit",
     "asmnostackframe", "implicitstatic", "global", "codegendecl", "unchecked",
-    "guard",
+    "guard", "uses",
     
     "auto", "bool", "catch", "char", "class",
     "const_cast", "default", "delete", "double",
diff --git a/doc/manual.txt b/doc/manual.txt
index c0d4f2627..d6b9f296e 100644
--- a/doc/manual.txt
+++ b/doc/manual.txt
@@ -5616,7 +5616,6 @@ Memory allocation requires no lock at all! This design easily scales to massive
 multicore processors that will become the norm in the future.
 
 
-
 Thread pragma
 -------------
 
@@ -5626,12 +5625,32 @@ violations of the `no heap sharing restriction`:idx:\: This restriction implies
 that it is invalid to construct a data structure that consists of memory 
 allocated from different (thread local) heaps. 
 
-Since the semantic checking of threads requires whole program analysis, 
-it is quite expensive and can be turned off with ``--threadanalysis:off`` to 
-improve compile times.
+A thread proc is passed to ``createThread`` or ``spawn`` and invoked 
+indirectly; so the ``thread`` pragma implies ``procvar``.
+
+
+GC safety
+---------
+
+We call a proc ``p`` `GC safe`:idx: when it doesn't access any global variable
+that contains GC'ed memory (``string``, ``seq``, ``ref`` or a closure) either
+directly or indirectly through a call to a GC unsafe proc. 
+
+The `gcsafe`:idx: annotation can be used to mark a proc to be gcsafe
+otherwise this property is inferred by the compiler. Note that ``noSideEfect``
+implies ``gcsafe``. The only way to create a thread is via ``spawn`` or
+``createThead``. ``spawn`` is usually the preferable method. Either way
+the invoked proc must not use ``var`` parameters nor must any of its parameters
+contain a ``ref`` or ``closure`` type. This enforces
+the *no heap sharing restriction*. 
+
+Routines that are imported from C are always assumed to be ``gcsafe``.
+
+Future directions:
 
-A thread proc is passed to ``createThread`` and invoked indirectly; so the
-``thread`` pragma implies ``procvar``.
+- For structured fork&join parallelism more efficient parameter passing can
+  be performed and much more can be proven safe.
+- A shared GC'ed heap is planned.
 
 
 Threadvar pragma
@@ -5648,78 +5667,6 @@ initialized within the ``var`` section. (Every thread local variable needs to
 be replicated at thread creation.)
 
 
-Actor model
------------
-
-**Caution**: This section is already outdated! XXX
-
-Nimrod supports the `actor model`:idx: of concurrency natively:
-
-.. code-block:: nimrod
-  type
-    TMsgKind = enum
-      mLine, mEof
-    TMsg = object
-      case k: TMsgKind
-      of mEof: nil
-      of mLine: data: string
-
-  var
-    thr: TThread[TMsg]
-    printedLines = 0
-    m: TMsg
-
-  proc print() {.thread.} =
-    while true:
-      var x = recv[TMsg]()
-      if x.k == mEof: break
-      echo x.data
-      discard atomicInc(printedLines)
-
-  createThread(thr, print)
-
-  var input = open("readme.txt")
-  while not endOfFile(input):
-    m.data = input.readLine()
-    thr.send(m)
-  close(input)
-  m.k = mEof
-  thr.send(m)
-  joinThread(thr)
-
-  echo printedLines
-
-In the actor model threads communicate only over sending messages (`send`:idx: 
-and `recv`:idx: built-ins), not by sharing memory. Every thread has 
-an `inbox`:idx: that keeps incoming messages until the thread requests a new
-message via the ``recv`` operation. The inbox is an unlimited FIFO queue.
-
-In the above example the ``print`` thread also communicates with its 
-parent thread over the ``printedLines`` global variable. In general, it is 
-highly advisable to only read from globals, but not to write to them. In fact
-a write to a global that contains GC'ed memory is always wrong, because it
-violates the *no heap sharing restriction*:
-
-.. code-block:: nimrod
-  var 
-    global: string
-    t: TThread[string]
-  
-  proc horrible() {.thread.} =
-    global = "string in thread local heap!"
-
-  createThread(t, horrible)
-  joinThread(t)
-  
-For the above code the compiler produces "Warning: write to foreign heap". This
-warning might become an error message in future versions of the compiler.
-
-Creating a thread is an expensive operation, because a new stack and heap needs
-to be created for the thread. It is therefore highly advisable that a thread
-handles a large amount of work. Nimrod prefers *coarse grained* 
-over *fine grained* concurrency.
-
-
 Threads and exceptions
 ----------------------
 
@@ -5727,6 +5674,7 @@ The interaction between threads and exceptions is simple: A *handled* exception
 in one thread cannot affect any other thread. However, an *unhandled* 
 exception in one thread terminates the whole *process*!
 
+
 Taint mode
 ==========
 
diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim
index 30cc9026b..23459ade6 100644
--- a/lib/packages/docutils/rst.nim
+++ b/lib/packages/docutils/rst.nim
@@ -311,8 +311,8 @@ proc newSharedState(options: TRstParseOptions,
   result.subs = @[]
   result.refs = @[]
   result.options = options
-  result.msgHandler = if isNil(msgHandler): defaultMsgHandler else: msgHandler
-  result.findFile = if isNil(findFile): defaultFindFile else: findFile
+  result.msgHandler = if not isNil(msgHandler): msgHandler else: defaultMsgHandler
+  result.findFile = if not isNil(findFile): findFile else: defaultFindFile
   
 proc rstMessage(p: TRstParser, msgKind: TMsgKind, arg: string) = 
   p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line, 
diff --git a/lib/posix/epoll.nim b/lib/posix/epoll.nim
index 57a2f001f..ee04348e8 100644
--- a/lib/posix/epoll.nim
+++ b/lib/posix/epoll.nim
@@ -7,6 +7,8 @@
 #    distribution, for details about the copyright.
 #
 
+{.deadCodeElim:on.}
+
 from posix import TSocketHandle
 
 const
diff --git a/lib/posix/inotify.nim b/lib/posix/inotify.nim
index 28dcd652f..852eb12fa 100644
--- a/lib/posix/inotify.nim
+++ b/lib/posix/inotify.nim
@@ -7,6 +7,8 @@
 #    distribution, for details about the copyright.
 #
 
+{.deadCodeElim:on.}
+
 # Get the platform-dependent flags.  
 # Structure describing an inotify event.  
 type 
diff --git a/lib/posix/linux.nim b/lib/posix/linux.nim
index 1ed1af3b6..be591e29a 100644
--- a/lib/posix/linux.nim
+++ b/lib/posix/linux.nim
@@ -1,3 +1,5 @@
+{.deadCodeElim:on.}
+
 import posix
 
 const
@@ -22,4 +24,5 @@ const
 
 # fn should be of type proc (a2: pointer): void {.cdecl.}
 proc clone*(fn: pointer; child_stack: pointer; flags: cint;
-            arg: pointer; ptid: ptr TPid; tls: pointer; ctid: ptr TPid): cint {.importc, header: "<sched.h>".}
+            arg: pointer; ptid: ptr TPid; tls: pointer; 
+            ctid: ptr TPid): cint {.importc, header: "<sched.h>".}
diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim
index 131f23fdd..e206447cc 100644
--- a/lib/posix/posix.nim
+++ b/lib/posix/posix.nim
@@ -27,6 +27,8 @@
 ## resulting C code will just ``#include <XYZ.h>`` and *not* define the
 ## symbols declared here.
 
+{.deadCodeElim:on.}
+
 from times import TTime
 
 const
diff --git a/lib/system.nim b/lib/system.nim
index dae418b7f..ede9eae59 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -192,6 +192,9 @@ when defined(nimNewShared):
   type
     `shared`* {.magic: "Shared".}
     guarded* {.magic: "Guarded".}
+else:
+  {.pragma: gcsafe.}
+#{.pragma: gcsafe.}
 
 const NoFakeVars* = defined(NimrodVM) ## true if the backend doesn't support \
   ## "fake variables" like 'var EBADF {.importc.}: cint'.
@@ -1207,20 +1210,20 @@ proc substr*(s: string, first, last: int): string {.
   ## or `limit`:idx: a string's length.
 
 when not defined(nimrodVM):
-  proc zeroMem*(p: pointer, size: int) {.importc, noDecl.}
+  proc zeroMem*(p: pointer, size: int) {.importc, noDecl, gcsafe.}
     ## overwrites the contents of the memory at ``p`` with the value 0.
     ## Exactly ``size`` bytes will be overwritten. Like any procedure
     ## dealing with raw memory this is *unsafe*.
 
   proc copyMem*(dest, source: pointer, size: int) {.
-    importc: "memcpy", header: "<string.h>".}
+    importc: "memcpy", header: "<string.h>", gcsafe.}
     ## copies the contents from the memory at ``source`` to the memory
     ## at ``dest``. Exactly ``size`` bytes will be copied. The memory
     ## regions may not overlap. Like any procedure dealing with raw
     ## memory this is *unsafe*.
 
   proc moveMem*(dest, source: pointer, size: int) {.
-    importc: "memmove", header: "<string.h>".}
+    importc: "memmove", header: "<string.h>", gcsafe.}
     ## copies the contents from the memory at ``source`` to the memory
     ## at ``dest``. Exactly ``size`` bytes will be copied. The memory
     ## regions may overlap, ``moveMem`` handles this case appropriately
@@ -1235,14 +1238,14 @@ when not defined(nimrodVM):
     ## *unsafe*.
 
   when hostOS != "standalone":
-    proc alloc*(size: int): pointer {.noconv, rtl, tags: [].}
+    proc alloc*(size: int): pointer {.noconv, rtl, tags: [], gcsafe.}
       ## allocates a new memory block with at least ``size`` bytes. The
       ## block has to be freed with ``realloc(block, 0)`` or
       ## ``dealloc(block)``. The block is not initialized, so reading
       ## from it before writing to it is undefined behaviour!
       ## The allocated memory belongs to its allocating thread!
       ## Use `allocShared` to allocate from a shared heap.
-    proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline.} =
+    proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline, gcsafe.} =
       ## allocates a new memory block with at least ``T.sizeof * size``
       ## bytes. The block has to be freed with ``resize(block, 0)`` or
       ## ``free(block)``. The block is not initialized, so reading
@@ -1250,14 +1253,14 @@ when not defined(nimrodVM):
       ## The allocated memory belongs to its allocating thread!
       ## Use `createSharedU` to allocate from a shared heap.
       cast[ptr T](alloc(T.sizeof * size))
-    proc alloc0*(size: int): pointer {.noconv, rtl, tags: [].}
+    proc alloc0*(size: int): pointer {.noconv, rtl, tags: [], gcsafe.}
       ## allocates a new memory block with at least ``size`` bytes. The
       ## block has to be freed with ``realloc(block, 0)`` or
       ## ``dealloc(block)``. The block is initialized with all bytes
       ## containing zero, so it is somewhat safer than ``alloc``.
       ## The allocated memory belongs to its allocating thread!
       ## Use `allocShared0` to allocate from a shared heap.
-    proc create*(T: typedesc, size = 1.Positive): ptr T {.inline.} =
+    proc create*(T: typedesc, size = 1.Positive): ptr T {.inline, gcsafe.} =
       ## allocates a new memory block with at least ``T.sizeof * size``
       ## bytes. The block has to be freed with ``resize(block, 0)`` or
       ## ``free(block)``. The block is initialized with all bytes
@@ -1265,7 +1268,8 @@ when not defined(nimrodVM):
       ## The allocated memory belongs to its allocating thread!
       ## Use `createShared` to allocate from a shared heap.
       cast[ptr T](alloc0(T.sizeof * size))
-    proc realloc*(p: pointer, newSize: int): pointer {.noconv, rtl, tags: [].}
+    proc realloc*(p: pointer, newSize: int): pointer {.noconv, rtl, tags: [], 
+                                                       gcsafe.}
       ## grows or shrinks a given memory block. If p is **nil** then a new
       ## memory block is returned. In either way the block has at least
       ## ``newSize`` bytes. If ``newSize == 0`` and p is not **nil**
@@ -1273,7 +1277,7 @@ when not defined(nimrodVM):
       ## be freed with ``dealloc``.
       ## The allocated memory belongs to its allocating thread!
       ## Use `reallocShared` to reallocate from a shared heap.
-    proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline.} =
+    proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline, gcsafe.} =
       ## grows or shrinks a given memory block. If p is **nil** then a new
       ## memory block is returned. In either way the block has at least
       ## ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is not
@@ -1282,7 +1286,7 @@ when not defined(nimrodVM):
       ## its allocating thread!
       ## Use `resizeShared` to reallocate from a shared heap.
       cast[ptr T](realloc(p, T.sizeof * newSize))
-    proc dealloc*(p: pointer) {.noconv, rtl, tags: [].}
+    proc dealloc*(p: pointer) {.noconv, rtl, tags: [], gcsafe.}
       ## frees the memory allocated with ``alloc``, ``alloc0`` or
       ## ``realloc``. This procedure is dangerous! If one forgets to
       ## free the memory a leak occurs; if one tries to access freed
@@ -1290,22 +1294,23 @@ when not defined(nimrodVM):
       ## or other memory may be corrupted. 
       ## The freed memory must belong to its allocating thread!
       ## Use `deallocShared` to deallocate from a shared heap.
-    proc free*[T](p: ptr T) {.inline.} =
+    proc free*[T](p: ptr T) {.inline, gcsafe.} =
       dealloc(p)
-    proc allocShared*(size: int): pointer {.noconv, rtl.}
+    proc allocShared*(size: int): pointer {.noconv, rtl, gcsafe.}
       ## allocates a new memory block on the shared heap with at
       ## least ``size`` bytes. The block has to be freed with
       ## ``reallocShared(block, 0)`` or ``deallocShared(block)``. The block
       ## is not initialized, so reading from it before writing to it is 
       ## undefined behaviour!
-    proc createSharedU*(T: typedesc, size = 1.Positive): ptr T {.inline.} =
+    proc createSharedU*(T: typedesc, size = 1.Positive): ptr T {.inline, 
+                                                                 gcsafe.} =
       ## allocates a new memory block on the shared heap with at
       ## least ``T.sizeof * size`` bytes. The block has to be freed with
       ## ``resizeShared(block, 0)`` or ``freeShared(block)``. The block
       ## is not initialized, so reading from it before writing to it is 
       ## undefined behaviour!
       cast[ptr T](allocShared(T.sizeof * size))
-    proc allocShared0*(size: int): pointer {.noconv, rtl.}
+    proc allocShared0*(size: int): pointer {.noconv, rtl, gcsafe.}
       ## allocates a new memory block on the shared heap with at 
       ## least ``size`` bytes. The block has to be freed with
       ## ``reallocShared(block, 0)`` or ``deallocShared(block)``.
@@ -1318,7 +1323,8 @@ when not defined(nimrodVM):
       ## The block is initialized with all bytes
       ## containing zero, so it is somewhat safer than ``createSharedU``.
       cast[ptr T](allocShared0(T.sizeof * size))
-    proc reallocShared*(p: pointer, newSize: int): pointer {.noconv, rtl.}
+    proc reallocShared*(p: pointer, newSize: int): pointer {.noconv, rtl, 
+                                                             gcsafe.}
       ## grows or shrinks a given memory block on the heap. If p is **nil**
       ## then a new memory block is returned. In either way the block has at
       ## least ``newSize`` bytes. If ``newSize == 0`` and p is not **nil**
@@ -1331,13 +1337,13 @@ when not defined(nimrodVM):
       ## not **nil** ``resizeShared`` calls ``freeShared(p)``. In other
       ## cases the block has to be freed with ``freeShared``.
       cast[ptr T](reallocShared(p, T.sizeof * newSize))
-    proc deallocShared*(p: pointer) {.noconv, rtl.}
+    proc deallocShared*(p: pointer) {.noconv, rtl, gcsafe.}
       ## frees the memory allocated with ``allocShared``, ``allocShared0`` or
       ## ``reallocShared``. This procedure is dangerous! If one forgets to
       ## free the memory a leak occurs; if one tries to access freed
       ## memory (or just freeing it twice!) a core dump may happen
       ## or other memory may be corrupted.
-    proc freeShared*[T](p: ptr T) {.inline.} =
+    proc freeShared*[T](p: ptr T) {.inline, gcsafe.} =
       ## frees the memory allocated with ``createShared``, ``createSharedU`` or
       ## ``resizeShared``. This procedure is dangerous! If one forgets to
       ## free the memory a leak occurs; if one tries to access freed
@@ -1898,7 +1904,7 @@ const nimrodStackTrace = compileOption("stacktrace")
 # of the code
 
 var
-  globalRaiseHook*: proc (e: ref E_Base): bool {.nimcall.}
+  globalRaiseHook*: proc (e: ref E_Base): bool {.nimcall, gcsafe.}
     ## with this hook you can influence exception handling on a global level.
     ## If not nil, every 'raise' statement ends up calling this hook. Ordinary
     ## application code should never set this hook! You better know what you
@@ -1906,7 +1912,7 @@ var
     ## exception is caught and does not propagate further through the call
     ## stack.
 
-  localRaiseHook* {.threadvar.}: proc (e: ref E_Base): bool {.nimcall.}
+  localRaiseHook* {.threadvar.}: proc (e: ref E_Base): bool {.nimcall, gcsafe.}
     ## with this hook you can influence exception handling on a
     ## thread local level.
     ## If not nil, every 'raise' statement ends up calling this hook. Ordinary
@@ -1914,7 +1920,7 @@ var
     ## do when setting this. If ``localRaiseHook`` returns false, the exception
     ## is caught and does not propagate further through the call stack.
     
-  outOfMemHook*: proc () {.nimcall, tags: [].}
+  outOfMemHook*: proc () {.nimcall, tags: [], gcsafe.}
     ## set this variable to provide a procedure that should be called 
     ## in case of an `out of memory`:idx: event. The standard handler
     ## writes an error message and terminates the program. `outOfMemHook` can
@@ -1965,7 +1971,7 @@ elif hostOS != "standalone":
       inc(i)
   {.pop.}
 
-proc echo*[T](x: varargs[T, `$`]) {.magic: "Echo", tags: [FWriteIO].}
+proc echo*[T](x: varargs[T, `$`]) {.magic: "Echo", tags: [FWriteIO], gcsafe.}
   ## special built-in that takes a variable number of arguments. Each argument
   ## is converted to a string via ``$``, so it works for user-defined
   ## types that have an overloaded ``$`` operator.
@@ -2119,14 +2125,15 @@ when not defined(JS): #and not defined(NimrodVM):
         ## `useStdoutAsStdmsg` compile-time switch.
 
     proc open*(f: var TFile, filename: string,
-               mode: TFileMode = fmRead, bufSize: int = -1): bool {.tags: [].}
+               mode: TFileMode = fmRead, bufSize: int = -1): bool {.tags: [],
+               gcsafe.}
       ## Opens a file named `filename` with given `mode`.
       ##
       ## Default mode is readonly. Returns true iff the file could be opened.
       ## This throws no exception if the file could not be opened.
 
     proc open*(f: var TFile, filehandle: TFileHandle,
-               mode: TFileMode = fmRead): bool {.tags: [].}
+               mode: TFileMode = fmRead): bool {.tags: [], gcsafe.}
       ## Creates a ``TFile`` from a `filehandle` with given `mode`.
       ##
       ## Default mode is readonly. Returns true iff the file could be opened.
@@ -2141,7 +2148,7 @@ when not defined(JS): #and not defined(NimrodVM):
         sysFatal(EIO, "cannot open: ", filename)
 
     proc reopen*(f: TFile, filename: string, mode: TFileMode = fmRead): bool {.
-      tags: [].}
+      tags: [], gcsafe.}
       ## reopens the file `f` with given `filename` and `mode`. This 
       ## is often used to redirect the `stdin`, `stdout` or `stderr`
       ## file variables.
@@ -2151,7 +2158,7 @@ when not defined(JS): #and not defined(NimrodVM):
     proc close*(f: TFile) {.importc: "fclose", header: "<stdio.h>", tags: [].}
       ## Closes the file.
 
-    proc endOfFile*(f: TFile): bool {.tags: [].}
+    proc endOfFile*(f: TFile): bool {.tags: [], gcsafe.}
       ## Returns true iff `f` is at the end.
       
     proc readChar*(f: TFile): char {.
@@ -2161,39 +2168,40 @@ when not defined(JS): #and not defined(NimrodVM):
       importc: "fflush", header: "<stdio.h>", tags: [FWriteIO].}
       ## Flushes `f`'s buffer.
 
-    proc readAll*(file: TFile): TaintedString {.tags: [FReadIO].}
+    proc readAll*(file: TFile): TaintedString {.tags: [FReadIO], gcsafe.}
       ## Reads all data from the stream `file`.
       ##
       ## Raises an IO exception in case of an error. It is an error if the
       ## current file position is not at the beginning of the file.
     
-    proc readFile*(filename: string): TaintedString {.tags: [FReadIO].}
+    proc readFile*(filename: string): TaintedString {.tags: [FReadIO], gcsafe.}
       ## Opens a file named `filename` for reading. Then calls `readAll`
       ## and closes the file afterwards. Returns the string. 
       ## Raises an IO exception in case of an error.
 
-    proc writeFile*(filename, content: string) {.tags: [FWriteIO].}
+    proc writeFile*(filename, content: string) {.tags: [FWriteIO], gcsafe.}
       ## Opens a file named `filename` for writing. Then writes the
       ## `content` completely to the file and closes the file afterwards.
       ## Raises an IO exception in case of an error.
 
-    proc write*(f: TFile, r: float32) {.tags: [FWriteIO].}
-    proc write*(f: TFile, i: int) {.tags: [FWriteIO].}
-    proc write*(f: TFile, i: BiggestInt) {.tags: [FWriteIO].}
-    proc write*(f: TFile, r: BiggestFloat) {.tags: [FWriteIO].}
-    proc write*(f: TFile, s: string) {.tags: [FWriteIO].}
-    proc write*(f: TFile, b: bool) {.tags: [FWriteIO].}
-    proc write*(f: TFile, c: char) {.tags: [FWriteIO].}
-    proc write*(f: TFile, c: cstring) {.tags: [FWriteIO].}
-    proc write*(f: TFile, a: varargs[string, `$`]) {.tags: [FWriteIO].}
+    proc write*(f: TFile, r: float32) {.tags: [FWriteIO], gcsafe.}
+    proc write*(f: TFile, i: int) {.tags: [FWriteIO], gcsafe.}
+    proc write*(f: TFile, i: BiggestInt) {.tags: [FWriteIO], gcsafe.}
+    proc write*(f: TFile, r: BiggestFloat) {.tags: [FWriteIO], gcsafe.}
+    proc write*(f: TFile, s: string) {.tags: [FWriteIO], gcsafe.}
+    proc write*(f: TFile, b: bool) {.tags: [FWriteIO], gcsafe.}
+    proc write*(f: TFile, c: char) {.tags: [FWriteIO], gcsafe.}
+    proc write*(f: TFile, c: cstring) {.tags: [FWriteIO], gcsafe.}
+    proc write*(f: TFile, a: varargs[string, `$`]) {.tags: [FWriteIO], gcsafe.}
       ## Writes a value to the file `f`. May throw an IO exception.
 
-    proc readLine*(f: TFile): TaintedString  {.tags: [FReadIO].}
+    proc readLine*(f: TFile): TaintedString  {.tags: [FReadIO], gcsafe.}
       ## reads a line of text from the file `f`. May throw an IO exception.
       ## A line of text may be delimited by ``CR``, ``LF`` or
       ## ``CRLF``. The newline character(s) are not part of the returned string.
     
-    proc readLine*(f: TFile, line: var TaintedString): bool {.tags: [FReadIO].}
+    proc readLine*(f: TFile, line: var TaintedString): bool {.tags: [FReadIO], 
+                  gcsafe.}
       ## reads a line of text from the file `f` into `line`. `line` must not be
       ## ``nil``! May throw an IO exception.
       ## A line of text may be delimited by ``CR``, ``LF`` or
@@ -2201,53 +2209,55 @@ when not defined(JS): #and not defined(NimrodVM):
       ## Returns ``false`` if the end of the file has been reached, ``true``
       ## otherwise. If ``false`` is returned `line` contains no new data.
 
-    proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline, tags: [FWriteIO].}
+    proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline, 
+                             tags: [FWriteIO], gcsafe.}
       ## writes the values `x` to `f` and then writes "\n".
       ## May throw an IO exception.
 
-    proc getFileSize*(f: TFile): int64 {.tags: [FReadIO].}
+    proc getFileSize*(f: TFile): int64 {.tags: [FReadIO], gcsafe.}
       ## retrieves the file size (in bytes) of `f`.
 
     proc readBytes*(f: TFile, a: var openArray[int8], start, len: int): int {.
-      tags: [FReadIO].}
+      tags: [FReadIO], gcsafe.}
       ## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns
       ## the actual number of bytes that have been read which may be less than
       ## `len` (if not as many bytes are remaining), but not greater.
 
     proc readChars*(f: TFile, a: var openArray[char], start, len: int): int {.
-      tags: [FReadIO].}
+      tags: [FReadIO], gcsafe.}
       ## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns
       ## the actual number of bytes that have been read which may be less than
       ## `len` (if not as many bytes are remaining), but not greater.
 
-    proc readBuffer*(f: TFile, buffer: pointer, len: int): int {.tags: [FReadIO].}
+    proc readBuffer*(f: TFile, buffer: pointer, len: int): int {.
+      tags: [FReadIO], gcsafe.}
       ## reads `len` bytes into the buffer pointed to by `buffer`. Returns
       ## the actual number of bytes that have been read which may be less than
       ## `len` (if not as many bytes are remaining), but not greater.
 
     proc writeBytes*(f: TFile, a: openArray[int8], start, len: int): int {.
-      tags: [FWriteIO].}
+      tags: [FWriteIO], gcsafe.}
       ## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns
       ## the number of actual written bytes, which may be less than `len` in case
       ## of an error.
 
     proc writeChars*(f: TFile, a: openArray[char], start, len: int): int {.
-      tags: [FWriteIO].}
+      tags: [FWriteIO], gcsafe.}
       ## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns
       ## the number of actual written bytes, which may be less than `len` in case
       ## of an error.
 
     proc writeBuffer*(f: TFile, buffer: pointer, len: int): int {.
-      tags: [FWriteIO].}
+      tags: [FWriteIO], gcsafe.}
       ## writes the bytes of buffer pointed to by the parameter `buffer` to the
       ## file `f`. Returns the number of actual written bytes, which may be less
       ## than `len` in case of an error.
 
-    proc setFilePos*(f: TFile, pos: int64)
+    proc setFilePos*(f: TFile, pos: int64) {.gcsafe.}
       ## sets the position of the file pointer that is used for read/write
       ## operations. The file's first byte has the index zero.
 
-    proc getFilePos*(f: TFile): int64
+    proc getFilePos*(f: TFile): int64 {.gcsafe.}
       ## retrieves the current position of the file pointer that is used to
       ## read from the file `f`. The file's first byte has the index zero.
 
@@ -2290,10 +2300,12 @@ when not defined(JS): #and not defined(NimrodVM):
       dealloc(a)
 
   when not defined(NimrodVM):
-    proc atomicInc*(memLoc: var int, x: int = 1): int {.inline, discardable.}
+    proc atomicInc*(memLoc: var int, x: int = 1): int {.inline, 
+      discardable, gcsafe.}
       ## atomic increment of `memLoc`. Returns the value after the operation.
     
-    proc atomicDec*(memLoc: var int, x: int = 1): int {.inline, discardable.}
+    proc atomicDec*(memLoc: var int, x: int = 1): int {.inline, 
+      discardable, gcsafe.}
       ## atomic decrement of `memLoc`. Returns the value after the operation.
 
     include "system/atomics"
diff --git a/lib/system/avltree.nim b/lib/system/avltree.nim
index fc965d6aa..bced15d6a 100644
--- a/lib/system/avltree.nim
+++ b/lib/system/avltree.nim
@@ -51,7 +51,7 @@ proc split(t: var PAvlNode) =
     t.link[0] = temp
     inc t.level
 
-proc add(a: var TMemRegion, t: var PAvlNode, key, upperBound: int) =
+proc add(a: var TMemRegion, t: var PAvlNode, key, upperBound: int) {.gcsafe.} =
   if t == bottom:
     t = allocAvlNode(a, key, upperBound)
   else:
@@ -64,7 +64,7 @@ proc add(a: var TMemRegion, t: var PAvlNode, key, upperBound: int) =
     skew(t)
     split(t)
 
-proc del(a: var TMemRegion, t: var PAvlNode, x: int) =
+proc del(a: var TMemRegion, t: var PAvlNode, x: int) {.gcsafe.} =
   if t == bottom: return
   a.last = t
   if x <% t.key:
diff --git a/lib/system/cgprocs.nim b/lib/system/cgprocs.nim
index e30cfa469..d483c61bd 100644
--- a/lib/system/cgprocs.nim
+++ b/lib/system/cgprocs.nim
@@ -9,7 +9,7 @@
 
 # Headers for procs that the code generator depends on ("compilerprocs")
 
-proc addChar(s: NimString, c: char): NimString {.compilerProc.}
+proc addChar(s: NimString, c: char): NimString {.compilerProc, gcsafe.}
 
 type
   TLibHandle = pointer       # private type
@@ -21,5 +21,5 @@ proc nimGetProcAddr(lib: TLibHandle, name: cstring): TProcAddr {.compilerproc.}
 
 proc nimLoadLibraryError(path: string) {.compilerproc, noinline.}
 
-proc setStackBottom(theStackBottom: pointer) {.compilerRtl, noinline.}
+proc setStackBottom(theStackBottom: pointer) {.compilerRtl, noinline, gcsafe.}
 
diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim
index e50ba7b9f..612a9e729 100644
--- a/lib/system/excpt.nim
+++ b/lib/system/excpt.nim
@@ -11,7 +11,7 @@
 # use the heap (and nor exceptions) do not include the GC or memory allocator.
 
 var
-  errorMessageWriter*: (proc(msg: string) {.tags: [FWriteIO].})
+  errorMessageWriter*: (proc(msg: string) {.tags: [FWriteIO], gcsafe.})
     ## Function that will be called
     ## instead of stdmsg.write when printing stacktrace.
     ## Unstable API.
@@ -32,10 +32,10 @@ proc showErrorMessage(data: cstring) =
   else:
     writeToStdErr(data)
 
-proc chckIndx(i, a, b: int): int {.inline, compilerproc.}
-proc chckRange(i, a, b: int): int {.inline, compilerproc.}
-proc chckRangeF(x, a, b: float): float {.inline, compilerproc.}
-proc chckNil(p: pointer) {.noinline, compilerproc.}
+proc chckIndx(i, a, b: int): int {.inline, compilerproc, gcsafe.}
+proc chckRange(i, a, b: int): int {.inline, compilerproc, gcsafe.}
+proc chckRangeF(x, a, b: float): float {.inline, compilerproc, gcsafe.}
+proc chckNil(p: pointer) {.noinline, compilerproc, gcsafe.}
 
 var
   framePtr {.rtlThreadVar.}: PFrame
@@ -322,5 +322,5 @@ when not defined(noSignalHandler):
 
 proc setControlCHook(hook: proc () {.noconv.}) =
   # ugly cast, but should work on all architectures:
-  type TSignalHandler = proc (sig: cint) {.noconv.}
+  type TSignalHandler = proc (sig: cint) {.noconv, gcsafe.}
   c_signal(SIGINT, cast[TSignalHandler](hook))
diff --git a/lib/system/gc.nim b/lib/system/gc.nim
index ec1760914..3b85fe600 100644
--- a/lib/system/gc.nim
+++ b/lib/system/gc.nim
@@ -51,7 +51,7 @@ type
     waZctDecRef, waPush, waCycleDecRef, waMarkGray, waScan, waScanBlack, 
     waCollectWhite,
 
-  TFinalizer {.compilerproc.} = proc (self: pointer) {.nimcall.}
+  TFinalizer {.compilerproc.} = proc (self: pointer) {.nimcall, gcsafe.}
     # A ref type can have a finalizer that is called before the object's
     # storage is freed.
 
@@ -152,11 +152,11 @@ template gcTrace(cell, state: expr): stmt {.immediate.} =
   when traceGC: traceCell(cell, state)
 
 # forward declarations:
-proc collectCT(gch: var TGcHeap)
-proc isOnStack*(p: pointer): bool {.noinline.}
-proc forAllChildren(cell: PCell, op: TWalkOp)
-proc doOperation(p: pointer, op: TWalkOp)
-proc forAllChildrenAux(dest: pointer, mt: PNimType, op: TWalkOp)
+proc collectCT(gch: var TGcHeap) {.gcsafe.}
+proc isOnStack*(p: pointer): bool {.noinline, gcsafe.}
+proc forAllChildren(cell: PCell, op: TWalkOp) {.gcsafe.}
+proc doOperation(p: pointer, op: TWalkOp) {.gcsafe.}
+proc forAllChildrenAux(dest: pointer, mt: PNimType, op: TWalkOp) {.gcsafe.}
 # we need the prototype here for debugging purposes
 
 when hasThreadSupport and hasSharedHeap:
@@ -294,7 +294,7 @@ proc initGC() =
 
 when useMarkForDebug or useBackupGc:
   type
-    TGlobalMarkerProc = proc () {.nimcall.}
+    TGlobalMarkerProc = proc () {.nimcall, gcsafe.}
   var
     globalMarkersLen: int
     globalMarkers: array[0.. 7_000, TGlobalMarkerProc]
@@ -311,7 +311,7 @@ proc cellsetReset(s: var TCellSet) =
   deinit(s)
   init(s)
 
-proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) =
+proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) {.gcsafe.} =
   var d = cast[TAddress](dest)
   case n.kind
   of nkSlot: forAllChildrenAux(cast[pointer](d +% n.offset), n.typ, op)
@@ -680,10 +680,11 @@ proc doOperation(p: pointer, op: TWalkOp) =
 proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} =
   doOperation(d, TWalkOp(op))
 
-proc collectZCT(gch: var TGcHeap): bool
+proc collectZCT(gch: var TGcHeap): bool {.gcsafe.}
 
 when useMarkForDebug or useBackupGc:
-  proc markStackAndRegistersForSweep(gch: var TGcHeap) {.noinline, cdecl.}
+  proc markStackAndRegistersForSweep(gch: var TGcHeap) {.noinline, cdecl,
+                                                         gcsafe.}
 
 proc collectRoots(gch: var TGcHeap) =
   for s in elements(gch.cycleRoots):
diff --git a/lib/system/hti.nim b/lib/system/hti.nim
index 9d8ece7df..64174e60f 100644
--- a/lib/system/hti.nim
+++ b/lib/system/hti.nim
@@ -85,7 +85,7 @@ type
     base: ptr TNimType
     node: ptr TNimNode # valid for tyRecord, tyObject, tyTuple, tyEnum
     finalizer: pointer # the finalizer for the type
-    marker: proc (p: pointer, op: int) {.nimcall.} # marker proc for GC
+    marker: proc (p: pointer, op: int) {.nimcall, gcsafe.} # marker proc for GC
   PNimType = ptr TNimType
   
 # node.len may be the ``first`` element of a set
diff --git a/lib/system/syslocks.nim b/lib/system/syslocks.nim
index 5e3b04b7f..b8ed29cfc 100644
--- a/lib/system/syslocks.nim
+++ b/lib/system/syslocks.nim
@@ -52,7 +52,7 @@ when defined(Windows):
   proc closeHandle(hObject: THandle) {.stdcall, noSideEffect,
     dynlib: "kernel32", importc: "CloseHandle".}
   proc waitForSingleObject(hHandle: THandle, dwMilliseconds: int32): int32 {.
-    stdcall, dynlib: "kernel32", importc: "WaitForSingleObject".}
+    stdcall, dynlib: "kernel32", importc: "WaitForSingleObject", noSideEffect.}
 
   proc signalSysCond(hEvent: TSysCond) {.stdcall, noSideEffect,
     dynlib: "kernel32", importc: "SetEvent".}
@@ -89,16 +89,16 @@ else:
 
   proc releaseSys(L: var TSysLock) {.noSideEffect,
     importc: "pthread_mutex_unlock", header: "<pthread.h>".}
-  proc deinitSys(L: var TSysLock) {.
+  proc deinitSys(L: var TSysLock) {.noSideEffect,
     importc: "pthread_mutex_destroy", header: "<pthread.h>".}
 
   proc initSysCond(cond: var TSysCond, cond_attr: pointer = nil) {.
-    importc: "pthread_cond_init", header: "<pthread.h>".}
+    importc: "pthread_cond_init", header: "<pthread.h>", noSideEffect.}
   proc waitSysCond(cond: var TSysCond, lock: var TSysLock) {.
-    importc: "pthread_cond_wait", header: "<pthread.h>".}
+    importc: "pthread_cond_wait", header: "<pthread.h>", noSideEffect.}
   proc signalSysCond(cond: var TSysCond) {.
-    importc: "pthread_cond_signal", header: "<pthread.h>".}
+    importc: "pthread_cond_signal", header: "<pthread.h>", noSideEffect.}
   
-  proc deinitSysCond(cond: var TSysCond) {.
+  proc deinitSysCond(cond: var TSysCond) {.noSideEffect,
     importc: "pthread_cond_destroy", header: "<pthread.h>".}
   
diff --git a/lib/system/sysspawn.nim b/lib/system/sysspawn.nim
new file mode 100644
index 000000000..3a641aba6
--- /dev/null
+++ b/lib/system/sysspawn.nim
@@ -0,0 +1,172 @@
+# Implements Nimrod's 'spawn'.
+
+{.push stackTrace:off.}
+include system.syslocks
+
+when (defined(x86) or defined(amd64)) and defined(gcc):
+  proc cpuRelax {.inline.} =
+    {.emit: """asm volatile("pause" ::: "memory");""".}
+elif (defined(x86) or defined(amd64)) and defined(vcc):
+  proc cpuRelax {.importc: "YieldProcessor", header: "<windows.h>".}
+elif defined(intelc):
+  proc cpuRelax {.importc: "_mm_pause", header: "xmmintrin.h".}
+else:
+  from os import sleep
+
+  proc cpuRelax {.inline.} = os.sleep(1)
+
+when defined(windows):
+  proc interlockedCompareExchange(p: pointer; exchange, comparand: int32): int32
+    {.importc: "InterlockedCompareExchange", header: "<windows.h>", cdecl.}
+
+  proc cas(p: ptr bool; oldValue, newValue: bool): bool =
+    interlockedCompareExchange(p, newValue.int32, oldValue.int32) != 0
+
+else:
+  # this is valid for GCC and Intel C++
+  proc cas(p: ptr bool; oldValue, newValue: bool): bool
+    {.importc: "__sync_bool_compare_and_swap", nodecl.}
+
+# We declare our own condition variables here to get rid of the dummy lock
+# on Windows:
+
+type
+  CondVar = object
+    c: TSysCond
+    when defined(posix):
+      stupidLock: TSysLock
+
+proc createCondVar(): CondVar =
+  initSysCond(result.c)
+  when defined(posix):
+    initSysLock(result.stupidLock)
+    acquireSys(result.stupidLock)
+
+proc await(cv: var CondVar) =
+  when defined(posix):
+    waitSysCond(cv.c, cv.stupidLock)
+  else:
+    waitSysCondWindows(cv.c)
+
+proc signal(cv: var CondVar) = signalSysCond(cv.c)
+
+type
+  FastCondVar = object
+    event, slowPath: bool
+    slow: CondVar
+
+proc createFastCondVar(): FastCondVar =
+  initSysCond(result.slow.c)
+  when defined(posix):
+    initSysLock(result.slow.stupidLock)
+    acquireSys(result.slow.stupidLock)
+  result.event = false
+  result.slowPath = false
+
+proc await(cv: var FastCondVar) =
+  #for i in 0 .. 50:
+  #  if cas(addr cv.event, true, false):
+  #    # this is a HIT: Triggers > 95% in my tests.
+  #    return
+  #  cpuRelax()
+  #cv.slowPath = true
+  await(cv.slow)
+  cv.event = false
+
+proc signal(cv: var FastCondVar) =
+  cv.event = true
+  #if cas(addr cv.slowPath, true, false):
+  signal(cv.slow)
+
+{.pop.}
+
+# ----------------------------------------------------------------------------
+
+type
+  WorkerProc = proc (thread, args: pointer) {.nimcall, gcsafe.}
+  Worker = object
+    taskArrived: CondVar
+    taskStarted: FastCondVar #\
+    # task data:
+    f: WorkerProc
+    data: pointer
+    ready: bool # put it here for correct alignment!
+
+proc nimArgsPassingDone(p: pointer) {.compilerProc.} =
+  let w = cast[ptr Worker](p)
+  signal(w.taskStarted)
+
+var gSomeReady = createFastCondVar()
+
+proc slave(w: ptr Worker) {.thread.} =
+  while true:
+    w.ready = true # If we instead signal "workerReady" we need the scheduler
+                   # to notice this. The scheduler could then optimize the
+                   # layout of the worker threads (e.g. keep the list sorted)
+                   # so that no search for a "ready" thread is necessary.
+                   # This might be implemented later, but is more tricky than
+                   # it looks because 'spawn' itself can run concurrently.
+    signal(gSomeReady)
+    await(w.taskArrived)
+    assert(not w.ready)
+    if w.data != nil:
+      w.f(w, w.data)
+      w.data = nil
+
+const NumThreads = 4
+
+var
+  workers: array[NumThreads, TThread[ptr Worker]]
+  workersData: array[NumThreads, Worker]
+
+proc setup() =
+  for i in 0.. <NumThreads:
+    workersData[i].taskArrived = createCondVar()
+    workersData[i].taskStarted = createFastCondVar()
+    createThread(workers[i], slave, addr(workersData[i]))
+
+proc preferSpawn*(): bool =
+  ## Use this proc to determine quickly if a 'spawn' or a direct call is
+  ## preferable. If it returns 'true' a 'spawn' may make sense. In general
+  ## it is not necessary to call this directly; use 'spawnX' instead.
+  result = gSomeReady.event
+
+proc spawn*(call: stmt) {.magic: "Spawn".}
+  ## always spawns a new task, so that the 'call' is never executed on
+  ## the calling thread. 'call' has to be proc call 'p(...)' where 'p'
+  ## is gcsafe and has 'void' as the return type.
+
+template spawnX*(call: stmt) =
+  ## spawns a new task if a CPU core is ready, otherwise executes the
+  ## call in the calling thread. Usually it is advised to
+  ## use 'spawn' in order to not block the producer for an unknown
+  ## amount of time. 'call' has to be proc call 'p(...)' where 'p'
+  ## is gcsafe and has 'void' as the return type.
+  if preferSpawn(): spawn call
+  else: call
+
+proc nimSpawn(fn: WorkerProc; data: pointer) {.compilerProc.} =
+  # implementation of 'spawn' that is used by the code generator.
+  while true:
+    for i in 0.. high(workers):
+      let w = addr(workersData[i])
+      if cas(addr w.ready, true, false):
+        w.data = data
+        w.f = fn
+        signal(w.taskArrived)
+        await(w.taskStarted)
+        return
+    await(gSomeReady)
+
+proc sync*() =
+  ## a simple barrier to wait for all spawn'ed tasks. If you need more elaborate
+  ## waiting, you have to use an explicit barrier.
+  while true:
+    var allReady = true
+    for i in 0 .. high(workers):
+      if not allReady: break
+      allReady = allReady and workersData[i].ready
+    if allReady: break
+    await(gSomeReady)
+
+setup()
diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim
index 69a3c5c81..7024943b3 100644
--- a/lib/windows/winlean.nim
+++ b/lib/windows/winlean.nim
@@ -522,7 +522,7 @@ proc inet_addr*(cp: cstring): int32 {.
   stdcall, importc: "inet_addr", dynlib: ws2dll.} 
 
 proc WSAFDIsSet(s: TSocketHandle, FDSet: var TFdSet): bool {.
-  stdcall, importc: "__WSAFDIsSet", dynlib: ws2dll.}
+  stdcall, importc: "__WSAFDIsSet", dynlib: ws2dll, noSideEffect.}
 
 proc FD_ISSET*(Socket: TSocketHandle, FDSet: var TFdSet): cint = 
   result = if WSAFDIsSet(Socket, FDSet): 1'i32 else: 0'i32