summary refs log tree commit diff stats
path: root/compiler/isolation_check.nim
blob: 2674605dcf869eb4ab079ffbda62521806a49253 (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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#
#
#           The Nim Compiler
#        (c) Copyright 2020 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Implementation of the check that `recover` needs, see
## https://github.com/nim-lang/RFCs/issues/244 for more details.

import
  ast, types, renderer, intsets

when defined(nimPreviewSlimSystem):
  import std/assertions

proc canAlias(arg, ret: PType; marker: var IntSet): bool

proc canAliasN(arg: PType; n: PNode; marker: var IntSet): bool =
  case n.kind
  of nkRecList:
    for i in 0..<n.len:
      result = canAliasN(arg, n[i], marker)
      if result: return
  of nkRecCase:
    assert(n[0].kind == nkSym)
    result = canAliasN(arg, n[0], marker)
    if result: return
    for i in 1..<n.len:
      case n[i].kind
      of nkOfBranch, nkElse:
        result = canAliasN(arg, lastSon(n[i]), marker)
        if result: return
      else: discard
  of nkSym:
    result = canAlias(arg, n.sym.typ, marker)
  else: discard

proc canAlias(arg, ret: PType; marker: var IntSet): bool =
  if containsOrIncl(marker, ret.id):
    return false

  if ret.kind in {tyPtr, tyPointer}:
    # unsafe so we don't care:
    return false
  if compareTypes(arg, ret, dcEqIgnoreDistinct):
    return true
  case ret.kind
  of tyObject:
    if isFinal(ret):
      result = canAliasN(arg, ret.n, marker)
      if not result and ret.len > 0 and ret[0] != nil:
        result = canAlias(arg, ret[0], marker)
    else:
      result = true
  of tyTuple:
    for i in 0..<ret.len:
      result = canAlias(arg, ret[i], marker)
      if result: break
  of tyArray, tySequence, tyDistinct, tyGenericInst,
     tyAlias, tyInferred, tySink, tyLent, tyOwned, tyRef:
    result = canAlias(arg, ret.lastSon, marker)
  of tyProc:
    result = ret.callConv == ccClosure
  else:
    result = false

proc isValueOnlyType(t: PType): bool =
  # t doesn't contain pointers and references
  proc wrap(t: PType): bool {.nimcall.} = t.kind in {tyRef, tyPtr, tyVar, tyLent}
  result = not types.searchTypeFor(t, wrap)

proc canAlias*(arg, ret: PType): bool =
  if isValueOnlyType(arg):
    # can alias only with addr(arg.x) and we don't care if it is not safe
    result = false
  else:
    var marker = initIntSet()
    result = canAlias(arg, ret, marker)

proc containsVariable(n: PNode): bool =
  case n.kind
  of nodesToIgnoreSet:
    result = false
  of nkSym:
    result = n.sym.kind in {skForVar, skParam, skVar, skLet, skConst, skResult, skTemp}
  else:
    for ch in n:
      if containsVariable(ch): return true
    result = false

proc checkIsolate*(n: PNode): bool =
  if types.containsTyRef(n.typ):
    # XXX Maybe require that 'n.typ' is acyclic. This is not much
    # worse than the already exisiting inheritance and closure restrictions.
    case n.kind
    of nkCharLit..nkNilLit:
      result = true
    of nkCallKinds:
      # XXX: as long as we don't update the analysis while examining arguments
      #      we can do an early check of the return type, otherwise this is a
      #      bug and needs to be moved below
      if tfNoSideEffect notin n[0].typ.flags:
        return false
      for i in 1..<n.len:
        if checkIsolate(n[i]):
          discard "fine, it is isolated already"
        else:
          let argType = n[i].typ
          if argType != nil and not isCompileTimeOnly(argType) and containsTyRef(argType):
            if argType.canAlias(n.typ) or containsVariable(n[i]):
              # bug #19013: Alias information is not enough, we need to check for potential
              # "overlaps". I claim the problem can only happen by reading again from a location
              # that materialized which is only possible if a variable that contains a `ref`
              # is involved.
              return false
      result = true
    of nkIfStmt, nkIfExpr:
      for it in n:
        result = checkIsolate(it.lastSon)
        if not result: break
    of nkCaseStmt:
      for i in 1..<n.len:
        result = checkIsolate(n[i].lastSon)
        if not result: break
    of nkObjConstr:
      result = true
      for i in 1..<n.len:
        result = checkIsolate(n[i].lastSon)
        if not result: break
    of nkBracket, nkTupleConstr, nkPar:
      for it in n:
        result = checkIsolate(it)
        if not result: break
    of nkHiddenStdConv, nkHiddenSubConv, nkCast, nkConv:
      result = checkIsolate(n[1])
    of nkObjUpConv, nkObjDownConv, nkDotExpr:
      result = checkIsolate(n[0])
    of nkStmtList, nkStmtListExpr:
      if n.len > 0:
        result = checkIsolate(n[^1])
      else:
        result = false
    else:
      # unanalysable expression:
      result = false
  else:
    # no ref, no cry:
    result = true