diff options
author | Araq <rumpf_a@web.de> | 2018-03-30 02:32:13 +0200 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2018-03-30 02:32:13 +0200 |
commit | 9de05ec3e04e8a2321b29896fe9fa7db4c6e65f3 (patch) | |
tree | 8667e2a5c64d93b29b748feaf29066d64fa925d1 | |
parent | 1d9343080de8e19835c3f6568630ba759afbb94f (diff) | |
download | Nim-9de05ec3e04e8a2321b29896fe9fa7db4c6e65f3.tar.gz |
further steps in implementing sink parameters; refs #7041
-rw-r--r-- | compiler/ast.nim | 2 | ||||
-rw-r--r-- | compiler/commands.nim | 2 | ||||
-rw-r--r-- | compiler/destroyer.nim | 157 | ||||
-rw-r--r-- | compiler/msgs.nim | 5 | ||||
-rw-r--r-- | compiler/options.nim | 7 | ||||
-rw-r--r-- | compiler/pragmas.nim | 8 | ||||
-rw-r--r-- | compiler/semobjconstr.nim | 2 | ||||
-rw-r--r-- | compiler/wordrecg.nim | 4 | ||||
-rw-r--r-- | lib/system.nim | 5 | ||||
-rw-r--r-- | lib/system/chcks.nim | 5 |
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") |