summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ccgexprs.nim6
-rw-r--r--compiler/commands.nim4
-rw-r--r--compiler/installer.ini1
-rw-r--r--compiler/options.nim1
-rw-r--r--compiler/renderer.nim129
-rw-r--r--doc/advopt.txt1
-rw-r--r--lib/pure/coro.nim2
-rw-r--r--lib/pure/httpclient.nim15
-rw-r--r--lib/system.nim78
-rw-r--r--lib/system/alloc.nim8
-rw-r--r--lib/system/ansi_c.nim18
-rw-r--r--lib/system/excpt.nim2
-rw-r--r--lib/system/gc2.nim190
-rw-r--r--lib/system/mmdisp.nim2
-rw-r--r--lib/system/repr.nim1
-rw-r--r--lib/system/sysio.nim110
-rw-r--r--tools/heapdump2dot.nim66
-rw-r--r--web/news.txt7
18 files changed, 419 insertions, 222 deletions
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index fcc36e4fd..1a5334a98 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -2111,8 +2111,10 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
       initLocExpr(p, n.sons[0], a)
   of nkAsmStmt: genAsmStmt(p, n)
   of nkTryStmt:
-    if p.module.compileToCpp: genTryCpp(p, n, d)
-    else: genTry(p, n, d)
+    if p.module.compileToCpp and optNoCppExceptions notin gGlobalOptions:
+      genTryCpp(p, n, d)
+    else:
+      genTry(p, n, d)
   of nkRaiseStmt: genRaiseStmt(p, n)
   of nkTypeSection:
     # we have to emit the type information for object types here to support
diff --git a/compiler/commands.nim b/compiler/commands.nim
index 8dacebd83..2622d64f4 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -619,6 +619,10 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
     cAssembler = nameToCC(arg)
     if cAssembler notin cValidAssemblers:
       localError(info, errGenerated, "'$1' is not a valid assembler." % [arg])
+  of "nocppexceptions":
+    expectNoArg(switch, arg, pass, info)
+    incl(gGlobalOptions, optNoCppExceptions)
+    defineSymbol("noCppExceptions")
   else:
     if strutils.find(switch, '.') >= 0: options.setConfigVar(switch, arg)
     else: invalidCmdLineOption(pass, switch, info)
diff --git a/compiler/installer.ini b/compiler/installer.ini
index 95c07e003..12d9baf82 100644
--- a/compiler/installer.ini
+++ b/compiler/installer.ini
@@ -104,6 +104,7 @@ Files: "lib/pure/concurrency/*.cfg"
 Files: "lib/impure/*.nim"
 Files: "lib/impure/nre/private/*.nim"
 Files: "lib/wrappers/*.nim"
+Files: "lib/arch/*.nim"
 
 Files: "lib/wrappers/readline/*.nim"
 Files: "lib/wrappers/linenoise/*.nim"
diff --git a/compiler/options.nim b/compiler/options.nim
index 82d18e242..29cdd96fb 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -66,6 +66,7 @@ type                          # please make sure we have under 32 options
                               # also: generate header file
     optIdeDebug               # idetools: debug mode
     optIdeTerse               # idetools: use terse descriptions
+    optNoCppExceptions        # use C exception handling even with CPP
   TGlobalOptions* = set[TGlobalOption]
   TCommands* = enum           # Nim's commands
                               # **keep binary compatible**
diff --git a/compiler/renderer.nim b/compiler/renderer.nim
index 34688c798..d63cba4be 100644
--- a/compiler/renderer.nim
+++ b/compiler/renderer.nim
@@ -803,6 +803,12 @@ proc doParamsAux(g: var TSrcGen, params: PNode) =
     putWithSpace(g, tkOpr, "->")
     gsub(g, params.sons[0])
 
+proc gsub(g: var TSrcGen; n: PNode; i: int) =
+  if i < n.len:
+    gsub(g, n[i])
+  else:
+    put(g, tkOpr, "<<" & $i & "th child missing for " & $n.kind & " >>")
+
 proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
   if isNil(n): return
   var
@@ -837,8 +843,8 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     gcomma(g, n, 1)
     put(g, tkParRi, ")")
   of nkCallStrLit:
-    gsub(g, n.sons[0])
-    if n.sons[1].kind == nkRStrLit:
+    gsub(g, n, 0)
+    if n.len > 1 and n.sons[1].kind == nkRStrLit:
       put(g, tkRStrLit, '\"' & replace(n[1].strVal, "\"", "\"\"") & '\"')
     else:
       gsub(g, n.sons[1])
@@ -846,10 +852,10 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
   of nkCast:
     put(g, tkCast, "cast")
     put(g, tkBracketLe, "[")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkBracketRi, "]")
     put(g, tkParLe, "(")
-    gsub(g, n.sons[1])
+    gsub(g, n, 1)
     put(g, tkParRi, ")")
   of nkAddr:
     put(g, tkAddr, "addr")
@@ -860,29 +866,29 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
   of nkStaticExpr:
     put(g, tkStatic, "static")
     put(g, tkSpaces, Space)
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
   of nkBracketExpr:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkBracketLe, "[")
     gcomma(g, n, 1)
     put(g, tkBracketRi, "]")
   of nkCurlyExpr:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkCurlyLe, "{")
     gcomma(g, n, 1)
     put(g, tkCurlyRi, "}")
   of nkPragmaExpr:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     gcomma(g, n, 1)
   of nkCommand:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkSpaces, Space)
     gcomma(g, n, 1)
   of nkExprEqExpr, nkAsgn, nkFastAsgn:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkSpaces, Space)
     putWithSpace(g, tkEquals, "=")
-    gsub(g, n.sons[1])
+    gsub(g, n, 1)
   of nkChckRangeF:
     put(g, tkSymbol, "chckRangeF")
     put(g, tkParLe, "(")
@@ -936,33 +942,34 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     gcomma(g, n, c)
     put(g, tkBracketRi, "]")
   of nkDotExpr:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkDot, ".")
-    gsub(g, n.sons[1])
+    gsub(g, n, 1)
   of nkBind:
     putWithSpace(g, tkBind, "bind")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
   of nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
   of nkLambda:
     putWithSpace(g, tkProc, "proc")
-    gsub(g, n.sons[paramsPos])
-    gsub(g, n.sons[pragmasPos])
+    gsub(g, n, paramsPos)
+    gsub(g, n, pragmasPos)
     put(g, tkSpaces, Space)
     putWithSpace(g, tkEquals, "=")
-    gsub(g, n.sons[bodyPos])
+    gsub(g, n, bodyPos)
   of nkDo:
     putWithSpace(g, tkDo, "do")
-    doParamsAux(g, n.sons[paramsPos])
-    gsub(g, n.sons[pragmasPos])
+    if paramsPos < n.len:
+      doParamsAux(g, n.sons[paramsPos])
+    gsub(g, n, pragmasPos)
     put(g, tkColon, ":")
-    gsub(g, n.sons[bodyPos])
+    gsub(g, n, bodyPos)
   of nkConstDef, nkIdentDefs:
     gcomma(g, n, 0, -3)
     var L = sonsLen(n)
     if L >= 2 and n.sons[L - 2].kind != nkEmpty:
       putWithSpace(g, tkColon, ":")
-      gsub(g, n.sons[L - 2])
+      gsub(g, n, L - 2)
     if L >= 1 and n.sons[L - 1].kind != nkEmpty:
       put(g, tkSpaces, Space)
       putWithSpace(g, tkEquals, "=")
@@ -975,20 +982,20 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     putWithSpace(g, tkEquals, "=")
     gsub(g, lastSon(n), c)
   of nkExprColonExpr:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     putWithSpace(g, tkColon, ":")
-    gsub(g, n.sons[1])
+    gsub(g, n, 1)
   of nkInfix:
-    gsub(g, n.sons[1])
+    gsub(g, n, 1)
     put(g, tkSpaces, Space)
-    gsub(g, n.sons[0])        # binary operator
+    gsub(g, n, 0)        # binary operator
     if not fits(g, lsub(n.sons[2]) + lsub(n.sons[0]) + 1):
       optNL(g, g.indent + longIndentWid)
     else:
       put(g, tkSpaces, Space)
-    gsub(g, n.sons[2])
+    gsub(g, n, 2)
   of nkPrefix:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     if n.len > 1:
       put(g, tkSpaces, Space)
       if n.sons[1].kind == nkInfix:
@@ -998,14 +1005,14 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
       else:
         gsub(g, n.sons[1])
   of nkPostfix:
-    gsub(g, n.sons[1])
-    gsub(g, n.sons[0])
+    gsub(g, n, 1)
+    gsub(g, n, 0)
   of nkRange:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkDotDot, "..")
-    gsub(g, n.sons[1])
+    gsub(g, n, 1)
   of nkDerefExpr:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkOpr, "[]")
   of nkAccQuoted:
     put(g, tkAccent, "`")
@@ -1016,19 +1023,19 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     put(g, tkAccent, "`")
   of nkIfExpr:
     putWithSpace(g, tkIf, "if")
-    gsub(g, n.sons[0].sons[0])
+    if n.len > 0: gsub(g, n.sons[0], 0)
     putWithSpace(g, tkColon, ":")
-    gsub(g, n.sons[0].sons[1])
+    if n.len > 0: gsub(g, n.sons[0], 1)
     gsons(g, n, emptyContext, 1)
   of nkElifExpr:
     putWithSpace(g, tkElif, " elif")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     putWithSpace(g, tkColon, ":")
-    gsub(g, n.sons[1])
+    gsub(g, n, 1)
   of nkElseExpr:
     put(g, tkElse, " else")
     putWithSpace(g, tkColon, ":")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
   of nkTypeOfExpr:
     put(g, tkType, "type")
     put(g, tkParLe, "(")
@@ -1065,10 +1072,10 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     else:
       put(g, tkDistinct, "distinct")
   of nkTypeDef:
-    gsub(g, n.sons[0])
-    gsub(g, n.sons[1])
+    gsub(g, n, 0)
+    gsub(g, n, 1)
     put(g, tkSpaces, Space)
-    if n.sons[2].kind != nkEmpty:
+    if n.len > 2 and n.sons[2].kind != nkEmpty:
       putWithSpace(g, tkEquals, "=")
       gsub(g, n.sons[2])
   of nkObjectTy:
@@ -1090,19 +1097,19 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     putNL(g)
   of nkOfInherit:
     putWithSpace(g, tkOf, "of")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
   of nkProcTy:
     if sonsLen(n) > 0:
       putWithSpace(g, tkProc, "proc")
-      gsub(g, n.sons[0])
-      gsub(g, n.sons[1])
+      gsub(g, n, 0)
+      gsub(g, n, 1)
     else:
       put(g, tkProc, "proc")
   of nkIteratorTy:
     if sonsLen(n) > 0:
       putWithSpace(g, tkIterator, "iterator")
-      gsub(g, n.sons[0])
-      gsub(g, n.sons[1])
+      gsub(g, n, 0)
+      gsub(g, n, 1)
     else:
       put(g, tkIterator, "iterator")
   of nkStaticTy:
@@ -1123,10 +1130,10 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     else:
       put(g, tkEnum, "enum")
   of nkEnumFieldDef:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkSpaces, Space)
     putWithSpace(g, tkEquals, "=")
-    gsub(g, n.sons[1])
+    gsub(g, n, 1)
   of nkStmtList, nkStmtListExpr, nkStmtListType: gstmts(g, n, emptyContext)
   of nkIfStmt:
     putWithSpace(g, tkIf, "if")
@@ -1183,22 +1190,22 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
       gsub(g, n.sons[0])
   of nkReturnStmt:
     putWithSpace(g, tkReturn, "return")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
   of nkRaiseStmt:
     putWithSpace(g, tkRaise, "raise")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
   of nkYieldStmt:
     putWithSpace(g, tkYield, "yield")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
   of nkDiscardStmt:
     putWithSpace(g, tkDiscard, "discard")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
   of nkBreakStmt:
     putWithSpace(g, tkBreak, "break")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
   of nkContinueStmt:
     putWithSpace(g, tkContinue, "continue")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
   of nkPragma:
     if renderNoPragmas notin g.flags:
       if g.inPragma <= 0:
@@ -1226,7 +1233,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
       putWithSpace(g, tkImport, "import")
     else:
       putWithSpace(g, tkExport, "export")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkSpaces, Space)
     putWithSpace(g, tkExcept, "except")
     gcommaAux(g, n, g.indent, 1)
@@ -1234,7 +1241,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     putNL(g)
   of nkFromStmt:
     putWithSpace(g, tkFrom, "from")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkSpaces, Space)
     putWithSpace(g, tkImport, "import")
     gcomma(g, n, emptyContext, 1)
@@ -1257,10 +1264,10 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     gcoms(g)
     gstmts(g, lastSon(n), c)
   of nkImportAs:
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkSpaces, Space)
     putWithSpace(g, tkAs, "as")
-    gsub(g, n.sons[1])
+    gsub(g, n, 1)
   of nkBindStmt:
     putWithSpace(g, tkBind, "bind")
     gcomma(g, n, c)
@@ -1270,7 +1277,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
   of nkElifBranch:
     optNL(g)
     putWithSpace(g, tkElif, "elif")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     putWithSpace(g, tkColon, ":")
     gcoms(g)
     gstmts(g, n.sons[1], c)
@@ -1304,7 +1311,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     put(g, tkParLe, "(")
     gsemicolon(g, n, 1)
     put(g, tkParRi, ")")
-    if n.sons[0].kind != nkEmpty:
+    if n.len > 0 and n.sons[0].kind != nkEmpty:
       putWithSpace(g, tkColon, ":")
       gsub(g, n.sons[0])
   of nkTupleTy:
@@ -1316,7 +1323,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     put(g, tkTuple, "tuple")
   of nkMetaNode_Obsolete:
     put(g, tkParLe, "(META|")
-    gsub(g, n.sons[0])
+    gsub(g, n, 0)
     put(g, tkParRi, ")")
   of nkGotoState, nkState:
     var c: TContext
diff --git a/doc/advopt.txt b/doc/advopt.txt
index 02849498f..02aada4fb 100644
--- a/doc/advopt.txt
+++ b/doc/advopt.txt
@@ -69,6 +69,7 @@ Advanced options:
   --putenv:key=value        set an environment variable
   --NimblePath:PATH         add a path for Nimble support
   --noNimblePath            deactivate the Nimble path
+  --noCppExceptions         use default exception handling with C++ backend
   --excludePath:PATH        exclude a path from the list of search paths
   --dynlibOverride:SYMBOL   marks SYMBOL so that dynlib:SYMBOL
                             has no effect and can be statically linked instead;
diff --git a/lib/pure/coro.nim b/lib/pure/coro.nim
index c5724f26f..2a81b7317 100644
--- a/lib/pure/coro.nim
+++ b/lib/pure/coro.nim
@@ -7,7 +7,7 @@
 #    distribution, for details about the copyright.
 #
 
-when not defined(nimCoroutines):
+when not defined(nimCoroutines) and not defined(nimdoc):
   {.error: "Coroutines require -d:nimCoroutines".}
 
 import os, times
diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim
index 1b91132db..603763386 100644
--- a/lib/pure/httpclient.nim
+++ b/lib/pure/httpclient.nim
@@ -455,14 +455,15 @@ proc redirection(status: string): bool =
     if status.startsWith(i):
       return true
 
-proc getNewLocation(lastUrl: string, headers: StringTableRef): string =
+proc getNewLocation(lastURL: string, headers: StringTableRef): string =
   result = headers.getOrDefault"Location"
   if result == "": httpError("location header expected")
   # Relative URLs. (Not part of the spec, but soon will be.)
   let r = parseUri(result)
   if r.hostname == "" and r.path != "":
-    let origParsed = parseUri(lastUrl)
-    result = origParsed.hostname & "/" & r.path
+    var parsed = parseUri(lastURL)
+    parsed.path = r.path
+    result = $parsed
 
 proc get*(url: string, extraHeaders = "", maxRedirects = 5,
           sslContext: SSLContext = defaultSSLContext,
@@ -481,7 +482,7 @@ proc get*(url: string, extraHeaders = "", maxRedirects = 5,
       let redirectTo = getNewLocation(lastURL, result.headers)
       result = request(redirectTo, httpGET, extraHeaders, "", sslContext,
                        timeout, userAgent, proxy)
-      lastUrl = redirectTo
+      lastURL = redirectTo
 
 proc getContent*(url: string, extraHeaders = "", maxRedirects = 5,
                  sslContext: SSLContext = defaultSSLContext,
@@ -528,14 +529,14 @@ proc post*(url: string, extraHeaders = "", body = "",
 
   result = request(url, httpPOST, xh, xb, sslContext, timeout, userAgent,
                    proxy)
-  var lastUrl = ""
+  var lastURL = url
   for i in 1..maxRedirects:
     if result.status.redirection():
       let redirectTo = getNewLocation(lastURL, result.headers)
       var meth = if result.status != "307": httpGet else: httpPost
       result = request(redirectTo, meth, xh, xb, sslContext, timeout,
                        userAgent, proxy)
-      lastUrl = redirectTo
+      lastURL = redirectTo
 
 proc postContent*(url: string, extraHeaders = "", body = "",
                   maxRedirects = 5,
@@ -827,7 +828,7 @@ proc get*(client: AsyncHttpClient, url: string): Future[Response] {.async.} =
     if result.status.redirection():
       let redirectTo = getNewLocation(lastURL, result.headers)
       result = await client.request(redirectTo, httpGET)
-      lastUrl = redirectTo
+      lastURL = redirectTo
 
 proc post*(client: AsyncHttpClient, url: string, body = "", multipart: MultipartData = nil): Future[Response] {.async.} =
   ## Connects to the hostname specified by the URL and performs a POST request.
diff --git a/lib/system.nim b/lib/system.nim
index 2c049b6b6..c28d0df8b 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -2589,6 +2589,30 @@ when not defined(JS): #and not defined(nimscript):
     var
       strDesc = TNimType(size: sizeof(string), kind: tyString, flags: {ntfAcyclic})
 
+
+  # ----------------- IO Part ------------------------------------------------
+  type
+    CFile {.importc: "FILE", header: "<stdio.h>",
+            final, incompletestruct.} = object
+    File* = ptr CFile ## The type representing a file handle.
+
+    FileMode* = enum           ## The file mode when opening a file.
+      fmRead,                   ## Open the file for read access only.
+      fmWrite,                  ## Open the file for write access only.
+      fmReadWrite,              ## Open the file for read and write access.
+                                ## If the file does not exist, it will be
+                                ## created.
+      fmReadWriteExisting,      ## Open the file for read and write access.
+                                ## If the file does not exist, it will not be
+                                ## created.
+      fmAppend                  ## Open the file for writing only; append data
+                                ## at the end.
+
+    FileHandle* = cint ## type that represents an OS file handle; this is
+                       ## useful for low-level file access
+
+  {.deprecated: [TFile: File, TFileHandle: FileHandle, TFileMode: FileMode].}
+
   when not defined(nimscript):
     include "system/ansi_c"
 
@@ -2600,58 +2624,32 @@ when not defined(JS): #and not defined(nimscript):
       elif x > y: result = 1
       else: result = 0
 
-  const pccHack = if defined(pcc): "_" else: "" # Hack for PCC
-  when not defined(nimscript):
+  when not defined(nimscript) and hostOS != "standalone":
     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",
+      proc setmode(handle, mode: int) {.importc: "setmode",
                                         header: "<io.h>".}
-      proc fileno(f: C_TextFileStar): int {.importc: pccHack & "fileno",
+      proc fileno(f: C_TextFileStar): int {.importc: "fileno",
                                             header: "<fcntl.h>".}
       var
-        O_BINARY {.importc: pccHack & "O_BINARY", nodecl.}: int
+        O_BINARY {.importc: "O_BINARY", nodecl.}: int
 
-      # we use binary mode in Windows:
+      # we use binary mode on Windows:
       setmode(fileno(c_stdin), O_BINARY)
       setmode(fileno(c_stdout), O_BINARY)
 
     when defined(endb):
       proc endbStep()
 
-  # ----------------- IO Part ------------------------------------------------
-  when hostOS != "standalone":
-    type
-      CFile {.importc: "FILE", header: "<stdio.h>",
-              final, incompletestruct.} = object
-      File* = ptr CFile ## The type representing a file handle.
-
-      FileMode* = enum           ## The file mode when opening a file.
-        fmRead,                   ## Open the file for read access only.
-        fmWrite,                  ## Open the file for write access only.
-        fmReadWrite,              ## Open the file for read and write access.
-                                  ## If the file does not exist, it will be
-                                  ## created.
-        fmReadWriteExisting,      ## Open the file for read and write access.
-                                  ## If the file does not exist, it will not be
-                                  ## created.
-        fmAppend                  ## Open the file for writing only; append data
-                                  ## at the end.
-
-      FileHandle* = cint ## type that represents an OS file handle; this is
-                         ## useful for low-level file access
-
-    {.deprecated: [TFile: File, TFileHandle: FileHandle, TFileMode: FileMode].}
-
-    when not defined(nimscript):
-      # 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.
+    # 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 +2945,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) and hostOS != "standalone":
     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/excpt.nim b/lib/system/excpt.nim
index df28c1493..8d1e04b8d 100644
--- a/lib/system/excpt.nim
+++ b/lib/system/excpt.nim
@@ -216,7 +216,7 @@ proc raiseExceptionAux(e: ref Exception) =
     if not localRaiseHook(e): return
   if globalRaiseHook != nil:
     if not globalRaiseHook(e): return
-  when defined(cpp):
+  when defined(cpp) and not defined(noCppExceptions):
     if e[] of OutOfMemError:
       showErrorMessage(e.name)
       quitOrDebug()
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/repr.nim b/lib/system/repr.nim
index 986994203..7f18ed31c 100644
--- a/lib/system/repr.nim
+++ b/lib/system/repr.nim
@@ -76,6 +76,7 @@ proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} =
   # we read an 'int' but this may have been too large, so mask the other bits:
   let e = if typ.size == 1: e and 0xff
           elif typ.size == 2: e and 0xffff
+          elif typ.size == 4: e and 0xffffffff
           else: e
   # XXX we need a proper narrowing based on signedness here
   #e and ((1 shl (typ.size*8)) - 1)
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"
diff --git a/web/news.txt b/web/news.txt
index bf665c85c..d854347a5 100644
--- a/web/news.txt
+++ b/web/news.txt
@@ -17,6 +17,13 @@ Library Additions
 - The rlocks module has been added providing reentrant lock synchronization
   primitive
 
+Compiler Additions
+------------------
+
+- Added a new ``--noCppExceptions`` switch that allows to use default exception
+  handling (no ``throw`` or ``try``/``catch`` generated) when compiling to C++
+  code
+
 
 2016-01-27 Nim in Action is now available!
 ==========================================