# # # 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. ## 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 intsets, 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 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' assignments: seq[Assignment] # list of all assignments in this proc proc allRoots(n: PNode; result: var seq[ptr TSym]; info: var set[RootInfo]) = case n.kind of nkSym: if n.sym.kind in {skParam, skVar, skTemp, skLet, skResult, skForVar}: if n.sym.kind in {skResult, skParam}: incl(info, rootIsResultOrParam) result.add(cast[ptr TSym](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.. 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 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 i in 0..