further steps in implementing sink parameters; refs #7041

This commit is contained in:
Araq
2018-03-30 02:32:13 +02:00
parent 1d9343080d
commit 9de05ec3e0
10 changed files with 177 additions and 20 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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",

View File

@@ -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,

View File

@@ -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")