summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ast.nim5
-rw-r--r--compiler/jsgen.nim28
-rw-r--r--compiler/options.nim1
-rw-r--r--compiler/sempass2.nim46
-rw-r--r--compiler/transf.nim2
-rw-r--r--compiler/writetracking.nim262
-rw-r--r--lib/pure/coro.nim36
-rw-r--r--lib/pure/strutils.nim27
-rw-r--r--lib/system/endb.nim22
-rw-r--r--lib/system/sysio.nim11
-rw-r--r--lib/wrappers/iup.nim2
-rw-r--r--lib/wrappers/odbcsql.nim4
-rw-r--r--tests/js/tbyvar.nim10
-rw-r--r--web/news.txt2
14 files changed, 400 insertions, 58 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index a0a5d204a..0ad0a0718 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -291,12 +291,12 @@ const
   sfNoForward* = sfRegister
     # forward declarations are not required (per module)
 
-  sfNoRoot* = sfBorrow # a local variable is provably no root so it doesn't
-                       # require RC ops
   sfCompileToCpp* = sfInfixCall       # compile the module as C++ code
   sfCompileToObjc* = sfNamedParamCall # compile the module as Objective-C code
   sfExperimental* = sfOverriden       # module uses the .experimental switch
   sfGoto* = sfOverriden               # var is used for 'goto' code generation
+  sfWrittenTo* = sfBorrow             # param is assigned to
+  sfEscapes* = sfProcvar              # param escapes
 
 const
   # getting ready for the future expr/stmt merge
@@ -527,6 +527,7 @@ const
     # deprecated and this mess can be cleaned up.
   tfVoid* = tfVarargs # for historical reasons we conflated 'void' with
                       # 'empty' ('@[]' has the type 'seq[empty]').
+  tfReturnsNew* = tfInheritable
   skError* = skUnknown
 
   # type flags that are essential for type equality:
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 4a0e22db7..c72365cce 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -1032,7 +1032,7 @@ proc genDeref(p: PProc, n: PNode, r: var TCompRes) =
     if a.typ != etyBaseIndex: internalError(n.info, "genDeref")
     r.res = "$1[$2]" % [a.address, a.res]
 
-proc genArg(p: PProc, n: PNode, r: var TCompRes) =
+proc genArgNoParam(p: PProc, n: PNode, r: var TCompRes) =
   var a: TCompRes
   gen(p, n, a)
   if a.typ == etyBaseIndex:
@@ -1042,6 +1042,20 @@ proc genArg(p: PProc, n: PNode, r: var TCompRes) =
   else:
     add(r.res, a.res)
 
+proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes) =
+  var a: TCompRes
+  gen(p, n, a)
+  if skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs} and
+      a.typ == etyBaseIndex:
+    add(r.res, "$1[$2]" % [a.address, a.res])
+  elif a.typ == etyBaseIndex:
+    add(r.res, a.address)
+    add(r.res, ", ")
+    add(r.res, a.res)
+  else:
+    add(r.res, a.res)
+
+
 proc genArgs(p: PProc, n: PNode, r: var TCompRes) =
   add(r.res, "(")
   var hasArgs = false
@@ -1052,13 +1066,17 @@ proc genArgs(p: PProc, n: PNode, r: var TCompRes) =
 
   for i in countup(1, sonsLen(n) - 1):
     let it = n.sons[i]
+    var paramType : PNode = nil
     if i < sonsLen(typ):
       assert(typ.n.sons[i].kind == nkSym)
-      let paramType = typ.n.sons[i]
+      paramType = typ.n.sons[i]
       if paramType.typ.isCompileTimeOnly: continue
 
     if hasArgs: add(r.res, ", ")
-    genArg(p, it, r)
+    if paramType.isNil:
+      genArgNoParam(p, it, r)
+    else:
+      genArg(p, it, paramType.sym, r)
     hasArgs = true
   add(r.res, ")")
   r.kind = resExpr
@@ -1083,7 +1101,7 @@ proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) =
   add(r.res, "(")
   for i in countup(2, sonsLen(n) - 1):
     if i > 2: add(r.res, ", ")
-    genArg(p, n.sons[i], r)
+    genArgNoParam(p, n.sons[i], r)
   add(r.res, ")")
   r.kind = resExpr
 
@@ -1097,7 +1115,7 @@ proc genEcho(p: PProc, n: PNode, r: var TCompRes) =
     let it = n.sons[i]
     if it.typ.isCompileTimeOnly: continue
     if i > 0: add(r.res, ", ")
-    genArg(p, it, r)
+    genArgNoParam(p, it, r)
   add(r.res, ")")
   r.kind = resExpr
 
diff --git a/compiler/options.nim b/compiler/options.nim
index adf2017d6..1f167e2a6 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -13,6 +13,7 @@ import
 const
   hasTinyCBackend* = defined(tinyc)
   useEffectSystem* = true
+  useWriteTracking* = false
   hasFFI* = defined(useFFI)
   newScopeForIf* = true
   useCaas* = not defined(noCaas)
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index 3431ee2ff..d363eee77 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -9,7 +9,7 @@
 
 import
   intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
-  wordrecg, strutils, options, guards
+  wordrecg, strutils, options, guards, writetracking
 
 # Second semantic checking pass over the AST. Necessary because the old
 # way had some inherent problems. Performs:
@@ -17,7 +17,7 @@ import
 # * effect+exception tracking
 # * "usage before definition" checking
 # * checks for invalid usages of compiletime magics (not implemented)
-# * checks for invalid usages of PNimNode (not implemented)
+# * checks for invalid usages of NimNode (not implemented)
 # * later: will do an escape analysis for closures at least
 
 # Predefined effects:
@@ -29,21 +29,6 @@ import
 #   --> a TR macro can annotate the proc with user defined annotations
 #   --> the effect system can access these
 
-# Load&Store analysis is performed on *paths*. A path is an access like
-# obj.x.y[i].z; splitting paths up causes some problems:
-#
-# var x = obj.x
-# var z = x.y[i].z
-#
-# Alias analysis is affected by this too! A good solution is *type splitting*:
-# T becomes T1 and T2 if it's known that T1 and T2 can't alias.
-#
-# An aliasing problem and a race condition are effectively the same problem.
-# Type based alias analysis is nice but not sufficient; especially splitting
-# an array and filling it in parallel should be supported but is not easily
-# done: It essentially requires a built-in 'indexSplit' operation and dependent
-# typing.
-
 # ------------------------ exception and tag tracking -------------------------
 
 discard """
@@ -438,17 +423,41 @@ proc documentEffect(n, x: PNode, effectType: TSpecialWord, idx: int): PNode =
     result = newNode(nkExprColonExpr, n.info, @[
       newIdentNode(getIdent(specialWords[effectType]), n.info), effects])
 
+proc documentWriteEffect(n: PNode; flag: TSymFlag; pragmaName: string): PNode =
+  let s = n.sons[namePos].sym
+  let params = s.typ.n
+
+  var effects = newNodeI(nkBracket, n.info)
+  for i in 1 ..< params.len:
+    if params[i].kind == nkSym and flag in params[i].sym.flags:
+      effects.add params[i]
+
+  if effects.len > 0:
+    result = newNode(nkExprColonExpr, n.info, @[
+      newIdentNode(getIdent(pragmaName), n.info), effects])
+
+proc documentNewEffect(n: PNode): PNode =
+  let s = n.sons[namePos].sym
+  if tfReturnsNew in s.typ.flags:
+    result = newIdentNode(getIdent("new"), n.info)
+
 proc documentRaises*(n: PNode) =
   if n.sons[namePos].kind != nkSym: return
   let pragmas = n.sons[pragmasPos]
   let p1 = documentEffect(n, pragmas, wRaises, exceptionEffects)
   let p2 = documentEffect(n, pragmas, wTags, tagEffects)
+  let p3 = documentWriteEffect(n, sfWrittenTo, "writes")
+  let p4 = documentNewEffect(n)
+  let p5 = documentWriteEffect(n, sfEscapes, "escapes")
 
-  if p1 != nil or p2 != nil:
+  if p1 != nil or p2 != nil or p3 != nil or p4 != nil or p5 != nil:
     if pragmas.kind == nkEmpty:
       n.sons[pragmasPos] = newNodeI(nkPragma, n.info)
     if p1 != nil: n.sons[pragmasPos].add p1
     if p2 != nil: n.sons[pragmasPos].add p2
+    if p3 != nil: n.sons[pragmasPos].add p3
+    if p4 != nil: n.sons[pragmasPos].add p4
+    if p5 != nil: n.sons[pragmasPos].add p5
 
 template notGcSafe(t): expr = {tfGcSafe, tfNoSideEffect} * t.flags == {}
 
@@ -900,6 +909,7 @@ proc trackProc*(s: PSym, body: PNode) =
     message(s.info, warnLockLevel,
       "declared lock level is $1, but real lock level is $2" %
         [$s.typ.lockLevel, $t.maxLockLevel])
+  when useWriteTracking: trackWrites(s, body)
 
 proc trackTopLevelStmt*(module: PSym; n: PNode) =
   if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef,
diff --git a/compiler/transf.nim b/compiler/transf.nim
index 5c7472a39..4ca40ab74 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -115,7 +115,7 @@ proc transformSymAux(c: PTransf, n: PNode): PNode =
   #  return liftIterSym(n)
   var b: PNode
   var tc = c.transCon
-  if sfBorrow in n.sym.flags:
+  if sfBorrow in n.sym.flags and n.sym.kind in routineKinds:
     # simply exchange the symbol:
     b = n.sym.getBody
     if b.kind != nkSym: internalError(n.info, "wrong AST for borrowed symbol")
diff --git a/compiler/writetracking.nim b/compiler/writetracking.nim
new file mode 100644
index 000000000..db3e6c53a
--- /dev/null
+++ b/compiler/writetracking.nim
@@ -0,0 +1,262 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2015 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements the write tracking analysis. Read my block post for
+## a basic description of the algorithm and ideas.
+
+import idents, ast, astalgo, trees, renderer, msgs, types
+
+const
+  debug = false
+
+type
+  AssignToResult = enum
+    asgnNil,   # 'nil' is fine
+    asgnNew,   # 'new(result)'
+    asgnOther  # result = fooBar # not a 'new' --> 'result' might not 'new'
+  NewLocation = enum
+    newNone,
+    newLit,
+    newCall
+  W = object # WriteTrackContext
+    owner: PSym
+    returnsNew: AssignToResult # assignments to 'result'
+    markAsWrittenTo, markAsEscaping: PNode
+    assignments: seq[(PNode, PNode)] # list of all assignments in this proc
+
+proc returnsNewExpr*(n: PNode): NewLocation =
+  case n.kind
+  of nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit,
+      nkFloatLit..nkFloat64Lit, nkNilLit:
+    result = newLit
+  of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv,
+      nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkOfBranch,
+      nkElifBranch, nkElse, nkExceptBranch, nkFinally, nkCast:
+    result = returnsNewExpr(n.lastSon)
+  of nkCurly, nkBracket, nkPar, nkObjConstr, nkClosure,
+      nkIfExpr, nkIfStmt, nkWhenStmt, nkCaseStmt, nkTryStmt:
+    result = newLit
+    for i in ord(n.kind == nkObjConstr) .. <n.len:
+      let x = returnsNewExpr(n.sons[i])
+      case x
+      of newNone: return newNone
+      of newLit: discard
+      of newCall: result = newCall
+  of nkCallKinds:
+    if n.sons[0].typ != nil and tfReturnsNew in n.sons[0].typ.flags:
+      result = newCall
+  else:
+    result = newNone
+
+proc deps(w: var W; dest, src: PNode) =
+  # let x = (localA, localB)
+  # compute 'returnsNew' property:
+  let retNew = returnsNewExpr(src)
+  if dest.kind == nkSym and dest.sym.kind == skResult:
+    if retNew != newNone:
+      if w.returnsNew != asgnOther: w.returnsNew = asgnNew
+    else:
+      w.returnsNew = asgnOther
+  # mark the dependency, but
+  # rule out obviously innocent assignments like 'somebool = true'
+  if dest.kind == nkSym and retNew == newLit: discard
+  else: w.assignments.add((dest, src))
+
+proc depsArgs(w: var W; n: PNode) =
+  if n.sons[0].typ.isNil: return
+  var typ = skipTypes(n.sons[0].typ, abstractInst)
+  if typ.kind != tyProc: return
+  # echo n.info, " ", n, " ", w.owner.name.s, " ", typeToString(typ)
+  assert(sonsLen(typ) == sonsLen(typ.n))
+  for i in 1 ..< n.len:
+    let it = n.sons[i]
+    if i < sonsLen(typ):
+      assert(typ.n.sons[i].kind == nkSym)
+      let paramType = typ.n.sons[i]
+      if paramType.typ.isCompileTimeOnly: continue
+      if sfWrittenTo in paramType.sym.flags or paramType.typ.kind == tyVar:
+        # p(f(x, y), X, g(h, z))
+        deps(w, it, w.markAsWrittenTo)
+      if sfEscapes in paramType.sym.flags:
+        deps(w, it, w.markAsEscaping)
+
+proc deps(w: var W; n: PNode) =
+  case n.kind
+  of nkLetSection, nkVarSection:
+    for child in n:
+      let last = lastSon(child)
+      if last.kind == nkEmpty: continue
+      if child.kind == nkVarTuple and last.kind == nkPar:
+        internalAssert child.len-2 == last.len
+        for i in 0 .. child.len-3:
+          deps(w, child.sons[i], last.sons[i])
+      else:
+        for i in 0 .. child.len-3:
+          deps(w, child.sons[i], last)
+  of nkAsgn, nkFastAsgn:
+    deps(w, n.sons[0], n.sons[1])
+  else:
+    for i in 0 ..< n.safeLen:
+      deps(w, n.sons[i])
+    if n.kind in nkCallKinds:
+      if getMagic(n) in {mNew, mNewFinalize, mNewSeq}:
+        # may not look like an assignment, but it is:
+        deps(w, n.sons[1], newNodeIT(nkObjConstr, n.info, n.sons[1].typ))
+      else:
+        depsArgs(w, n)
+
+type
+  RootInfo = enum
+    rootIsResultOrParam,
+    rootIsHeapAccess
+
+proc allRoots(n: PNode; result: var seq[PSym]; info: var set[RootInfo]) =
+  case n.kind
+  of nkSym:
+    if n.sym.kind in {skParam, skVar, skTemp, skLet, skResult, skForVar}:
+      if result.isNil: result = @[]
+      if n.sym notin result:
+        if n.sym.kind in {skResult, skParam}: incl(info, rootIsResultOrParam)
+        result.add n.sym
+  of nkHiddenDeref, nkDerefExpr:
+    incl(info, rootIsHeapAccess)
+    allRoots(n.sons[0], result, info)
+  of nkDotExpr, nkBracketExpr, nkCheckedFieldExpr,
+      nkHiddenAddr, nkObjUpConv, nkObjDownConv:
+    allRoots(n.sons[0], result, info)
+  of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkConv,
+      nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkOfBranch,
+      nkElifBranch, nkElse, nkExceptBranch, nkFinally, nkCast:
+    allRoots(n.lastSon, result, info)
+  of nkCallKinds:
+    if getMagic(n) == mSlice:
+      allRoots(n.sons[1], result, info)
+    else:
+      # we do significantly better here by using the available escape
+      # information:
+      if n.sons[0].typ.isNil: return
+      var typ = n.sons[0].typ
+      if typ != nil:
+        typ = skipTypes(typ, abstractInst)
+        if typ.kind != tyProc: typ = nil
+        else: assert(sonsLen(typ) == sonsLen(typ.n))
+
+      for i in 1 ..< n.len:
+        let it = n.sons[i]
+        if typ != nil and i < sonsLen(typ):
+          assert(typ.n.sons[i].kind == nkSym)
+          let paramType = typ.n.sons[i]
+          if paramType.typ.isCompileTimeOnly: continue
+          if sfEscapes in paramType.sym.flags or paramType.typ.kind == tyVar:
+            allRoots(it, result, info)
+        else:
+          allRoots(it, result, info)
+  else:
+    for i in 0..<n.safeLen:
+      allRoots(n.sons[i], result, info)
+
+proc allRoots(n: PNode; result: var seq[PSym]) =
+  var dummy: set[RootInfo]
+  allRoots(n, result, dummy)
+
+proc hasSym(n: PNode; x: PSym): bool =
+  when false:
+    if n.kind == nkSym:
+      result = n.sym == x
+    else:
+      for i in 0..safeLen(n)-1:
+        if hasSym(n.sons[i], x): return true
+  else:
+    var tmp: seq[PSym]
+    allRoots(n, tmp)
+    result = not tmp.isNil and x in tmp
+
+when debug:
+  proc `$`*(x: PSym): string = x.name.s
+
+proc possibleAliases(w: W; result: var seq[PSym]) =
+  var todo = 0
+  # this is an expensive fixpoint iteration. We could speed up this analysis
+  # by a smarter data-structure but we wait until prolifing shows us it's
+  # expensive. Usually 'w.assignments' is small enough.
+  while todo < result.len:
+    let x = result[todo]
+    inc todo
+    when debug:
+      if w.owner.name.s == "m3": echo "select ", x, " ", todo, " ", result.len
+    for dest, src in items(w.assignments):
+      if src.hasSym(x):
+        # dest = f(..., s, ...)
+        allRoots(dest, result)
+        when debug:
+          if w.owner.name.s == "m3": echo "A ", result
+      elif dest.kind == nkSym and dest.sym == x:
+        # s = f(..., x, ....)
+        allRoots(src, result)
+        when debug:
+          if w.owner.name.s == "m3": echo "B ", result
+      else:
+        when debug:
+          if w.owner.name.s == "m3": echo "C ", x, " ", todo, " ", result.len
+
+proc markDirty(w: W) =
+  for dest, src in items(w.assignments):
+    var r: seq[PSym] = nil
+    var info: set[RootInfo]
+    allRoots(dest, r, info)
+    when debug:
+      if w.owner.info ?? "temp18":
+        echo "ASGN ", dest,  " = ", src, " |", heapAccess, " ", r.name.s
+    if rootIsHeapAccess in info or src == w.markAsWrittenTo:
+      # we have an assignment like:
+      # local.foo = bar
+      # --> check which parameter it may alias and mark these parameters
+      # as dirty:
+      possibleAliases(w, r)
+      for a in r:
+        if a.kind == skParam and a.owner == w.owner:
+          incl(a.flags, sfWrittenTo)
+
+proc markEscaping(w: W) =
+  # let p1 = p
+  # let p2 = q
+  # p2.x = call(..., p1, ...)
+  for dest, src in items(w.assignments):
+    var r: seq[PSym] = nil
+    var info: set[RootInfo]
+    allRoots(dest, r, info)
+
+    if (r.len > 0) and (info != {} or src == w.markAsEscaping):
+      possibleAliases(w, r)
+      var destIsParam = false
+      for a in r:
+        if a.kind in {skResult, skParam} and a.owner == w.owner:
+          destIsParam = true
+          break
+      if destIsParam:
+        var victims: seq[PSym] = @[]
+        allRoots(src, victims)
+        possibleAliases(w, victims)
+        for v in victims:
+          if v.kind == skParam and v.owner == w.owner:
+            incl(v.flags, sfEscapes)
+
+proc trackWrites*(owner: PSym; body: PNode) =
+  var w: W
+  w.owner = owner
+  w.markAsWrittenTo = newNodeI(nkArgList, body.info)
+  w.markAsEscaping = newNodeI(nkArgList, body.info)
+  w.assignments = @[]
+  deps(w, body)
+  markDirty(w)
+  markEscaping(w)
+  if w.returnsNew != asgnOther and not isEmptyType(owner.typ.sons[0]) and
+      containsGarbageCollectedRef(owner.typ.sons[0]):
+    incl(owner.typ.flags, tfReturnsNew)
+
diff --git a/lib/pure/coro.nim b/lib/pure/coro.nim
index 6ef5f6f54..8fa529474 100644
--- a/lib/pure/coro.nim
+++ b/lib/pure/coro.nim
@@ -15,8 +15,7 @@ import macros
 import arch
 import lists
 
-const coroDefaultStackSize = 512 * 1024
-
+const defaultStackSize = 512 * 1024
 
 type Coroutine = ref object
   # prev: ptr Coroutine
@@ -38,8 +37,7 @@ proc GC_addStack(starts: pointer) {.cdecl, importc.}
 proc GC_removeStack(starts: pointer) {.cdecl, importc.}
 proc GC_setCurrentStack(starts, pos: pointer) {.cdecl, importc.}
 
-
-proc coroStart*(c: proc(), stacksize: int=coroDefaultStackSize) =
+proc start*(c: proc(), stacksize: int=defaultStackSize) =
   ## Adds coroutine to event loop. It does not run immediately.
   var coro = Coroutine()
   coro.fn = c
@@ -49,20 +47,22 @@ proc coroStart*(c: proc(), stacksize: int=coroDefaultStackSize) =
   coroutines.append(coro)
 
 {.push stackTrace: off.}
-proc coroYield*(sleepTime: float=0) =
+proc suspend*(sleepTime: float=0) =
   ## Stops coroutine execution and resumes no sooner than after ``sleeptime`` seconds.
   ## Until then other coroutines are executed.
+  ##
+  ## This is similar to a `yield`:idx:, or a `yieldFrom`:idx in Python.
   var oldFrame = getFrame()
   var sp {.volatile.}: pointer
   GC_setCurrentStack(current.stack, cast[pointer](addr sp))
-  current.sleepTime = sleep_time
+  current.sleepTime = sleepTime
   current.lastRun = epochTime()
   if setjmp(current.ctx) == 0:
     longjmp(mainCtx, 1)
   setFrame(oldFrame)
 {.pop.}
 
-proc coroRun*() =
+proc run*() =
   ## Starts main event loop which exits when all coroutines exit. Calling this proc
   ## starts execution of first coroutine.
   var node = coroutines.head
@@ -108,18 +108,16 @@ proc coroRun*() =
     else:
       node = node.next
 
-
-proc coroAlive*(c: proc()): bool =
+proc alive*(c: proc()): bool =
   ## Returns ``true`` if coroutine has not returned, ``false`` otherwise.
   for coro in items(coroutines):
     if coro.fn == c:
       return true
 
-proc coroWait*(c: proc(), interval=0.01) =
+proc wait*(c: proc(), interval=0.01) =
   ## Returns only after coroutine ``c`` has returned. ``interval`` is time in seconds how often.
-  while coroAlive(c):
-    coroYield interval
-
+  while alive(c):
+    suspend interval
 
 when isMainModule:
   var stackCheckValue = 1100220033
@@ -128,18 +126,18 @@ when isMainModule:
   proc c1() =
     for i in 0 .. 3:
       echo "c1"
-      coroYield 0.05
+      suspend 0.05
     echo "c1 exits"
 
 
   proc c2() =
     for i in 0 .. 3:
       echo "c2"
-      coroYield 0.025
-    coroWait(c1)
+      suspend 0.025
+    wait(c1)
     echo "c2 exits"
 
-  coroStart(c1)
-  coroStart(c2)
-  coroRun()
+  start(c1)
+  start(c2)
+  run()
   echo "done ", stackCheckValue
diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim
index e3f99b895..ae3bd7f63 100644
--- a/lib/pure/strutils.nim
+++ b/lib/pure/strutils.nim
@@ -1197,8 +1197,8 @@ proc editDistance*(a, b: string): int {.noSideEffect,
 
 # floating point formating:
 
-proc c_sprintf(buf, frmt: cstring) {.header: "<stdio.h>", importc: "sprintf",
-                                     varargs, noSideEffect.}
+proc c_sprintf(buf, frmt: cstring): cint {.header: "<stdio.h>",
+                                     importc: "sprintf", varargs, noSideEffect.}
 
 type
   FloatFormatMode* = enum ## the different modes of floating point formating
@@ -1209,7 +1209,8 @@ type
 {.deprecated: [TFloatFormat: FloatFormatMode].}
 
 proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault,
-                         precision: range[0..32] = 16): string {.
+                         precision: range[0..32] = 16;
+                         decimalSep = '.'): string {.
                          noSideEffect, rtl, extern: "nsu$1".} =
   ## Converts a floating point value `f` to a string.
   ##
@@ -1225,6 +1226,7 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault,
   var
     frmtstr {.noinit.}: array[0..5, char]
     buf {.noinit.}: array[0..2500, char]
+    L: cint
   frmtstr[0] = '%'
   if precision > 0:
     frmtstr[1] = '#'
@@ -1232,15 +1234,20 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault,
     frmtstr[3] = '*'
     frmtstr[4] = floatFormatToChar[format]
     frmtstr[5] = '\0'
-    c_sprintf(buf, frmtstr, precision, f)
+    L = c_sprintf(buf, frmtstr, precision, f)
   else:
     frmtstr[1] = floatFormatToChar[format]
     frmtstr[2] = '\0'
-    c_sprintf(buf, frmtstr, f)
-  result = $buf
+    L = c_sprintf(buf, frmtstr, f)
+  result = newString(L)
+  for i in 0 ..< L:
+    # Depending on the locale either dot or comma is produced,
+    # but nothing else is possible:
+    if buf[i] in {'.', ','}: result[i] = decimalsep
+    else: result[i] = buf[i]
 
 proc formatFloat*(f: float, format: FloatFormatMode = ffDefault,
-                  precision: range[0..32] = 16): string {.
+                  precision: range[0..32] = 16; decimalSep = '.'): string {.
                   noSideEffect, rtl, extern: "nsu$1".} =
   ## Converts a floating point value `f` to a string.
   ##
@@ -1250,7 +1257,7 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault,
   ## of significant digits to be printed.
   ## `precision`'s default value is the maximum number of meaningful digits
   ## after the decimal point for Nim's ``float`` type.
-  result = formatBiggestFloat(f, format, precision)
+  result = formatBiggestFloat(f, format, precision, decimalSep)
 
 proc formatSize*(bytes: BiggestInt, decimalSep = '.'): string =
   ## Rounds and formats `bytes`. Examples:
@@ -1464,8 +1471,8 @@ when isMainModule:
   doAssert wordWrap(inp, 10, false) == outp
 
   doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001"
-  doAssert formatBiggestFloat(0.00000000001, ffScientific, 1) in
-                                                   ["1.0e-11", "1.0e-011"]
+  doAssert formatBiggestFloat(0.00000000001, ffScientific, 1, ',') in
+                                                   ["1,0e-11", "1,0e-011"]
 
   doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c"
   when not defined(testing):
diff --git a/lib/system/endb.nim b/lib/system/endb.nim
index cba11ac5e..b2cc5624b 100644
--- a/lib/system/endb.nim
+++ b/lib/system/endb.nim
@@ -298,7 +298,7 @@ proc dbgEvaluate(stream: File, s: cstring, start: int, f: PFrame) =
     for i in 0 .. f.len-1:
       let v = getLocal(f, i)
       if c_strcmp(v.name, dbgTemp.data) == 0:
-        writeVariable(stream, v)  
+        writeVariable(stream, v)
 
 proc dbgOut(s: cstring, start: int, currFrame: PFrame) =
   var dbgTemp: StaticStr
@@ -396,7 +396,13 @@ proc commandPrompt() =
       again = false
       quit(1) # BUGFIX: quit with error code > 0
     elif ?"e" or ?"eval":
+      var
+        prevState = dbgState
+        prevSkipFrame = dbgSkipToFrame
+      dbgState = dbSkipCurrent
       dbgEvaluate(stdout, dbgUser.data, i, dbgFramePtr)
+      dbgState = prevState
+      dbgSkipToFrame = prevSkipFrame
     elif ?"o" or ?"out":
       dbgOut(dbgUser.data, i, dbgFramePtr)
     elif ?"stackframe":
@@ -404,9 +410,21 @@ proc commandPrompt() =
     elif ?"w" or ?"where":
       dbgShowExecutionPoint()
     elif ?"l" or ?"locals":
+      var
+        prevState = dbgState
+        prevSkipFrame = dbgSkipToFrame
+      dbgState = dbSkipCurrent
       listLocals(stdout, dbgFramePtr)
+      dbgState = prevState
+      dbgSkipToFrame = prevSkipFrame
     elif ?"g" or ?"globals":
+      var
+        prevState = dbgState
+        prevSkipFrame = dbgSkipToFrame
+      dbgState = dbSkipCurrent
       listGlobals(stdout)
+      dbgState = prevState
+      dbgSkipToFrame = prevSkipFrame
     elif ?"u" or ?"up":
       if dbgDown <= 0:
         debugOut("[Warning] cannot go up any further ")
@@ -484,7 +502,7 @@ proc dbgWriteStackTrace(f: PFrame) =
     inc(i)
     b = b.prev
   for j in countdown(i-1, 0):
-    if tempFrames[j] == nil: 
+    if tempFrames[j] == nil:
       write(stdout, "(")
       write(stdout, skipped)
       write(stdout, " calls omitted) ...")
diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim
index 265f92fa2..3d0b2aa8a 100644
--- a/lib/system/sysio.nim
+++ b/lib/system/sysio.nim
@@ -90,12 +90,21 @@ proc readLine(f: File, line: var TaintedString): bool =
     let m = memchr(addr line.string[pos], '\l'.ord, space)
     if m != nil:
       # \l found: Could be our own or the one by fgets, in any case, we're done
-      let last = cast[ByteAddress](m) - cast[ByteAddress](addr line.string[0])
+      var last = cast[ByteAddress](m) - cast[ByteAddress](addr line.string[0])
       if last > 0 and line.string[last-1] == '\c':
         line.string.setLen(last-1)
         return true
+        # We have to distinguish between two possible cases:
+        # \0\l\0 => line ending in a null character.
+        # \0\l\l => last line without newline, null was put there by fgets.
+      elif last > 0 and line.string[last-1] == '\0':
+        if last < pos + space - 1 and line.string[last+1] != '\0':
+          dec last
       line.string.setLen(last)
       return true
+    else:
+      # fgets will have inserted a null byte at the end of the string.
+      dec space
     # No \l found: Increase buffer and read more
     inc pos, space
     space = 128 # read in 128 bytes at a time
diff --git a/lib/wrappers/iup.nim b/lib/wrappers/iup.nim
index cbd9b5ae9..d910173ca 100644
--- a/lib/wrappers/iup.nim
+++ b/lib/wrappers/iup.nim
@@ -307,6 +307,8 @@ proc menuv*(children: ptr PIhandle): PIhandle {.
 
 proc button*(title, action: cstring): PIhandle {.
   importc: "IupButton", cdecl, dynlib: dllname.}
+proc link*(url, title: cstring): PIhandle {.
+  importc: "IupLink", cdecl, dynlib: dllname.}
 proc canvas*(action: cstring): PIhandle {.
   importc: "IupCanvas", cdecl, dynlib: dllname.}
 proc dialog*(child: PIhandle): PIhandle {.
diff --git a/lib/wrappers/odbcsql.nim b/lib/wrappers/odbcsql.nim
index 971861a6a..43ad80f76 100644
--- a/lib/wrappers/odbcsql.nim
+++ b/lib/wrappers/odbcsql.nim
@@ -680,8 +680,12 @@ proc SQLBrowseConnect*(hdbc: SqlHDBC, szConnStrIn: PSQLCHAR,
     dynlib: odbclib, importc.}
 proc SQLExecDirect*(StatementHandle: SqlHStmt, StatementText: PSQLCHAR,
                     TextLength: TSqlInteger): TSqlSmallInt{.dynlib: odbclib, importc.}
+proc SQLExecDirectW*(StatementHandle: SqlHStmt, StatementText: WideCString,
+                    TextLength: TSqlInteger): TSqlSmallInt{.dynlib: odbclib, importc.}
 proc SQLPrepare*(StatementHandle: SqlHStmt, StatementText: PSQLCHAR,
                  TextLength: TSqlInteger): TSqlSmallInt{.dynlib: odbclib, importc.}
+proc SQLPrepareW*(StatementHandle: SqlHStmt, StatementText: WideCString,
+                 TextLength: TSqlInteger): TSqlSmallInt{.dynlib: odbclib, importc.}
 proc SQLCloseCursor*(StatementHandle: SqlHStmt): TSqlSmallInt{.dynlib: odbclib,
     importc.}
 proc SQLExecute*(StatementHandle: SqlHStmt): TSqlSmallInt{.dynlib: odbclib, importc.}
diff --git a/tests/js/tbyvar.nim b/tests/js/tbyvar.nim
index 1269e6f66..9714cd56b 100644
--- a/tests/js/tbyvar.nim
+++ b/tests/js/tbyvar.nim
@@ -31,3 +31,13 @@ proc main =
   echo y
 
 main()
+
+# Test: pass var seq to var openarray
+var s = @[2, 1]
+proc foo(a: var openarray[int]) = a[0] = 123
+
+proc bar(s: var seq[int], a: int) =
+  doAssert(a == 5)
+  foo(s)
+s.bar(5)
+doAssert(s == @[123, 1])
diff --git a/web/news.txt b/web/news.txt
index 040442695..a13334e3f 100644
--- a/web/news.txt
+++ b/web/news.txt
@@ -62,6 +62,8 @@ News
   - Define ``nimPinToCpu`` to make the ``threadpool`` use explicit thread
     affinities. This can speed up or slow down the thread pool; it's up to you
     to benchmark it.
+  - ``strutils.formatFloat`` and ``formatBiggestFloat`` do not depend on the C
+    locale anymore and now take an optional ``decimalSep = '.'`` parameter.
 
 
   Language Additions