Skip to content

Commit 26c7390

Browse files
committed
Refactoring: Focus internal module ID generation in InternalModuleIDGenerator.
It is an evolution of `ModuleIDs` into a pair of classes that store more information about what they need to avoid, so that we don't have to repeat it at use site. It also concentrates all the logic about internal module ID generation, which will allow the future fix for case-insensitive filesystems to be more localized.
1 parent 9b4448b commit 26c7390

File tree

7 files changed

+179
-98
lines changed

7 files changed

+179
-98
lines changed

linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/FewestModulesAnalyzer.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,8 @@ private[modulesplitter] final class FewestModulesAnalyzer extends ModuleAnalyzer
3232
// Fast path.
3333
new SingleModuleAnalysis(info.publicModuleDependencies.head._1)
3434
} else {
35-
val prefix = ModuleIDs.freeInternalPrefix(
36-
avoid = info.publicModuleDependencies.keys)
37-
38-
val moduleMap = new Tagger(info).tagAll(prefix)
35+
val modulesToAvoid = info.publicModuleDependencies.keys
36+
val moduleMap = new Tagger(info).tagAll(modulesToAvoid)
3937

4038
new FullAnalysis(moduleMap)
4139
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Scala.js (https://www.scala-js.org/)
3+
*
4+
* Copyright EPFL.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (https://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package org.scalajs.linker.frontend.modulesplitter
14+
15+
import org.scalajs.ir.Names.{ClassName, ObjectClass}
16+
import org.scalajs.linker.standard.ModuleSet.ModuleID
17+
18+
/** Generators for internal module IDs. */
19+
private[modulesplitter] object InternalModuleIDGenerator {
20+
21+
/** Generator based on `ClassName`s. */
22+
final class ForClassNames(avoid: Iterable[ModuleID]) {
23+
private val avoidSet: Set[ModuleID] = avoid.toSet
24+
25+
/** Picks a representative from a list of classes.
26+
*
27+
* Guarantees to return the same value independent of the order of [[names]].
28+
*/
29+
def representativeClass(names: List[ClassName]): ClassName = {
30+
require(names.nonEmpty)
31+
32+
/* Take the lexicographically smallest name as a stable name of the
33+
* module, with the exception of j.l.Object which identifies the root
34+
* module.
35+
*
36+
* We do this, because it is simple and stable (i.e. does not depend
37+
* on traversal order).
38+
*/
39+
if (names.contains(ObjectClass)) ObjectClass
40+
else names.min
41+
}
42+
43+
/** Builds an ID for the class with name [[name]].
44+
*
45+
* The result is guaranteed to be:
46+
* - Different from any public module ID.
47+
* - Different for each ClassName.
48+
* - Deterministic.
49+
*/
50+
def forClassName(name: ClassName): ModuleID = {
51+
/* Build a module ID that doesn't collide with others.
52+
*
53+
* We observe:
54+
* - Class names are unique, so they never collide with each other.
55+
* - Appending a dot ('.') to a class name results in an illegal class name.
56+
*
57+
* So we append dots until we hit a ModuleID not used by a public module.
58+
*
59+
* Note that this is stable, because it does not depend on the order we
60+
* iterate over nodes.
61+
*/
62+
var moduleID = ModuleID(name.nameString)
63+
while (avoidSet.contains(moduleID))
64+
moduleID = ModuleID(moduleID.id + ".")
65+
moduleID
66+
}
67+
}
68+
69+
/** Generator based on digests. */
70+
final class ForDigests private (internalModuleIDPrefix: String) {
71+
def this(avoid: Iterable[ModuleID]) =
72+
this(freeInternalPrefix(avoid))
73+
74+
def forDigest(digest: Array[Byte]): ModuleID = {
75+
@inline def hexDigit(digit: Int): Char =
76+
Character.forDigit(digit & 0x0f, 16)
77+
78+
val id = new java.lang.StringBuilder(internalModuleIDPrefix)
79+
80+
for (b <- digest) {
81+
id.append(hexDigit(b >> 4))
82+
id.append(hexDigit(b))
83+
}
84+
85+
ModuleID(id.toString())
86+
}
87+
}
88+
89+
/** Creates a prefix that is not a prefix of any of the IDs in [[avoid]] */
90+
private def freeInternalPrefix(avoid: Iterable[ModuleID]): String = {
91+
Iterator
92+
.iterate("internal-")(_ + "-")
93+
.find(p => !avoid.exists(_.id.startsWith(p)))
94+
.get
95+
}
96+
}

linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/ModuleIDs.scala

Lines changed: 0 additions & 71 deletions
This file was deleted.

linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/SmallModulesForAnalyzer.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,9 @@ private final class SmallModulesForAnalyzer(
3434
def analyze(info: ModuleAnalyzer.DependencyInfo): ModuleAnalyzer.Analysis = {
3535
val (targetClassToRepr, reprToModuleID) = smallRun(info, packages)
3636

37-
val prefix = ModuleIDs.freeInternalPrefix(
38-
info.publicModuleDependencies.keys ++ reprToModuleID.values)
39-
37+
val modulesToAvoid = info.publicModuleDependencies.keys ++ reprToModuleID.values
4038
val largeModuleMap =
41-
new Tagger(info, excludedClasses = targetClassToRepr.keySet).tagAll(prefix)
39+
new Tagger(info, excludedClasses = targetClassToRepr.keySet).tagAll(modulesToAvoid)
4240

4341
new SmallModulesForAnalyzer.Analysis(targetClassToRepr, reprToModuleID, largeModuleMap)
4442
}
@@ -67,6 +65,9 @@ private object SmallModulesForAnalyzer {
6765
private final class SmallRun(info: ModuleAnalyzer.DependencyInfo,
6866
packages: List[ClassName]) extends StrongConnect(info) {
6967

68+
private val internalModIDGenerator =
69+
new InternalModuleIDGenerator.ForClassNames(info.publicModuleDependencies.keys)
70+
7071
/* We expect this to contain relatively few classes.
7172
*
7273
* So instead of keeping the underlying graph and relying on [[moduleIndex]],
@@ -81,8 +82,8 @@ private object SmallModulesForAnalyzer {
8182
val targetNames = classNames.filter(clazz => packages.exists(inPackage(clazz, _)))
8283

8384
if (targetNames.nonEmpty) {
84-
val repr = ModuleIDs.representativeClass(targetNames)
85-
val id = ModuleIDs.forClassName(info.publicModuleDependencies.keySet, repr)
85+
val repr = internalModIDGenerator.representativeClass(targetNames)
86+
val id = internalModIDGenerator.forClassName(repr)
8687
reprToModuleID(repr) = id
8788
for (className <- classNames)
8889
targetClassToRepr(className) = repr

linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/SmallestModulesAnalyzer.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,17 @@ private[modulesplitter] object SmallestModulesAnalyzer {
3737
private final class Run(info: ModuleAnalyzer.DependencyInfo)
3838
extends StrongConnect(info) with ModuleAnalyzer.Analysis {
3939

40+
private val internalModIDGenerator =
41+
new InternalModuleIDGenerator.ForClassNames(info.publicModuleDependencies.keys)
42+
4043
private[this] val moduleIndexToID = mutable.Map.empty[Int, ModuleID]
4144

4245
def moduleForClass(className: ClassName): Option[ModuleID] =
4346
moduleIndex(className).map(moduleIndexToID)
4447

4548
protected def emitModule(moduleIndex: Int, classNames: List[ClassName]): Unit = {
46-
val repr = ModuleIDs.representativeClass(classNames)
47-
val id = ModuleIDs.forClassName(info.publicModuleDependencies.keySet, repr)
49+
val repr = internalModIDGenerator.representativeClass(classNames)
50+
val id = internalModIDGenerator.forClassName(repr)
4851
moduleIndexToID(moduleIndex) = id
4952
}
5053
}

linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/Tagger.scala

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.scalajs.ir.Names.ClassName
2424
import org.scalajs.ir.SHA1
2525
import org.scalajs.linker.standard.ModuleSet.ModuleID
2626

27+
import InternalModuleIDGenerator.ForDigests
2728

2829
/** Tagger groups classes into coarse modules.
2930
*
@@ -163,13 +164,14 @@ private class Tagger(infos: ModuleAnalyzer.DependencyInfo,
163164

164165
private[this] val allPaths = mutable.Map.empty[ClassName, Paths]
165166

166-
final def tagAll(internalModuleIDPrefix: String): scala.collection.Map[ClassName, ModuleID] = {
167+
final def tagAll(modulesToAvoid: Iterable[ModuleID]): scala.collection.Map[ClassName, ModuleID] = {
168+
val internalModIDGenerator = new InternalModuleIDGenerator.ForDigests(modulesToAvoid)
167169
tagEntryPoints()
168170
for {
169171
(className, paths) <- allPaths
170172
if !excludedClasses.contains(className)
171173
} yield {
172-
className -> paths.moduleID(internalModuleIDPrefix)
174+
className -> paths.moduleID(internalModIDGenerator)
173175
}
174176
}
175177

@@ -247,7 +249,7 @@ private object Tagger {
247249
hopCountsChanged || stepsChanged
248250
}
249251

250-
def moduleID(internalModuleIDPrefix: String): ModuleID = {
252+
def moduleID(internalModIDGenerator: ForDigests): ModuleID = {
251253
if (direct.size == 1 && dynamic.isEmpty && maxExcludedHopCount == 0) {
252254
/* Class is only used by a single public module. Put it there.
253255
*
@@ -276,18 +278,7 @@ private object Tagger {
276278
for (className <- dynamicEnds)
277279
digestBuilder.updateUTF8String(className.encoded)
278280

279-
// Build a hex string of the hash with the right prefix.
280-
@inline def hexDigit(digit: Int): Char =
281-
Character.forDigit(digit & 0x0f, 16)
282-
283-
val id = new java.lang.StringBuilder(internalModuleIDPrefix)
284-
285-
for (b <- digestBuilder.finalizeDigest()) {
286-
id.append(hexDigit(b >> 4))
287-
id.append(hexDigit(b))
288-
}
289-
290-
ModuleID(id.toString())
281+
internalModIDGenerator.forDigest(digestBuilder.finalizeDigest())
291282
}
292283
}
293284

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Scala.js (https://www.scala-js.org/)
3+
*
4+
* Copyright EPFL.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (https://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package org.scalajs.linker.frontend.modulesplitter
14+
15+
import org.junit.Test
16+
import org.junit.Assert._
17+
18+
import org.scalajs.ir.Names.ClassName
19+
20+
import org.scalajs.linker.standard.ModuleSet.ModuleID
21+
22+
/** Whitebox tests for `InternalModuleIDGenerator`. */
23+
class InternalModuleIDGeneratorTest {
24+
@Test def testForClassName(): Unit = {
25+
val testPublicModuleIDs = List(ModuleID("test.Public"), ModuleID("test.OtherPublic"))
26+
val generator = new InternalModuleIDGenerator.ForClassNames(testPublicModuleIDs)
27+
28+
def test(expected: String, classNameString: String): Unit =
29+
assertEquals(expected, generator.forClassName(ClassName(classNameString)).id)
30+
31+
test("java.lang.String", "java.lang.String")
32+
test("java.lang.StringBuilder", "java.lang.StringBuilder")
33+
34+
test("test-S.foo--Bar", "test-S.foo--Bar")
35+
36+
test("test.été", "test.été")
37+
test("test.Été", "test.Été")
38+
39+
test("test.dz", "test.dz") // U+01F3 Latin Small Letter Dz
40+
test("test.DZ", "test.DZ") // U+01F1 Latin Capital Letter Dz
41+
test("test.Dz", "test.Dz") // U+01F2 Latin Capital Letter D with Small Letter Z
42+
43+
test("test.Public.", "test.Public")
44+
test("test.OtherPublic.", "test.OtherPublic")
45+
}
46+
47+
@Test def testForDigest(): Unit = {
48+
val goodModuleID = ModuleID("good")
49+
val otherGoodModuleID = ModuleID("othergood")
50+
val collidingModuleID = ModuleID("internal-mod")
51+
52+
val digest = Array(0x12.toByte, 0x34.toByte, 0xef.toByte)
53+
54+
val generator1 = new InternalModuleIDGenerator.ForDigests(Nil)
55+
assertEquals("internal-1234ef", generator1.forDigest(digest).id)
56+
57+
val generator2 = new InternalModuleIDGenerator.ForDigests(List(goodModuleID, otherGoodModuleID))
58+
assertEquals("internal-1234ef", generator2.forDigest(digest).id)
59+
60+
val generator3 = new InternalModuleIDGenerator.ForDigests(List(goodModuleID, collidingModuleID))
61+
assertEquals("internal--1234ef", generator3.forDigest(digest).id)
62+
}
63+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy