summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2018-03-30 02:32:13 +0200
committerAraq <rumpf_a@web.de>2018-03-30 02:32:13 +0200
commit9de05ec3e04e8a2321b29896fe9fa7db4c6e65f3 (patch)
tree8667e2a5c64d93b29b748feaf29066d64fa925d1
parent1d9343080de8e19835c3f6568630ba759afbb94f (diff)
downloadNim-9de05ec3e04e8a2321b29896fe9fa7db4c6e65f3.tar.gz
further steps in implementing sink parameters; refs #7041
-rw-r--r--compiler/ast.nim2
-rw-r--r--compiler/commands.nim2
-rw-r--r--compiler/destroyer.nim157
-rw-r--r--compiler/msgs.nim5
-rw-r--r--compiler/options.nim7
-rw-r--r--compiler/pragmas.nim8
-rw-r--r--compiler/semobjconstr.nim2
-rw-r--r--compiler/wordrecg.nim4
-rw-r--r--lib/system.nim5
-rw-r--r--lib/system/chcks.nim5
10 files changed, 177 insertions, 20 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index a28a7e7e3..68bf95772 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -453,6 +453,8 @@ type
     nfPreventCg # this node should be ignored by the codegen
     nfBlockArg  # this a stmtlist appearing in a call (e.g. a do block)
     nfFromTemplate # a top-level node returned from a template
+    nfPreventDestructor # prevent destructor injectsion for the node
+                        # (but not necessarily its children)
 
   TNodeFlags* = set[TNodeFlag]
   TTypeFlag* = enum   # keep below 32 for efficiency reasons (now: beyond that)
diff --git a/compiler/commands.nim b/compiler/commands.nim
index 766b78798..2e9b6f8db 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -257,6 +257,7 @@ proc testCompileOption*(switch: string, info: TLineInfo): bool =
   of "rangechecks": result = contains(gOptions, optRangeCheck)
   of "boundchecks": result = contains(gOptions, optBoundsCheck)
   of "overflowchecks": result = contains(gOptions, optOverflowCheck)
+  of "movechecks": result = contains(gOptions, optMoveCheck)
   of "linedir": result = contains(gOptions, optLineDir)
   of "assertions", "a": result = contains(gOptions, optAssert)
   of "deadcodeelim": result = contains(gGlobalOptions, optDeadCodeElim)
@@ -493,6 +494,7 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
   of "rangechecks": processOnOffSwitch({optRangeCheck}, arg, pass, info)
   of "boundchecks": processOnOffSwitch({optBoundsCheck}, arg, pass, info)
   of "overflowchecks": processOnOffSwitch({optOverflowCheck}, arg, pass, info)
+  of "movechecks": processOnOffSwitch({optMoveCheck}, arg, pass, info)
   of "linedir": processOnOffSwitch({optLineDir}, arg, pass, info)
   of "assertions", "a": processOnOffSwitch({optAssert}, arg, pass, info)
   of "deadcodeelim": processOnOffSwitchG({optDeadCodeElim}, arg, pass, info)
diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim
index 55da69985..9acc89be4 100644
--- a/compiler/destroyer.nim
+++ b/compiler/destroyer.nim
@@ -89,11 +89,36 @@
 ##       tmp.bar))
 ## destroy(tmp.bar)
 ## destroy(tmp.x); destroy(tmp.y)
+##
+
+##[
+From https://github.com/nim-lang/Nim/wiki/Destructors
+
+Rule      Pattern                 Transformed into
+----      -------                 ----------------
+1.1	      var x: T; stmts	        var x: T; try stmts
+                                  finally: `=destroy`(x)
+1.2       var x: sink T; stmts    var x: sink T; stmts; ensureEmpty(x)
+2         x = f()                 `=sink`(x, f())
+3         x = lastReadOf z        `=sink`(x, z)
+4.1       y = sinkParam           `=sink`(y, sinkParam)
+4.2       x = y                   `=`(x, y) # a copy
+5.1       f_sink(g())             f_sink(g())
+5.2       f_sink(y)               f_sink(copy y); # copy unless we can see it's the last read
+5.3       f_sink(move y)          f_sink(y); reset(y) # explicit moves empties 'y'
+5.4       f_noSink(g())           var tmp = bitwiseCopy(g()); f(tmp); `=destroy`(tmp)
+
+Remarks: Rule 1.2 is not yet implemented because ``sink`` is currently
+  not allowed as a local variable.
+
+``move`` builtin needs to be implemented.
 
+XXX Think about nfPreventDestructor logic.
+]##
 
 import
   intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
-  strutils, options, dfa, lowerings, rodread
+  strutils, options, dfa, lowerings, rodread, tables
 
 const
   InterestingSyms = {skVar, skResult, skLet}
@@ -106,6 +131,14 @@ type
     tmpObj: PType
     tmp: PSym
     destroys, topLevelVars: PNode
+    toDropBit: Table[int, PSym]
+
+proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode =
+  # XXX why are temps fields in an object here?
+  let f = newSym(skField, getIdent(":d" & $c.tmpObj.n.len), c.owner, info)
+  f.typ = typ
+  rawAddField c.tmpObj, f
+  result = rawDirectAccess(c.tmp, f)
 
 proc isHarmlessVar*(s: PSym; c: Con): bool =
   # 's' is harmless if it used only once and its
@@ -212,14 +245,44 @@ proc genDestroy(t: PType; dest: PNode): PNode =
 proc addTopVar(c: var Con; v: PNode) =
   c.topLevelVars.add newTree(nkIdentDefs, v, emptyNode, emptyNode)
 
+proc dropBit(c: var Con; s: PSym): PSym =
+  result = c.toDropBit.getOrDefault(s.id)
+  assert result != nil
+
+proc registerDropBit(c: var Con; s: PSym) =
+  let result = newSym(skTemp, getIdent(s.name.s & "_AliveBit"), c.owner, s.info)
+  result.typ = getSysType(tyBool)
+  let trueVal = newIntTypeNode(nkIntLit, 1, result.typ)
+  c.topLevelVars.add newTree(nkIdentDefs, newSymNode result, emptyNode, trueVal)
+  c.toDropBit[s.id] = result
+  # generate:
+  #  if not sinkParam_AliveBit: `=destroy`(sinkParam)
+  c.destroys.add newTree(nkIfStmt,
+    newTree(nkElifBranch, newSymNode result, genDestroy(s.typ, newSymNode s)))
+
 proc p(n: PNode; c: var Con): PNode
 
 template recurse(n, dest) =
   for i in 0..<n.len:
     dest.add p(n[i], c)
 
+proc isSinkParam(s: PSym): bool {.inline.} =
+  result = s.kind == skParam and s.typ.kind == tySink
+
+const constrExprs = nkCallKinds+{nkObjConstr}
+
+proc destructiveMoveSink(n: PNode; c: var Con): PNode =
+  # generate:  (chckMove(sinkParam_AliveBit); sinkParam_AliveBit = false; sinkParam)
+  result = newNodeIT(nkStmtListExpr, n.info, n.typ)
+  let bit = newSymNode dropBit(c, n.sym)
+  if optMoveCheck in c.owner.options:
+    result.add callCodegenProc("chckMove", bit)
+  result.add newTree(nkAsgn, bit,
+    newIntTypeNode(nkIntLit, 0, getSysType(tyBool)))
+  result.add n
+
 proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
-  if ri.kind in nkCallKinds+{nkObjConstr}:
+  if ri.kind in constrExprs:
     result = genSink(ri.typ, dest)
     # watch out and no not transform 'ri' twice if it's a call:
     let ri2 = copyNode(ri)
@@ -228,10 +291,82 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
   elif ri.kind == nkSym and isHarmlessVar(ri.sym, c):
     result = genSink(ri.typ, dest)
     result.add p(ri, c)
+  elif ri.kind == nkSym and isSinkParam(ri.sym):
+    result = genSink(ri.typ, dest)
+    result.add destructiveMoveSink(ri, c)
   else:
     result = genCopy(ri.typ, dest)
     result.add p(ri, c)
 
+proc passCopyToSink(n: PNode; c: var Con): PNode =
+  result = newNodeIT(nkStmtListExpr, n.info, n.typ)
+  let tmp = getTemp(c, n.typ, n.info)
+  var m = genCopy(n.typ, tmp)
+  m.add n
+  result.add m
+  result.add tmp
+  incl result.flags, nfPreventDestructor
+  message(n.info, hintPerformance,
+    "passing '$1' to a sink parameter introduces an implicit copy; " &
+    "use 'move($1)' to prevent it" % $n)
+
+proc genReset(n: PNode; c: var Con): PNode =
+  result = newNodeI(nkCall, n.info)
+  result.add(newSymNode(createMagic("reset", mReset)))
+  # The mReset builtin does not take the address:
+  result.add n
+
+proc destructiveMoveVar(n: PNode; c: var Con): PNode =
+  # generate: (let tmp = v; reset(v); tmp)
+  result = newNodeIT(nkStmtListExpr, n.info, n.typ)
+
+  var temp = newSym(skLet, getIdent("blitTmp"), c.owner, n.info)
+  var v = newNodeI(nkLetSection, n.info)
+  let tempAsNode = newSymNode(temp)
+
+  var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
+  vpart.sons[0] = tempAsNode
+  vpart.sons[1] = ast.emptyNode
+  vpart.sons[2] = n
+  add(v, vpart)
+
+  result.add v
+  result.add genReset(n, c)
+  result.add tempAsNode
+  incl result.flags, nfPreventDestructor
+
+proc handleSinkParams(n: PNode; c: var Con) =
+  # first pass: introduce copies for stuff passed to
+  # 'sink' parameters. Introduce destructor guards for
+  # 'sink' parameters.
+  assert n.kind in nkCallKinds
+  # Rule 5.2: Compensate for 'sink' parameters with copies
+  # at the callsite (unless of course we can prove its the
+  # last read):
+  let parameters = n.typ
+  let L = if parameters != nil: parameters.len else: 0
+  for i in 1 ..< L:
+    let t = parameters[i]
+    if t.kind == tySink:
+      if n[i].kind in constrExprs:
+        incl(n[i].flags, nfPreventDestructor)
+      elif n[i].kind == nkSym and isHarmlessVar(n[i].sym, c):
+        # if x is a variable and it its last read we eliminate its
+        # destructor invokation, but don't. We need to reset its memory
+        # to disable its destructor which we have not elided:
+        n.sons[i] = destructiveMoveVar(n[i], c)
+        when false:
+          # XXX we need to find a way to compute "all paths consume 'x'"
+          c.symsNoDestructors.incl n[i].sym.id
+          # however, not emiting the copy operation is correct here.
+      elif n[i].kind == nkSym and isSinkParam(n[i].sym):
+        # mark the sink parameter as used:
+        n.sons[i] = destructiveMoveSink(n[i], c)
+      else:
+        # an object that is not temporary but passed to a 'sink' parameter
+        # results in a copy.
+        n.sons[i] = passCopyToSink(n[i], c)
+
 proc p(n: PNode; c: var Con): PNode =
   case n.kind
   of nkVarSection, nkLetSection:
@@ -266,22 +401,21 @@ proc p(n: PNode; c: var Con): PNode =
         varSection.add itCopy
         result.add varSection
   of nkCallKinds:
-    if n.typ != nil and hasDestructor(n.typ):
+    if n.typ != nil and hasDestructor(n.typ) and nfPreventDestructor notin n.flags:
       discard "produce temp creation"
       result = newNodeIT(nkStmtListExpr, n.info, n.typ)
-      let f = newSym(skField, getIdent(":d" & $c.tmpObj.n.len), c.owner, n.info)
-      f.typ = n.typ
-      rawAddField c.tmpObj, f
-      var m = genSink(n.typ, rawDirectAccess(c.tmp, f))
+      let tmp = getTemp(c, n.typ, n.info)
+      var m = genSink(n.typ, tmp)
       var call = copyNode(n)
       recurse(n, call)
       m.add call
       result.add m
-      result.add rawDirectAccess(c.tmp, f)
-      c.destroys.add genDestroy(n.typ, rawDirectAccess(c.tmp, f))
+      result.add tmp
+      c.destroys.add genDestroy(n.typ, tmp)
     else:
       result = copyNode(n)
       recurse(n, result)
+    #handleSinkParams(result, c)
   of nkAsgn, nkFastAsgn:
     if hasDestructor(n[0].typ):
       result = moveOrCopy(n[0], n[1], c)
@@ -311,6 +445,11 @@ proc injectDestructorCalls*(owner: PSym; n: PNode): PNode =
   for i in 0..<c.g.len:
     if c.g[i].kind in {goto, fork}:
       c.jumpTargets.incl(i+c.g[i].dest)
+  if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}:
+    let params = owner.typ.n
+    for i in 1 ..< params.len:
+      let param = params[i].sym
+      if param.typ.kind == tySink: registerDropBit(c, param)
   let body = p(n, c)
   if c.tmp.typ.n.len > 0:
     c.addTopVar(newSymNode c.tmp)
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index 09413128b..70504cfc9 100644
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -134,7 +134,7 @@ type
     hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath,
     hintConditionAlwaysTrue, hintName, hintPattern,
     hintExecuting, hintLinking, hintDependency,
-    hintSource, hintStackTrace, hintGCStats,
+    hintSource, hintPerformance, hintStackTrace, hintGCStats,
     hintUser, hintUserRaw
 
 const
@@ -438,6 +438,7 @@ const
     hintLinking: "",
     hintDependency: "$1",
     hintSource: "$1",
+    hintPerformance: "$1",
     hintStackTrace: "$1",
     hintGCStats: "$1",
     hintUser: "$1",
@@ -460,7 +461,7 @@ const
     "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
     "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf",
     "Path", "CondTrue", "Name", "Pattern", "Exec", "Link", "Dependency",
-    "Source", "StackTrace", "GCStats",
+    "Source", "Performance", "StackTrace", "GCStats",
     "User", "UserRaw"]
 
 const
diff --git a/compiler/options.nim b/compiler/options.nim
index 65f5a6245..be13e15d7 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -25,7 +25,7 @@ type                          # please make sure we have under 32 options
   TOption* = enum             # **keep binary compatible**
     optNone, optObjCheck, optFieldCheck, optRangeCheck, optBoundsCheck,
     optOverflowCheck, optNilCheck,
-    optNaNCheck, optInfCheck,
+    optNaNCheck, optInfCheck, optMoveCheck,
     optAssert, optLineDir, optWarns, optHints,
     optOptimizeSpeed, optOptimizeSize, optStackTrace, # stack tracing support
     optLineTrace,             # line tracing support (includes stack tracing)
@@ -119,13 +119,14 @@ var
 
 const
   ChecksOptions* = {optObjCheck, optFieldCheck, optRangeCheck, optNilCheck,
-    optOverflowCheck, optBoundsCheck, optAssert, optNaNCheck, optInfCheck}
+    optOverflowCheck, optBoundsCheck, optAssert, optNaNCheck, optInfCheck,
+    optMoveCheck}
 
 var
   gOptions*: TOptions = {optObjCheck, optFieldCheck, optRangeCheck,
                          optBoundsCheck, optOverflowCheck, optAssert, optWarns,
                          optHints, optStackTrace, optLineTrace,
-                         optPatterns, optNilCheck}
+                         optPatterns, optNilCheck, optMoveCheck}
   gGlobalOptions*: TGlobalOptions = {optThreadAnalysis}
   gExitcode*: int8
   gCmd*: TCommands = cmdNone  # the command
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 1b8078628..1295ee18d 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -40,7 +40,8 @@ const
     wTags, wLocks, wGcSafe, wExportNims, wUsed}
   exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe}
   stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks,
-    wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints,
+    wBoundchecks, wOverflowchecks, wNilchecks, wMovechecks, wAssertions,
+    wWarnings, wHints,
     wLinedir, wStacktrace, wLinetrace, wOptimization, wHint, wWarning, wError,
     wFatal, wDefine, wUndef, wCompile, wLink, wLinksys, wPure, wPush, wPop,
     wBreakpoint, wWatchPoint, wPassl, wPassc, wDeadCodeElim, wDeprecated,
@@ -323,6 +324,7 @@ proc processOption(c: PContext, n: PNode): bool =
     of wFloatchecks: onOff(c, n, {optNaNCheck, optInfCheck})
     of wNanChecks: onOff(c, n, {optNaNCheck})
     of wInfChecks: onOff(c, n, {optInfCheck})
+    of wMovechecks: onOff(c, n, {optMoveCheck})
     of wAssertions: onOff(c, n, {optAssert})
     of wWarnings: onOff(c, n, {optWarns})
     of wHints: onOff(c, n, {optHints})
@@ -693,7 +695,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
     inc c.instCounter
     if c.instCounter > 100:
       globalError(it.info, errRecursiveDependencyX, userPragma.name.s)
-    
+
     pragma(c, sym, userPragma.ast, validPragmas)
     n.sons[i..i] = userPragma.ast.sons # expand user pragma with its content
     i.inc(userPragma.ast.len - 1) # inc by -1 is ok, user pragmas was empty
@@ -906,7 +908,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
       of wCodegenDecl: processCodegenDecl(c, it, sym)
       of wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks,
          wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints,
-         wLinedir, wStacktrace, wLinetrace, wOptimization,
+         wLinedir, wStacktrace, wLinetrace, wOptimization, wMovechecks,
          wCallconv,
          wDebugger, wProfiler, wFloatchecks, wNanChecks, wInfChecks,
          wPatterns:
diff --git a/compiler/semobjconstr.nim b/compiler/semobjconstr.nim
index 63a4eb99a..e2296a0d2 100644
--- a/compiler/semobjconstr.nim
+++ b/compiler/semobjconstr.nim
@@ -76,7 +76,7 @@ proc semConstrField(c: PContext, flags: TExprFlags,
     return initValue
 
 proc caseBranchMatchesExpr(branch, matched: PNode): bool =
-  for i in 0 .. (branch.len - 2):
+  for i in 0 .. branch.len-2:
     if exprStructuralEquivalent(branch[i], matched):
       return true
 
diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim
index e458cad03..76d91d4e7 100644
--- a/compiler/wordrecg.nim
+++ b/compiler/wordrecg.nim
@@ -52,7 +52,7 @@ type
     wNimcall, wStdcall, wCdecl, wSafecall, wSyscall, wInline, wNoInline,
     wFastcall, wClosure, wNoconv, wOn, wOff, wChecks, wRangechecks,
     wBoundchecks, wOverflowchecks, wNilchecks,
-    wFloatchecks, wNanChecks, wInfChecks,
+    wFloatchecks, wNanChecks, wInfChecks, wMoveChecks,
     wAssertions, wPatterns, wWarnings,
     wHints, wOptimization, wRaises, wWrites, wReads, wSize, wEffects, wTags,
     wDeadCodeElim, wSafecode, wPackage, wNoForward, wReorder, wNoRewrite,
@@ -139,7 +139,7 @@ const
     "cdecl", "safecall", "syscall", "inline", "noinline", "fastcall", "closure",
     "noconv", "on", "off", "checks", "rangechecks", "boundchecks",
     "overflowchecks", "nilchecks",
-    "floatchecks", "nanchecks", "infchecks",
+    "floatchecks", "nanchecks", "infchecks", "movechecks",
 
     "assertions", "patterns", "warnings", "hints",
     "optimization", "raises", "writes", "reads", "size", "effects", "tags",
diff --git a/lib/system.nim b/lib/system.nim
index 7116f8818..7eda30276 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -617,6 +617,11 @@ type
     ##
     ## This is only raised if the ``segfaults.nim`` module was imported!
 
+when defined(nimNewRuntime):
+  type
+    MoveError* = object of SystemError ## \
+      ## Raised on attempts to re-sink an already consumed ``sink`` parameter.
+
 {.deprecated: [TObject: RootObj, PObject: RootRef, TEffect: RootEffect,
   FTime: TimeEffect, FIO: IOEffect, FReadIO: ReadIOEffect,
   FWriteIO: WriteIOEffect, FExecIO: ExecIOEffect,
diff --git a/lib/system/chcks.nim b/lib/system/chcks.nim
index 69b680dbd..d3651f659 100644
--- a/lib/system/chcks.nim
+++ b/lib/system/chcks.nim
@@ -52,6 +52,11 @@ proc chckNil(p: pointer) =
   if p == nil:
     sysFatal(NilAccessError, "attempt to write to a nil address")
 
+when defined(nimNewRuntime):
+  proc chckMove(b: bool) {.compilerproc.} =
+    if not b:
+      sysFatal(MoveError, "attempt to access an object that was moved")
+
 proc chckNilDisp(p: pointer) {.compilerproc.} =
   if p == nil:
     sysFatal(NilAccessError, "cannot dispatch; dispatcher is nil")