cgmeth.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2013 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements code generation for multi methods.
  10. import
  11. intsets, options, ast, astalgo, msgs, idents, renderer, types, magicsys,
  12. sempass2, strutils, modulegraphs, lineinfos
  13. proc genConv(n: PNode, d: PType, downcast: bool; conf: ConfigRef): PNode =
  14. var dest = skipTypes(d, abstractPtrs)
  15. var source = skipTypes(n.typ, abstractPtrs)
  16. if (source.kind == tyObject) and (dest.kind == tyObject):
  17. var diff = inheritanceDiff(dest, source)
  18. if diff == high(int):
  19. # no subtype relation, nothing to do
  20. result = n
  21. elif diff < 0:
  22. result = newNodeIT(nkObjUpConv, n.info, d)
  23. addSon(result, n)
  24. if downcast: internalError(conf, n.info, "cgmeth.genConv: no upcast allowed")
  25. elif diff > 0:
  26. result = newNodeIT(nkObjDownConv, n.info, d)
  27. addSon(result, n)
  28. if not downcast:
  29. internalError(conf, n.info, "cgmeth.genConv: no downcast allowed")
  30. else:
  31. result = n
  32. else:
  33. result = n
  34. proc getDispatcher*(s: PSym): PSym =
  35. ## can return nil if is has no dispatcher.
  36. let dispn = lastSon(s.ast)
  37. if dispn.kind == nkSym:
  38. let disp = dispn.sym
  39. if sfDispatcher in disp.flags: result = disp
  40. proc methodCall*(n: PNode; conf: ConfigRef): PNode =
  41. result = n
  42. # replace ordinary method by dispatcher method:
  43. let disp = getDispatcher(result.sons[0].sym)
  44. if disp != nil:
  45. result.sons[0].sym = disp
  46. # change the arguments to up/downcasts to fit the dispatcher's parameters:
  47. for i in countup(1, sonsLen(result)-1):
  48. result.sons[i] = genConv(result.sons[i], disp.typ.sons[i], true, conf)
  49. else:
  50. localError(conf, n.info, "'" & $result.sons[0] & "' lacks a dispatcher")
  51. type
  52. MethodResult = enum No, Invalid, Yes
  53. proc sameMethodBucket(a, b: PSym): MethodResult =
  54. if a.name.id != b.name.id: return
  55. if sonsLen(a.typ) != sonsLen(b.typ):
  56. return
  57. for i in countup(1, sonsLen(a.typ) - 1):
  58. var aa = a.typ.sons[i]
  59. var bb = b.typ.sons[i]
  60. while true:
  61. aa = skipTypes(aa, {tyGenericInst, tyAlias})
  62. bb = skipTypes(bb, {tyGenericInst, tyAlias})
  63. if aa.kind == bb.kind and aa.kind in {tyVar, tyPtr, tyRef, tyLent}:
  64. aa = aa.lastSon
  65. bb = bb.lastSon
  66. else:
  67. break
  68. if sameType(a.typ.sons[i], b.typ.sons[i]):
  69. if aa.kind == tyObject and result != Invalid:
  70. result = Yes
  71. elif aa.kind == tyObject and bb.kind == tyObject:
  72. let diff = inheritanceDiff(bb, aa)
  73. if diff < 0:
  74. if result != Invalid:
  75. result = Yes
  76. else:
  77. return No
  78. elif diff != high(int) and sfFromGeneric notin (a.flags+b.flags):
  79. result = Invalid
  80. else:
  81. return No
  82. else:
  83. return No
  84. if result == Yes:
  85. # check for return type:
  86. if not sameTypeOrNil(a.typ.sons[0], b.typ.sons[0]):
  87. if b.typ.sons[0] != nil and b.typ.sons[0].kind == tyExpr:
  88. # infer 'auto' from the base to make it consistent:
  89. b.typ.sons[0] = a.typ.sons[0]
  90. else:
  91. return No
  92. proc attachDispatcher(s: PSym, dispatcher: PNode) =
  93. var L = s.ast.len-1
  94. var x = s.ast.sons[L]
  95. if x.kind == nkSym and sfDispatcher in x.sym.flags:
  96. # we've added a dispatcher already, so overwrite it
  97. s.ast.sons[L] = dispatcher
  98. else:
  99. s.ast.add(dispatcher)
  100. proc createDispatcher(s: PSym): PSym =
  101. var disp = copySym(s)
  102. incl(disp.flags, sfDispatcher)
  103. excl(disp.flags, sfExported)
  104. disp.typ = copyType(disp.typ, disp.typ.owner, false)
  105. # we can't inline the dispatcher itself (for now):
  106. if disp.typ.callConv == ccInline: disp.typ.callConv = ccDefault
  107. disp.ast = copyTree(s.ast)
  108. disp.ast.sons[bodyPos] = newNodeI(nkEmpty, s.info)
  109. disp.loc.r = nil
  110. if s.typ.sons[0] != nil:
  111. if disp.ast.sonsLen > resultPos:
  112. disp.ast.sons[resultPos].sym = copySym(s.ast.sons[resultPos].sym)
  113. else:
  114. # We've encountered a method prototype without a filled-in
  115. # resultPos slot. We put a placeholder in there that will
  116. # be updated in fixupDispatcher().
  117. disp.ast.addSon(newNodeI(nkEmpty, s.info))
  118. attachDispatcher(s, newSymNode(disp))
  119. # attach to itself to prevent bugs:
  120. attachDispatcher(disp, newSymNode(disp))
  121. return disp
  122. proc fixupDispatcher(meth, disp: PSym; conf: ConfigRef) =
  123. # We may have constructed the dispatcher from a method prototype
  124. # and need to augment the incomplete dispatcher with information
  125. # from later definitions, particularly the resultPos slot. Also,
  126. # the lock level of the dispatcher needs to be updated/checked
  127. # against that of the method.
  128. if disp.ast.sonsLen > resultPos and meth.ast.sonsLen > resultPos and
  129. disp.ast.sons[resultPos].kind == nkEmpty:
  130. disp.ast.sons[resultPos] = copyTree(meth.ast.sons[resultPos])
  131. # The following code works only with lock levels, so we disable
  132. # it when they're not available.
  133. when declared(TLockLevel):
  134. proc `<`(a, b: TLockLevel): bool {.borrow.}
  135. proc `==`(a, b: TLockLevel): bool {.borrow.}
  136. if disp.typ.lockLevel == UnspecifiedLockLevel:
  137. disp.typ.lockLevel = meth.typ.lockLevel
  138. elif meth.typ.lockLevel != UnspecifiedLockLevel and
  139. meth.typ.lockLevel != disp.typ.lockLevel:
  140. message(conf, meth.info, warnLockLevel,
  141. "method has lock level $1, but another method has $2" %
  142. [$meth.typ.lockLevel, $disp.typ.lockLevel])
  143. # XXX The following code silences a duplicate warning in
  144. # checkMethodeffects() in sempass2.nim for now.
  145. if disp.typ.lockLevel < meth.typ.lockLevel:
  146. disp.typ.lockLevel = meth.typ.lockLevel
  147. proc methodDef*(g: ModuleGraph; s: PSym, fromCache: bool) =
  148. let L = len(g.methods)
  149. var witness: PSym
  150. for i in countup(0, L - 1):
  151. let disp = g.methods[i].dispatcher
  152. case sameMethodBucket(disp, s)
  153. of Yes:
  154. add(g.methods[i].methods, s)
  155. attachDispatcher(s, lastSon(disp.ast))
  156. fixupDispatcher(s, disp, g.config)
  157. #echo "fixup ", disp.name.s, " ", disp.id
  158. when useEffectSystem: checkMethodEffects(g, disp, s)
  159. if {sfBase, sfFromGeneric} * s.flags == {sfBase} and
  160. g.methods[i].methods[0] != s:
  161. # already exists due to forwarding definition?
  162. localError(g.config, s.info, "method is not a base")
  163. return
  164. of No: discard
  165. of Invalid:
  166. if witness.isNil: witness = g.methods[i].methods[0]
  167. # create a new dispatcher:
  168. add(g.methods, (methods: @[s], dispatcher: createDispatcher(s)))
  169. #echo "adding ", s.info
  170. #if fromCache:
  171. # internalError(s.info, "no method dispatcher found")
  172. if witness != nil:
  173. localError(g.config, s.info, "invalid declaration order; cannot attach '" & s.name.s &
  174. "' to method defined here: " & g.config$witness.info)
  175. elif sfBase notin s.flags:
  176. message(g.config, s.info, warnUseBase)
  177. proc relevantCol(methods: TSymSeq, col: int): bool =
  178. # returns true iff the position is relevant
  179. var t = methods[0].typ.sons[col].skipTypes(skipPtrs)
  180. if t.kind == tyObject:
  181. for i in countup(1, high(methods)):
  182. let t2 = skipTypes(methods[i].typ.sons[col], skipPtrs)
  183. if not sameType(t2, t):
  184. return true
  185. proc cmpSignatures(a, b: PSym, relevantCols: IntSet): int =
  186. for col in countup(1, sonsLen(a.typ) - 1):
  187. if contains(relevantCols, col):
  188. var aa = skipTypes(a.typ.sons[col], skipPtrs)
  189. var bb = skipTypes(b.typ.sons[col], skipPtrs)
  190. var d = inheritanceDiff(aa, bb)
  191. if (d != high(int)) and d != 0:
  192. return d
  193. proc sortBucket(a: var TSymSeq, relevantCols: IntSet) =
  194. # we use shellsort here; fast and simple
  195. var n = len(a)
  196. var h = 1
  197. while true:
  198. h = 3 * h + 1
  199. if h > n: break
  200. while true:
  201. h = h div 3
  202. for i in countup(h, n - 1):
  203. var v = a[i]
  204. var j = i
  205. while cmpSignatures(a[j - h], v, relevantCols) >= 0:
  206. a[j] = a[j - h]
  207. j = j - h
  208. if j < h: break
  209. a[j] = v
  210. if h == 1: break
  211. proc genDispatcher(g: ModuleGraph; methods: TSymSeq, relevantCols: IntSet): PSym =
  212. var base = lastSon(methods[0].ast).sym
  213. result = base
  214. var paramLen = sonsLen(base.typ)
  215. var nilchecks = newNodeI(nkStmtList, base.info)
  216. var disp = newNodeI(nkIfStmt, base.info)
  217. var ands = getSysMagic(g, unknownLineInfo(), "and", mAnd)
  218. var iss = getSysMagic(g, unknownLineInfo(), "of", mOf)
  219. let boolType = getSysType(g, unknownLineInfo(), tyBool)
  220. for col in countup(1, paramLen - 1):
  221. if contains(relevantCols, col):
  222. let param = base.typ.n.sons[col].sym
  223. if param.typ.skipTypes(abstractInst).kind in {tyRef, tyPtr}:
  224. addSon(nilchecks, newTree(nkCall,
  225. newSymNode(getCompilerProc(g, "chckNilDisp")), newSymNode(param)))
  226. for meth in countup(0, high(methods)):
  227. var curr = methods[meth] # generate condition:
  228. var cond: PNode = nil
  229. for col in countup(1, paramLen - 1):
  230. if contains(relevantCols, col):
  231. var isn = newNodeIT(nkCall, base.info, boolType)
  232. addSon(isn, newSymNode(iss))
  233. let param = base.typ.n.sons[col].sym
  234. addSon(isn, newSymNode(param))
  235. addSon(isn, newNodeIT(nkType, base.info, curr.typ.sons[col]))
  236. if cond != nil:
  237. var a = newNodeIT(nkCall, base.info, boolType)
  238. addSon(a, newSymNode(ands))
  239. addSon(a, cond)
  240. addSon(a, isn)
  241. cond = a
  242. else:
  243. cond = isn
  244. let retTyp = base.typ.sons[0]
  245. let call = newNodeIT(nkCall, base.info, retTyp)
  246. addSon(call, newSymNode(curr))
  247. for col in countup(1, paramLen - 1):
  248. addSon(call, genConv(newSymNode(base.typ.n.sons[col].sym),
  249. curr.typ.sons[col], false, g.config))
  250. var ret: PNode
  251. if retTyp != nil:
  252. var a = newNodeI(nkFastAsgn, base.info)
  253. addSon(a, newSymNode(base.ast.sons[resultPos].sym))
  254. addSon(a, call)
  255. ret = newNodeI(nkReturnStmt, base.info)
  256. addSon(ret, a)
  257. else:
  258. ret = call
  259. if cond != nil:
  260. var a = newNodeI(nkElifBranch, base.info)
  261. addSon(a, cond)
  262. addSon(a, ret)
  263. addSon(disp, a)
  264. else:
  265. disp = ret
  266. nilchecks.add disp
  267. nilchecks.flags.incl nfTransf # should not be further transformed
  268. result.ast.sons[bodyPos] = nilchecks
  269. proc generateMethodDispatchers*(g: ModuleGraph): PNode =
  270. result = newNode(nkStmtList)
  271. for bucket in countup(0, len(g.methods) - 1):
  272. var relevantCols = initIntSet()
  273. for col in countup(1, sonsLen(g.methods[bucket].methods[0].typ) - 1):
  274. if relevantCol(g.methods[bucket].methods, col): incl(relevantCols, col)
  275. sortBucket(g.methods[bucket].methods, relevantCols)
  276. addSon(result,
  277. newSymNode(genDispatcher(g, g.methods[bucket].methods, relevantCols)))