Skip to content

Commit 33ebf01

Browse files
committed
address comments
1 parent 273239d commit 33ebf01

File tree

1 file changed

+32
-58
lines changed

1 file changed

+32
-58
lines changed

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

Lines changed: 32 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import xsbti.api.DefinitionType
2525

2626
import scala.collection.mutable
2727
import scala.util.hashing.MurmurHash3
28+
import scala.util.chaining.*
2829

2930
/** This phase sends a representation of the API of classes to sbt via callbacks.
3031
*
@@ -141,14 +142,17 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
141142
/** This cache is necessary to avoid unstable name hashing when `typeCache` is present,
142143
* see the comment in the `RefinedType` case in `computeType`
143144
* The cache key is (api of RefinedType#parent, api of RefinedType#refinedInfo).
144-
*/
145+
*/
145146
private val refinedTypeCache = new mutable.HashMap[(api.Type, api.Definition), api.Structure]
146147

147-
/** This cache is necessary to avoid infinite loops when hashing the body of inline definitions.
148-
* Its keys represent the root inline definitions, and its values are seen inline references within
149-
* the rhs of the key. If a symbol is present in the value set, then do not hash its signature or inline body.
148+
/** This cache is necessary to avoid infinite loops when hashing an inline "Body" annotation.
149+
* Its values are transitively seen inline references within a call chain starting from a single "origin" inline
150+
* definition. Avoid hashing an inline "Body" annotation if its associated definition is already in the cache.
151+
* Precondition: the cache is empty whenever we hash a new "origin" inline "Body" annotation.
150152
*/
151-
private val seenInlineCache = mutable.HashMap.empty[Symbol, mutable.HashSet[Symbol]]
153+
private val seenInlineCache = mutable.HashSet.empty[Symbol]
154+
155+
/** This cache is optional, it avoids recomputing hashes of inline "Body" annotations. */
152156
private val inlineBodyCache = mutable.HashMap.empty[Symbol, Int]
153157

154158
private val allNonLocalClassesInSrc = new mutable.HashSet[xsbti.api.ClassLike]
@@ -357,15 +361,18 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
357361
*/
358362
def apiDef(sym: TermSymbol, inlineOrigin: Symbol): api.Def = {
359363

360-
val inlineExtras = new mutable.ListBuffer[Int => Int]
364+
var seenInlineExtras = false
365+
var inlineExtras = 41
361366

362367
def mixInlineParam(p: Symbol): Unit =
363368
if inlineOrigin.exists && p.is(Inline) then
364-
inlineExtras += hashInlineParam(p)
369+
seenInlineExtras = true
370+
inlineExtras = hashInlineParam(p, inlineExtras)
365371

366372
def inlineExtrasAnnot: Option[api.Annotation] =
367-
Option.when(inlineOrigin.exists && inlineExtras.nonEmpty) {
368-
marker(s"${hashList(inlineExtras.toList)("inlineExtras".hashCode)}")
373+
val h = inlineExtras
374+
Option.when(seenInlineExtras) {
375+
marker(s"${MurmurHash3.finalizeHash(h, "inlineExtras".hashCode)}")
369376
}
370377

371378
def tparamList(pt: TypeLambda): List[api.TypeParameter] =
@@ -654,25 +661,15 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
654661
// could store the hash as an annotation when pickling an inline def
655662
// and retrieve it here instead of computing it on the fly.
656663

657-
def registerInlineHash(inlineBodyHash: Int): Unit =
658-
annots += marker(inlineBodyHash.toString)
659-
660-
def nestedHash(root: Symbol): Unit =
661-
if !seenInlineCache(root).contains(s) then
662-
seenInlineCache(root) += s
663-
registerInlineHash(treeHash(inlineBody, inlineOrigin = root))
664+
def hash[U](inlineOrigin: Symbol): Int =
665+
assert(seenInlineCache.add(s)) // will fail if already seen, guarded by treeHash
666+
treeHash(inlineBody, inlineOrigin)
664667

665-
def originHash(root: Symbol): Unit =
666-
def computeHash(): Int =
667-
assert(!seenInlineCache.contains(root))
668-
seenInlineCache.put(root, mutable.HashSet(root))
669-
val res = treeHash(inlineBody, inlineOrigin = root)
670-
seenInlineCache.remove(root)
671-
res
672-
registerInlineHash(inlineBodyCache.getOrElseUpdate(root, computeHash()))
668+
val inlineHash =
669+
if inlineOrigin.exists then hash(inlineOrigin)
670+
else inlineBodyCache.getOrElseUpdate(s, hash(inlineOrigin = s).tap(_ => seenInlineCache.clear()))
673671

674-
if inlineOrigin.exists then nestedHash(root = inlineOrigin)
675-
else originHash(root = s)
672+
annots += marker(inlineHash.toString)
676673

677674
end if
678675

@@ -712,32 +709,19 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
712709
MurmurHash3.mix(h, n.toString.hashCode)
713710
end nameHash
714711

715-
def typeHash(tp: Type, initHash: Int): Int =
716-
// Go through `apiType` to get a value with a stable hash, it'd
717-
// be better to use Murmur here too instead of relying on
718-
// `hashCode`, but that would essentially mean duplicating
719-
// https://github.com/sbt/zinc/blob/develop/internal/zinc-apiinfo/src/main/scala/xsbt/api/HashAPI.scala
720-
// and at that point we might as well do type hashing on our own
721-
// representation.
722-
var h = initHash
723-
tp match
724-
case ConstantType(c) =>
725-
h = constantHash(c, h)
726-
case TypeBounds(lo, hi) => // TODO when does this happen?
727-
h = MurmurHash3.mix(h, apiType(lo).hashCode)
728-
h = MurmurHash3.mix(h, apiType(hi).hashCode)
729-
case tp =>
730-
h = MurmurHash3.mix(h, apiType(tp).hashCode)
731-
h
732-
end typeHash
733-
734712
def constantHash(c: Constant, initHash: Int): Int =
735713
var h = MurmurHash3.mix(initHash, c.tag)
736714
c.tag match
737715
case NullTag =>
738716
// No value to hash, the tag is enough.
739717
case ClazzTag =>
740-
h = typeHash(c.typeValue, h)
718+
// Go through `apiType` to get a value with a stable hash, it'd
719+
// be better to use Murmur here too instead of relying on
720+
// `hashCode`, but that would essentially mean duplicating
721+
// https://github.com/sbt/zinc/blob/develop/internal/zinc-apiinfo/src/main/scala/xsbt/api/HashAPI.scala
722+
// and at that point we might as well do type hashing on our own
723+
// representation.
724+
h = MurmurHash3.mix(h, apiType(c.typeValue).hashCode)
741725
case _ =>
742726
h = MurmurHash3.mix(h, c.value.hashCode)
743727
h
@@ -758,7 +742,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
758742
p match
759743
case ref: RefTree @unchecked =>
760744
val sym = ref.symbol
761-
if sym.is(Inline, butNot = Param) && !seenInlineCache(inlineOrigin).contains(sym) then
745+
if sym.is(Inline, butNot = Param) && !seenInlineCache.contains(sym) then
762746
// An inline method that calls another inline method will eventually inline the call
763747
// at a non-inline callsite, in this case if the implementation of the nested call
764748
// changes, then the callsite will have a different API, we should hash the definition
@@ -824,20 +808,10 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
824808
MurmurHash3.finalizeHash(h, len)
825809
end hashTparamsExtras
826810

827-
private def hashList(extraHashes: List[Int => Int])(initHash: Int): Int =
828-
var h = initHash
829-
var fs = extraHashes
830-
var len = 0
831-
while fs.nonEmpty do
832-
h = fs.head(h)
833-
fs = fs.tail
834-
len += 1
835-
MurmurHash3.finalizeHash(h, len)
836-
837811
/** Mix in the name hash also because otherwise switching which
838812
* parameter is inline will not affect the hash.
839813
*/
840-
private def hashInlineParam(p: Symbol)(h: Int) =
814+
private def hashInlineParam(p: Symbol, h: Int) =
841815
MurmurHash3.mix(p.name.toString.hashCode, MurmurHash3.mix(h, InlineParamHash))
842816

843817
def apiAnnotation(annot: Annotation): api.Annotation = {

0 commit comments

Comments
 (0)