From 08d0286e21020a9788d1f97e8390d5736a865ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 15 Feb 2024 11:48:06 +0100 Subject: [PATCH 1/7] Inline `genUnchecked` at its two call sites. It had been factored out in `SJSGen` because of one condition that happens to be repeated. However, it is clearer that it does the right thing at each of its call sites. Given the amount of information that needed to be passed to this helper, inlining it twice actually removes additional checks. Ultimately, it seems simpler this way. --- .../scalajs/linker/backend/emitter/CoreJSLib.scala | 5 ++++- .../linker/backend/emitter/FunctionEmitter.scala | 11 +++++++++-- .../scalajs/linker/backend/emitter/SJSGen.scala | 14 -------------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala index b36e35783d..89e4ad86df 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala @@ -1171,7 +1171,10 @@ private[emitter] object CoreJSLib { // Both values have the same "data" (could also be falsy values) If(srcData && genIdentBracketSelect(srcData, "isArrayClass"), { // Fast path: the values are array of the same type - genUncheckedArraycopy(List(src, srcPos, dest, destPos, length)) + if (esVersion >= ESVersion.ES2015 && nullPointers == CheckedBehavior.Unchecked) + Apply(src DOT "copyTo", List(srcPos, dest, destPos, length)) + else + genCallHelper(VarField.systemArraycopy, src, srcPos, dest, destPos, length) }, { genCallHelper(VarField.throwArrayStoreException, Null()) }) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 32b8baca4f..75b4bdeb61 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -875,12 +875,19 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { implicit val env = env0 val jsArgs = newArgs.map(transformExprNoChar(_)) + def genUnchecked(): js.Tree = { + if (esFeatures.esVersion >= ESVersion.ES2015 && semantics.nullPointers == CheckedBehavior.Unchecked) + js.Apply(jsArgs.head DOT "copyTo", jsArgs.tail) + else + genCallHelper(VarField.systemArraycopy, jsArgs: _*) + } + if (semantics.arrayStores == Unchecked) { - genUncheckedArraycopy(jsArgs) + genUnchecked() } else { (src.tpe, dest.tpe) match { case (PrimArray(srcPrimRef), PrimArray(destPrimRef)) if srcPrimRef == destPrimRef => - genUncheckedArraycopy(jsArgs) + genUnchecked() case (RefArray(), RefArray()) => genCallHelper(VarField.systemArraycopyRefs, jsArgs: _*) case _ => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala index 4541d3a292..e7ea19a9db 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala @@ -149,20 +149,6 @@ private[emitter] final class SJSGen( } } - def genUncheckedArraycopy(args: List[Tree])( - implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): Tree = { - import TreeDSL._ - - assert(args.lengthCompare(5) == 0, - s"wrong number of args for genUncheckedArrayCopy: $args") - - if (esFeatures.esVersion >= ESVersion.ES2015 && semantics.nullPointers == CheckedBehavior.Unchecked) - Apply(args.head DOT "copyTo", args.tail) - else - genCallHelper(VarField.systemArraycopy, args: _*) - } - def genSelect(receiver: Tree, field: irt.FieldIdent)( implicit pos: Position): Tree = { DotSelect(receiver, Ident(genName(field.name))(field.pos)) From 44aacace31712d2abc558458db282ef7b1ef820a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 26 Feb 2024 10:03:06 +0100 Subject: [PATCH 2/7] Introduce `javascript.Trees.DelayedIdent`. We introduce a new kind of node in JS ASTs: `DelayedIdent`. A delayed ident is like an `Ident`, but its `name` is provided by a resolver, to be determined later. This allows us to build a JS AST with `DelayedIdent`s whose final names will only be known later. Since pretty-printing requires to resolve the name, it might throw and is not so well suited to `show` for debugging purposes anymore. We therefore introduce `JSTreeShowPrinter`, which avoids resolving the names. Instead, it uses the `debugString` method of the resolver, which can be constructed to display meaningful debugging information. `DelayedIdent` is not yet actually used in this commit, but will be in a subsequent commit for minifying property names. --- .../closure/ClosureAstTransformer.scala | 16 ++--- .../linker/backend/emitter/JSGen.scala | 6 +- .../linker/backend/javascript/Printers.scala | 33 +++++++++- .../linker/backend/javascript/Trees.scala | 61 ++++++++++++++++--- .../backend/javascript/PrintersTest.scala | 37 ++++++++++- 5 files changed, 127 insertions(+), 26 deletions(-) diff --git a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala index 79ad4562ec..cad0fd9434 100644 --- a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala +++ b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala @@ -210,9 +210,9 @@ private class ClosureAstTransformer(featureSet: FeatureSet, private def transformClassMember(member: Tree): Node = { implicit val pos = member.pos - def newFixedPropNode(token: Token, static: Boolean, name: Ident, + def newFixedPropNode(token: Token, static: Boolean, name: MaybeDelayedIdent, function: Node): Node = { - val node = Node.newString(token, name.name) + val node = Node.newString(token, name.resolveName()) node.addChildToBack(function) node.setStaticMember(static) node @@ -258,7 +258,7 @@ private class ClosureAstTransformer(featureSet: FeatureSet, val node = newComputedPropNode(static, nameExpr, function) node.putBooleanProp(Node.COMPUTED_PROP_METHOD, true) node - case name: Ident => + case name: MaybeDelayedIdent => newFixedPropNode(Token.MEMBER_FUNCTION_DEF, static, name, function) } @@ -274,7 +274,7 @@ private class ClosureAstTransformer(featureSet: FeatureSet, val node = newComputedPropNode(static, nameExpr, function) node.putBooleanProp(Node.COMPUTED_PROP_GETTER, true) node - case name: Ident => + case name: MaybeDelayedIdent => newFixedPropNode(Token.GETTER_DEF, static, name, function) } @@ -290,7 +290,7 @@ private class ClosureAstTransformer(featureSet: FeatureSet, val node = newComputedPropNode(static, nameExpr, function) node.putBooleanProp(Node.COMPUTED_PROP_SETTER, true) node - case name: Ident => + case name: MaybeDelayedIdent => newFixedPropNode(Token.SETTER_DEF, static, name, function) } @@ -321,7 +321,7 @@ private class ClosureAstTransformer(featureSet: FeatureSet, args.foreach(arg => node.addChildToBack(transformExpr(arg))) node case DotSelect(qualifier, item) => - val node = Node.newString(Token.GETPROP, item.name) + val node = Node.newString(Token.GETPROP, item.resolveName()) node.addChildToBack(transformExpr(qualifier)) setNodePosition(node, item.pos.orElse(pos)) case BracketSelect(qualifier, item) => @@ -435,8 +435,8 @@ private class ClosureAstTransformer(featureSet: FeatureSet, val transformedValue = transformExpr(value) val node = name match { - case Ident(name, _) => - Node.newString(Token.STRING_KEY, name) + case name: MaybeDelayedIdent => + Node.newString(Token.STRING_KEY, name.resolveName()) case StringLiteral(name) => val node = Node.newString(Token.STRING_KEY, name) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala index 9d0150c2c9..4da09323c5 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala @@ -148,9 +148,9 @@ private[emitter] final class JSGen(val config: Emitter.Config) { def genPropSelect(qual: Tree, item: PropertyName)( implicit pos: Position): Tree = { item match { - case item: Ident => DotSelect(qual, item) - case item: StringLiteral => genBracketSelect(qual, item) - case ComputedName(tree) => genBracketSelect(qual, tree) + case item: MaybeDelayedIdent => DotSelect(qual, item) + case item: StringLiteral => genBracketSelect(qual, item) + case ComputedName(tree) => genBracketSelect(qual, tree) } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala index a6d632a1cd..2b99af7010 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala @@ -747,9 +747,13 @@ object Printers { protected def print(ident: Ident): Unit = printEscapeJS(ident.name) + protected def print(ident: DelayedIdent): Unit = + printEscapeJS(ident.resolveName()) + private final def print(propName: PropertyName): Unit = propName match { - case lit: StringLiteral => print(lit: Tree) - case ident: Ident => print(ident) + case lit: StringLiteral => print(lit: Tree) + case ident: Ident => print(ident) + case ident: DelayedIdent => print(ident) case ComputedName(tree) => print("[") @@ -799,6 +803,14 @@ object Printers { sourceMap.endNode(column) } + override protected def print(ident: DelayedIdent): Unit = { + if (ident.pos.isDefined) + sourceMap.startIdentNode(column, ident.pos, ident.originalName) + printEscapeJS(ident.resolveName()) + if (ident.pos.isDefined) + sourceMap.endNode(column) + } + override protected def print(printedTree: PrintedTree): Unit = { super.print(printedTree) sourceMap.insertFragment(printedTree.sourceMapFragment) @@ -830,4 +842,21 @@ object Printers { } } + /** Shows a `Tree` for debugging purposes, not for pretty-printing. */ + private[javascript] def showTree(tree: Tree): String = { + val writer = new ByteArrayWriter() + val printer = new Printers.JSTreeShowPrinter(writer) + printer.printTree(tree, isStat = true) + new String(writer.toByteArray(), StandardCharsets.US_ASCII) + } + + /** A printer that shows `Tree`s for debugging, not for pretty-printing. */ + private class JSTreeShowPrinter(_out: ByteArrayWriter, initIndent: Int = 0) + extends JSTreePrinter(_out, initIndent) { + override protected def print(ident: DelayedIdent): Unit = { + print("") + } + } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala index ec5b72e850..0c0b820e82 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala @@ -31,12 +31,7 @@ object Trees { abstract sealed class Tree { val pos: Position - def show: String = { - val writer = new ByteArrayWriter() - val printer = new Printers.JSTreePrinter(writer) - printer.printTree(this, isStat = true) - new String(writer.toByteArray(), StandardCharsets.US_ASCII) - } + def show: String = Printers.showTree(this) } // Constructor comment / annotation. @@ -50,16 +45,26 @@ object Trees { def pos: Position } + sealed trait MaybeDelayedIdent extends PropertyName { + def resolveName(): String + } + sealed case class Ident(name: String, originalName: OriginalName)( - implicit val pos: Position) extends PropertyName { - require(Ident.isValidJSIdentifierName(name), - s"'$name' is not a valid JS identifier name") + implicit val pos: Position) extends MaybeDelayedIdent { + Ident.requireValidJSIdentifierName(name) + + def resolveName(): String = name } object Ident { def apply(name: String)(implicit pos: Position): Ident = new Ident(name, NoOriginalName) + def requireValidJSIdentifierName(name: String): Unit = { + require(isValidJSIdentifierName(name), + s"'$name' is not a valid JS identifier name") + } + /** Tests whether the given string is a valid `IdentifierName` for the * ECMAScript language specification. * @@ -87,6 +92,42 @@ object Trees { } } + /** An ident whose real name will be resolved later. */ + sealed case class DelayedIdent(resolver: DelayedIdent.Resolver, originalName: OriginalName)( + implicit val pos: Position) + extends MaybeDelayedIdent { + + def resolveName(): String = { + val name = resolver.resolve() + Ident.requireValidJSIdentifierName(name) + name + } + } + + object DelayedIdent { + def apply(resolver: DelayedIdent.Resolver)(implicit pos: Position): DelayedIdent = + new DelayedIdent(resolver, NoOriginalName) + + /** Resolver for the eventual name of a `DelayedIdent`. */ + trait Resolver { + /** Resolves the eventual name of the delayed ident. + * + * @throws java.lang.IllegalStateException + * if this resolver is not yet ready to resolve a name + */ + def resolve(): String + + /** A string representing this resolver for debugging purposes. + * + * The result of this method is used when calling `show` on the + * associated `DelayedIdent`. Once the resolver is ready, this method is + * encouraged to return the same string as `resolve()`, but it is not + * mandatory to do so. + */ + def debugString: String + } + } + sealed case class ComputedName(tree: Tree) extends PropertyName { def pos: Position = tree.pos } @@ -231,7 +272,7 @@ object Trees { implicit val pos: Position) extends Tree - sealed case class DotSelect(qualifier: Tree, item: Ident)( + sealed case class DotSelect(qualifier: Tree, item: MaybeDelayedIdent)( implicit val pos: Position) extends Tree diff --git a/linker/shared/src/test/scala/org/scalajs/linker/backend/javascript/PrintersTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/backend/javascript/PrintersTest.scala index ba4848f668..a2a8108fa8 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/backend/javascript/PrintersTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/backend/javascript/PrintersTest.scala @@ -30,12 +30,16 @@ class PrintersTest { private implicit def str2ident(name: String): Ident = Ident(name, ir.OriginalName.NoOriginalName) - private def assertPrintEquals(expected: String, tree: Tree): Unit = { + private def printTree(tree: Tree): String = { val out = new ByteArrayWriter val printer = new Printers.JSTreePrinter(out) printer.printStat(tree) - assertEquals(expected.stripMargin.trim + "\n", - new String(out.toByteArray(), UTF_8)) + new String(out.toByteArray(), UTF_8) + } + + private def assertPrintEquals(expected: String, tree: Tree): Unit = { + val printResult = printTree(tree) + assertEquals(expected.stripMargin.trim + "\n", printResult) } @Test def printFunctionDef(): Unit = { @@ -159,6 +163,33 @@ class PrintersTest { ) } + @Test def delayedIdentPrintVersusShow(): Unit = { + locally { + object resolver extends DelayedIdent.Resolver { + def resolve(): String = "foo" + def debugString: String = "bar" + } + + val tree = DotSelect(VarRef("x"), DelayedIdent(resolver)) + + assertPrintEquals("x.foo;", tree) + assertEquals("x.;", tree.show) + } + + // Even when `resolve()` throws, `show` still succeeds based on `debugString`. + locally { + object resolver extends DelayedIdent.Resolver { + def resolve(): String = throw new IllegalStateException("not ready") + def debugString: String = "bar" + } + + val tree = DotSelect(VarRef("x"), DelayedIdent(resolver)) + + assertThrows(classOf[IllegalStateException], () => printTree(tree)) + assertEquals("x.;", tree.show) + } + } + @Test def showPrintedTree(): Unit = { val tree = PrintedTree("test".getBytes(UTF_8), SourceMapWriter.Fragment.Empty) From 298c60a0b5817a04ffd507e6ea0a793beb0a0486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 26 Feb 2024 10:33:22 +0100 Subject: [PATCH 3/7] Make `JSTreePrinter.printTree` protected. The only remaining public method is now `printStat(tree: Tree)`. --- .../org/scalajs/linker/backend/javascript/Printers.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala index 2b99af7010..09a3b8648a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala @@ -139,7 +139,7 @@ object Printers { * - No leading indent. * - No trailing newline. */ - def printTree(tree: Tree, isStat: Boolean): Unit = { + protected def printTree(tree: Tree, isStat: Boolean): Unit = { def printSeparatorIfStat() = { if (isStat) print(';') @@ -781,7 +781,7 @@ object Printers { private var column = 0 - override def printTree(tree: Tree, isStat: Boolean): Unit = { + override protected def printTree(tree: Tree, isStat: Boolean): Unit = { val pos = tree.pos if (pos.isDefined) sourceMap.startNode(column, pos) @@ -846,13 +846,16 @@ object Printers { private[javascript] def showTree(tree: Tree): String = { val writer = new ByteArrayWriter() val printer = new Printers.JSTreeShowPrinter(writer) - printer.printTree(tree, isStat = true) + printer.printTreeForShow(tree) new String(writer.toByteArray(), StandardCharsets.US_ASCII) } /** A printer that shows `Tree`s for debugging, not for pretty-printing. */ private class JSTreeShowPrinter(_out: ByteArrayWriter, initIndent: Int = 0) extends JSTreePrinter(_out, initIndent) { + def printTreeForShow(tree: Tree): Unit = + printTree(tree, isStat = true) + override protected def print(ident: DelayedIdent): Unit = { print(" Date: Mon, 26 Feb 2024 11:56:16 +0100 Subject: [PATCH 4/7] Refactoring: Restrict responsibility of gen member idents to SJSGen. Previously, `SJSGen` generated `Ident`s for field members, but only names for method members. Generating the `Ident`s for methods was left in `ClassEmitter` and `Function`. Now, we concentrate that responsibility in `SJSGen` only. In addition, we make a clear distinction between idents generated for *definitions*, which receive an `OriginalName`, and those used for *use sites*, which never receive one. --- .../linker/backend/emitter/ClassEmitter.scala | 23 +++++-------------- .../linker/backend/emitter/CoreJSLib.scala | 8 ++++--- .../backend/emitter/FunctionEmitter.scala | 7 ++---- .../linker/backend/emitter/SJSGen.scala | 23 +++++++++++++++---- 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala index 211b335e29..e0154b1782 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala @@ -355,7 +355,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } yield { val field = anyField.asInstanceOf[FieldDef] implicit val pos = field.pos - js.Assign(genSelect(js.This(), field.name, field.originalName), + js.Assign(genSelectForDef(js.This(), field.name, field.originalName), genZeroOf(field.ftpe)) } } @@ -422,8 +422,11 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val zero = genBoxedZeroOf(field.ftpe) field match { case FieldDef(_, name, originalName, _) => + /* TODO This seems to be dead code, which is somehow reassuring + * because I don't know what it is supposed to achieve. + */ WithGlobals( - js.Assign(js.DotSelect(classVarRef, genMemberFieldIdent(name, originalName)), zero)) + js.Assign(genSelectForDef(classVarRef, name, originalName), zero)) case JSFieldDef(_, name, _) => for (propName <- genMemberNameTree(name)) yield js.Assign(genPropSelect(classVarRef, propName), zero) @@ -472,7 +475,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { for { methodFun <- desugarToFunction(className, method.args, method.body.get, method.resultType) } yield { - val jsMethodName = genMemberMethodIdent(method.name, method.originalName) + val jsMethodName = genMethodIdentForDef(method.name, method.originalName) if (useESClass) { js.MethodDef(static = false, jsMethodName, methodFun.args, methodFun.restParam, methodFun.body) @@ -659,20 +662,6 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } } - private def genMemberFieldIdent(ident: FieldIdent, - originalName: OriginalName): js.Ident = { - val jsName = genName(ident.name) - js.Ident(jsName, genOriginalName(ident.name, originalName, jsName))( - ident.pos) - } - - private def genMemberMethodIdent(ident: MethodIdent, - originalName: OriginalName): js.Ident = { - val jsName = genMethodName(ident.name) - js.Ident(jsName, genOriginalName(ident.name, originalName, jsName))( - ident.pos) - } - def needInstanceTests(tree: LinkedClass)( implicit globalKnowledge: GlobalKnowledge): Boolean = { tree.hasInstanceTests || (tree.hasRuntimeTypeInfo && diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala index 89e4ad86df..3fdefbbfb5 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala @@ -877,7 +877,7 @@ private[emitter] object CoreJSLib { if (implementedInObject) { val staticObjectCall: Tree = { - val fun = globalVar(VarField.c, ObjectClass).prototype DOT genMethodName(methodName) + val fun = globalVar(VarField.c, ObjectClass).prototype DOT genMethodIdent(methodName) Return(Apply(fun DOT "call", instance :: args)) } @@ -1504,7 +1504,8 @@ private[emitter] object CoreJSLib { Nil } - val clone = MethodDef(static = false, Ident(genMethodName(cloneMethodName)), Nil, None, { + val cloneMethodIdent = genMethodIdentForDef(cloneMethodName, NoOriginalName) + val clone = MethodDef(static = false, cloneMethodIdent, Nil, None, { Return(New(ArrayClass, Apply(genIdentBracketSelect(This().u, "slice"), Nil) :: Nil)) }) @@ -1809,7 +1810,8 @@ private[emitter] object CoreJSLib { Nil } - val clone = MethodDef(static = false, Ident(genMethodName(cloneMethodName)), Nil, None, { + val cloneMethodIdent = genMethodIdentForDef(cloneMethodName, NoOriginalName) + val clone = MethodDef(static = false, cloneMethodIdent, Nil, None, { Return(New(ArrayClass, Apply(genIdentBracketSelect(This().u, "slice"), Nil) :: Nil)) }) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 75b4bdeb61..95cf6e1c33 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -2252,7 +2252,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { val newArgs = transformTypedArgs(method.name, args) def genNormalApply(): js.Tree = - js.Apply(newReceiver(false) DOT transformMethodIdent(method), newArgs) + js.Apply(newReceiver(false) DOT genMethodIdent(method), newArgs) def genDispatchApply(): js.Tree = js.Apply(globalVar(VarField.dp, methodName), newReceiver(false) :: newArgs) @@ -2315,7 +2315,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genApplyStaticLike(VarField.f, className, method, transformedArgs) } else { val fun = - globalVar(VarField.c, className).prototype DOT transformMethodIdent(method) + globalVar(VarField.c, className).prototype DOT genMethodIdent(method) js.Apply(fun DOT "call", transformedArgs) } @@ -3207,9 +3207,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { private def transformLabelIdent(ident: LabelIdent): js.Ident = js.Ident(genName(ident.name))(ident.pos) - private def transformMethodIdent(ident: MethodIdent): js.Ident = - js.Ident(genMethodName(ident.name))(ident.pos) - private def transformLocalVarIdent(ident: LocalIdent): js.Ident = js.Ident(transformLocalName(ident.name))(ident.pos) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala index e7ea19a9db..615b1e94a0 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala @@ -154,7 +154,7 @@ private[emitter] final class SJSGen( DotSelect(receiver, Ident(genName(field.name))(field.pos)) } - def genSelect(receiver: Tree, field: irt.FieldIdent, + def genSelectForDef(receiver: Tree, field: irt.FieldIdent, originalName: OriginalName)( implicit pos: Position): Tree = { val jsName = genName(field.name) @@ -164,7 +164,7 @@ private[emitter] final class SJSGen( def genApply(receiver: Tree, methodName: MethodName, args: List[Tree])( implicit pos: Position): Tree = { - Apply(DotSelect(receiver, Ident(genMethodName(methodName))), args) + Apply(DotSelect(receiver, genMethodIdent(methodName)), args) } def genApply(receiver: Tree, methodName: MethodName, args: Tree*)( @@ -172,8 +172,23 @@ private[emitter] final class SJSGen( genApply(receiver, methodName, args.toList) } - def genMethodName(methodName: MethodName): String = - genName(methodName) + def genMethodIdent(methodIdent: irt.MethodIdent): Ident = + genMethodIdent(methodIdent.name)(methodIdent.pos) + + def genMethodIdentForDef(methodIdent: irt.MethodIdent, + originalName: OriginalName): Ident = { + genMethodIdentForDef(methodIdent.name, originalName)(methodIdent.pos) + } + + def genMethodIdent(methodName: MethodName)(implicit pos: Position): Ident = + Ident(genName(methodName)) + + def genMethodIdentForDef(methodName: MethodName, originalName: OriginalName)( + implicit pos: Position): Ident = { + val jsName = genName(methodName) + val jsOrigName = genOriginalName(methodName, originalName, jsName) + Ident(jsName, jsOrigName) + } def genJSPrivateSelect(receiver: Tree, field: irt.FieldIdent)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, From 41b7b9c8751087602a197fc8ad591c74809e2f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 24 Jan 2024 14:37:59 +0100 Subject: [PATCH 5/7] Minify property names ourselves in fullLink when we don't use GCC. When emitting ES modules, we cannot use Closure because it does not support ES modules the way we need it to. This results in files that are much larger than with other module kinds. Off-the-shelf JavaScript bundlers/minifier can compensate for that to a large extent for local and file-local variables, but they do not have enough semantic information to do it on property names. We therefore add our own property name compressor. When enabled, the emitter computes the frequency of every field and method name in the entire program. It then uses those frequencies to allocate short names to them, with the shortest ones allocated to the most used properties. In order to compute the frequencies, we count how many times `genMethodName` is called for any particular `MethodName` (same for other kinds of names) during JS AST generation. That means that while we generate the JS AST, we do not know the final frequencies, and therefore the eventually allocated names. We use `DelayedIdent`s to defer the actual resolution until after the frequencies are computed. Obviously, this breaks any sort of incremental behavior. Since we do not cache the frequency counts per calling method, we have to force re-generation of the whole AST at each run to re-count. Therefore, we invalidate all the emitter caches on every run when the new minifier is enabled. This should not be a problem as it is only intended to be used for fullLink. An alternative would be to store the counts along with global refs in `WithGlobals`, but the overhead would then leak pretty strongly on incremental runs that do not minify. This strategy also prevents fusing AST generation and pretty-printing. When minifying, we demand that the `postTransformer` be `PostTransformer.Identity`. This adds a bit of handling to `BasicLinkerBackend` to deal with the two possible kinds of trees received from the emitter, but nothing too invasive. We automatically enable the new minifier under fullLink when GCC is disabled. This can be overridden with a `scalaJSLinkerConfig` setting. --- Jenkinsfile | 110 +++++---- .../linker/interface/StandardConfig.scala | 22 ++ .../closure/ClosureLinkerBackend.scala | 15 +- .../linker/backend/BasicLinkerBackend.scala | 85 +++++-- .../linker/backend/LinkerBackendImpl.scala | 21 +- .../backend/emitter/ArrayClassProperty.scala | 46 ++++ .../linker/backend/emitter/CoreJSLib.scala | 37 ++- .../linker/backend/emitter/Emitter.scala | 47 +++- .../backend/emitter/FunctionEmitter.scala | 22 +- .../backend/emitter/NameCompressor.scala | 230 ++++++++++++++++++ .../linker/backend/emitter/NameGen.scala | 4 +- .../linker/backend/emitter/SJSGen.scala | 86 ++++++- .../linker/backend/emitter/TreeDSL.scala | 3 +- .../standard/StandardLinkerBackend.scala | 1 + .../org/scalajs/linker/EmitterTest.scala | 2 +- .../org/scalajs/linker/LibrarySizeTest.scala | 3 +- .../backend/emitter/NameCompressorTest.scala | 53 ++++ project/Build.scala | 81 ++++-- .../sbtplugin/ScalaJSPluginInternal.scala | 1 + .../scalajs/testsuite/utils/BuildInfo.scala | 3 +- .../scalajs/testsuite/utils/Platform.scala | 5 +- .../testsuite/compiler/OptimizerTest.scala | 9 +- .../testsuite/jsinterop/MiscInteropTest.scala | 4 +- .../testsuite/library/StackTraceTest.scala | 2 +- .../scalajs/testsuite/utils/Platform.scala | 4 +- .../scalajs/testsuite/compiler/LongTest.scala | 6 +- .../compiler/ReflectiveCallTest.scala | 2 +- 27 files changed, 758 insertions(+), 146 deletions(-) create mode 100644 linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ArrayClassProperty.scala create mode 100644 linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameCompressor.scala create mode 100644 linker/shared/src/test/scala/org/scalajs/linker/backend/emitter/NameCompressorTest.scala diff --git a/Jenkinsfile b/Jenkinsfile index 158b73cc4e..1c9dc60c29 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -185,6 +185,9 @@ def Tasks = [ reversi$v/fastLinkJS \ reversi$v/fullLinkJS \ reversi$v/checksizes && + sbtretry ++$scala \ + 'set Global/enableMinifyEverywhere := true' \ + reversi$v/checksizes && sbtretry ++$scala javalibintf/compile:doc compiler$v/compile:doc library$v/compile:doc \ testInterface$v/compile:doc testBridge$v/compile:doc && sbtretry ++$scala headerCheck && @@ -199,68 +202,84 @@ def Tasks = [ "test-suite-default-esversion": ''' setJavaVersion $java npm install && - sbtretry ++$scala jUnitTestOutputsJVM$v/test jUnitTestOutputsJS$v/test testBridge$v/test \ + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + jUnitTestOutputsJVM$v/test jUnitTestOutputsJS$v/test testBridge$v/test \ 'set scalaJSStage in Global := FullOptStage' jUnitTestOutputsJS$v/test testBridge$v/test && - sbtretry ++$scala $testSuite$v/test $testSuite$v/testHtmlJSDom && - sbtretry 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + $testSuite$v/test $testSuite$v/testHtmlJSDom && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSStage in Global := FullOptStage' \ + $testSuite$v/test \ $testSuite$v/testHtmlJSDom && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)))' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)).withOptimizer(false))' \ - ++$scala $testSuite$v/test && - sbtretry \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)))' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)).withOptimizer(false))' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAvoidLetsAndConsts(false).withAvoidClasses(false)))' \ - ++$scala $testSuite$v/test && - sbtretry \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAvoidLetsAndConsts(false).withAvoidClasses(false)))' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ - ++$scala $testSuite$v/test && - sbtretry \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test && - sbtretry \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ - ++$scala $testSuite$v/test && - sbtretry \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ - ++$scala $testSuite$v/test && - sbtretry \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallModulesFor(List("org.scalajs.testsuite"))))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ - ++$scala $testSuite$v/test && - sbtretry \ + $testSuite$v/test && + # The following tests the same thing whether testMinify is true or false; we also set it for regularity. + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test + $testSuite$v/test ''', "test-suite-custom-esversion-force-polyfills": ''' @@ -504,9 +523,10 @@ mainScalaVersions.each { scalaVersion -> quickMatrix.add([task: "main", scala: scalaVersion, java: javaVersion]) quickMatrix.add([task: "tools", scala: scalaVersion, java: javaVersion]) } - quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testSuite: "testSuite"]) + quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testMinify: "false", testSuite: "testSuite"]) + quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testMinify: "true", testSuite: "testSuite"]) quickMatrix.add([task: "test-suite-custom-esversion", scala: scalaVersion, java: mainJavaVersion, esVersion: "ES5_1", testSuite: "testSuite"]) - quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testSuite: "scalaTestSuite"]) + quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testMinify: "false", testSuite: "scalaTestSuite"]) quickMatrix.add([task: "test-suite-custom-esversion", scala: scalaVersion, java: mainJavaVersion, esVersion: "ES5_1", testSuite: "scalaTestSuite"]) quickMatrix.add([task: "bootstrap", scala: scalaVersion, java: mainJavaVersion]) quickMatrix.add([task: "partest-fastopt", scala: scalaVersion, java: mainJavaVersion]) @@ -527,7 +547,7 @@ otherScalaVersions.each { scalaVersion -> } mainScalaVersions.each { scalaVersion -> otherJavaVersions.each { javaVersion -> - quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: javaVersion, testSuite: "testSuite"]) + quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: javaVersion, testMinify: "false", testSuite: "testSuite"]) } fullMatrix.add([task: "partest-noopt", scala: scalaVersion, java: mainJavaVersion]) fullMatrix.add([task: "partest-fullopt", scala: scalaVersion, java: mainJavaVersion]) diff --git a/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/StandardConfig.scala b/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/StandardConfig.scala index d867c1d1bf..40644b5b9f 100644 --- a/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/StandardConfig.scala +++ b/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/StandardConfig.scala @@ -46,6 +46,19 @@ final class StandardConfig private ( val relativizeSourceMapBase: Option[URI], /** Name patterns for output. */ val outputPatterns: OutputPatterns, + /** Apply Scala.js-specific minification of the produced .js files. + * + * When enabled, the linker more aggressively reduces the size of the + * generated code, at the cost of readability and debuggability. It does + * not perform size optimizations that would negatively impact run-time + * performance. + * + * The focus is on optimizations that general-purpose JavaScript minifiers + * cannot do on their own. For the best results, we expect the Scala.js + * minifier to be used in conjunction with a general-purpose JavaScript + * minifier. + */ + val minify: Boolean, /** Whether to use the Google Closure Compiler pass, if it is available. * On the JavaScript platform, this does not have any effect. */ @@ -80,6 +93,7 @@ final class StandardConfig private ( sourceMap = true, relativizeSourceMapBase = None, outputPatterns = OutputPatterns.Defaults, + minify = false, closureCompilerIfAvailable = false, prettyPrint = false, batchMode = false, @@ -148,6 +162,9 @@ final class StandardConfig private ( def withOutputPatterns(f: OutputPatterns => OutputPatterns): StandardConfig = copy(outputPatterns = f(outputPatterns)) + def withMinify(minify: Boolean): StandardConfig = + copy(minify = minify) + def withClosureCompilerIfAvailable(closureCompilerIfAvailable: Boolean): StandardConfig = copy(closureCompilerIfAvailable = closureCompilerIfAvailable) @@ -173,6 +190,7 @@ final class StandardConfig private ( | sourceMap = $sourceMap, | relativizeSourceMapBase = $relativizeSourceMapBase, | outputPatterns = $outputPatterns, + | minify = $minify, | closureCompilerIfAvailable = $closureCompilerIfAvailable, | prettyPrint = $prettyPrint, | batchMode = $batchMode, @@ -192,6 +210,7 @@ final class StandardConfig private ( sourceMap: Boolean = sourceMap, outputPatterns: OutputPatterns = outputPatterns, relativizeSourceMapBase: Option[URI] = relativizeSourceMapBase, + minify: Boolean = minify, closureCompilerIfAvailable: Boolean = closureCompilerIfAvailable, prettyPrint: Boolean = prettyPrint, batchMode: Boolean = batchMode, @@ -209,6 +228,7 @@ final class StandardConfig private ( sourceMap, relativizeSourceMapBase, outputPatterns, + minify, closureCompilerIfAvailable, prettyPrint, batchMode, @@ -237,6 +257,7 @@ object StandardConfig { .addField("relativizeSourceMapBase", config.relativizeSourceMapBase.map(_.toASCIIString())) .addField("outputPatterns", config.outputPatterns) + .addField("minify", config.minify) .addField("closureCompilerIfAvailable", config.closureCompilerIfAvailable) .addField("prettyPrint", config.prettyPrint) @@ -264,6 +285,7 @@ object StandardConfig { * - `sourceMap`: `true` * - `relativizeSourceMapBase`: `None` * - `outputPatterns`: [[OutputPatterns.Defaults]] + * - `minify`: `false` * - `closureCompilerIfAvailable`: `false` * - `prettyPrint`: `false` * - `batchMode`: `false` diff --git a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala index 64160204ac..7532e0be47 100644 --- a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala +++ b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala @@ -54,13 +54,19 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) s"Cannot use module kind $moduleKind with the Closure Compiler") private[this] val emitter = { + // Note that we do not transfer `minify` -- Closure will do its own thing anyway val emitterConfig = Emitter.Config(config.commonConfig.coreSpec) .withJSHeader(config.jsHeader) .withOptimizeBracketSelects(false) .withTrackAllGlobalRefs(true) .withInternalModulePattern(m => OutputPatternsImpl.moduleName(config.outputPatterns, m.id)) - new Emitter(emitterConfig, ClosureLinkerBackend.PostTransformer) + // Do not apply ClosureAstTransformer eagerly: + // The ASTs used by closure are highly mutable, so re-using them is non-trivial. + // Since closure is slow anyways, we haven't built the optimization. + val postTransformer = Emitter.PostTransformer.Identity + + new Emitter(emitterConfig, postTransformer) } val symbolRequirements: SymbolRequirement = emitter.symbolRequirements @@ -296,11 +302,4 @@ private object ClosureLinkerBackend { Function.prototype.apply; var NaN = 0.0/0.0, Infinity = 1.0/0.0, undefined = void 0; """ - - private object PostTransformer extends Emitter.PostTransformer[js.Tree] { - // Do not apply ClosureAstTransformer eagerly: - // The ASTs used by closure are highly mutable, so re-using them is non-trivial. - // Since closure is slow anyways, we haven't built the optimization. - def transformStats(trees: List[js.Tree], indent: Int): List[js.Tree] = trees - } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala index e07e31597b..fa7e616880 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala @@ -41,16 +41,19 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) private[this] var totalModules = 0 private[this] val rewrittenModules = new AtomicInteger(0) - private[this] val emitter = { + private[this] val bodyPrinter: BodyPrinter = { + if (config.minify) IdentityPostTransformerBasedBodyPrinter + else if (config.sourceMap) PrintedTreeWithSourceMapBodyPrinter + else PrintedTreeWithoutSourceMapBodyPrinter + } + + private[this] val emitter: Emitter[bodyPrinter.TreeType] = { val emitterConfig = Emitter.Config(config.commonConfig.coreSpec) .withJSHeader(config.jsHeader) .withInternalModulePattern(m => OutputPatternsImpl.moduleName(config.outputPatterns, m.id)) + .withMinify(config.minify) - val postTransformer = - if (config.sourceMap) PostTransformerWithSourceMap - else PostTransformerWithoutSourceMap - - new Emitter(emitterConfig, postTransformer) + new Emitter(emitterConfig, bodyPrinter.postTransformer) } val symbolRequirements: SymbolRequirement = emitter.symbolRequirements @@ -82,13 +85,14 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) val skipContentCheck = !isFirstRun isFirstRun = false - val allChanged = + val allChanged0 = printedModuleSetCache.updateGlobal(emitterResult.header, emitterResult.footer) + val allChanged = allChanged0 || config.minify val writer = new OutputWriter(output, config, skipContentCheck) { protected def writeModuleWithoutSourceMap(moduleID: ModuleID, force: Boolean): Option[ByteBuffer] = { val cache = printedModuleSetCache.getModuleCache(moduleID) - val (printedTrees, changed) = emitterResult.body(moduleID) + val (trees, changed) = emitterResult.body(moduleID) if (force || changed || allChanged) { rewrittenModules.incrementAndGet() @@ -98,8 +102,7 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) jsFileWriter.write(printedModuleSetCache.headerBytes) jsFileWriter.writeASCIIString("'use strict';\n") - for (printedTree <- printedTrees) - jsFileWriter.write(printedTree.jsCode) + bodyPrinter.printWithoutSourceMap(trees, jsFileWriter) jsFileWriter.write(printedModuleSetCache.footerBytes) @@ -112,7 +115,7 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) protected def writeModuleWithSourceMap(moduleID: ModuleID, force: Boolean): Option[(ByteBuffer, ByteBuffer)] = { val cache = printedModuleSetCache.getModuleCache(moduleID) - val (printedTrees, changed) = emitterResult.body(moduleID) + val (trees, changed) = emitterResult.body(moduleID) if (force || changed || allChanged) { rewrittenModules.incrementAndGet() @@ -133,10 +136,7 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) jsFileWriter.writeASCIIString("'use strict';\n") smWriter.nextLine() - for (printedTree <- printedTrees) { - jsFileWriter.write(printedTree.jsCode) - smWriter.insertFragment(printedTree.sourceMapFragment) - } + bodyPrinter.printWithSourceMap(trees, jsFileWriter, smWriter) jsFileWriter.write(printedModuleSetCache.footerBytes) jsFileWriter.write(("//# sourceMappingURL=" + sourceMapURI + "\n").getBytes(StandardCharsets.UTF_8)) @@ -240,6 +240,57 @@ private object BasicLinkerBackend { } } + private abstract class BodyPrinter { + type TreeType >: Null <: js.Tree + + val postTransformer: Emitter.PostTransformer[TreeType] + + def printWithoutSourceMap(trees: List[TreeType], jsFileWriter: ByteArrayWriter): Unit + def printWithSourceMap(trees: List[TreeType], jsFileWriter: ByteArrayWriter, smWriter: SourceMapWriter): Unit + } + + private object IdentityPostTransformerBasedBodyPrinter extends BodyPrinter { + type TreeType = js.Tree + + val postTransformer: Emitter.PostTransformer[TreeType] = Emitter.PostTransformer.Identity + + def printWithoutSourceMap(trees: List[TreeType], jsFileWriter: ByteArrayWriter): Unit = { + val printer = new Printers.JSTreePrinter(jsFileWriter) + for (tree <- trees) + printer.printStat(tree) + } + + def printWithSourceMap(trees: List[TreeType], jsFileWriter: ByteArrayWriter, smWriter: SourceMapWriter): Unit = { + val printer = new Printers.JSTreePrinterWithSourceMap(jsFileWriter, smWriter, initIndent = 0) + for (tree <- trees) + printer.printStat(tree) + } + } + + private abstract class PrintedTreeBasedBodyPrinter( + val postTransformer: Emitter.PostTransformer[js.PrintedTree] + ) extends BodyPrinter { + type TreeType = js.PrintedTree + + def printWithoutSourceMap(trees: List[TreeType], jsFileWriter: ByteArrayWriter): Unit = { + for (tree <- trees) + jsFileWriter.write(tree.jsCode) + } + + def printWithSourceMap(trees: List[TreeType], jsFileWriter: ByteArrayWriter, smWriter: SourceMapWriter): Unit = { + for (tree <- trees) { + jsFileWriter.write(tree.jsCode) + smWriter.insertFragment(tree.sourceMapFragment) + } + } + } + + private object PrintedTreeWithoutSourceMapBodyPrinter + extends PrintedTreeBasedBodyPrinter(PostTransformerWithoutSourceMap) + + private object PrintedTreeWithSourceMapBodyPrinter + extends PrintedTreeBasedBodyPrinter(PostTransformerWithSourceMap) + private object PostTransformerWithoutSourceMap extends Emitter.PostTransformer[js.PrintedTree] { def transformStats(trees: List[js.Tree], indent: Int): List[js.PrintedTree] = { if (trees.isEmpty) { @@ -248,7 +299,7 @@ private object BasicLinkerBackend { val jsCodeWriter = new ByteArrayWriter() val printer = new Printers.JSTreePrinter(jsCodeWriter, indent) - trees.map(printer.printStat(_)) + trees.foreach(printer.printStat(_)) js.PrintedTree(jsCodeWriter.toByteArray(), SourceMapWriter.Fragment.Empty) :: Nil } @@ -264,7 +315,7 @@ private object BasicLinkerBackend { val smFragmentBuilder = new SourceMapWriter.FragmentBuilder() val printer = new Printers.JSTreePrinterWithSourceMap(jsCodeWriter, smFragmentBuilder, indent) - trees.map(printer.printStat(_)) + trees.foreach(printer.printStat(_)) smFragmentBuilder.complete() js.PrintedTree(jsCodeWriter.toByteArray(), smFragmentBuilder.result()) :: Nil diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/LinkerBackendImpl.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/LinkerBackendImpl.scala index e1795d4493..0fc8f5169b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/LinkerBackendImpl.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/LinkerBackendImpl.scala @@ -53,6 +53,8 @@ object LinkerBackendImpl { val outputPatterns: OutputPatterns, /** Base path to relativize paths in the source map. */ val relativizeSourceMapBase: Option[URI], + /** Whether to use Scala.js' minifier for property names. */ + val minify: Boolean, /** Whether to use the Google Closure Compiler pass, if it is available. * On the JavaScript platform, this does not have any effect. */ @@ -69,6 +71,7 @@ object LinkerBackendImpl { sourceMap = true, outputPatterns = OutputPatterns.Defaults, relativizeSourceMapBase = None, + minify = false, closureCompilerIfAvailable = false, prettyPrint = false, maxConcurrentWrites = 50) @@ -91,6 +94,9 @@ object LinkerBackendImpl { def withRelativizeSourceMapBase(relativizeSourceMapBase: Option[URI]): Config = copy(relativizeSourceMapBase = relativizeSourceMapBase) + def withMinify(minify: Boolean): Config = + copy(minify = minify) + def withClosureCompilerIfAvailable(closureCompilerIfAvailable: Boolean): Config = copy(closureCompilerIfAvailable = closureCompilerIfAvailable) @@ -106,12 +112,21 @@ object LinkerBackendImpl { sourceMap: Boolean = sourceMap, outputPatterns: OutputPatterns = outputPatterns, relativizeSourceMapBase: Option[URI] = relativizeSourceMapBase, + minify: Boolean = minify, closureCompilerIfAvailable: Boolean = closureCompilerIfAvailable, prettyPrint: Boolean = prettyPrint, maxConcurrentWrites: Int = maxConcurrentWrites): Config = { - new Config(commonConfig, jsHeader, sourceMap, outputPatterns, - relativizeSourceMapBase, closureCompilerIfAvailable, prettyPrint, - maxConcurrentWrites) + new Config( + commonConfig, + jsHeader, + sourceMap, + outputPatterns, + relativizeSourceMapBase, + minify, + closureCompilerIfAvailable, + prettyPrint, + maxConcurrentWrites + ) } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ArrayClassProperty.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ArrayClassProperty.scala new file mode 100644 index 0000000000..c33d0a99e7 --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ArrayClassProperty.scala @@ -0,0 +1,46 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker.backend.emitter + +import org.scalajs.ir.OriginalName + +/** Represents a property of one of the special `ArrayClass`es. + * + * These properties live in the same namespace as Scala field and method + * names, because the `ArrayClass`es extend `j.l.Object`. Therefore, they + * must take part in the global property minification algorithm. + */ +final class ArrayClassProperty(val nonMinifiedName: String) + extends Comparable[ArrayClassProperty] { + + val originalName: OriginalName = OriginalName(nonMinifiedName) + + def compareTo(that: ArrayClassProperty): Int = + this.nonMinifiedName.compareTo(that.nonMinifiedName) + + override def toString(): String = s"ArrayClassProperty($nonMinifiedName)" +} + +object ArrayClassProperty { + /** `ArrayClass.u`: the underlying array of typed array. */ + val u: ArrayClassProperty = new ArrayClassProperty("u") + + /** `ArrayClass.get()`: gets one element. */ + val get: ArrayClassProperty = new ArrayClassProperty("get") + + /** `ArrayClass.set()`: sets one element. */ + val set: ArrayClassProperty = new ArrayClassProperty("set") + + /** `ArrayClass.copyTo()`: copies from that array to another array. */ + val copyTo: ArrayClassProperty = new ArrayClassProperty("copyTo") +} diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala index 3fdefbbfb5..a96dea3018 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala @@ -1131,7 +1131,7 @@ private[emitter] object CoreJSLib { ) ::: condDefs(esVersion >= ESVersion.ES2015 && nullPointers != CheckedBehavior.Unchecked)( defineFunction5(VarField.systemArraycopy) { (src, srcPos, dest, destPos, length) => - Apply(src DOT "copyTo", List(srcPos, dest, destPos, length)) + genArrayClassPropApply(src, ArrayClassProperty.copyTo, srcPos, dest, destPos, length) } ) ::: @@ -1155,7 +1155,8 @@ private[emitter] object CoreJSLib { srcPos, dest.u.length, destPos, length) }, For(let(i, 0), i < length, i := ((i + 1) | 0), { - Apply(dest DOT "set", List((destPos + i) | 0, BracketSelect(srcArray, (srcPos + i) | 0))) + genArrayClassPropApply(dest, ArrayClassProperty.set, + (destPos + i) | 0, BracketSelect(srcArray, (srcPos + i) | 0)) }) ) }) @@ -1172,7 +1173,7 @@ private[emitter] object CoreJSLib { If(srcData && genIdentBracketSelect(srcData, "isArrayClass"), { // Fast path: the values are array of the same type if (esVersion >= ESVersion.ES2015 && nullPointers == CheckedBehavior.Unchecked) - Apply(src DOT "copyTo", List(srcPos, dest, destPos, length)) + genArrayClassPropApply(src, ArrayClassProperty.copyTo, srcPos, dest, destPos, length) else genCallHelper(VarField.systemArraycopy, src, srcPos, dest, destPos, length) }, { @@ -1444,14 +1445,17 @@ private[emitter] object CoreJSLib { genCallHelper(VarField.throwArrayIndexOutOfBoundsException, i)) } + val getName = genArrayClassPropertyForDef(ArrayClassProperty.get) + val setName = genArrayClassPropertyForDef(ArrayClassProperty.set) + List( - MethodDef(static = false, Ident("get"), paramList(i), None, { + MethodDef(static = false, getName, paramList(i), None, { Block( boundsCheck, Return(BracketSelect(This().u, i)) ) }), - MethodDef(static = false, Ident("set"), paramList(i, v), None, { + MethodDef(static = false, setName, paramList(i, v), None, { Block( boundsCheck, BracketSelect(This().u, i) := v @@ -1465,8 +1469,10 @@ private[emitter] object CoreJSLib { val i = varRef("i") val v = varRef("v") + val setName = genArrayClassPropertyForDef(ArrayClassProperty.set) + List( - MethodDef(static = false, Ident("set"), paramList(i, v), None, { + MethodDef(static = false, setName, paramList(i, v), None, { BracketSelect(This().u, i) := v }) ) @@ -1479,7 +1485,10 @@ private[emitter] object CoreJSLib { val dest = varRef("dest") val destPos = varRef("destPos") val length = varRef("length") - val methodDef = MethodDef(static = false, Ident("copyTo"), + + val copyToName = genArrayClassPropertyForDef(ArrayClassProperty.copyTo) + + val methodDef = MethodDef(static = false, copyToName, paramList(srcPos, dest, destPos, length), None, { if (isTypedArray) { Block( @@ -1771,6 +1780,8 @@ private[emitter] object CoreJSLib { val i = varRef("i") val v = varRef("v") + val setName = genArrayClassPropertyForDef(ArrayClassProperty.set) + val boundsCheck = condTree(arrayIndexOutOfBounds != CheckedBehavior.Unchecked) { If((i < 0) || (i >= This().u.length), genCallHelper(VarField.throwArrayIndexOutOfBoundsException, i)) @@ -1783,7 +1794,7 @@ private[emitter] object CoreJSLib { } List( - MethodDef(static = false, Ident("set"), paramList(i, v), None, { + MethodDef(static = false, setName, paramList(i, v), None, { Block( boundsCheck, storeCheck, @@ -1800,7 +1811,10 @@ private[emitter] object CoreJSLib { val dest = varRef("dest") val destPos = varRef("destPos") val length = varRef("length") - val methodDef = MethodDef(static = false, Ident("copyTo"), + + val copyToName = genArrayClassPropertyForDef(ArrayClassProperty.copyTo) + + val methodDef = MethodDef(static = false, copyToName, paramList(srcPos, dest, destPos, length), None, { genCallHelper(VarField.arraycopyGeneric, This().u, srcPos, dest.u, destPos, length) @@ -2237,5 +2251,10 @@ private[emitter] object CoreJSLib { private def double(d: Double): DoubleLiteral = DoubleLiteral(d) private def bigInt(i: Long): BigIntLiteral = BigIntLiteral(i) + + // cannot extend AnyVal because this is not a static class + private implicit class CustomTreeOps(private val self: Tree) { + def u: Tree = genArrayClassPropSelect(self, ArrayClassProperty.u) + } } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala index 1a8b9bc517..506dec4d4a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala @@ -39,6 +39,9 @@ final class Emitter[E >: Null <: js.Tree]( import Emitter._ import config._ + require(!config.minify || postTransformer == PostTransformer.Identity, + "When using the 'minify' option, the postTransformer must be Identity.") + private implicit val globalRefTracking: GlobalRefTracking = config.topLevelGlobalRefTracking @@ -49,10 +52,14 @@ final class Emitter[E >: Null <: js.Tree]( private val nameGen: NameGen = new NameGen private class State(val lastMentionedDangerousGlobalRefs: Set[String]) { + val nameCompressor = + if (minify) Some(new NameCompressor(config)) + else None + val sjsGen: SJSGen = { val jsGen = new JSGen(config) val varGen = new VarGen(jsGen, nameGen, lastMentionedDangerousGlobalRefs) - new SJSGen(jsGen, nameGen, varGen) + new SJSGen(jsGen, nameGen, varGen, nameCompressor) } val classEmitter: ClassEmitter = new ClassEmitter(sjsGen) @@ -87,7 +94,7 @@ final class Emitter[E >: Null <: js.Tree]( def emit(moduleSet: ModuleSet, logger: Logger): Result[E] = { val WithGlobals(body, globalRefs) = emitInternal(moduleSet, logger) - moduleKind match { + val result = moduleKind match { case ModuleKind.NoModule => assert(moduleSet.modules.size <= 1) val topLevelVars = moduleSet.modules @@ -112,6 +119,19 @@ final class Emitter[E >: Null <: js.Tree]( case ModuleKind.ESModule | ModuleKind.CommonJSModule => new Result(config.jsHeader, body, "", Nil, globalRefs) } + + for (compressor <- state.nameCompressor) { + compressor.allocateNames(moduleSet, logger) + + /* Throw away the whole state, but keep the mentioned dangerous global refs. + * Note that instances of the name compressor's entries are still alive + * at this point, since they are referenced from `DelayedIdent` nodes in + * the result trees. + */ + state = new State(state.lastMentionedDangerousGlobalRefs) + } + + result } private def emitInternal(moduleSet: ModuleSet, @@ -1084,7 +1104,8 @@ object Emitter { val jsHeader: String, val internalModulePattern: ModuleID => String, val optimizeBracketSelects: Boolean, - val trackAllGlobalRefs: Boolean + val trackAllGlobalRefs: Boolean, + val minify: Boolean ) { private def this( semantics: Semantics, @@ -1097,7 +1118,9 @@ object Emitter { jsHeader = "", internalModulePattern = "./" + _.id, optimizeBracketSelects = true, - trackAllGlobalRefs = false) + trackAllGlobalRefs = false, + minify = false + ) } private[emitter] val topLevelGlobalRefTracking: GlobalRefTracking = @@ -1127,6 +1150,9 @@ object Emitter { def withTrackAllGlobalRefs(trackAllGlobalRefs: Boolean): Config = copy(trackAllGlobalRefs = trackAllGlobalRefs) + def withMinify(minify: Boolean): Config = + copy(minify = minify) + private def copy( semantics: Semantics = semantics, moduleKind: ModuleKind = moduleKind, @@ -1134,9 +1160,12 @@ object Emitter { jsHeader: String = jsHeader, internalModulePattern: ModuleID => String = internalModulePattern, optimizeBracketSelects: Boolean = optimizeBracketSelects, - trackAllGlobalRefs: Boolean = trackAllGlobalRefs): Config = { + trackAllGlobalRefs: Boolean = trackAllGlobalRefs, + minify: Boolean = minify + ): Config = { new Config(semantics, moduleKind, esFeatures, jsHeader, - internalModulePattern, optimizeBracketSelects, trackAllGlobalRefs) + internalModulePattern, optimizeBracketSelects, trackAllGlobalRefs, + minify) } } @@ -1149,6 +1178,12 @@ object Emitter { def transformStats(trees: List[js.Tree], indent: Int): List[E] } + object PostTransformer { + object Identity extends PostTransformer[js.Tree] { + def transformStats(trees: List[js.Tree], indent: Int): List[js.Tree] = trees + } + } + private final class DesugaredClassCache[E >: Null] { val privateJSFields = new OneTimeCache[WithGlobals[E]] val storeJSSuperClass = new OneTimeCache[WithGlobals[E]] diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 95cf6e1c33..6a8b9ca3dd 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -632,12 +632,11 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { } if (checked) { - js.Apply(js.DotSelect(genArray, js.Ident("set")), - List(genIndex, genRhs)) + genArrayClassPropApply(genArray, ArrayClassProperty.set, genIndex, genRhs) } else { js.Assign( js.BracketSelect( - js.DotSelect(genArray, js.Ident("u"))(lhs.pos), + genArrayClassPropSelect(genArray, ArrayClassProperty.u)(lhs.pos), genIndex)(lhs.pos), genRhs) } @@ -877,7 +876,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def genUnchecked(): js.Tree = { if (esFeatures.esVersion >= ESVersion.ES2015 && semantics.nullPointers == CheckedBehavior.Unchecked) - js.Apply(jsArgs.head DOT "copyTo", jsArgs.tail) + genArrayClassPropApply(jsArgs.head, ArrayClassProperty.copyTo, jsArgs.tail) else genCallHelper(VarField.systemArraycopy, jsArgs: _*) } @@ -2657,17 +2656,19 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genArrayValue(typeRef, elems.map(transformExpr(_, preserveChar)))) case ArrayLength(array) => - genIdentBracketSelect(js.DotSelect(transformExprNoChar(checkNotNull(array)), - js.Ident("u")), "length") + val newArray = transformExprNoChar(checkNotNull(array)) + genIdentBracketSelect( + genArrayClassPropSelect(newArray, ArrayClassProperty.u), + "length") case ArraySelect(array, index) => val newArray = transformExprNoChar(checkNotNull(array)) val newIndex = transformExprNoChar(index) semantics.arrayIndexOutOfBounds match { case CheckedBehavior.Compliant | CheckedBehavior.Fatal => - js.Apply(js.DotSelect(newArray, js.Ident("get")), List(newIndex)) + genArrayClassPropApply(newArray, ArrayClassProperty.get, newIndex) case CheckedBehavior.Unchecked => - js.BracketSelect(js.DotSelect(newArray, js.Ident("u")), newIndex) + js.BracketSelect(genArrayClassPropSelect(newArray, ArrayClassProperty.u), newIndex) } case tree: RecordSelect => @@ -2766,12 +2767,13 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Transient(ArrayToTypedArray(expr, primRef)) => val value = transformExprNoChar(checkNotNull(expr)) + val valueUnderlying = genArrayClassPropSelect(value, ArrayClassProperty.u) if (es2015) { - js.Apply(genIdentBracketSelect(value.u, "slice"), Nil) + js.Apply(genIdentBracketSelect(valueUnderlying, "slice"), Nil) } else { val typedArrayClass = extractWithGlobals(typedArrayRef(primRef).get) - js.New(typedArrayClass, value.u :: Nil) + js.New(typedArrayClass, valueUnderlying :: Nil) } case Transient(TypedArrayToArray(expr, primRef)) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameCompressor.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameCompressor.scala new file mode 100644 index 0000000000..f23287c6bf --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameCompressor.scala @@ -0,0 +1,230 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker.backend.emitter + +import scala.annotation.{switch, tailrec} + +import java.util.Comparator + +import scala.collection.mutable + +import org.scalajs.ir.Names._ +import org.scalajs.linker.backend.javascript.Trees.DelayedIdent.Resolver +import org.scalajs.linker.standard.ModuleSet +import org.scalajs.logging.Logger + +private[emitter] final class NameCompressor(config: Emitter.Config) { + import NameCompressor._ + + private val entries: EntryMap = mutable.AnyRefMap.empty + + private var namesAllocated: Boolean = false + + def allocateNames(moduleSet: ModuleSet, logger: Logger): Unit = { + assert(!namesAllocated, "Cannot allocate names a second time") + + val propertyNamesToAvoid = logger.time("Name compressor: Collect property names to avoid") { + collectPropertyNamesToAvoid(moduleSet) + } + + logger.time("Name compressor: Allocate property names") { + allocatePropertyNames(entries, propertyNamesToAvoid) + } + + namesAllocated = true + } + + def genResolverFor(fieldName: FieldName): Resolver = + entries.getOrElseUpdate(fieldName, new FieldNameEntry(fieldName)).genResolver() + + def genResolverFor(methodName: MethodName): Resolver = + entries.getOrElseUpdate(methodName, new MethodNameEntry(methodName)).genResolver() + + def genResolverFor(prop: ArrayClassProperty): Resolver = + entries.getOrElseUpdate(prop, new ArrayClassPropEntry(prop)).genResolver() + + /** Collects the property names to avoid for Scala instance members. + * + * We collect the names of exported members in Scala classes. These live in + * the same namespace as Scala methods and fields. Therefore, we must avoid + * them when allocating names for that namespace. + */ + private def collectPropertyNamesToAvoid(moduleSet: ModuleSet): Set[String] = { + import org.scalajs.ir.Trees._ + + val builder = Set.newBuilder[String] + + builder ++= BasePropertyNamesToAvoid + + for { + module <- moduleSet.modules + linkedClass <- module.classDefs + if linkedClass.kind.isClass + exportedMember <- linkedClass.exportedMembers + } { + (exportedMember: @unchecked) match { + case JSMethodDef(_, StringLiteral(name), _, _, _) => + builder += name + case JSPropertyDef(_, StringLiteral(name), _, _) => + builder += name + } + } + + builder.result() + } +} + +private[emitter] object NameCompressor { + /** Base set of names that should be avoided when allocating property names + * in any namespace. + * + * This set contains: + * + * - the reserved JS identifiers (not technically invalid by spec, but JS + * minifiers tend to avoid them anyway: `foo.if` is playing with fire), + * - the `"then"` name, because it is used to identify `Thenable`s by + * spec and therefore lives in the same namespace as the properties of + * *all* objects, + */ + private val BasePropertyNamesToAvoid: Set[String] = + NameGen.ReservedJSIdentifierNames + "then" + + private def allocatePropertyNames(entries: EntryMap, + namesToAvoid: collection.Set[String]): Unit = { + val comparator: Comparator[PropertyNameEntry] = + Comparator.comparingInt[PropertyNameEntry](_.occurrences).reversed() // by decreasing order of occurrences + .thenComparing(Comparator.naturalOrder[PropertyNameEntry]()) // tie-break + + val orderedEntries = entries.values.toArray + java.util.Arrays.sort(orderedEntries, comparator) + + val generator = new NameGenerator(namesToAvoid) + + for (entry <- orderedEntries) + entry.allocatedName = generator.nextString() + } + + /** Keys of this map are `FieldName | MethodName | ArrayClassProperty`. */ + private type EntryMap = mutable.AnyRefMap[AnyRef, PropertyNameEntry] + + private sealed abstract class PropertyNameEntry extends Comparable[PropertyNameEntry] { + var occurrences: Int = 0 + var allocatedName: String = null + + protected def debugString: String + + private object resolver extends Resolver { + def resolve(): String = { + if (allocatedName == null) + throw new IllegalStateException(s"Cannot resolve name before it was allocated, for $this") + allocatedName + } + + def debugString: String = PropertyNameEntry.this.debugString + + override def toString(): String = debugString + } + + private def incOccurrences(): Unit = { + if (allocatedName != null) + throw new IllegalStateException(s"Cannot increase occurrences after name was allocated for $this") + occurrences += 1 + } + + def genResolver(): Resolver = { + incOccurrences() + resolver + } + + def compareTo(that: PropertyNameEntry): Int = (this, that) match { + case (x: FieldNameEntry, y: FieldNameEntry) => + x.fieldName.compareTo(y.fieldName) + + case (x: MethodNameEntry, y: MethodNameEntry) => + x.methodName.compareTo(y.methodName) + + case (x: ArrayClassPropEntry, y: ArrayClassPropEntry) => + x.property.compareTo(y.property) + + case _ => + def ordinalFor(x: PropertyNameEntry): Int = x match { + case _: FieldNameEntry => 1 + case _: MethodNameEntry => 2 + case _: ArrayClassPropEntry => 3 + } + ordinalFor(this) - ordinalFor(that) + } + } + + private final class FieldNameEntry(val fieldName: FieldName) + extends PropertyNameEntry { + protected def debugString: String = fieldName.nameString + + override def toString(): String = s"FieldNameEntry(${fieldName.nameString})" + } + + private final class MethodNameEntry(val methodName: MethodName) + extends PropertyNameEntry { + protected def debugString: String = methodName.nameString + + override def toString(): String = s"MethodNameEntry(${methodName.nameString})" + } + + private final class ArrayClassPropEntry(val property: ArrayClassProperty) + extends PropertyNameEntry { + protected def debugString: String = property.nonMinifiedName + + override def toString(): String = s"ArrayClassPropEntry(${property.nonMinifiedName})" + } + + // private[emitter] for tests + private[emitter] final class NameGenerator(namesToAvoid: collection.Set[String]) { + /* 6 because 52 * (62**5) > Int.MaxValue + * i.e., to exceed this size we would need more than Int.MaxValue different names. + */ + private val charArray = new Array[Char](6) + charArray(0) = 'a' + private var charCount = 1 + + @tailrec + private def incAtIndex(idx: Int): Unit = { + (charArray(idx): @switch) match { + case '9' => + charArray(idx) = 'a' + case 'z' => + charArray(idx) = 'A' + case 'Z' => + if (idx > 0) { + charArray(idx) = '0' + incAtIndex(idx - 1) + } else { + java.util.Arrays.fill(charArray, '0') + charArray(0) = 'a' + charCount += 1 + } + case c => + charArray(idx) = (c + 1).toChar + } + } + + @tailrec + final def nextString(): String = { + val s = new String(charArray, 0, charCount) + incAtIndex(charCount - 1) + if (namesToAvoid.contains(s)) + nextString() + else + s + } + } +} diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameGen.scala index 552dd545bd..ae8502211d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameGen.scala @@ -301,7 +301,7 @@ private[emitter] final class NameGen { } } -private object NameGen { +private[emitter] object NameGen { private final val FullwidthSpacingUnderscore = '\uff3f' private final val GreekSmallLetterDelta = '\u03b4' @@ -371,7 +371,7 @@ private object NameGen { * not actually mean `void 0`, and who knows what JS engine performance * cliffs we can trigger with that. */ - private final val ReservedJSIdentifierNames: Set[String] = Set( + private[emitter] final val ReservedJSIdentifierNames: Set[String] = Set( "arguments", "await", "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else", "enum", "eval", "export", "extends", "false", "finally", "for", "function", "if", diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala index 615b1e94a0..9e8810afc3 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala @@ -32,7 +32,8 @@ import PolyfillableBuiltin._ private[emitter] final class SJSGen( val jsGen: JSGen, val nameGen: NameGen, - val varGen: VarGen + val varGen: VarGen, + val nameCompressor: Option[NameCompressor] ) { import jsGen._ @@ -151,15 +152,36 @@ private[emitter] final class SJSGen( def genSelect(receiver: Tree, field: irt.FieldIdent)( implicit pos: Position): Tree = { - DotSelect(receiver, Ident(genName(field.name))(field.pos)) + DotSelect(receiver, genFieldIdent(field.name)(field.pos)) } def genSelectForDef(receiver: Tree, field: irt.FieldIdent, originalName: OriginalName)( implicit pos: Position): Tree = { - val jsName = genName(field.name) - val jsOrigName = genOriginalName(field.name, originalName, jsName) - DotSelect(receiver, Ident(jsName, jsOrigName)(field.pos)) + DotSelect(receiver, genFieldIdentForDef(field.name, originalName)(field.pos)) + } + + private def genFieldIdent(fieldName: FieldName)( + implicit pos: Position): MaybeDelayedIdent = { + nameCompressor match { + case None => + Ident(genName(fieldName)) + case Some(compressor) => + DelayedIdent(compressor.genResolverFor(fieldName)) + } + } + + private def genFieldIdentForDef(fieldName: FieldName, + originalName: OriginalName)( + implicit pos: Position): MaybeDelayedIdent = { + nameCompressor match { + case None => + val jsName = genName(fieldName) + val jsOrigName = genOriginalName(fieldName, originalName, jsName) + Ident(jsName, jsOrigName) + case Some(compressor) => + DelayedIdent(compressor.genResolverFor(fieldName), originalName.orElse(fieldName)) + } } def genApply(receiver: Tree, methodName: MethodName, args: List[Tree])( @@ -172,22 +194,60 @@ private[emitter] final class SJSGen( genApply(receiver, methodName, args.toList) } - def genMethodIdent(methodIdent: irt.MethodIdent): Ident = + def genMethodIdent(methodIdent: irt.MethodIdent): MaybeDelayedIdent = genMethodIdent(methodIdent.name)(methodIdent.pos) def genMethodIdentForDef(methodIdent: irt.MethodIdent, - originalName: OriginalName): Ident = { + originalName: OriginalName): MaybeDelayedIdent = { genMethodIdentForDef(methodIdent.name, originalName)(methodIdent.pos) } - def genMethodIdent(methodName: MethodName)(implicit pos: Position): Ident = - Ident(genName(methodName)) + def genMethodIdent(methodName: MethodName)(implicit pos: Position): MaybeDelayedIdent = { + nameCompressor match { + case None => Ident(genName(methodName)) + case Some(compressor) => DelayedIdent(compressor.genResolverFor(methodName)) + } + } def genMethodIdentForDef(methodName: MethodName, originalName: OriginalName)( - implicit pos: Position): Ident = { - val jsName = genName(methodName) - val jsOrigName = genOriginalName(methodName, originalName, jsName) - Ident(jsName, jsOrigName) + implicit pos: Position): MaybeDelayedIdent = { + nameCompressor match { + case None => + val jsName = genName(methodName) + val jsOrigName = genOriginalName(methodName, originalName, jsName) + Ident(jsName, jsOrigName) + case Some(compressor) => + DelayedIdent(compressor.genResolverFor(methodName), originalName.orElse(methodName)) + } + } + + def genArrayClassPropApply(receiver: Tree, prop: ArrayClassProperty, args: Tree*)( + implicit pos: Position): Tree = { + genArrayClassPropApply(receiver, prop, args.toList) + } + + def genArrayClassPropApply(receiver: Tree, prop: ArrayClassProperty, args: List[Tree])( + implicit pos: Position): Tree = { + Apply(genArrayClassPropSelect(receiver, prop), args) + } + + def genArrayClassPropSelect(qualifier: Tree, prop: ArrayClassProperty)( + implicit pos: Position): Tree = { + DotSelect(qualifier, genArrayClassProperty(prop)) + } + + def genArrayClassProperty(prop: ArrayClassProperty)(implicit pos: Position): MaybeDelayedIdent = { + nameCompressor match { + case None => Ident(prop.nonMinifiedName) + case Some(compressor) => DelayedIdent(compressor.genResolverFor(prop)) + } + } + + def genArrayClassPropertyForDef(prop: ArrayClassProperty)(implicit pos: Position): MaybeDelayedIdent = { + nameCompressor match { + case None => Ident(prop.nonMinifiedName) + case Some(compressor) => DelayedIdent(compressor.genResolverFor(prop), prop.originalName) + } } def genJSPrivateSelect(receiver: Tree, field: irt.FieldIdent)( diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/TreeDSL.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/TreeDSL.scala index 0063f17d20..540936dc78 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/TreeDSL.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/TreeDSL.scala @@ -24,7 +24,7 @@ private[emitter] object TreeDSL { extends AnyVal { /** Select a member */ - def DOT(field: Ident)(implicit pos: Position): DotSelect = + def DOT(field: MaybeDelayedIdent)(implicit pos: Position): DotSelect = DotSelect(self, field) /** Select a member */ @@ -112,7 +112,6 @@ private[emitter] object TreeDSL { def prototype(implicit pos: Position): Tree = self DOT "prototype" def length(implicit pos: Position): Tree = self DOT "length" - def u(implicit pos: Position): Tree = self DOT "u" } def typeof(expr: Tree)(implicit pos: Position): Tree = diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/StandardLinkerBackend.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/StandardLinkerBackend.scala index 4ea98e8679..ff27b8452f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/StandardLinkerBackend.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/StandardLinkerBackend.scala @@ -23,6 +23,7 @@ object StandardLinkerBackend { .withSourceMap(config.sourceMap) .withOutputPatterns(config.outputPatterns) .withRelativizeSourceMapBase(config.relativizeSourceMapBase) + .withMinify(config.minify) .withClosureCompilerIfAvailable(config.closureCompilerIfAvailable) .withPrettyPrint(config.prettyPrint) .withMaxConcurrentWrites(config.maxConcurrentWrites) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala index 1f7884c0f1..50e726106e 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala @@ -63,7 +63,7 @@ class EmitterTest { config = config) fullContent <- linkToContent(classDefs, moduleInitializers = MainTestModuleInitializers, - config = config.withClosureCompilerIfAvailable(true)) + config = config.withClosureCompilerIfAvailable(true).withMinify(true)) } yield { def testContent(content: String): Unit = { if (!content.startsWith(header)) { diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala index aa851ca438..33b1bd7ff4 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -71,7 +71,7 @@ class LibrarySizeTest { testLinkedSizes( expectedFastLinkSize = 150063, - expectedFullLinkSizeWithoutClosure = 130664, + expectedFullLinkSizeWithoutClosure = 95680, expectedFullLinkSizeWithClosure = 21325, classDefs, moduleInitializers = MainTestModuleInitializers @@ -98,6 +98,7 @@ object LibrarySizeTest { val fullLinkConfig = config .withSemantics(_.optimized) .withClosureCompilerIfAvailable(true) + .withMinify(true) val fastLinker = StandardImpl.linker(config) val fullLinker = StandardImpl.linker(fullLinkConfig) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/backend/emitter/NameCompressorTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/backend/emitter/NameCompressorTest.scala new file mode 100644 index 0000000000..db2f5e722b --- /dev/null +++ b/linker/shared/src/test/scala/org/scalajs/linker/backend/emitter/NameCompressorTest.scala @@ -0,0 +1,53 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker.backend.emitter + +import org.junit.Test +import org.junit.Assert._ + +class NameCompressorTest { + @Test def testNameGenerator(): Unit = { + // all the one-letter strings + val letterStrings = (('a' to 'z') ++ ('A' to 'Z')).map(_.toString()) + + // all the one-letter-or-digit strings + val letterOrDigitStrings = ('0' to '9').map(_.toString()) ++ letterStrings + + val expectedOneCharIdents = letterStrings + + val expectedTwoCharIdents = for { + firstChar <- letterStrings + secondChar <- letterOrDigitStrings + ident = firstChar + secondChar + if ident != "do" && ident != "if" && ident != "in" // reserved JS identifiers that will be avoided + } yield { + ident + } + + val firstFewExpectedThreeCharIdents = { + letterOrDigitStrings.map("a0" + _) ++ + letterOrDigitStrings.map("a1" + _) + } + + val expectedSequenceStart = + expectedOneCharIdents ++ expectedTwoCharIdents ++ firstFewExpectedThreeCharIdents + + // Now actually test + + val namesToAvoid = NameGen.ReservedJSIdentifierNames + val generator = new NameCompressor.NameGenerator(namesToAvoid) + + for (expected <- expectedSequenceStart) + assertEquals(expected, generator.nextString()) + } +} diff --git a/project/Build.scala b/project/Build.scala index a14202f7f9..ffcd864afe 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -53,6 +53,9 @@ object ExposedValues extends AutoPlugin { val default213ScalaVersion: SettingKey[String] = settingKey("the default Scala 2.13.x version for this build (derived from cross213ScalaVersions)") + val enableMinifyEverywhere: SettingKey[Boolean] = + settingKey("force usage of the `minify` option of the linker in all contexts (fast and full)") + // set scalaJSLinkerConfig in someProject ~= makeCompliant val makeCompliant: StandardConfig => StandardConfig = { _.withSemantics { semantics => @@ -102,6 +105,8 @@ object ExposedValues extends AutoPlugin { } } +import ExposedValues.autoImport.enableMinifyEverywhere + final case class ExpectedSizes(fastLink: Range, fullLink: Range, fastLinkGz: Range, fullLinkGz: Range) @@ -143,6 +148,15 @@ object MyScalaJSPlugin extends AutoPlugin { } override def globalSettings: Seq[Setting[_]] = Def.settings( + // can be overridden with a 'set' command + enableMinifyEverywhere := false, + + scalaJSLinkerConfig := { + scalaJSLinkerConfig.value + .withCheckIR(true) + .withMinify(enableMinifyEverywhere.value) + }, + fullClasspath in scalaJSLinkerImpl := { (fullClasspath in (Build.linker.v2_12, Runtime)).value }, @@ -201,10 +215,24 @@ object MyScalaJSPlugin extends AutoPlugin { libDeps.filterNot(dep => blacklist.contains(dep.name)) }, - scalaJSLinkerConfig ~= (_.withCheckIR(true)), - wantSourceMaps := true, + // If `enableMinifyEverywhere` is used, make sure to deactive GCC in fullLinkJS + Compile / fullLinkJS / scalaJSLinkerConfig := { + val prev = (Compile / fullLinkJS / scalaJSLinkerConfig).value + if (enableMinifyEverywhere.value) + prev.withClosureCompiler(false) + else + prev + }, + Test / fullLinkJS / scalaJSLinkerConfig := { + val prev = (Test / fullLinkJS / scalaJSLinkerConfig).value + if (enableMinifyEverywhere.value) + prev.withClosureCompiler(false) + else + prev + }, + jsEnv := new NodeJSEnv( NodeJSEnv.Config().withSourceMap(wantSourceMaps.value)), @@ -231,6 +259,7 @@ object MyScalaJSPlugin extends AutoPlugin { checksizes := { val logger = streams.value.log + val useMinifySizes = enableMinifyEverywhere.value val maybeExpected = expectedSizes.value /* The deprecated tasks do exactly what we want in terms of module / @@ -239,7 +268,7 @@ object MyScalaJSPlugin extends AutoPlugin { val fast = (fastOptJS in Compile).value.data val full = (fullOptJS in Compile).value.data - val desc = s"${thisProject.value.id} Scala ${scalaVersion.value}" + val desc = s"${thisProject.value.id} Scala ${scalaVersion.value}, useMinifySizes = $useMinifySizes" maybeExpected.fold { logger.info(s"Ignoring checksizes for " + desc) @@ -1963,23 +1992,42 @@ object Build { MyScalaJSPlugin.expectedSizes := { val default212Version = default212ScalaVersion.value val default213Version = default213ScalaVersion.value + val useMinifySizes = enableMinifyEverywhere.value scalaVersion.value match { case `default212Version` => - Some(ExpectedSizes( - fastLink = 640000 to 641000, - fullLink = 101000 to 102000, - fastLinkGz = 77000 to 78000, - fullLinkGz = 26000 to 27000, - )) + if (!useMinifySizes) { + Some(ExpectedSizes( + fastLink = 640000 to 641000, + fullLink = 101000 to 102000, + fastLinkGz = 77000 to 78000, + fullLinkGz = 26000 to 27000, + )) + } else { + Some(ExpectedSizes( + fastLink = 538000 to 539000, + fullLink = 371000 to 372000, + fastLinkGz = 71000 to 72000, + fullLinkGz = 51000 to 52000, + )) + } case `default213Version` => - Some(ExpectedSizes( - fastLink = 462000 to 463000, - fullLink = 99000 to 100000, - fastLinkGz = 60000 to 61000, - fullLinkGz = 26000 to 27000, - )) + if (!useMinifySizes) { + Some(ExpectedSizes( + fastLink = 462000 to 463000, + fullLink = 99000 to 100000, + fastLinkGz = 60000 to 61000, + fullLinkGz = 26000 to 27000, + )) + } else { + Some(ExpectedSizes( + fastLink = 373000 to 374000, + fullLink = 332000 to 333000, + fastLinkGz = 55000 to 56000, + fullLinkGz = 50000 to 51000, + )) + } case _ => None @@ -2227,7 +2275,8 @@ object Build { "isNoModule" -> (moduleKind == ModuleKind.NoModule), "isESModule" -> (moduleKind == ModuleKind.ESModule), "isCommonJSModule" -> (moduleKind == ModuleKind.CommonJSModule), - "isFullOpt" -> (stage == Stage.FullOpt), + "usesClosureCompiler" -> linkerConfig.closureCompiler, + "hasMinifiedNames" -> (linkerConfig.closureCompiler || linkerConfig.minify), "compliantAsInstanceOfs" -> (sems.asInstanceOfs == CheckedBehavior.Compliant), "compliantArrayIndexOutOfBounds" -> (sems.arrayIndexOutOfBounds == CheckedBehavior.Compliant), "compliantArrayStores" -> (sems.arrayStores == CheckedBehavior.Compliant), diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala index 6f1d6f66a4..02d4eb89f1 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala @@ -470,6 +470,7 @@ private[sbtplugin] object ScalaJSPluginInternal { prevConfig .withSemantics(_.optimized) .withClosureCompiler(useClosure) + .withMinify(true) // ignored if we actually use Closure .withCheckIR(true) // for safety, fullOpt is slow anyways. }, diff --git a/test-suite/js/src/main/scala-ide-stubs/org/scalajs/testsuite/utils/BuildInfo.scala b/test-suite/js/src/main/scala-ide-stubs/org/scalajs/testsuite/utils/BuildInfo.scala index cbd1a4f46d..d577790522 100644 --- a/test-suite/js/src/main/scala-ide-stubs/org/scalajs/testsuite/utils/BuildInfo.scala +++ b/test-suite/js/src/main/scala-ide-stubs/org/scalajs/testsuite/utils/BuildInfo.scala @@ -22,7 +22,8 @@ private[utils] object BuildInfo { final val isNoModule = false final val isESModule = false final val isCommonJSModule = false - final val isFullOpt = false + final val usesClosureCompiler = false + final val hasMinifiedNames = false final val compliantAsInstanceOfs = false final val compliantArrayIndexOutOfBounds = false final val compliantArrayStores = false diff --git a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index cbf49e2d92..ac1c4132b3 100644 --- a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -68,7 +68,10 @@ object Platform { def sourceMaps: Boolean = BuildInfo.hasSourceMaps && executingInNodeJS - def isInFullOpt: Boolean = BuildInfo.isFullOpt + def usesClosureCompiler: Boolean = BuildInfo.usesClosureCompiler + + def hasMinifiedNames: Boolean = BuildInfo.hasMinifiedNames + def isInProductionMode: Boolean = BuildInfo.productionMode def hasCompliantAsInstanceOfs: Boolean = BuildInfo.compliantAsInstanceOfs diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/OptimizerTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/OptimizerTest.scala index 2e8878cef9..833ae38e64 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/OptimizerTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/OptimizerTest.scala @@ -321,19 +321,22 @@ class OptimizerTest { } @Test def foldingDoubleWithDecimalAndString(): Unit = { - assumeFalse("Assumed not executing in FullOpt", isInFullOpt) + assumeFalse("GCC wrongly optimizes this code", usesClosureCompiler) + assertEquals("1.2323919403474454e+21hello", 1.2323919403474454E21 + "hello") assertEquals("hello1.2323919403474454e+21", "hello" + 1.2323919403474454E21) } @Test def foldingDoubleThatJVMWouldPrintInScientificNotationAndString(): Unit = { - assumeFalse("Assumed not executing in FullOpt", isInFullOpt) + assumeFalse("GCC wrongly optimizes this code", usesClosureCompiler) + assertEquals("123456789012345hello", 123456789012345d + "hello") assertEquals("hello123456789012345", "hello" + 123456789012345d) } @Test def foldingDoublesToString(): Unit = { - assumeFalse("Assumed not executing in FullOpt", isInFullOpt) + assumeFalse("GCC wrongly optimizes this code", usesClosureCompiler) + @noinline def toStringNoInline(v: Double): String = v.toString @inline def test(v: Double): Unit = assertEquals(toStringNoInline(v), v.toString) diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/MiscInteropTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/MiscInteropTest.scala index 2877c74ac4..f477cffcc6 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/MiscInteropTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/MiscInteropTest.scala @@ -42,7 +42,7 @@ class MiscInteropTest { assumeFalse( "GCC wrongly optimizes this code, " + "see https://github.com/google/closure-compiler/issues/3498", - isInFullOpt) + usesClosureCompiler) @noinline def nonExistentGlobalVarNoInline(): Any = js.Dynamic.global.thisGlobalVarDoesNotExist @@ -197,7 +197,7 @@ class MiscInteropTest { // Emitted classes @Test def meaningfulNameProperty(): Unit = { - assumeFalse("Assumed not executing in FullOpt", isInFullOpt) + assumeFalse("Need non-minified names", hasMinifiedNames) def nameOf(obj: Any): js.Any = obj.asInstanceOf[js.Dynamic].constructor.name diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/StackTraceTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/StackTraceTest.scala index d931b4bb0d..03315cf014 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/StackTraceTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/StackTraceTest.scala @@ -52,7 +52,7 @@ class StackTraceTest { @Test def decodeClassNameAndMethodName(): Unit = { assumeTrue("Assume Node.js", executingInNodeJS) - assumeFalse("Assume fullopt-stage", isInFullOpt) + assumeFalse("Assume non-minified names", hasMinifiedNames) val Error = js.constructorOf[js.Error] val oldStackTraceLimit = Error.stackTraceLimit diff --git a/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index a3d908bf8e..6b0a9c412a 100644 --- a/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -40,7 +40,9 @@ object Platform { else Integer.parseInt(v.takeWhile(_.isDigit)) } - def isInFullOpt: Boolean = false + def usesClosureCompiler: Boolean = false + + def hasMinifiedNames: Boolean = false def hasCompliantAsInstanceOfs: Boolean = true def hasCompliantArrayIndexOutOfBounds: Boolean = true diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/LongTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/LongTest.scala index a21f8ff802..dd9ed7a5a6 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/LongTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/LongTest.scala @@ -620,7 +620,7 @@ class LongTest { test(-1, lg(-1)) // Closure seems to incorrectly rewrite the constant on the right :-( - val epsilon = if (isInFullOpt) 1E4f else 0.0f + val epsilon = if (usesClosureCompiler) 1E4f else 0.0f test(9.223372E18f, MaxVal, epsilon) test(-9.223372E18f, MinVal, epsilon) @@ -674,7 +674,7 @@ class LongTest { test(-1, lg(-1)) // Closure seems to incorrectly rewrite the constant on the right :-( - val epsilon = if (isInFullOpt) 1E4 else 0.0 + val epsilon = if (usesClosureCompiler) 1E4 else 0.0 test(9.223372036854776E18, MaxVal, epsilon) test(-9.223372036854776E18, MinVal, epsilon) @@ -722,7 +722,7 @@ class LongTest { test(lg(0), -Double.MinPositiveValue) test(MaxVal, twoPow63) test(MaxVal, twoPow63NextUp) - if (!isInFullOpt) { + if (!usesClosureCompiler) { // GCC incorrectly rewrites the Double constants on the rhs test(lg(-1024, 2147483647), twoPow63NextDown) test(MinVal, -twoPow63) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/ReflectiveCallTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/ReflectiveCallTest.scala index 1fe0250028..98bd0f275e 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/ReflectiveCallTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/ReflectiveCallTest.scala @@ -346,7 +346,7 @@ class ReflectiveCallTest { assumeFalse( "GCC is a bit too eager in its optimizations in this error case", - Platform.isInFullOpt) + Platform.usesClosureCompiler) type ObjWithAnyRefPrimitives = Any { def eq(that: AnyRef): Boolean From 792866359d5cfe459e05295c680de4297c0e7cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 25 Jan 2024 15:34:14 +0100 Subject: [PATCH 6/7] Compress the ancestor names used for instance tests. --- .../linker/backend/emitter/ClassEmitter.scala | 7 ++-- .../linker/backend/emitter/CoreJSLib.scala | 4 +- .../backend/emitter/NameCompressor.scala | 40 +++++++++++++++---- .../linker/backend/emitter/SJSGen.scala | 7 ++++ .../org/scalajs/linker/LibrarySizeTest.scala | 2 +- project/Build.scala | 16 ++++---- 6 files changed, 55 insertions(+), 21 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala index e0154b1782..9fa8a09156 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala @@ -821,7 +821,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { ancestors: js.Tree)( implicit pos: Position): js.Tree = { import TreeDSL._ - ancestors DOT genName(className) + ancestors DOT genAncestorIdent(className) } def genTypeData(className: ClassName, kind: ClassKind, @@ -852,7 +852,8 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } val ancestorsRecord = js.ObjectConstr( - ancestors.withFilter(_ != ObjectClass).map(ancestor => (js.Ident(genName(ancestor)), js.IntLiteral(1)))) + ancestors.withFilter(_ != ObjectClass).map(ancestor => (genAncestorIdent(ancestor), js.IntLiteral(1))) + ) val isInstanceFunWithGlobals: WithGlobals[js.Tree] = { if (globalKnowledge.isAncestorOfHijackedClass(className)) { @@ -901,7 +902,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { isInstanceFunWithGlobals.flatMap { isInstanceFun => val allParams = List( - js.ObjectConstr(List(js.Ident(genName(className)) -> js.IntLiteral(0))), + js.ObjectConstr(List(genAncestorIdent(className) -> js.IntLiteral(0))), js.BooleanLiteral(kind == ClassKind.Interface), js.StringLiteral(RuntimeClassNameMapperImpl.map( semantics.runtimeClassNameMapper, className.nameString)), diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala index a96dea3018..23e3242c30 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala @@ -1705,8 +1705,8 @@ private[emitter] object CoreJSLib { else Skip(), privateFieldSet("ancestors", ObjectConstr(List( - Ident(genName(CloneableClass)) -> 1, - Ident(genName(SerializableClass)) -> 1 + genAncestorIdent(CloneableClass) -> 1, + genAncestorIdent(SerializableClass) -> 1 ))), privateFieldSet("componentData", componentData), privateFieldSet("arrayBase", arrayBase), diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameCompressor.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameCompressor.scala index f23287c6bf..0db5ead5bf 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameCompressor.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameCompressor.scala @@ -17,6 +17,7 @@ import scala.annotation.{switch, tailrec} import java.util.Comparator import scala.collection.mutable +import scala.reflect.ClassTag import org.scalajs.ir.Names._ import org.scalajs.linker.backend.javascript.Trees.DelayedIdent.Resolver @@ -27,6 +28,7 @@ private[emitter] final class NameCompressor(config: Emitter.Config) { import NameCompressor._ private val entries: EntryMap = mutable.AnyRefMap.empty + private val ancestorEntries: AncestorEntryMap = mutable.AnyRefMap.empty private var namesAllocated: Boolean = false @@ -41,6 +43,10 @@ private[emitter] final class NameCompressor(config: Emitter.Config) { allocatePropertyNames(entries, propertyNamesToAvoid) } + logger.time("Name compressor: Allocate ancestor names") { + allocatePropertyNames(ancestorEntries, BasePropertyNamesToAvoid) + } + namesAllocated = true } @@ -53,6 +59,9 @@ private[emitter] final class NameCompressor(config: Emitter.Config) { def genResolverFor(prop: ArrayClassProperty): Resolver = entries.getOrElseUpdate(prop, new ArrayClassPropEntry(prop)).genResolver() + def genResolverForAncestor(ancestor: ClassName): Resolver = + ancestorEntries.getOrElseUpdate(ancestor, new AncestorNameEntry(ancestor)).genResolver() + /** Collects the property names to avoid for Scala instance members. * * We collect the names of exported members in Scala classes. These live in @@ -99,11 +108,11 @@ private[emitter] object NameCompressor { private val BasePropertyNamesToAvoid: Set[String] = NameGen.ReservedJSIdentifierNames + "then" - private def allocatePropertyNames(entries: EntryMap, - namesToAvoid: collection.Set[String]): Unit = { - val comparator: Comparator[PropertyNameEntry] = - Comparator.comparingInt[PropertyNameEntry](_.occurrences).reversed() // by decreasing order of occurrences - .thenComparing(Comparator.naturalOrder[PropertyNameEntry]()) // tie-break + private def allocatePropertyNames[K <: AnyRef, E <: BaseEntry with Comparable[E]: ClassTag]( + entries: mutable.AnyRefMap[K, E], namesToAvoid: collection.Set[String]): Unit = { + val comparator: Comparator[E] = + Comparator.comparingInt[E](_.occurrences).reversed() // by decreasing order of occurrences + .thenComparing(Comparator.naturalOrder[E]()) // tie-break val orderedEntries = entries.values.toArray java.util.Arrays.sort(orderedEntries, comparator) @@ -117,7 +126,9 @@ private[emitter] object NameCompressor { /** Keys of this map are `FieldName | MethodName | ArrayClassProperty`. */ private type EntryMap = mutable.AnyRefMap[AnyRef, PropertyNameEntry] - private sealed abstract class PropertyNameEntry extends Comparable[PropertyNameEntry] { + private type AncestorEntryMap = mutable.AnyRefMap[ClassName, AncestorNameEntry] + + private sealed abstract class BaseEntry { var occurrences: Int = 0 var allocatedName: String = null @@ -130,7 +141,7 @@ private[emitter] object NameCompressor { allocatedName } - def debugString: String = PropertyNameEntry.this.debugString + def debugString: String = BaseEntry.this.debugString override def toString(): String = debugString } @@ -145,6 +156,10 @@ private[emitter] object NameCompressor { incOccurrences() resolver } + } + + private sealed abstract class PropertyNameEntry + extends BaseEntry with Comparable[PropertyNameEntry] { def compareTo(that: PropertyNameEntry): Int = (this, that) match { case (x: FieldNameEntry, y: FieldNameEntry) => @@ -187,6 +202,17 @@ private[emitter] object NameCompressor { override def toString(): String = s"ArrayClassPropEntry(${property.nonMinifiedName})" } + private final class AncestorNameEntry(val ancestor: ClassName) + extends BaseEntry with Comparable[AncestorNameEntry] { + + def compareTo(that: AncestorNameEntry): Int = + this.ancestor.compareTo(that.ancestor) + + protected def debugString: String = ancestor.nameString + + override def toString(): String = s"AncestorNameEntry(${ancestor.nameString})" + } + // private[emitter] for tests private[emitter] final class NameGenerator(namesToAvoid: collection.Set[String]) { /* 6 because 52 * (62**5) > Int.MaxValue diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala index 9e8810afc3..d1c46bc023 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala @@ -250,6 +250,13 @@ private[emitter] final class SJSGen( } } + def genAncestorIdent(ancestor: ClassName)(implicit pos: Position): MaybeDelayedIdent = { + nameCompressor match { + case None => Ident(genName(ancestor)) + case Some(compressor) => DelayedIdent(compressor.genResolverForAncestor(ancestor)) + } + } + def genJSPrivateSelect(receiver: Tree, field: irt.FieldIdent)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala index 33b1bd7ff4..2d9e678334 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -71,7 +71,7 @@ class LibrarySizeTest { testLinkedSizes( expectedFastLinkSize = 150063, - expectedFullLinkSizeWithoutClosure = 95680, + expectedFullLinkSizeWithoutClosure = 93868, expectedFullLinkSizeWithClosure = 21325, classDefs, moduleInitializers = MainTestModuleInitializers diff --git a/project/Build.scala b/project/Build.scala index ffcd864afe..de6ff233a9 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2005,10 +2005,10 @@ object Build { )) } else { Some(ExpectedSizes( - fastLink = 538000 to 539000, - fullLink = 371000 to 372000, - fastLinkGz = 71000 to 72000, - fullLinkGz = 51000 to 52000, + fastLink = 499000 to 500000, + fullLink = 341000 to 342000, + fastLinkGz = 69000 to 70000, + fullLinkGz = 50000 to 51000, )) } @@ -2022,10 +2022,10 @@ object Build { )) } else { Some(ExpectedSizes( - fastLink = 373000 to 374000, - fullLink = 332000 to 333000, - fastLinkGz = 55000 to 56000, - fullLinkGz = 50000 to 51000, + fastLink = 352000 to 353000, + fullLink = 312000 to 313000, + fastLinkGz = 54000 to 55000, + fullLinkGz = 49000 to 50000, )) } From 280870dec269cfe02ad14835f3affbe48f261ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 25 Jan 2024 17:23:46 +0100 Subject: [PATCH 7/7] Minify core (internal) property names to one letter each. --- .../linker/backend/emitter/ClassEmitter.scala | 12 +- .../linker/backend/emitter/CoreJSLib.scala | 234 +++++++++--------- .../backend/emitter/FunctionEmitter.scala | 6 +- .../linker/backend/emitter/SJSGen.scala | 108 +++++++- .../org/scalajs/linker/LibrarySizeTest.scala | 2 +- project/Build.scala | 8 +- 6 files changed, 236 insertions(+), 134 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala index 9fa8a09156..11f8c3df33 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala @@ -705,7 +705,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { !(!( genIsScalaJSObject(obj) && genIsClassNameInAncestors(className, - obj DOT "$classData" DOT "ancestors") + obj DOT cpn.classData DOT cpn.ancestors) )) } @@ -781,9 +781,9 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { globalFunctionDef(VarField.isArrayOf, className, List(objParam, depthParam), None, { js.Return(!(!({ genIsScalaJSObject(obj) && - ((obj DOT "$classData" DOT "arrayDepth") === depth) && + ((obj DOT cpn.classData DOT cpn.arrayDepth) === depth) && genIsClassNameInAncestors(className, - obj DOT "$classData" DOT "arrayBase" DOT "ancestors") + obj DOT cpn.classData DOT cpn.arrayBase DOT cpn.ancestors) }))) }) } @@ -814,7 +814,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { private def genIsScalaJSObject(obj: js.Tree)(implicit pos: Position): js.Tree = { import TreeDSL._ - obj && (obj DOT "$classData") + obj && (obj DOT cpn.classData) } private def genIsClassNameInAncestors(className: ClassName, @@ -915,7 +915,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val prunedParams = allParams.reverse.dropWhile(_.isInstanceOf[js.Undefined]).reverse - val typeData = js.Apply(js.New(globalVar(VarField.TypeData, CoreVar), Nil) DOT "initClass", + val typeData = js.Apply(js.New(globalVar(VarField.TypeData, CoreVar), Nil) DOT cpn.initClass, prunedParams) globalVarDef(VarField.d, className, typeData) @@ -927,7 +927,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { globalKnowledge: GlobalKnowledge, pos: Position): js.Tree = { import TreeDSL._ - globalVar(VarField.c, className).prototype DOT "$classData" := globalVar(VarField.d, className) + globalVar(VarField.c, className).prototype DOT cpn.classData := globalVar(VarField.d, className) } def genModuleAccessor(className: ClassName, isJSClass: Boolean)( diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala index 23e3242c30..6dc814cd87 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala @@ -106,7 +106,7 @@ private[emitter] object CoreJSLib { // Conditional global references that we often use private def ReflectRef = globalRef("Reflect") - private val classData = Ident("$classData") + private val classData = Ident(cpn.classData) private val orderedPrimRefsWithoutVoid = { List(BooleanRef, CharRef, ByteRef, ShortRef, IntRef, LongRef, @@ -522,7 +522,7 @@ private[emitter] object CoreJSLib { condDefs(!allowBigIntsForLongs)(List( globalVar(VarField.L0, CoreVar) := genScalaClassNew( LongImpl.RuntimeLongClass, LongImpl.initFromParts, 0, 0), - genClassDataOf(LongRef) DOT "zero" := globalVar(VarField.L0, CoreVar) + genClassDataOf(LongRef) DOT cpn.zero := globalVar(VarField.L0, CoreVar) )) } @@ -547,14 +547,14 @@ private[emitter] object CoreJSLib { val ctor = { val c = varRef("c") MethodDef(static = false, Ident("constructor"), paramList(c), None, { - This() DOT "c" := c + This() DOT cpn.c := c }) } val toStr = { MethodDef(static = false, Ident("toString"), Nil, None, { Return(Apply(genIdentBracketSelect(StringRef, "fromCharCode"), - (This() DOT "c") :: Nil)) + (This() DOT cpn.c) :: Nil)) }) } @@ -594,7 +594,7 @@ private[emitter] object CoreJSLib { str("char") }, { If(genIsScalaJSObject(value), { - genIdentBracketSelect(value DOT classData, "name") + genIdentBracketSelect(value DOT classData, cpn.name) }, { typeof(value) }) @@ -693,10 +693,10 @@ private[emitter] object CoreJSLib { val i = varRef("i") Block( - const(result, New(arrayClassData DOT "constr", + const(result, New(arrayClassData DOT cpn.constr, BracketSelect(lengths, lengthIndex) :: Nil)), If(lengthIndex < (lengths.length - 1), Block( - const(subArrayClassData, arrayClassData DOT "componentData"), + const(subArrayClassData, arrayClassData DOT cpn.componentData), const(subLengthIndex, lengthIndex + 1), const(underlying, result.u), For(let(i, 0), i < underlying.length, i.++, { @@ -719,7 +719,7 @@ private[emitter] object CoreJSLib { defineFunction1(VarField.objectOrArrayClone) { instance => // return instance.$classData.isArrayClass ? instance.clone__O() : $objectClone(instance); - Return(If(genIdentBracketSelect(instance DOT classData, "isArrayClass"), + Return(If(genIdentBracketSelect(instance DOT classData, cpn.isArrayClass), genApply(instance, cloneMethodName, Nil), genCallHelper(VarField.objectClone, instance))) } @@ -804,7 +804,7 @@ private[emitter] object CoreJSLib { condDefs(globalKnowledge.isClassClassInstantiated)( defineObjectGetClassBasedFun(VarField.objectGetClass, className => genClassOf(className), - instance => Apply(instance DOT classData DOT "getClassOf", Nil), + instance => Apply(instance DOT classData DOT cpn.getClassOf, Nil), Null() ) ) ::: @@ -813,7 +813,7 @@ private[emitter] object CoreJSLib { StringLiteral(RuntimeClassNameMapperImpl.map( semantics.runtimeClassNameMapper, className.nameString)) }, - instance => genIdentBracketSelect(instance DOT classData, "name"), + instance => genIdentBracketSelect(instance DOT classData, cpn.name), { if (nullPointers == CheckedBehavior.Unchecked) genApply(Null(), getNameMethodName, Nil) @@ -1137,7 +1137,7 @@ private[emitter] object CoreJSLib { condDefs(arrayStores != CheckedBehavior.Unchecked)( defineFunction5(VarField.systemArraycopyRefs) { (src, srcPos, dest, destPos, length) => - If(Apply(genIdentBracketSelect(dest DOT classData, "isAssignableFrom"), List(src DOT classData)), { + If(Apply(genIdentBracketSelect(dest DOT classData, cpn.isAssignableFrom), List(src DOT classData)), { /* Fast-path, no need for array store checks. This always applies * for arrays of the same type, and a fortiori, when `src eq dest`. */ @@ -1170,7 +1170,7 @@ private[emitter] object CoreJSLib { const(srcData, src && (src DOT classData)), If(srcData === (dest && (dest DOT classData)), { // Both values have the same "data" (could also be falsy values) - If(srcData && genIdentBracketSelect(srcData, "isArrayClass"), { + If(srcData && genIdentBracketSelect(srcData, cpn.isArrayClass), { // Fast path: the values are array of the same type if (esVersion >= ESVersion.ES2015 && nullPointers == CheckedBehavior.Unchecked) genArrayClassPropApply(src, ArrayClassProperty.copyTo, srcPos, dest, destPos, length) @@ -1387,7 +1387,7 @@ private[emitter] object CoreJSLib { ( defineUnbox(VarField.uV, BoxedUnitClass, _ => Undefined()) ::: defineUnbox(VarField.uZ, BoxedBooleanClass, v => !(!v)) ::: - defineUnbox(VarField.uC, BoxedCharacterClass, v => If(v === Null(), 0, v DOT "c")) ::: + defineUnbox(VarField.uC, BoxedCharacterClass, v => If(v === Null(), 0, v DOT cpn.c)) ::: defineUnbox(VarField.uB, BoxedByteClass, _ | 0) ::: defineUnbox(VarField.uS, BoxedShortClass, _ | 0) ::: defineUnbox(VarField.uI, BoxedIntegerClass, _ | 0) ::: @@ -1405,7 +1405,7 @@ private[emitter] object CoreJSLib { // Unboxes for Chars and Longs ( defineFunction1(VarField.uC) { v => - Return(If(v === Null(), 0, v DOT "c")) + Return(If(v === Null(), 0, v DOT cpn.c)) } ::: defineFunction1(VarField.uJ) { v => Return(If(v === Null(), genLongZero(), v)) @@ -1585,34 +1585,34 @@ private[emitter] object CoreJSLib { val ctor = { MethodDef(static = false, Ident("constructor"), Nil, None, { Block( - privateFieldSet("constr", Undefined()), + privateFieldSet(cpn.constr, Undefined()), if (globalKnowledge.isParentDataAccessed) - privateFieldSet("parentData", Undefined()) + privateFieldSet(cpn.parentData, Undefined()) else Skip(), - privateFieldSet("ancestors", Null()), - privateFieldSet("componentData", Null()), - privateFieldSet("arrayBase", Null()), - privateFieldSet("arrayDepth", int(0)), - privateFieldSet("zero", Null()), - privateFieldSet("arrayEncodedName", str("")), - privateFieldSet("_classOf", Undefined()), - privateFieldSet("_arrayOf", Undefined()), + privateFieldSet(cpn.ancestors, Null()), + privateFieldSet(cpn.componentData, Null()), + privateFieldSet(cpn.arrayBase, Null()), + privateFieldSet(cpn.arrayDepth, int(0)), + privateFieldSet(cpn.zero, Null()), + privateFieldSet(cpn.arrayEncodedName, str("")), + privateFieldSet(cpn._classOf, Undefined()), + privateFieldSet(cpn._arrayOf, Undefined()), /* A lambda for the logic of the public `isAssignableFrom`, * without its fast-path. See the comment on the definition of * `isAssignableFrom` for the rationale of this decomposition. */ - privateFieldSet("isAssignableFromFun", Undefined()), + privateFieldSet(cpn.isAssignableFromFun, Undefined()), - privateFieldSet("wrapArray", Undefined()), - privateFieldSet("isJSType", bool(false)), + privateFieldSet(cpn.wrapArray, Undefined()), + privateFieldSet(cpn.isJSType, bool(false)), - publicFieldSet("name", str("")), - publicFieldSet("isPrimitive", bool(false)), - publicFieldSet("isInterface", bool(false)), - publicFieldSet("isArrayClass", bool(false)), - publicFieldSet("isInstance", Undefined()) + publicFieldSet(cpn.name, str("")), + publicFieldSet(cpn.isPrimitive, bool(false)), + publicFieldSet(cpn.isInterface, bool(false)), + publicFieldSet(cpn.isArrayClass, bool(false)), + publicFieldSet(cpn.isInstance, Undefined()) ) }) } @@ -1627,22 +1627,22 @@ private[emitter] object CoreJSLib { val that = varRef("that") val depth = varRef("depth") val obj = varRef("obj") - MethodDef(static = false, Ident("initPrim"), + MethodDef(static = false, Ident(cpn.initPrim), paramList(zero, arrayEncodedName, displayName, arrayClass, typedArrayClass), None, { Block( - privateFieldSet("ancestors", ObjectConstr(Nil)), - privateFieldSet("zero", zero), - privateFieldSet("arrayEncodedName", arrayEncodedName), + privateFieldSet(cpn.ancestors, ObjectConstr(Nil)), + privateFieldSet(cpn.zero, zero), + privateFieldSet(cpn.arrayEncodedName, arrayEncodedName), const(self, This()), // capture `this` for use in arrow fun - privateFieldSet("isAssignableFromFun", + privateFieldSet(cpn.isAssignableFromFun, genArrowFunction(paramList(that), Return(that === self))), - publicFieldSet("name", displayName), - publicFieldSet("isPrimitive", bool(true)), - publicFieldSet("isInstance", + publicFieldSet(cpn.name, displayName), + publicFieldSet(cpn.isPrimitive, bool(true)), + publicFieldSet(cpn.isInstance, genArrowFunction(paramList(obj), Return(bool(false)))), If(arrayClass !== Undefined(), { // it is undefined for void - privateFieldSet("_arrayOf", - Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT "initSpecializedArray", + privateFieldSet(cpn._arrayOf, + Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT cpn.initSpecializedArray, List(This(), arrayClass, typedArrayClass))) }), Return(This()) @@ -1662,29 +1662,29 @@ private[emitter] object CoreJSLib { val that = varRef("that") val depth = varRef("depth") val obj = varRef("obj") - MethodDef(static = false, Ident("initClass"), + MethodDef(static = false, Ident(cpn.initClass), paramList(internalNameObj, isInterface, fullName, ancestors, isJSType, parentData, isInstance), None, { Block( const(internalName, genCallHelper(VarField.propertyName, internalNameObj)), if (globalKnowledge.isParentDataAccessed) - privateFieldSet("parentData", parentData) + privateFieldSet(cpn.parentData, parentData) else Skip(), - privateFieldSet("ancestors", ancestors), - privateFieldSet("arrayEncodedName", str("L") + fullName + str(";")), - privateFieldSet("isAssignableFromFun", { + privateFieldSet(cpn.ancestors, ancestors), + privateFieldSet(cpn.arrayEncodedName, str("L") + fullName + str(";")), + privateFieldSet(cpn.isAssignableFromFun, { genArrowFunction(paramList(that), { - Return(!(!(BracketSelect(that DOT "ancestors", internalName)))) + Return(!(!(BracketSelect(that DOT cpn.ancestors, internalName)))) }) }), - privateFieldSet("isJSType", !(!isJSType)), - publicFieldSet("name", fullName), - publicFieldSet("isInterface", isInterface), - publicFieldSet("isInstance", isInstance || { + privateFieldSet(cpn.isJSType, !(!isJSType)), + publicFieldSet(cpn.name, fullName), + publicFieldSet(cpn.isInterface, isInterface), + publicFieldSet(cpn.isInstance, isInstance || { genArrowFunction(paramList(obj), { Return(!(!(obj && (obj DOT classData) && - BracketSelect(obj DOT classData DOT "ancestors", internalName)))) + BracketSelect(obj DOT classData DOT cpn.ancestors, internalName)))) }) }), Return(This()) @@ -1698,22 +1698,22 @@ private[emitter] object CoreJSLib { Block( arrayClass.prototype DOT classData := This(), - const(name, str("[") + (componentData DOT "arrayEncodedName")), - privateFieldSet("constr", arrayClass), + const(name, str("[") + (componentData DOT cpn.arrayEncodedName)), + privateFieldSet(cpn.constr, arrayClass), if (globalKnowledge.isParentDataAccessed) - privateFieldSet("parentData", genClassDataOf(ObjectClass)) + privateFieldSet(cpn.parentData, genClassDataOf(ObjectClass)) else Skip(), - privateFieldSet("ancestors", ObjectConstr(List( + privateFieldSet(cpn.ancestors, ObjectConstr(List( genAncestorIdent(CloneableClass) -> 1, genAncestorIdent(SerializableClass) -> 1 ))), - privateFieldSet("componentData", componentData), - privateFieldSet("arrayBase", arrayBase), - privateFieldSet("arrayDepth", arrayDepth), - privateFieldSet("arrayEncodedName", name), - publicFieldSet("name", name), - publicFieldSet("isArrayClass", bool(true)) + privateFieldSet(cpn.componentData, componentData), + privateFieldSet(cpn.arrayBase, arrayBase), + privateFieldSet(cpn.arrayDepth, arrayDepth), + privateFieldSet(cpn.arrayEncodedName, name), + publicFieldSet(cpn.name, name), + publicFieldSet(cpn.isArrayClass, bool(true)) ) } @@ -1726,15 +1726,15 @@ private[emitter] object CoreJSLib { val that = varRef("that") val obj = varRef("obj") val array = varRef("array") - MethodDef(static = false, Ident("initSpecializedArray"), + MethodDef(static = false, Ident(cpn.initSpecializedArray), paramList(componentData, arrayClass, typedArrayClass, isAssignableFromFun), None, { Block( initArrayCommonBody(arrayClass, componentData, componentData, 1), const(self, This()), // capture `this` for use in arrow fun - privateFieldSet("isAssignableFromFun", isAssignableFromFun || { + privateFieldSet(cpn.isAssignableFromFun, isAssignableFromFun || { genArrowFunction(paramList(that), Return(self === that)) }), - privateFieldSet("wrapArray", { + privateFieldSet(cpn.wrapArray, { If(typedArrayClass, { genArrowFunction(paramList(array), { Return(New(arrayClass, New(typedArrayClass, array :: Nil) :: Nil)) @@ -1745,7 +1745,7 @@ private[emitter] object CoreJSLib { }) }) }), - publicFieldSet("isInstance", + publicFieldSet(cpn.isInstance, genArrowFunction(paramList(obj), Return(obj instanceof arrayClass))), Return(This()) ) @@ -1762,7 +1762,7 @@ private[emitter] object CoreJSLib { val self = varRef("self") val obj = varRef("obj") val array = varRef("array") - MethodDef(static = false, Ident("initArray"), + MethodDef(static = false, Ident(cpn.initArray), paramList(componentData), None, { val ArrayClassDef = { val ctor = { @@ -1788,8 +1788,8 @@ private[emitter] object CoreJSLib { } val storeCheck = { - If((v !== Null()) && !(componentData DOT "isJSType") && - !Apply(genIdentBracketSelect(componentData, "isInstance"), v :: Nil), + If((v !== Null()) && !(componentData DOT cpn.isJSType) && + !Apply(genIdentBracketSelect(componentData, cpn.isInstance), v :: Nil), genCallHelper(VarField.throwArrayStoreException, v)) } @@ -1847,28 +1847,28 @@ private[emitter] object CoreJSLib { Block( ArrayClassDef, - const(arrayBase, (componentData DOT "arrayBase") || componentData), - const(arrayDepth, (componentData DOT "arrayDepth") + 1), + const(arrayBase, (componentData DOT cpn.arrayBase) || componentData), + const(arrayDepth, (componentData DOT cpn.arrayDepth) + 1), initArrayCommonBody(ArrayClass, componentData, arrayBase, arrayDepth), const(isAssignableFromFun, { genArrowFunction(paramList(that), { val thatDepth = varRef("thatDepth") Block( - const(thatDepth, that DOT "arrayDepth"), + const(thatDepth, that DOT cpn.arrayDepth), Return(If(thatDepth === arrayDepth, { - Apply(arrayBase DOT "isAssignableFromFun", (that DOT "arrayBase") :: Nil) + Apply(arrayBase DOT cpn.isAssignableFromFun, (that DOT cpn.arrayBase) :: Nil) }, { (thatDepth > arrayDepth) && (arrayBase === genClassDataOf(ObjectClass)) })) ) }) }), - privateFieldSet("isAssignableFromFun", isAssignableFromFun), - privateFieldSet("wrapArray", genArrowFunction(paramList(array), { + privateFieldSet(cpn.isAssignableFromFun, isAssignableFromFun), + privateFieldSet(cpn.wrapArray, genArrowFunction(paramList(array), { Return(New(ArrayClass, array :: Nil)) })), const(self, This()), // don't rely on the lambda being called with `this` as receiver - publicFieldSet("isInstance", genArrowFunction(paramList(obj), { + publicFieldSet(cpn.isInstance, genArrowFunction(paramList(obj), { val data = varRef("data") Block( const(data, obj && (obj DOT classData)), @@ -1884,24 +1884,24 @@ private[emitter] object CoreJSLib { } val getArrayOf = { - MethodDef(static = false, Ident("getArrayOf"), Nil, None, { + MethodDef(static = false, Ident(cpn.getArrayOf), Nil, None, { Block( - If(!(This() DOT "_arrayOf"), - This() DOT "_arrayOf" := - Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT "initArray", This() :: Nil), + If(!(This() DOT cpn._arrayOf), + This() DOT cpn._arrayOf := + Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT cpn.initArray, This() :: Nil), Skip()), - Return(This() DOT "_arrayOf") + Return(This() DOT cpn._arrayOf) ) }) } def getClassOf = { - MethodDef(static = false, Ident("getClassOf"), Nil, None, { + MethodDef(static = false, Ident(cpn.getClassOf), Nil, None, { Block( - If(!(This() DOT "_classOf"), - This() DOT "_classOf" := genScalaClassNew(ClassClass, ObjectArgConstructorName, This()), + If(!(This() DOT cpn._classOf), + This() DOT cpn._classOf := genScalaClassNew(ClassClass, ObjectArgConstructorName, This()), Skip()), - Return(This() DOT "_classOf") + Return(This() DOT cpn._classOf) ) }) } @@ -1917,21 +1917,21 @@ private[emitter] object CoreJSLib { * We only need a polymorphic dispatch in the slow path. */ val that = varRef("that") - MethodDef(static = false, StringLiteral("isAssignableFrom"), + MethodDef(static = false, StringLiteral(cpn.isAssignableFrom), paramList(that), None, { Return( (This() === that) || // fast path - Apply(This() DOT "isAssignableFromFun", that :: Nil)) + Apply(This() DOT cpn.isAssignableFromFun, that :: Nil)) }) } def checkCast = { val obj = varRef("obj") - MethodDef(static = false, StringLiteral("checkCast"), paramList(obj), None, + MethodDef(static = false, StringLiteral(cpn.checkCast), paramList(obj), None, if (asInstanceOfs != CheckedBehavior.Unchecked) { - If((obj !== Null()) && !(This() DOT "isJSType") && - !Apply(genIdentBracketSelect(This(), "isInstance"), obj :: Nil), - genCallHelper(VarField.throwClassCastException, obj, genIdentBracketSelect(This(), "name")), + If((obj !== Null()) && !(This() DOT cpn.isJSType) && + !Apply(genIdentBracketSelect(This(), cpn.isInstance), obj :: Nil), + genCallHelper(VarField.throwClassCastException, obj, genIdentBracketSelect(This(), cpn.name)), Skip()) } else { Skip() @@ -1940,17 +1940,17 @@ private[emitter] object CoreJSLib { } def getSuperclass = { - MethodDef(static = false, StringLiteral("getSuperclass"), Nil, None, { - Return(If(This() DOT "parentData", - Apply(This() DOT "parentData" DOT "getClassOf", Nil), + MethodDef(static = false, StringLiteral(cpn.getSuperclass), Nil, None, { + Return(If(This() DOT cpn.parentData, + Apply(This() DOT cpn.parentData DOT cpn.getClassOf, Nil), Null())) }) } def getComponentType = { - MethodDef(static = false, StringLiteral("getComponentType"), Nil, None, { - Return(If(This() DOT "componentData", - Apply(This() DOT "componentData" DOT "getClassOf", Nil), + MethodDef(static = false, StringLiteral(cpn.getComponentType), Nil, None, { + Return(If(This() DOT cpn.componentData, + Apply(This() DOT cpn.componentData DOT cpn.getClassOf, Nil), Null())) }) } @@ -1959,12 +1959,12 @@ private[emitter] object CoreJSLib { val lengths = varRef("lengths") val arrayClassData = varRef("arrayClassData") val i = varRef("i") - MethodDef(static = false, StringLiteral("newArrayOfThisClass"), + MethodDef(static = false, StringLiteral(cpn.newArrayOfThisClass), paramList(lengths), None, { Block( let(arrayClassData, This()), For(let(i, 0), i < lengths.length, i.++, { - arrayClassData := Apply(arrayClassData DOT "getArrayOf", Nil) + arrayClassData := Apply(arrayClassData DOT cpn.getArrayOf, Nil) }), Return(genCallHelper(VarField.newArrayObject, arrayClassData, lengths)) ) @@ -2013,14 +2013,14 @@ private[emitter] object CoreJSLib { val forObj = extractWithGlobals(globalFunctionDef(VarField.isArrayOf, ObjectClass, paramList(obj, depth), None, { Block( - const(data, obj && (obj DOT "$classData")), + const(data, obj && (obj DOT cpn.classData)), If(!data, { Return(BooleanLiteral(false)) }, { Block( - const(arrayDepth, data DOT "arrayDepth"), + const(arrayDepth, data DOT cpn.arrayDepth), Return(If(arrayDepth === depth, { - !genIdentBracketSelect(data DOT "arrayBase", "isPrimitive") + !genIdentBracketSelect(data DOT cpn.arrayBase, cpn.isPrimitive) }, { arrayDepth > depth })) @@ -2034,8 +2034,8 @@ private[emitter] object CoreJSLib { val depth = varRef("depth") extractWithGlobals(globalFunctionDef(VarField.isArrayOf, primRef, paramList(obj, depth), None, { Return(!(!(obj && (obj DOT classData) && - ((obj DOT classData DOT "arrayDepth") === depth) && - ((obj DOT classData DOT "arrayBase") === genClassDataOf(primRef))))) + ((obj DOT classData DOT cpn.arrayDepth) === depth) && + ((obj DOT classData DOT cpn.arrayBase) === genClassDataOf(primRef))))) })) } @@ -2089,27 +2089,27 @@ private[emitter] object CoreJSLib { extractWithGlobals( globalVarDef(VarField.d, ObjectClass, New(globalVar(VarField.TypeData, CoreVar), Nil))) ::: List( - privateFieldSet("ancestors", ObjectConstr(Nil)), - privateFieldSet("arrayEncodedName", str("L" + fullName + ";")), - privateFieldSet("isAssignableFromFun", { + privateFieldSet(cpn.ancestors, ObjectConstr(Nil)), + privateFieldSet(cpn.arrayEncodedName, str("L" + fullName + ";")), + privateFieldSet(cpn.isAssignableFromFun, { genArrowFunction(paramList(that), { - Return(!genIdentBracketSelect(that, "isPrimitive")) + Return(!genIdentBracketSelect(that, cpn.isPrimitive)) }) }), - publicFieldSet("name", str(fullName)), - publicFieldSet("isInstance", + publicFieldSet(cpn.name, str(fullName)), + publicFieldSet(cpn.isInstance, genArrowFunction(paramList(obj), Return(obj !== Null()))), - privateFieldSet("_arrayOf", { - Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT "initSpecializedArray", List( + privateFieldSet(cpn._arrayOf, { + Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT cpn.initSpecializedArray, List( typeDataVar, globalVar(VarField.ac, ObjectClass), Undefined(), // typedArray genArrowFunction(paramList(that), { val thatDepth = varRef("thatDepth") Block( - const(thatDepth, that DOT "arrayDepth"), + const(thatDepth, that DOT cpn.arrayDepth), Return(If(thatDepth === 1, { - !genIdentBracketSelect(that DOT "arrayBase", "isPrimitive") + !genIdentBracketSelect(that DOT cpn.arrayBase, cpn.isPrimitive) }, { (thatDepth > 1) })) @@ -2117,7 +2117,7 @@ private[emitter] object CoreJSLib { }) )) }), - globalVar(VarField.c, ObjectClass).prototype DOT "$classData" := typeDataVar + globalVar(VarField.c, ObjectClass).prototype DOT cpn.classData := typeDataVar ) } @@ -2143,7 +2143,7 @@ private[emitter] object CoreJSLib { } extractWithGlobals(globalVarDef(VarField.d, primRef, { - Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT "initPrim", + Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT cpn.initPrim, List(zero, str(primRef.charCode.toString()), str(primRef.displayName), if (primRef == VoidRef) Undefined() diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 6a8b9ca3dd..a518948b97 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -2744,7 +2744,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.DotSelect( genSelect(transformExprNoChar(checkNotNull(runtimeClass)), FieldIdent(dataFieldName)), - js.Ident("zero")) + js.Ident(cpn.zero)) case Transient(NativeArrayWrapper(elemClass, nativeArray)) => val newNativeArray = transformExprNoChar(nativeArray) @@ -2758,8 +2758,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { transformExprNoChar(checkNotNull(elemClass)), FieldIdent(dataFieldName)) val arrayClassData = js.Apply( - js.DotSelect(elemClassData, js.Ident("getArrayOf")), Nil) - js.Apply(arrayClassData DOT "wrapArray", newNativeArray :: Nil) + js.DotSelect(elemClassData, js.Ident(cpn.getArrayOf)), Nil) + js.Apply(arrayClassData DOT cpn.wrapArray, newNativeArray :: Nil) } case Transient(ObjectClassName(obj)) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala index d1c46bc023..b52be22348 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala @@ -43,6 +43,108 @@ private[emitter] final class SJSGen( val useBigIntForLongs = esFeatures.allowBigIntsForLongs + /** Core Property Names. */ + object cpn { + // --- Scala.js objects --- + + /** The class-wide classData field of Scala.js objects, which references their TypeData. */ + val classData = "$classData" // always in full; it is used as identification of Scala.js objects + + // --- Class --- + + /** `Char.c`: the int value of the character. */ + val c = "c" + + // --- TypeData private fields --- + + /** `TypeData.constr`: the run-time constructor of the class. */ + val constr = if (minify) "C" else "constr" + + /** `TypeData.parentData`: the super class data. */ + val parentData = if (minify) "P" else "parentData" + + /** `TypeData.ancestors`: dictionary where keys are the ancestor names of all ancestors. */ + val ancestors = if (minify) "n" else "ancestors" + + /** `TypeData.componentData`: the `TypeData` of the component type of an array type. */ + val componentData = if (minify) "O" else "componentData" + + /** `TypeData.arrayBase`: the `TypeData` of the base type of an array type. */ + val arrayBase = if (minify) "B" else "arrayBase" + + /** `TypeData.arrayDepth`: the depth of an array type. */ + val arrayDepth = if (minify) "D" else "arrayDepth" + + /** `TypeData.zero`: the zero value of the type. */ + val zero = if (minify) "z" else "zero" + + /** `TypeData.arrayEncodedName`: the name of the type as it appears in its array type's name. */ + val arrayEncodedName = if (minify) "E" else "arrayEncodedName" + + /** `TypeData._classOf`: the field storing the `jl.Class` instance for that type. */ + val _classOf = if (minify) "L" else "_classOf" + + /** `TypeData._arrayOf`: the field storing the `TypeData` for that type's array type. */ + val _arrayOf = if (minify) "A" else "_arrayOf" + + /** `TypeData.isAssignableFromFun`: the implementation of `jl.Class.isAssignableFrom` without fast path. */ + val isAssignableFromFun = if (minify) "F" else "isAssignableFromFun" + + /** `TypeData.wrapArray`: the function to create an ArrayClass instance from a JS array of its elements. */ + val wrapArray = if (minify) "w" else "wrapArray" + + /** `TypeData.isJSType`: whether it is a JS type. */ + val isJSType = if (minify) "J" else "isJSType" + + // --- TypeData constructors --- + + val initPrim = if (minify) "p" else "initPrim" + + val initClass = if (minify) "i" else "initClass" + + val initSpecializedArray = if (minify) "y" else "initSpecializedArray" + + val initArray = if (minify) "a" else "initArray" + + // --- TypeData private methods --- + + /** `TypeData.getArrayOf()`: the `Type` instance for that type's array type. */ + val getArrayOf = if (minify) "r" else "getArrayOf" + + /** `TypeData.getClassOf()`: the `jl.Class` instance for that type. */ + val getClassOf = if (minify) "l" else "getClassOf" + + // --- TypeData public fields --- never minified + + /** `TypeData.name`: public, the user name of the class (the result of `jl.Class.getName()`). */ + val name = "name" + + /** `TypeData.isPrimitive`: public, whether it is a primitive type. */ + val isPrimitive = "isPrimitive" + + /** `TypeData.isInterface`: public, whether it is an interface type. */ + val isInterface = "isInterface" + + /** `TypeData.isArrayClass`: public, whether it is an array type. */ + val isArrayClass = "isArrayClass" + + /** `TypeData.isInstance()`: public, implementation of `jl.Class.isInstance`. */ + val isInstance = "isInstance" + + /** `TypeData.isAssignableFrom()`: public, implementation of `jl.Class.isAssignableFrom`. */ + val isAssignableFrom = "isAssignableFrom" + + // --- TypeData public methods --- never minified + + val checkCast = "checkCast" + + val getSuperclass = "getSuperclass" + + val getComponentType = "getComponentType" + + val newArrayOfThisClass = "newArrayOfThisClass" + } + def genZeroOf(tpe: Type)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { @@ -627,14 +729,14 @@ private[emitter] final class SJSGen( case ArrayTypeRef(ClassRef(ObjectClass), 1) => globalVar(VarField.ac, ObjectClass) case _ => - genClassDataOf(arrayTypeRef) DOT "constr" + genClassDataOf(arrayTypeRef) DOT cpn.constr } } def genClassOf(typeRef: TypeRef)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { - Apply(DotSelect(genClassDataOf(typeRef), Ident("getClassOf")), Nil) + Apply(DotSelect(genClassDataOf(typeRef), Ident(cpn.getClassOf)), Nil) } def genClassOf(className: ClassName)( @@ -653,7 +755,7 @@ private[emitter] final class SJSGen( case ArrayTypeRef(base, dims) => val baseData = genClassDataOf(base) (1 to dims).foldLeft[Tree](baseData) { (prev, _) => - Apply(DotSelect(prev, Ident("getArrayOf")), Nil) + Apply(DotSelect(prev, Ident(cpn.getArrayOf)), Nil) } } } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala index 2d9e678334..dca2b88b47 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -71,7 +71,7 @@ class LibrarySizeTest { testLinkedSizes( expectedFastLinkSize = 150063, - expectedFullLinkSizeWithoutClosure = 93868, + expectedFullLinkSizeWithoutClosure = 92648, expectedFullLinkSizeWithClosure = 21325, classDefs, moduleInitializers = MainTestModuleInitializers diff --git a/project/Build.scala b/project/Build.scala index de6ff233a9..c6ae6168e5 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2005,8 +2005,8 @@ object Build { )) } else { Some(ExpectedSizes( - fastLink = 499000 to 500000, - fullLink = 341000 to 342000, + fastLink = 494000 to 495000, + fullLink = 337000 to 338000, fastLinkGz = 69000 to 70000, fullLinkGz = 50000 to 51000, )) @@ -2022,8 +2022,8 @@ object Build { )) } else { Some(ExpectedSizes( - fastLink = 352000 to 353000, - fullLink = 312000 to 313000, + fastLink = 347000 to 348000, + fullLink = 307000 to 308000, fastLinkGz = 54000 to 55000, fullLinkGz = 49000 to 50000, ))