summary refs log tree commit diff stats
path: root/compiler/cgmeth.nim
blob: 1d72952e20cca8a44b858b5f45edd711bc254fe8 (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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#
#
#           The Nim Compiler
#        (c) Copyright 2013 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module implements code generation for multi methods.

import
  intsets, options, ast, astalgo, msgs, idents, renderer, types, magicsys,
  sempass2, strutils, modulegraphs, configuration

proc genConv(n: PNode, d: PType, downcast: bool; conf: ConfigRef): PNode =
  var dest = skipTypes(d, abstractPtrs)
  var source = skipTypes(n.typ, abstractPtrs)
  if (source.kind == tyObject) and (dest.kind == tyObject):
    var diff = inheritanceDiff(dest, source)
    if diff == high(int):
      # no subtype relation, nothing to do
      result = n
    elif diff < 0:
      result = newNodeIT(nkObjUpConv, n.info, d)
      addSon(result, n)
      if downcast: internalError(conf, n.info, "cgmeth.genConv: no upcast allowed")
    elif diff > 0:
      result = newNodeIT(nkObjDownConv, n.info, d)
      addSon(result, n)
      if not downcast:
        internalError(conf, n.info, "cgmeth.genConv: no downcast allowed")
    else:
      result = n
  else:
    result = n

proc getDispatcher*(s: PSym): PSym =
  ## can return nil if is has no dispatcher.
  let dispn = lastSon(s.ast)
  if dispn.kind == nkSym:
    let disp = dispn.sym
    if sfDispatcher in disp.flags: result = disp

proc methodCall*(n: PNode; conf: ConfigRef): PNode =
  result = n
  # replace ordinary method by dispatcher method:
  let disp = getDispatcher(result.sons[0].sym)
  if disp != nil:
    result.sons[0].sym = disp
    # change the arguments to up/downcasts to fit the dispatcher's parameters:
    for i in countup(1, sonsLen(result)-1):
      result.sons[i] = genConv(result.sons[i], disp.typ.sons[i], true, conf)
  else:
    localError(conf, n.info, "'" & $result.sons[0] & "' lacks a dispatcher")

type
  MethodResult = enum No, Invalid, Yes

proc sameMethodBucket(a, b: PSym): MethodResult =
  if a.name.id != b.name.id: return
  if sonsLen(a.typ) != sonsLen(b.typ):
    return

  for i in countup(1, sonsLen(a.typ) - 1):
    var aa = a.typ.sons[i]
    var bb = b.typ.sons[i]
    while true:
      aa = skipTypes(aa, {tyGenericInst, tyAlias})
      bb = skipTypes(bb, {tyGenericInst, tyAlias})
      if aa.kind == bb.kind and aa.kind in {tyVar, tyPtr, tyRef, tyLent}:
        aa = aa.lastSon
        bb = bb.lastSon
      else:
        break
    if sameType(aa, bb):
      if aa.kind == tyObject and result != Invalid:
        result = Yes
    elif aa.kind == tyObject and bb.kind == tyObject:
      let diff = inheritanceDiff(bb, aa)
      if diff < 0:
        if result != Invalid:
          result = Yes
        else:
          return No
      elif diff != high(int):
        result = Invalid
      else:
        return No
    else:
      return No
  if result == Yes:
    # check for return type:
    if not sameTypeOrNil(a.typ.sons[0], b.typ.sons[0]):
      if b.typ.sons[0] != nil and b.typ.sons[0].kind == tyExpr:
        # infer 'auto' from the base to make it consistent:
        b.typ.sons[0] = a.typ.sons[0]
      else:
        return No

proc attachDispatcher(s: PSym, dispatcher: PNode) =
  var L = s.ast.len-1
  var x = s.ast.sons[L]
  if x.kind == nkSym and sfDispatcher in x.sym.flags:
    # we've added a dispatcher already, so overwrite it
    s.ast.sons[L] = dispatcher
  else:
    s.ast.add(dispatcher)

proc createDispatcher(s: PSym): PSym =
  var disp = copySym(s)
  incl(disp.flags, sfDispatcher)
  excl(disp.flags, sfExported)
  disp.typ = copyType(disp.typ, disp.typ.owner, false)
  # we can't inline the dispatcher itself (for now):
  if disp.typ.callConv == ccInline: disp.typ.callConv = ccDefault
  disp.ast = copyTree(s.ast)
  disp.ast.sons[bodyPos] = ast.emptyNode
  disp.loc.r = nil
  if s.typ.sons[0] != nil:
    if disp.ast.sonsLen > resultPos:
      disp.ast.sons[resultPos].sym = copySym(s.ast.sons[resultPos].sym)
    else:
      # We've encountered a method prototype without a filled-in
      # resultPos slot. We put a placeholder in there that will
      # be updated in fixupDispatcher().
      disp.ast.addSon(ast.emptyNode)
  attachDispatcher(s, newSymNode(disp))
  # attach to itself to prevent bugs:
  attachDispatcher(disp, newSymNode(disp))
  return disp

proc fixupDispatcher(meth, disp: PSym; conf: ConfigRef) =
  # We may have constructed the dispatcher from a method prototype
  # and need to augment the incomplete dispatcher with information
  # from later definitions, particularly the resultPos slot. Also,
  # the lock level of the dispatcher needs to be updated/checked
  # against that of the method.
  if disp.ast.sonsLen > resultPos and meth.ast.sonsLen > resultPos and
     disp.ast.sons[resultPos] == ast.emptyNode:
    disp.ast.sons[resultPos] = copyTree(meth.ast.sons[resultPos])

  # The following code works only with lock levels, so we disable
  # it when they're not available.
  when declared(TLockLevel):
    proc `<`(a, b: TLockLevel): bool {.borrow.}
    proc `==`(a, b: TLockLevel): bool {.borrow.}
    if disp.typ.lockLevel == UnspecifiedLockLevel:
      disp.typ.lockLevel = meth.typ.lockLevel
    elif meth.typ.lockLevel != UnspecifiedLockLevel and
         meth.typ.lockLevel != disp.typ.lockLevel:
      message(conf, meth.info, warnLockLevel,
        "method has lock level $1, but another method has $2" %
        [$meth.typ.lockLevel, $disp.typ.lockLevel])
      # XXX The following code silences a duplicate warning in
      # checkMethodeffects() in sempass2.nim for now.
      if disp.typ.lockLevel < meth.typ.lockLevel:
        disp.typ.lockLevel = meth.typ.lockLevel

proc methodDef*(g: ModuleGraph; s: PSym, fromCache: bool) =
  let L = len(g.methods)
  var witness: PSym
  for i in countup(0, L - 1):
    let disp = g.methods[i].dispatcher
    case sameMethodBucket(disp, s)
    of Yes:
      add(g.methods[i].methods, s)
      attachDispatcher(s, lastSon(disp.ast))
      fixupDispatcher(s, disp, g.config)
      #echo "fixup ", disp.name.s, " ", disp.id
      when useEffectSystem: checkMethodEffects(g, disp, s)
      if {sfBase, sfFromGeneric} * s.flags == {sfBase} and
           g.methods[i].methods[0] != s:
        # already exists due to forwarding definition?
        localError(g.config, s.info, "method is not a base")
      return
    of No: discard
    of Invalid:
      if witness.isNil: witness = g.methods[i].methods[0]
  # create a new dispatcher:
  add(g.methods, (methods: @[s], dispatcher: createDispatcher(s)))
  #echo "adding ", s.info
  #if fromCache:
  #  internalError(s.info, "no method dispatcher found")
  if witness != nil:
    localError(g.config, s.info, "invalid declaration order; cannot attach '" & s.name.s &
                       "' to method defined here: " & $witness.info)
  elif sfBase notin s.flags:
    message(g.config, s.info, warnUseBase)

proc relevantCol(methods: TSymSeq, col: int): bool =
  # returns true iff the position is relevant
  var t = methods[0].typ.sons[col].skipTypes(skipPtrs)
  if t.kind == tyObject:
    for i in countup(1, high(methods)):
      let t2 = skipTypes(methods[i].typ.sons[col], skipPtrs)
      if not sameType(t2, t):
        return true

proc cmpSignatures(a, b: PSym, relevantCols: IntSet): int =
  for col in countup(1, sonsLen(a.typ) - 1):
    if contains(relevantCols, col):
      var aa = skipTypes(a.typ.sons[col], skipPtrs)
      var bb = skipTypes(b.typ.sons[col], skipPtrs)
      var d = inheritanceDiff(aa, bb)
      if (d != high(int)) and d != 0:
        return d

proc sortBucket(a: var TSymSeq, relevantCols: IntSet) =
  # we use shellsort here; fast and simple
  var n = len(a)
  var h = 1
  while true:
    h = 3 * h + 1
    if h > n: break
  while true:
    h = h div 3
    for i in countup(h, n - 1):
      var v = a[i]
      var j = i
      while cmpSignatures(a[j - h], v, relevantCols) >= 0:
        a[j] = a[j - h]
        j = j - h
        if j < h: break
      a[j] = v
    if h == 1: break

proc genDispatcher(g: ModuleGraph; methods: TSymSeq, relevantCols: IntSet): PSym =
  var base = lastSon(methods[0].ast).sym
  result = base
  var paramLen = sonsLen(base.typ)
  var nilchecks = newNodeI(nkStmtList, base.info)
  var disp = newNodeI(nkIfStmt, base.info)
  var ands = getSysSym(g, unknownLineInfo(), "and")
  var iss = getSysSym(g, unknownLineInfo(), "of")
  let boolType = getSysType(g, unknownLineInfo(), tyBool)
  for col in countup(1, paramLen - 1):
    if contains(relevantCols, col):
      let param = base.typ.n.sons[col].sym
      if param.typ.skipTypes(abstractInst).kind in {tyRef, tyPtr}:
        addSon(nilchecks, newTree(nkCall,
            newSymNode(getCompilerProc(g, "chckNilDisp")), newSymNode(param)))
  for meth in countup(0, high(methods)):
    var curr = methods[meth]      # generate condition:
    var cond: PNode = nil
    for col in countup(1, paramLen - 1):
      if contains(relevantCols, col):
        var isn = newNodeIT(nkCall, base.info, boolType)
        addSon(isn, newSymNode(iss))
        let param = base.typ.n.sons[col].sym
        addSon(isn, newSymNode(param))
        addSon(isn, newNodeIT(nkType, base.info, curr.typ.sons[col]))
        if cond != nil:
          var a = newNodeIT(nkCall, base.info, boolType)
          addSon(a, newSymNode(ands))
          addSon(a, cond)
          addSon(a, isn)
          cond = a
        else:
          cond = isn
    let retTyp = base.typ.sons[0]
    let call = newNodeIT(nkCall, base.info, retTyp)
    addSon(call, newSymNode(curr))
    for col in countup(1, paramLen - 1):
      addSon(call, genConv(newSymNode(base.typ.n.sons[col].sym),
                           curr.typ.sons[col], false, g.config))
    var ret: PNode
    if retTyp != nil:
      var a = newNodeI(nkFastAsgn, base.info)
      addSon(a, newSymNode(base.ast.sons[resultPos].sym))
      addSon(a, call)
      ret = newNodeI(nkReturnStmt, base.info)
      addSon(ret, a)
    else:
      ret = call
    if cond != nil:
      var a = newNodeI(nkElifBranch, base.info)
      addSon(a, cond)
      addSon(a, ret)
      addSon(disp, a)
    else:
      disp = ret
  nilchecks.add disp
  result.ast.sons[bodyPos] = nilchecks

proc generateMethodDispatchers*(g: ModuleGraph): PNode =
  result = newNode(nkStmtList)
  for bucket in countup(0, len(g.methods) - 1):
    var relevantCols = initIntSet()
    for col in countup(1, sonsLen(g.methods[bucket].methods[0].typ) - 1):
      if relevantCol(g.methods[bucket].methods, col): incl(relevantCols, col)
    sortBucket(g.methods[bucket].methods, relevantCols)
    addSon(result,
           newSymNode(genDispatcher(g, g.methods[bucket].methods, relevantCols)))