diff --git a/compiler/writetracking.nim b/compiler/writetracking.nim index db3e6c53a..38258b07a 100644 --- a/compiler/writetracking.nim +++ b/compiler/writetracking.nim @@ -9,8 +9,13 @@ ## This module implements the write tracking analysis. Read my block post for ## a basic description of the algorithm and ideas. +## The algorithm operates in 2 phases: +## +## * Collecting information about assignments (and pass-by-var calls). +## * Computing an aliasing relation based on the assignments. This relation +## is then used to compute the 'writes' and 'escapes' effects. -import idents, ast, astalgo, trees, renderer, msgs, types +import intsets, idents, ast, astalgo, trees, renderer, msgs, types const debug = false @@ -24,106 +29,33 @@ type newNone, newLit, newCall + RootInfo = enum + rootIsResultOrParam, + rootIsHeapAccess, + rootIsSym, + markAsWrittenTo, + markAsEscaping + + Assignment = object # \ + # Note that the transitive closures MUST be computed in + # phase 2 of the algorithm. + dest, src: seq[ptr TSym] # we use 'ptr' here to save RC ops and GC cycles + destNoTc, srcNoTc: int # length of 'dest', 'src' without the + # transitive closure + destInfo: set[RootInfo] + info: TLineInfo + W = object # WriteTrackContext owner: PSym returnsNew: AssignToResult # assignments to 'result' - markAsWrittenTo, markAsEscaping: PNode - assignments: seq[(PNode, PNode)] # list of all assignments in this proc + assignments: seq[Assignment] # 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) .. 0 and a.dest[0] == x and rootIsSym in a.destInfo: + # x = f(..., y, ....) + for i in 0 ..< a.srcNoTc: addNoDup a.src[i] -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 markWriteOrEscape(w: var W) = + ## Both 'writes' and 'escapes' effects ultimately only care + ## about *parameters*. + ## However, due to aliasing, even locals that might not look as parameters + ## have to count as parameters if they can alias a parameter: + ## + ## .. code-block:: nim + ## proc modifies(n: Node) {.writes: [n].} = + ## let x = n + ## x.data = "abc" + ## + ## We call a symbol *parameter-like* if it is a parameter or can alias a + ## parameter. + ## Let ``p``, ``q`` be *parameter-like* and ``x``, ``y`` be general + ## expressions. + ## + ## A write then looks like ``p[] = x``. + ## An escape looks like ``p[] = q`` or more generally + ## like ``p[] = f(q)`` where ``f`` can forward ``q``. + for a in mitems(w.assignments): + if a.destInfo != {}: + possibleAliases(w, a.dest) -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 {rootIsHeapAccess, markAsWrittenTo} * a.destInfo != {}: + for p in a.dest: + if p.kind == skParam and p.owner == w.owner: + incl(p.flags, sfWrittenTo) - if (r.len > 0) and (info != {} or src == w.markAsEscaping): - possibleAliases(w, r) + if {rootIsResultOrParam, rootIsHeapAccess, markAsEscaping}*a.destInfo != {}: var destIsParam = false - for a in r: - if a.kind in {skResult, skParam} and a.owner == w.owner: + for p in a.dest: + if p.kind in {skResult, skParam} and p.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) + possibleAliases(w, a.src) + for p in a.src: + if p.kind == skParam and p.owner == w.owner: + incl(p.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 = @[] + # Phase 1: Collect and preprocess any assignments in the proc body: deps(w, body) - markDirty(w) - markEscaping(w) + # Phase 2: Compute the 'writes' and 'escapes' effects: + markWriteOrEscape(w) if w.returnsNew != asgnOther and not isEmptyType(owner.typ.sons[0]) and containsGarbageCollectedRef(owner.typ.sons[0]): incl(owner.typ.flags, tfReturnsNew) -