summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2016-02-17 14:52:02 +0100
committerAndreas Rumpf <rumpf_a@web.de>2016-02-17 14:52:02 +0100
commit8ec5c01cae61a727159ddfd0ea7c781d10be9f83 (patch)
tree4dd7c6ea7878c9d4af919d7fada4f0fda09f0b82
parent0fa2ed30e0ee5bbe1442ceae01759786a82e85f9 (diff)
downloadNim-8ec5c01cae61a727159ddfd0ea7c781d10be9f83.tar.gz
further progress on --gc:v2
-rw-r--r--lib/system.nim75
-rw-r--r--lib/system/alloc.nim8
-rw-r--r--lib/system/ansi_c.nim18
-rw-r--r--lib/system/gc2.nim190
-rw-r--r--lib/system/mmdisp.nim2
-rw-r--r--lib/system/sysio.nim110
-rw-r--r--tools/heapdump2dot.nim66
7 files changed, 321 insertions, 148 deletions
diff --git a/lib/system.nim b/lib/system.nim
index 2c049b6b6..152dd54ea 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -2589,35 +2589,6 @@ when not defined(JS): #and not defined(nimscript):
     var
       strDesc = TNimType(size: sizeof(string), kind: tyString, flags: {ntfAcyclic})
 
-  when not defined(nimscript):
-    include "system/ansi_c"
-
-    proc cmp(x, y: string): int =
-      result = int(c_strcmp(x, y))
-  else:
-    proc cmp(x, y: string): int =
-      if x < y: result = -1
-      elif x > y: result = 1
-      else: result = 0
-
-  const pccHack = if defined(pcc): "_" else: "" # Hack for PCC
-  when not defined(nimscript):
-    when defined(windows):
-      # work-around C's sucking abstraction:
-      # BUGFIX: stdin and stdout should be binary files!
-      proc setmode(handle, mode: int) {.importc: pccHack & "setmode",
-                                        header: "<io.h>".}
-      proc fileno(f: C_TextFileStar): int {.importc: pccHack & "fileno",
-                                            header: "<fcntl.h>".}
-      var
-        O_BINARY {.importc: pccHack & "O_BINARY", nodecl.}: int
-
-      # we use binary mode in Windows:
-      setmode(fileno(c_stdin), O_BINARY)
-      setmode(fileno(c_stdout), O_BINARY)
-
-    when defined(endb):
-      proc endbStep()
 
   # ----------------- IO Part ------------------------------------------------
   when hostOS != "standalone":
@@ -2643,15 +2614,43 @@ when not defined(JS): #and not defined(nimscript):
 
     {.deprecated: [TFile: File, TFileHandle: FileHandle, TFileMode: FileMode].}
 
-    when not defined(nimscript):
-      # text file handling:
+  when not defined(nimscript):
+    include "system/ansi_c"
+
+    proc cmp(x, y: string): int =
+      result = int(c_strcmp(x, y))
+  else:
+    proc cmp(x, y: string): int =
+      if x < y: result = -1
+      elif x > y: result = 1
+      else: result = 0
+
+  when not defined(nimscript):
+    when defined(windows):
+      # work-around C's sucking abstraction:
+      # BUGFIX: stdin and stdout should be binary files!
+      proc setmode(handle, mode: int) {.importc: "setmode",
+                                        header: "<io.h>".}
+      proc fileno(f: C_TextFileStar): int {.importc: "fileno",
+                                            header: "<fcntl.h>".}
       var
-        stdin* {.importc: "stdin", header: "<stdio.h>".}: File
-          ## The standard input stream.
-        stdout* {.importc: "stdout", header: "<stdio.h>".}: File
-          ## The standard output stream.
-        stderr* {.importc: "stderr", header: "<stdio.h>".}: File
-          ## The standard error stream.
+        O_BINARY {.importc: "O_BINARY", nodecl.}: int
+
+      # we use binary mode on Windows:
+      setmode(fileno(c_stdin), O_BINARY)
+      setmode(fileno(c_stdout), O_BINARY)
+
+    when defined(endb):
+      proc endbStep()
+
+    # text file handling:
+    var
+      stdin* {.importc: "stdin", header: "<stdio.h>".}: File
+        ## The standard input stream.
+      stdout* {.importc: "stdout", header: "<stdio.h>".}: File
+        ## The standard output stream.
+      stderr* {.importc: "stderr", header: "<stdio.h>".}: File
+        ## The standard error stream.
 
     when defined(useStdoutAsStdmsg):
       template stdmsg*: File = stdout
@@ -2947,7 +2946,7 @@ when not defined(JS): #and not defined(nimscript):
   else:
     include "system/sysio"
 
-  when declared(open) and declared(close) and declared(readline):
+  when not defined(nimscript):
     iterator lines*(filename: string): TaintedString {.tags: [ReadIOEffect].} =
       ## Iterates over any line in the file named `filename`.
       ##
diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim
index 6de8e19e7..67d380391 100644
--- a/lib/system/alloc.nim
+++ b/lib/system/alloc.nim
@@ -592,16 +592,16 @@ proc allocInv(a: MemRegion): bool =
   ## checks some (not all yet) invariants of the allocator's data structures.
   for s in low(a.freeSmallChunks)..high(a.freeSmallChunks):
     var c = a.freeSmallChunks[s]
-    while c != nil:
+    while not (c == nil):
       if c.next == c:
         echo "[SYSASSERT] c.next == c"
         return false
-      if c.size != s * MemAlign:
+      if not (c.size == s * MemAlign):
         echo "[SYSASSERT] c.size != s * MemAlign"
         return false
       var it = c.freeList
-      while it != nil:
-        if it.zeroField != 0:
+      while not (it == nil):
+        if not (it.zeroField == 0):
           echo "[SYSASSERT] it.zeroField != 0"
           c_printf("%ld %p\n", it.zeroField, it)
           return false
diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim
index 702559034..1bbd89fe7 100644
--- a/lib/system/ansi_c.nim
+++ b/lib/system/ansi_c.nim
@@ -23,14 +23,20 @@ proc c_strlen(a: cstring): int {.header: "<string.h>",
 proc c_memset(p: pointer, value: cint, size: int) {.
   header: "<string.h>", importc: "memset".}
 
-type
-  C_TextFile {.importc: "FILE", header: "<stdio.h>",
-               final, incompleteStruct.} = object
-  C_BinaryFile {.importc: "FILE", header: "<stdio.h>",
+when not declared(File):
+  type
+    C_TextFile {.importc: "FILE", header: "<stdio.h>",
                  final, incompleteStruct.} = object
-  C_TextFileStar = ptr C_TextFile
-  C_BinaryFileStar = ptr C_BinaryFile
+    C_BinaryFile {.importc: "FILE", header: "<stdio.h>",
+                   final, incompleteStruct.} = object
+    C_TextFileStar = ptr C_TextFile
+    C_BinaryFileStar = ptr C_BinaryFile
+else:
+  type
+    C_TextFileStar = File
+    C_BinaryFileStar = File
 
+type
   C_JmpBuf {.importc: "jmp_buf", header: "<setjmp.h>".} = object
 
 when not defined(vm):
diff --git a/lib/system/gc2.nim b/lib/system/gc2.nim
index 6b6b81824..6c44d509e 100644
--- a/lib/system/gc2.nim
+++ b/lib/system/gc2.nim
@@ -52,7 +52,8 @@ type
   WalkOp = enum
     waMarkGlobal,    # part of the backup mark&sweep
     waMarkGrey,
-    waZctDecRef #, waDebug
+    waZctDecRef,
+    waDebug
 
   Phase {.pure.} = enum
     None, Marking, Sweeping
@@ -78,7 +79,7 @@ type
 
   GcHeap = object # this contains the zero count and
                   # non-zero count table
-    black: int    # either 0 or 1.
+    black, red: int # either 0 or 1.
     stack: ptr GcStack
     stackBottom: pointer
     phase: Phase
@@ -95,6 +96,7 @@ type
     stat: GcStat
     additionalRoots: CellSeq # dummy roots for GC_ref/unref
     spaceIter: ObjectSpaceIter
+    dumpHeapFile: File # File that is used for GC_dumpHeap
 
 var
   gch {.rtlThreadVar.}: GcHeap
@@ -104,6 +106,7 @@ when not defined(useNimRtl):
 
 proc initGC() =
   when not defined(useNimRtl):
+    gch.red = (1-gch.black)
     gch.cycleThreshold = InitialCycleThreshold
     gch.stat.stackScans = 0
     gch.stat.cycleCollections = 0
@@ -117,6 +120,14 @@ proc initGC() =
     init(gch.additionalRoots)
     init(gch.greyStack)
 
+# Which color to use for new objects is tricky: When we're marking,
+# they have to be *white* so that everything is marked that is only
+# reachable from them. However, when we are sweeping, they have to
+# be black, so that we don't free them prematuredly. In order to save
+# a comparison gch.phase == Phase.Marking, we use the pseudo-color
+# 'red' for new objects.
+template allocColor(): untyped = gch.red
+
 template gcAssert(cond: bool, msg: string) =
   when defined(useGcAssert):
     if not cond:
@@ -156,15 +167,25 @@ template color(c): expr = c.refCount and colorMask
 template setColor(c, col) =
   c.refcount = c.refcount and not colorMask or col
 
-proc writeCell(msg: cstring, c: PCell) =
+proc writeCell(file: File; msg: cstring, c: PCell) =
   var kind = -1
   if c.typ != nil: kind = ord(c.typ.kind)
+  let col = if c.color == rcGrey: 'g'
+            elif c.color == gch.black: 'b'
+            else: 'w'
+  when useCellIds:
+    let id = c.id
+  else:
+    let id = c
   when leakDetector:
-    c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld from %s(%ld)\n",
-              msg, c, kind, c.refcount shr rcShift, c.filename, c.line)
+    c_fprintf(file, "%s %p %d rc=%ld color=%c from %s(%ld)\n",
+              msg, id, kind, c.refcount shr rcShift, col, c.filename, c.line)
   else:
-    c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld; color=%ld\n",
-              msg, c, kind, c.refcount shr rcShift, c.color)
+    c_fprintf(file, "%s %p %d rc=%ld color=%c\n",
+              msg, id, kind, c.refcount shr rcShift, col)
+
+proc writeCell(msg: cstring, c: PCell) =
+  c_stdout.writeCell(msg, c)
 
 proc myastToStr[T](x: T): string {.magic: "AstToStr", noSideEffect.}
 
@@ -236,7 +257,11 @@ proc nimGCunref(p: pointer) {.compilerProc.} =
     dec(i)
 
 template markGrey(x: PCell) =
-  if x.color == 1-gch.black and gch.phase == Phase.Marking:
+  if x.color != 1-gch.black and gch.phase == Phase.Marking:
+    if not isAllocatedPtr(gch.region, x):
+      c_fprintf(c_stdout, "[GC] markGrey proc: %p\n", x)
+      #GC_dumpHeap()
+      sysAssert(false, "wtf")
     x.setColor(rcGrey)
     add(gch.greyStack, x)
 
@@ -425,7 +450,7 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer =
       res.filename = framePtr.prev.filename
       res.line = framePtr.prev.line
   # refcount is zero, color is black, but mark it to be in the ZCT
-  res.refcount = ZctFlag or gch.black
+  res.refcount = ZctFlag or allocColor()
   sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3")
   # its refcount is zero, so add it to the ZCT:
   addNewObjToZCT(res, gch)
@@ -468,11 +493,11 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} =
   sysAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "newObj: 2")
   # now it is buffered in the ZCT
   res.typ = typ
-  when leakDetector and not hasThreadSupport:
+  when leakDetector:
     if framePtr != nil and framePtr.prev != nil:
       res.filename = framePtr.prev.filename
       res.line = framePtr.prev.line
-  res.refcount = rcIncrement or gch.black # refcount is 1
+  res.refcount = rcIncrement or allocColor() # refcount is 1
   sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3")
   when logGC: writeCell("new cell", res)
   gcTrace(res, csAllocated)
@@ -536,7 +561,7 @@ proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer =
       # A better fix would be to emit the location specific write barrier for
       # 'growObj', but this is lots of more work and who knows what new problems
       # this would create.
-      res.refcount = rcIncrement or gch.black
+      res.refcount = rcIncrement or allocColor()
       decRef(ol)
   else:
     sysAssert(ol.typ != nil, "growObj: 5")
@@ -581,6 +606,49 @@ template checkTime {.dirty.} =
         if duration >= gch.maxPause - 50_000:
           return false
 
+# ---------------- dump heap ----------------
+
+proc debugGraph(s: PCell) =
+  c_fprintf(gch.dumpHeapFile, "child %p\n", s)
+
+proc dumpRoot(gch: var GcHeap; s: PCell) =
+  if isAllocatedPtr(gch.region, s):
+    c_fprintf(gch.dumpHeapFile, "global_root %p\n", s)
+  else:
+    c_fprintf(gch.dumpHeapFile, "global_root_invalid %p\n", s)
+
+proc GC_dumpHeap*(file: File) =
+  ## Dumps the GCed heap's content to a file. Can be useful for
+  ## debugging. Produces an undocumented text file format that
+  ## can be translated into "dot" syntax via the "heapdump2dot" tool.
+  gch.dumpHeapFile = file
+  var spaceIter: ObjectSpaceIter
+  var d = gch.decStack.d
+  for i in 0 .. < gch.decStack.len:
+    if isAllocatedPtr(gch.region, d[i]):
+      c_fprintf(file, "onstack %p\n", d[i])
+    else:
+      c_fprintf(file, "onstack_invalid %p\n", d[i])
+  for i in 0 .. < globalMarkersLen: globalMarkers[i]()
+  while true:
+    let x = allObjectsAsProc(gch.region, addr spaceIter)
+    if spaceIter.state < 0: break
+    if isCell(x):
+      # cast to PCell is correct here:
+      var c = cast[PCell](x)
+      writeCell(file, "cell ", c)
+      forAllChildren(c, waDebug)
+      c_fprintf(file, "end\n")
+  gch.dumpHeapFile = nil
+
+proc GC_dumpHeap() =
+  var f: File
+  if open(f, "heap.txt", fmWrite):
+    GC_dumpHeap(f)
+    f.close()
+  else:
+    c_fprintf(stdout, "cannot write heap.txt")
+
 # ---------------- cycle collector -------------------------------------------
 
 proc freeCyclicCell(gch: var GcHeap, c: PCell) =
@@ -588,6 +656,9 @@ proc freeCyclicCell(gch: var GcHeap, c: PCell) =
 
   var d = gch.decStack.d
   for i in 0..gch.decStack.len-1:
+    if d[i] == c:
+      writeCell("freeing ", c)
+      GC_dumpHeap()
     gcAssert d[i] != c, "wtf man, freeing obviously alive stuff?!!"
 
   prepareDealloc(c)
@@ -603,8 +674,8 @@ proc freeCyclicCell(gch: var GcHeap, c: PCell) =
 proc sweep(gch: var GcHeap): bool =
   takeStartTime(100)
   #echo "loop start"
-  let black = gch.black
-  cfprintf(cstdout, "black is %d\n", black)
+  let white = 1-gch.black
+  #cfprintf(cstdout, "black is %d\n", black)
   while true:
     let x = allObjectsAsProc(gch.region, addr gch.spaceIter)
     if gch.spaceIter.state < 0: break
@@ -613,7 +684,7 @@ proc sweep(gch: var GcHeap): bool =
       # cast to PCell is correct here:
       var c = cast[PCell](x)
       gcAssert c.color != rcGrey, "cell is still grey?"
-      if c.color != black: freeCyclicCell(gch, c)
+      if c.color == white: freeCyclicCell(gch, c)
       # Since this is incremental, we MUST not set the object to 'white' here.
       # We could set all the remaining objects to white after the 'sweep'
       # completed but instead we flip the meaning of black/white to save one
@@ -624,11 +695,19 @@ proc sweep(gch: var GcHeap): bool =
   gch.spaceIter = ObjectSpaceIter()
   result = true
 
-proc markRoot(gch: var GcHeap, c: PCell) =
-  # since we start with 'black' cells, we need to mark them here too:
-  if c.color != rcGrey:
+proc markRoot(gch: var GcHeap, c: PCell) {.inline.} =
+  if c.color == 1-gch.black:
     c.setColor(rcGrey)
     add(gch.greyStack, c)
+  elif c.color == rcGrey:
+    var isGrey = false
+    var d = gch.decStack.d
+    for i in 0..gch.decStack.len-1:
+      if d[i] == c:
+        isGrey = true
+        break
+    if not isGrey:
+      gcAssert false, "markRoot: root is already grey?!"
 
 proc markIncremental(gch: var GcHeap): bool =
   var L = addr(gch.greyStack.len)
@@ -637,16 +716,30 @@ proc markIncremental(gch: var GcHeap): bool =
     var c = gch.greyStack.d[0]
     if not isAllocatedPtr(gch.region, c):
       c_fprintf(c_stdout, "[GC] not allocated anymore: %p\n", c)
+      #GC_dumpHeap()
+      sysAssert(false, "wtf")
 
-    sysAssert(isAllocatedPtr(gch.region, c), "markIncremental: isAllocatedPtr")
+    #sysAssert(isAllocatedPtr(gch.region, c), "markIncremental: isAllocatedPtr")
     gch.greyStack.d[0] = gch.greyStack.d[L[] - 1]
     dec(L[])
     takeTime()
     if c.color == rcGrey:
       c.setColor(gch.black)
       forAllChildren(c, waMarkGrey)
+    elif c.color == (1-gch.black):
+      gcAssert false, "wtf why are there white object in the greystack?"
     checkTime()
   gcAssert gch.greyStack.len == 0, "markIncremental: greystack not empty "
+
+  # assert that all local roots are black by now:
+  var d = gch.decStack.d
+  var errors = false
+  for i in 0..gch.decStack.len-1:
+    gcAssert(isAllocatedPtr(gch.region, d[i]), "markIncremental: isAllocatedPtr 2")
+    if d[i].color != gch.black:
+      writeCell("not black ", d[i])
+      errors = true
+  gcAssert(not errors, "wtf something wrong hre")
   result = true
 
 proc markGlobals(gch: var GcHeap) =
@@ -658,28 +751,6 @@ proc markLocals(gch: var GcHeap) =
     sysAssert isAllocatedPtr(gch.region, d[i]), "markLocals"
     markRoot(gch, d[i])
 
-when logGC:
-  var
-    cycleCheckA: array[100, PCell]
-    cycleCheckALen = 0
-
-  proc alreadySeen(c: PCell): bool =
-    for i in 0 .. <cycleCheckALen:
-      if cycleCheckA[i] == c: return true
-    if cycleCheckALen == len(cycleCheckA):
-      gcAssert(false, "cycle detection overflow")
-      quit 1
-    cycleCheckA[cycleCheckALen] = c
-    inc cycleCheckALen
-
-  proc debugGraph(s: PCell) =
-    if alreadySeen(s):
-      writeCell("child cell (already seen) ", s)
-    else:
-      writeCell("cell {", s)
-      forAllChildren(s, waDebug)
-      c_fprintf(c_stdout, "}\n")
-
 proc doOperation(p: pointer, op: WalkOp) =
   if p == nil: return
   var c: PCell = usrToCell(p)
@@ -697,17 +768,31 @@ proc doOperation(p: pointer, op: WalkOp) =
     decRef(c)
     #if c.refcount <% rcIncrement: addZCT(gch.zct, c)
   of waMarkGlobal:
+    template handleRoot =
+      if gch.dumpHeapFile.isNil:
+        markRoot(gch, c)
+      else:
+        dumpRoot(gch, c)
     when hasThreadSupport:
       # could point to a cell which we don't own and don't want to touch/trace
-      if isAllocatedPtr(gch.region, c):
-        markRoot(gch, c)
+      if isAllocatedPtr(gch.region, c): handleRoot()
     else:
-      markRoot(gch, c)
+      #gcAssert(isAllocatedPtr(gch.region, c), "doOperation: waMarkGlobal")
+      if not isAllocatedPtr(gch.region, c):
+        c_fprintf(c_stdout, "[GC] not allocated anymore: MarkGlobal %p\n", c)
+        #GC_dumpHeap()
+        sysAssert(false, "wtf")
+      handleRoot()
+    discard allocInv(gch.region)
   of waMarkGrey:
+    if not isAllocatedPtr(gch.region, c):
+      c_fprintf(c_stdout, "[GC] not allocated anymore: MarkGrey %p\n", c)
+      #GC_dumpHeap()
+      sysAssert(false, "wtf")
     if c.color == 1-gch.black:
       c.setColor(rcGrey)
       add(gch.greyStack, c)
-  #of waDebug: debugGraph(c)
+  of waDebug: debugGraph(c)
 
 proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} =
   doOperation(d, WalkOp(op))
@@ -717,20 +802,29 @@ proc collectZCT(gch: var GcHeap): bool {.benign.}
 proc collectCycles(gch: var GcHeap): bool =
   # ensure the ZCT 'color' is not used:
   while gch.zct.len > 0: discard collectZCT(gch)
+
   case gch.phase
-  of Phase.None, Phase.Marking:
-    #if gch.phase == Phase.None:
+  of Phase.None:
     gch.phase = Phase.Marking
     markGlobals(gch)
+
+    cfprintf(stdout, "collectCycles: introduced bug E %ld\n", gch.phase)
+    discard allocInv(gch.region)
+  of Phase.Marking:
+    # since locals do not have a write barrier, we need
+    # to keep re-scanning them :-( but there is really nothing we can
+    # do about that.
     markLocals(gch)
     if markIncremental(gch):
       gch.phase = Phase.Sweeping
+      gch.red = 1 - gch.red
   of Phase.Sweeping:
     gcAssert gch.greyStack.len == 0, "greystack not empty"
     if sweep(gch):
       gch.phase = Phase.None
       # flip black/white meanings:
       gch.black = 1 - gch.black
+      gcAssert gch.red == 1 - gch.black, "red color is wrong"
       result = true
 
 proc gcMark(gch: var GcHeap, p: pointer) {.inline.} =
@@ -770,7 +864,7 @@ proc collectZCT(gch: var GcHeap): bool =
     gch.zct.d[0] = gch.zct.d[L[] - 1]
     dec(L[])
     takeTime()
-    if c.refcount <% rcIncrement:
+    if c.refcount <% rcIncrement and c.color != rcGrey:
       # It may have a RC > 0, if it is in the hardware stack or
       # it has not been removed yet from the ZCT. This is because
       # ``incref`` does not bother to remove the cell from the ZCT
diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim
index 2e0fecd49..1e85853d1 100644
--- a/lib/system/mmdisp.nim
+++ b/lib/system/mmdisp.nim
@@ -514,7 +514,7 @@ else:
   include "system/alloc"
 
   include "system/cellsets"
-  when not leakDetector:
+  when not leakDetector and not useCellIds:
     sysAssert(sizeof(Cell) == sizeof(FreeCell), "sizeof FreeCell")
   when compileOption("gc", "v2"):
     include "system/gc2"
diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim
index 3d0b2aa8a..e81219a70 100644
--- a/lib/system/sysio.nim
+++ b/lib/system/sysio.nim
@@ -44,9 +44,37 @@ proc memchr(s: pointer, c: cint, n: csize): pointer {.
   importc: "memchr", header: "<string.h>", tags: [].}
 proc memset(s: pointer, c: cint, n: csize) {.
   header: "<string.h>", importc: "memset", tags: [].}
+proc fwrite(buf: pointer, size, n: int, f: File): int {.
+  importc: "fwrite", noDecl.}
+
+proc raiseEIO(msg: string) {.noinline, noreturn.} =
+  sysFatal(IOError, msg)
 
 {.push stackTrace:off, profiler:off.}
+proc readBuffer(f: File, buffer: pointer, len: Natural): int =
+  result = fread(buffer, 1, len, f)
+
+proc readBytes(f: File, a: var openArray[int8|uint8], start, len: Natural): int =
+  result = readBuffer(f, addr(a[start]), len)
+
+proc readChars(f: File, a: var openArray[char], start, len: Natural): int =
+  result = readBuffer(f, addr(a[start]), len)
+
 proc write(f: File, c: cstring) = fputs(c, f)
+
+proc writeBuffer(f: File, buffer: pointer, len: Natural): int =
+  result = fwrite(buffer, 1, len, f)
+
+proc writeBytes(f: File, a: openArray[int8|uint8], start, len: Natural): int =
+  var x = cast[ptr array[0..1000_000_000, int8]](a)
+  result = writeBuffer(f, addr(x[start]), len)
+proc writeChars(f: File, a: openArray[char], start, len: Natural): int =
+  var x = cast[ptr array[0..1000_000_000, int8]](a)
+  result = writeBuffer(f, addr(x[start]), len)
+
+proc write(f: File, s: string) =
+  if writeBuffer(f, cstring(s), s.len) != s.len:
+    raiseEIO("cannot write string to file")
 {.pop.}
 
 when NoFakeVars:
@@ -68,9 +96,6 @@ else:
 const
   BufSize = 4000
 
-proc raiseEIO(msg: string) {.noinline, noreturn.} =
-  sysFatal(IOError, msg)
-
 proc readLine(f: File, line: var TaintedString): bool =
   var pos = 0
   # Use the currently reserved space for a first try
@@ -157,6 +182,12 @@ proc rawFileSize(file: File): int =
   result = ftell(file)
   discard fseek(file, clong(oldPos), 0)
 
+proc endOfFile(f: File): bool =
+  # do not blame me; blame the ANSI C standard this is so brain-damaged
+  var c = fgetc(f)
+  ungetc(c, f)
+  return c < 0'i32
+
 proc readAllFile(file: File, len: int): string =
   # We acquire the filesize beforehand and hope it doesn't change.
   # Speeds things up.
@@ -188,26 +219,6 @@ proc readAll(file: File): TaintedString =
   else:
     result = readAllBuffer(file).TaintedString
 
-proc readFile(filename: string): TaintedString =
-  var f = open(filename)
-  try:
-    result = readAll(f).TaintedString
-  finally:
-    close(f)
-
-proc writeFile(filename, content: string) =
-  var f = open(filename, fmWrite)
-  try:
-    f.write(content)
-  finally:
-    close(f)
-
-proc endOfFile(f: File): bool =
-  # do not blame me; blame the ANSI C standard this is so brain-damaged
-  var c = fgetc(f)
-  ungetc(c, f)
-  return c < 0'i32
-
 proc writeLn[Ty](f: File, x: varargs[Ty, `$`]) =
   for i in items(x):
     write(f, i)
@@ -278,39 +289,12 @@ proc reopen(f: File, filename: string, mode: FileMode = fmRead): bool =
   result = p != nil
 
 proc fdopen(filehandle: FileHandle, mode: cstring): File {.
-  importc: pccHack & "fdopen", header: "<stdio.h>".}
+  importc: "fdopen", header: "<stdio.h>".}
 
 proc open(f: var File, filehandle: FileHandle, mode: FileMode): bool =
   f = fdopen(filehandle, FormatOpen[mode])
   result = f != nil
 
-proc fwrite(buf: pointer, size, n: int, f: File): int {.
-  importc: "fwrite", noDecl.}
-
-proc readBuffer(f: File, buffer: pointer, len: Natural): int =
-  result = fread(buffer, 1, len, f)
-
-proc readBytes(f: File, a: var openArray[int8|uint8], start, len: Natural): int =
-  result = readBuffer(f, addr(a[start]), len)
-
-proc readChars(f: File, a: var openArray[char], start, len: Natural): int =
-  result = readBuffer(f, addr(a[start]), len)
-
-{.push stackTrace:off, profiler:off.}
-proc writeBytes(f: File, a: openArray[int8|uint8], start, len: Natural): int =
-  var x = cast[ptr array[0..1000_000_000, int8]](a)
-  result = writeBuffer(f, addr(x[start]), len)
-proc writeChars(f: File, a: openArray[char], start, len: Natural): int =
-  var x = cast[ptr array[0..1000_000_000, int8]](a)
-  result = writeBuffer(f, addr(x[start]), len)
-proc writeBuffer(f: File, buffer: pointer, len: Natural): int =
-  result = fwrite(buffer, 1, len, f)
-
-proc write(f: File, s: string) =
-  if writeBuffer(f, cstring(s), s.len) != s.len:
-    raiseEIO("cannot write string to file")
-{.pop.}
-
 proc setFilePos(f: File, pos: int64) =
   if fseek(f, clong(pos), 0) != 0:
     raiseEIO("cannot set file position")
@@ -325,4 +309,28 @@ proc getFileSize(f: File): int64 =
   result = getFilePos(f)
   setFilePos(f, oldPos)
 
+when not declared(close):
+  proc close(f: File) {.
+    importc: "fclose", header: "<stdio.h>", tags: [].}
+
+proc readFile(filename: string): TaintedString =
+  var f: File
+  if open(f, filename):
+    try:
+      result = readAll(f).TaintedString
+    finally:
+      close(f)
+  else:
+    sysFatal(IOError, "cannot open: ", filename)
+
+proc writeFile(filename, content: string) =
+  var f: File
+  if open(f, filename, fmWrite):
+    try:
+      f.write(content)
+    finally:
+      close(f)
+  else:
+    sysFatal(IOError, "cannot open: ", filename)
+
 {.pop.}
diff --git a/tools/heapdump2dot.nim b/tools/heapdump2dot.nim
new file mode 100644
index 000000000..4cee6d674
--- /dev/null
+++ b/tools/heapdump2dot.nim
@@ -0,0 +1,66 @@
+
+include prelude
+
+proc main(input, output: string) =
+  type NodeKind = enum
+    local, localInvalid, global, globalInvalid
+  #c_fprintf(file, "%s %p %d rc=%ld color=%c\n",
+  #          msg, c, kind, c.refcount shr rcShift, col)
+  # cell  0x10a908190 22 rc=2 color=w
+  var i, o: File
+  var roots = initTable[string, NodeKind]()
+  if open(i, input):
+    if open(o, output, fmWrite):
+      o.writeLine("digraph $1 {\n" % extractFilename(input))
+      var currNode = ""
+      for line in lines(i):
+        let data = line.split()
+        if data.len == 0: continue
+        case data[0]
+        of "end":
+          currNode = ""
+        of "cell":
+          currNode = data[1]
+          let rc = data[3].substr("rc=".len)
+          let col = case data[4].substr("color=".len)
+                    of "b": "black"
+                    of "w": "green"
+                    of "g": "grey"
+                    else: ""
+          o.write("N" & currNode)
+          if currNode in roots:
+            let v = roots[currNode]
+            case v
+            of local: o.write(" [label=\"local \\N\" fillcolor=$1]" % col)
+            of localInvalid: o.write(" [label=\"local invalid \\N\" fillcolor=$1]" % col)
+            of global: o.write(" [label=\"global \\N\" fillcolor=$1]" % col)
+            of globalInvalid: o.write(" [label=\"global invalid \\N\" fillcolor=$1]" % col)
+          else:
+            o.write(" [fillcolor=$1]" % col)
+          o.writeLine(";")
+        of "child":
+          assert currNode.len > 0
+          o.writeLine("N$1 -> N$2;" % [currNode, data[1]])
+        of "global_root":
+          roots[data[1]] = global
+        of "global_root_invalid":
+          roots[data[1]] = globalInvalid
+        of "onstack":
+          roots[data[1]] = local
+        of "onstack_invalid":
+          roots[data[1]] = localInvalid
+        else: discard
+      close(i)
+      o.writeLine("\n}")
+      close(o)
+    else:
+      quit "error: cannot open " & output
+  else:
+    quit "error: cannot open " & input
+
+if paramCount() == 1:
+  main(paramStr(1), changeFileExt(paramStr(1), "dot"))
+elif paramCount() == 2:
+  main(paramStr(1), paramStr(2))
+else:
+  quit "usage: heapdump2dot inputfile outputfile"