summary refs log tree commit diff stats
path: root/compiler/writetracking.nim
blob: 141b496c13a25b45e5ad91e7fbce5ba90fc94131 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74<
import unittest, typetraits

type
  TFoo[T, U] = object
    x: T
    y: U

proc getTypeName1(t: typedesc): string = t.name
proc getTypeName2(t:pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#
#
#           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..<n.safeLen:
      allRoots(n.sons[i], result, info)

proc addAsgn(a: var Assignment; dest, src: PNode; destInfo: set[RootInfo]) =
  a.dest = @[]
  a.src = @[]
  a.destInfo = destInfo
  allRoots(dest, a.dest, a.destInfo)
  if dest.kind == nkSym: incl(a.destInfo, rootIsSym)
  if src != nil:
    var dummy: set[RootInfo]
    allRoots(src, a.src, dummy)
  a.destNoTc = a.dest.len
  a.srcNoTc = a.src.len
  a.info = dest.info
  #echo "ADDING ", dest.info, " ", a.destInfo

proc srcHasSym(a: Assignment; x: ptr TSym): bool =
  for i in 0 ..< a.srcNoTc:
    if a.src[i] == x: return true

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) .. <n.len:
      let x = returnsNewExpr(n.sons[i])
      case x
      of newNone: return newNone
      of newLit: discard
      of newCall: result = newCall
  of nkCallKinds:
    if n.sons[0].typ != nil and tfReturnsNew in n.sons[0].typ.flags:
      result = newCall
  else:
    result = newNone

proc deps(w: var W; dest, src: PNode; destInfo: set[RootInfo]) =
  # let x = (localA, localB)
  # compute 'returnsNew' property:
  let retNew = if src.isNil: newNone else: returnsNewExpr(src)
  if dest.kind == nkSym and dest.sym.kind == skResult:
    if retNew != newNone:
      if w.returnsNew != asgnOther: w.returnsNew = asgnNew
    else:
      w.returnsNew = asgnOther
  # mark the dependency, but
  # rule out obviously innocent assignments like 'somebool = true'
  if dest.kind == nkSym and retNew == newLit: discard
  else:
    let L = w.assignments.len
    w.assignments.setLen(L+1)
    addAsgn(w.assignments[L], dest, src, destInfo)

proc depsArgs(w: var W; n: PNode) =
  if n.sons[0].typ.isNil: return
  var typ = skipTypes(n.sons[0].typ, abstractInst)
  if typ.kind != tyProc: return
  # echo n.info, " ", n, " ", w.owner.name.s, " ", typeToString(typ)
  assert(sonsLen(typ) == sonsLen(typ.n))
  for i in 1 ..< n.len:
    let it = n.sons[i]
    if i < sonsLen(typ):
      assert(typ.n.sons[i].kind == nkSym)
      let paramType = typ.n.sons[i]
      if paramType.typ.isCompileTimeOnly: continue
      var destInfo: set[RootInfo] = {}
      if sfWrittenTo in paramType.sym.flags or paramType.typ.kind == tyVar:
        # p(f(x, y), X, g(h, z))
        destInfo.incl markAsWrittenTo
      if sfEscapes in paramType.sym.flags:
        destInfo.incl markAsEscaping
      if destInfo != {}:
        deps(w, it, nil, destInfo)

proc deps(w: var W; n: PNode) =
  case n.kind
  of nkLetSection, nkVarSection:
    for child in n:
      let last = lastSon(child)
      if last.kind == nkEmpty: continue
      if child.kind == nkVarTuple and last.kind == nkPar:
        internalAssert child.len-2 == last.len
        for i in 0 .. child.len-3:
          deps(w, child.sons[i], last.sons[i], {})
      else:
        for i in 0 .. child.len-3:
          deps(w, child.sons[i], last, {})
  of nkAsgn, nkFastAsgn:
    deps(w, n.sons[0], n.sons[1], {})
  else:
    for i in 0 ..< n.safeLen:
      deps(w, n.sons[i])
    if n.kind in nkCallKinds:
      if getMagic(n) in {mNew, mNewFinalize, mNewSeq}:
        # may not look like an assignment, but it is:
        deps(w, n.sons[1], newNodeIT(nkObjConstr, n.info, n.sons[1].typ), {})
      else:
        depsArgs(w, n)

proc possibleAliases(w: var W; result: var seq[ptr TSym]) =
  # this is an expensive fixpoint iteration. We could speed up this analysis
  # by a smarter data-structure but we wait until profiling shows us it's
  # expensive. Usually 'w.assignments' is small enough.
  var alreadySeen = initIntSet()
  template addNoDup(x) =
    if not alreadySeen.containsOrIncl(x.id): result.add x
  for x in result: alreadySeen.incl x.id

  var todo = 0
  while todo < result.len:
    let x = result[todo]
    inc todo
    for a in mitems(w.assignments):
      #if a.srcHasSym(x):
      #  # y = f(..., x, ...)
      #  for i in 0 ..< a.destNoTc: addNoDup a.dest[i]
      if a.destNoTc > 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 a in mitems(w.assignments):
    if a.destInfo != {}:
      possibleAliases(w, a.dest)

    if {rootIsHeapAccess, markAsWrittenTo} * a.destInfo != {}:
      for p in a.dest:
        if p.kind == skParam and p.owner == w.owner:
          incl(p.flags, sfWrittenTo)

    if {rootIsResultOrParam, rootIsHeapAccess, markAsEscaping}*a.destInfo != {}:
      var destIsParam = false
      for p in a.dest:
        if p.kind in {skResult, skParam} and p.owner == w.owner:
          destIsParam = true
          break
      if destIsParam:
        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.assignments = @[]
  # Phase 1: Collect and preprocess any assignments in the proc body:
  deps(w, body)
  # 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)