From a6a5597c28d2a01be43e3a240f610ae2724bcb55 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Wed, 27 Dec 2023 16:38:21 +0100 Subject: [PATCH 01/65] Towards 1.15.1. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 2 +- project/Build.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index b6476e0c09..8dfbb764e2 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.15.0", + current = "1.15.1-SNAPSHOT", binaryEmitted = "1.13" ) diff --git a/project/Build.scala b/project/Build.scala index 8deb00803d..93209c3ddb 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -338,7 +338,7 @@ object Build { val previousVersions = List("1.0.0", "1.0.1", "1.1.0", "1.1.1", "1.2.0", "1.3.0", "1.3.1", "1.4.0", "1.5.0", "1.5.1", "1.6.0", "1.7.0", "1.7.1", "1.8.0", "1.9.0", "1.10.0", "1.10.1", "1.11.0", "1.12.0", "1.13.0", - "1.13.1", "1.13.2", "1.14.0") + "1.13.1", "1.13.2", "1.14.0", "1.15.0") val previousVersion = previousVersions.last val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") From 102cae69d8b5fbbfb14a16eabbdbdd6aa1742242 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 28 Dec 2023 13:58:49 +0100 Subject: [PATCH 02/65] Add "create a release" to release steps --- RELEASING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASING.md b/RELEASING.md index 60965a93fa..b143e9a93e 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -26,6 +26,7 @@ 1. When merging the release announcement PR (after proper review): - Update the latest/ URLs (use `~/setlatestapi.sh ` on webserver) + - Create a release on the core scala-js repository. - Announce on Twitter using the @scala_js account - Announce on [Gitter](https://gitter.im/scala-js/scala-js) - Cross-post as an Announcement in Scala Users ([example][7]) From 3572a4b53b20f5d11964cef0e95852d184ca1d52 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 2 Jan 2024 14:06:44 +0100 Subject: [PATCH 03/65] Test that we can link twice on the same linker with GCC Ensures that we catch issues like this: https://github.com/scala-js/scala-js/pull/4917#issuecomment-1873311523 --- .../org/scalajs/linker/GCCLinkerTest.scala | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/linker/jvm/src/test/scala/org/scalajs/linker/GCCLinkerTest.scala b/linker/jvm/src/test/scala/org/scalajs/linker/GCCLinkerTest.scala index a399e4588b..1872712651 100644 --- a/linker/jvm/src/test/scala/org/scalajs/linker/GCCLinkerTest.scala +++ b/linker/jvm/src/test/scala/org/scalajs/linker/GCCLinkerTest.scala @@ -16,9 +16,13 @@ import org.junit.Test import org.scalajs.junit.async._ +import org.scalajs.logging._ + import org.scalajs.linker.interface.StandardConfig +import org.scalajs.linker.testutils.{MemClassDefIRFile, TestIRRepo} import org.scalajs.linker.testutils.LinkingUtils._ +import org.scalajs.linker.testutils.TestIRBuilder._ class GCCLinkerTest { import scala.concurrent.ExecutionContext.Implicits.global @@ -30,4 +34,30 @@ class GCCLinkerTest { */ testLink(Nil, Nil, config = StandardConfig().withClosureCompiler(true)) } + + @Test + def linkIncrementalSmoke(): AsyncResult = await { + /* Check that linking twice works. GCC trees are highly mutable, so if we + * (re-)use them wrongly over multiple runs, things can fail unexpectedly. + * + * We change something about the code in the second run to force the linker + * to actually re-run. + */ + def classDef(text: String) = + MemClassDefIRFile(mainTestClassDef(consoleLog(str(text)))) + + val moduleInitializers = MainTestModuleInitializers + + val config = StandardConfig().withCheckIR(true).withClosureCompiler(true) + val linker = StandardImpl.linker(config) + + val output = MemOutputDirectory() + val logger = new ScalaConsoleLogger(Level.Error) + + for { + lib <- TestIRRepo.minilib + _ <- linker.link(lib :+ classDef("test 1"), moduleInitializers, output, logger) + _ <- linker.link(lib :+ classDef("test 2"), moduleInitializers, output, logger) + } yield () + } } From e55b62448b4508a13a2f28e9b0dbc4922931d8d2 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 28 Dec 2023 16:32:42 +0100 Subject: [PATCH 04/65] Basic tests for javascript.Printers We're going to do modifications, so we add some tests. --- .../backend/javascript/PrintersTest.scala | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 linker/shared/src/test/scala/org/scalajs/linker/backend/javascript/PrintersTest.scala 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 new file mode 100644 index 0000000000..c43b1a28e9 --- /dev/null +++ b/linker/shared/src/test/scala/org/scalajs/linker/backend/javascript/PrintersTest.scala @@ -0,0 +1,164 @@ +/* + * 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.javascript + +import scala.language.implicitConversions + +import java.nio.charset.StandardCharsets + +import org.junit.Test +import org.junit.Assert._ + +import org.scalajs.ir + +import Trees._ + +class PrintersTest { + + private implicit val pos: ir.Position = ir.Position.NoPosition + + private implicit def str2ident(name: String): Ident = + Ident(name, ir.OriginalName.NoOriginalName) + + private def assertPrintEquals(expected: String, tree: Tree): Unit = { + val out = new ByteArrayWriter + val printer = new Printers.JSTreePrinter(out) + printer.printTopLevelTree(tree) + assertEquals(expected.stripMargin.trim + "\n", + new String(out.toByteArray(), StandardCharsets.UTF_8)) + } + + @Test def printFunctionDef(): Unit = { + assertPrintEquals( + """ + |function test() { + | const x = 2; + | return x + |} + """, + FunctionDef("test", Nil, None, Block( + Let("x", mutable = false, Some(IntLiteral(2))), + Return(VarRef("x")))) + ) + + assertPrintEquals( + """ + |function test() { + | /**/ + |} + """, + FunctionDef("test", Nil, None, Skip()) + ) + } + + @Test def printClassDef(): Unit = { + assertPrintEquals( + """ + |class MyClass extends foo.Other { + |} + """, + ClassDef(Some("MyClass"), Some(DotSelect(VarRef("foo"), "Other")), Nil) + ) + + assertPrintEquals( + """ + |class MyClass { + | foo() { + | /**/ + | }; + | get a() { + | return 1 + | }; + | set a(x) { + | /**/ + | }; + |} + """, + ClassDef(Some("MyClass"), None, List( + MethodDef(false, "foo", Nil, None, Skip()), + GetterDef(false, "a", Return(IntLiteral(1))), + SetterDef(false, "a", ParamDef("x"), Skip()) + )) + ) + } + + @Test def printDocComment(): Unit = { + assertPrintEquals( + """ + | /** test */ + """, + DocComment("test") + ) + } + + @Test def printFor(): Unit = { + assertPrintEquals( + """ + |for (let x = 1; (x < 15); x = (x + 1)) { + | /**/ + |}; + """, + For(Let("x", true, Some(IntLiteral(1))), + BinaryOp(ir.Trees.JSBinaryOp.<, VarRef("x"), IntLiteral(15)), + Assign(VarRef("x"), BinaryOp(ir.Trees.JSBinaryOp.+, VarRef("x"), IntLiteral(1))), + Skip()) + ) + } + + @Test def printForIn(): Unit = { + assertPrintEquals( + """ + |for (var x in foo) { + | /**/ + |}; + """, + ForIn(VarDef("x", None), VarRef("foo"), Skip()) + ) + } + + @Test def printIf(): Unit = { + assertPrintEquals( + """ + |if (false) { + | 1 + |}; + """, + If(BooleanLiteral(false), IntLiteral(1), Skip()) + ) + + assertPrintEquals( + """ + |if (false) { + | 1 + |} else { + | 2 + |}; + """, + If(BooleanLiteral(false), IntLiteral(1), IntLiteral(2)) + ) + + assertPrintEquals( + """ + |if (false) { + | 1 + |} else if (true) { + | 2 + |} else { + | 3 + |}; + """, + If(BooleanLiteral(false), IntLiteral(1), + If(BooleanLiteral(true), IntLiteral(2), IntLiteral(3))) + ) + } +} From c2ed2bb42f4dbe7e401c9b4ee130b251fe764aad Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Mon, 25 Dec 2023 20:20:25 +0100 Subject: [PATCH 05/65] Print semicolon as part of statement in javascript Printer This is necesary for a subsequent change, where we pre-print statements into byte buffers. When we later assemble the statement into the surrounding block, we do not have type information anymore (and hence cannot judge whether a semicolon is necessary). Note that this now prints a semicolon after the last statement in a block (which we didn't previously). This does increase fastopt size (somewhat artificially), but more closely corresponds: - to what a human would write - the ECMAScript spec (e.g. https://tc39.es/ecma262/#sec-expression-statement) --- .../linker/backend/javascript/Printers.scala | 66 +++++++++++++------ .../org/scalajs/linker/LibrarySizeTest.scala | 4 +- .../backend/javascript/PrintersTest.scala | 32 ++++----- project/Build.scala | 4 +- 4 files changed, 67 insertions(+), 39 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 2df5acc9f9..e2c57c92eb 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 @@ -73,17 +73,10 @@ object Printers { } case _ => printStat(tree) - if (shouldPrintSepAfterTree(tree)) - print(';') println() } } - protected def shouldPrintSepAfterTree(tree: Tree): Boolean = tree match { - case _:DocComment | _:FunctionDef | _:ClassDef => false - case _ => true - } - protected def printRow(ts: List[Tree], start: Char, end: Char): Unit = { print(start) var rest = ts @@ -105,11 +98,8 @@ object Printers { val x = rest.head rest = rest.tail printStat(x) - if (rest.nonEmpty) { - if (shouldPrintSepAfterTree(x)) - print(';') + if (rest.nonEmpty) println() - } } case _ => @@ -146,6 +136,11 @@ object Printers { printTree(tree, isStat = false) def printTree(tree: Tree, isStat: Boolean): Unit = { + def printSeparatorIfStat() = { + if (isStat) + print(';') + } + tree match { // Comments @@ -178,6 +173,8 @@ object Printers { print(" = ") print(rhs) } + // VarDef is an "expr" in a "For" / "ForIn" tree + printSeparatorIfStat() case Let(ident, mutable, optRhs) => print(if (mutable) "let " else "const ") @@ -186,6 +183,8 @@ object Printers { print(" = ") print(rhs) } + // Let is an "expr" in a "For" / "ForIn" tree + printSeparatorIfStat() case ParamDef(ident) => print(ident) @@ -210,10 +209,12 @@ object Printers { print(lhs) print(" = ") print(rhs) + printSeparatorIfStat() case Return(expr) => print("return ") print(expr) + print(';') case If(cond, thenp, elsep) => if (isStat) { @@ -306,19 +307,22 @@ object Printers { case Throw(expr) => print("throw ") print(expr) + print(';') case Break(label) => - if (label.isEmpty) print("break") + if (label.isEmpty) print("break;") else { print("break ") print(label.get) + print(';') } case Continue(label) => - if (label.isEmpty) print("continue") + if (label.isEmpty) print("continue;") else { print("continue ") print(label.get) + print(';') } case Switch(selector, cases, default) => @@ -354,7 +358,7 @@ object Printers { print('}') case Debugger() => - print("debugger") + print("debugger;") // Expressions @@ -375,6 +379,7 @@ object Printers { print(')') } printArgs(args) + printSeparatorIfStat() case DotSelect(qualifier, item) => qualifier match { @@ -387,27 +392,33 @@ object Printers { } print(".") print(item) + printSeparatorIfStat() case BracketSelect(qualifier, item) => print(qualifier) print('[') print(item) print(']') + printSeparatorIfStat() case Apply(fun, args) => print(fun) printArgs(args) + printSeparatorIfStat() case ImportCall(arg) => print("import(") print(arg) print(')') + printSeparatorIfStat() case NewTarget() => print("new.target") + printSeparatorIfStat() case ImportMeta() => print("import.meta") + printSeparatorIfStat() case Spread(items) => print("...") @@ -416,6 +427,7 @@ object Printers { case Delete(prop) => print("delete ") print(prop) + printSeparatorIfStat() case UnaryOp(op, lhs) => import ir.Trees.JSUnaryOp._ @@ -433,6 +445,7 @@ object Printers { } print(lhs) print(')') + printSeparatorIfStat() case IncDec(prefix, inc, arg) => val op = if (inc) "++" else "--" @@ -443,6 +456,7 @@ object Printers { if (!prefix) print(op) print(')') + printSeparatorIfStat() case BinaryOp(op, lhs, rhs) => import ir.Trees.JSBinaryOp._ @@ -482,13 +496,15 @@ object Printers { print(' ') print(rhs) print(')') + printSeparatorIfStat() case ArrayConstr(items) => printRow(items, '[', ']') + printSeparatorIfStat() case ObjectConstr(Nil) => if (isStat) - print("({})") // force expression position for the object literal + print("({});") // force expression position for the object literal else print("{}") @@ -514,18 +530,21 @@ object Printers { println() print('}') if (isStat) - print(')') + print(");") // Literals case Undefined() => print("(void 0)") + printSeparatorIfStat() case Null() => print("null") + printSeparatorIfStat() case BooleanLiteral(value) => print(if (value) "true" else "false") + printSeparatorIfStat() case IntLiteral(value) => if (value >= 0) { @@ -535,6 +554,7 @@ object Printers { print(value.toString) print(')') } + printSeparatorIfStat() case DoubleLiteral(value) => if (value == 0 && 1 / value < 0) { @@ -546,11 +566,13 @@ object Printers { print(value.toString) print(')') } + printSeparatorIfStat() case StringLiteral(value) => print('\"') printEscapeJS(value) print('\"') + printSeparatorIfStat() case BigIntLiteral(value) => if (value >= 0) { @@ -561,14 +583,17 @@ object Printers { print(value.toString) print("n)") } + printSeparatorIfStat() // Atomic expressions case VarRef(ident) => print(ident) + printSeparatorIfStat() case This() => print("this") + printSeparatorIfStat() case Function(arrow, args, restParam, body) => if (arrow) { @@ -595,6 +620,7 @@ object Printers { printBlock(body) print(')') } + printSeparatorIfStat() // Named function definition @@ -624,8 +650,7 @@ object Printers { var rest = members while (rest.nonEmpty) { println() - print(rest.head) - print(';') + printStat(rest.head) rest = rest.tail } undent(); println(); print('}') @@ -677,12 +702,14 @@ object Printers { } print(" } from ") print(from: Tree) + print(';') case ImportNamespace(binding, from) => print("import * as ") print(binding) print(" from ") print(from: Tree) + print(';') case Export(bindings) => print("export { ") @@ -699,7 +726,7 @@ object Printers { print(binding._2) rest = rest.tail } - print(" }") + print(" };") case ExportImport(bindings, from) => print("export { ") @@ -718,6 +745,7 @@ object Printers { } print(" } from ") print(from: Tree) + print(';') case _ => throw new IllegalArgumentException( 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 a64f546d68..6695edfb35 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -70,8 +70,8 @@ class LibrarySizeTest { ) testLinkedSizes( - expectedFastLinkSize = 150031, - expectedFullLinkSizeWithoutClosure = 130655, + expectedFastLinkSize = 150534, + expectedFullLinkSizeWithoutClosure = 131079, expectedFullLinkSizeWithClosure = 21394, classDefs, moduleInitializers = MainTestModuleInitializers 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 c43b1a28e9..621910f9a7 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 @@ -43,7 +43,7 @@ class PrintersTest { """ |function test() { | const x = 2; - | return x + | return x; |} """, FunctionDef("test", Nil, None, Block( @@ -75,13 +75,13 @@ class PrintersTest { |class MyClass { | foo() { | /**/ - | }; + | } | get a() { - | return 1 - | }; + | return 1; + | } | set a(x) { | /**/ - | }; + | } |} """, ClassDef(Some("MyClass"), None, List( @@ -106,7 +106,7 @@ class PrintersTest { """ |for (let x = 1; (x < 15); x = (x + 1)) { | /**/ - |}; + |} """, For(Let("x", true, Some(IntLiteral(1))), BinaryOp(ir.Trees.JSBinaryOp.<, VarRef("x"), IntLiteral(15)), @@ -120,7 +120,7 @@ class PrintersTest { """ |for (var x in foo) { | /**/ - |}; + |} """, ForIn(VarDef("x", None), VarRef("foo"), Skip()) ) @@ -130,8 +130,8 @@ class PrintersTest { assertPrintEquals( """ |if (false) { - | 1 - |}; + | 1; + |} """, If(BooleanLiteral(false), IntLiteral(1), Skip()) ) @@ -139,10 +139,10 @@ class PrintersTest { assertPrintEquals( """ |if (false) { - | 1 + | 1; |} else { - | 2 - |}; + | 2; + |} """, If(BooleanLiteral(false), IntLiteral(1), IntLiteral(2)) ) @@ -150,12 +150,12 @@ class PrintersTest { assertPrintEquals( """ |if (false) { - | 1 + | 1; |} else if (true) { - | 2 + | 2; |} else { - | 3 - |}; + | 3; + |} """, If(BooleanLiteral(false), IntLiteral(1), If(BooleanLiteral(true), IntLiteral(2), IntLiteral(3))) diff --git a/project/Build.scala b/project/Build.scala index 93209c3ddb..da13fdd781 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1967,7 +1967,7 @@ object Build { scalaVersion.value match { case `default212Version` => Some(ExpectedSizes( - fastLink = 772000 to 773000, + fastLink = 775000 to 776000, fullLink = 145000 to 146000, fastLinkGz = 91000 to 92000, fullLinkGz = 35000 to 36000, @@ -1975,7 +1975,7 @@ object Build { case `default213Version` => Some(ExpectedSizes( - fastLink = 480000 to 481000, + fastLink = 481000 to 483000, fullLink = 102000 to 103000, fastLinkGz = 62000 to 63000, fullLinkGz = 27000 to 28000, From 197af9a726414fc68345920fadc5bd77c419f0fd Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 28 Dec 2023 09:13:59 +0100 Subject: [PATCH 06/65] Do not print skip's in blocks --- .../scalajs/linker/backend/javascript/Printers.scala | 12 +++++++----- .../scala/org/scalajs/linker/LibrarySizeTest.scala | 4 ++-- .../linker/backend/javascript/PrintersTest.scala | 5 ----- project/Build.scala | 6 +++--- 4 files changed, 12 insertions(+), 15 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 e2c57c92eb..adf58ee214 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 @@ -90,19 +90,21 @@ object Printers { } protected def printBlock(tree: Tree): Unit = { - print('{'); indent(); println() + print('{'); indent(); tree match { + case Skip() => + // do not print anything + case tree: Block => var rest = tree.stats while (rest.nonEmpty) { - val x = rest.head + println() + printStat(rest.head) rest = rest.tail - printStat(x) - if (rest.nonEmpty) - println() } case _ => + println() printStat(tree) } undent(); println(); print('}') 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 6695edfb35..9dcc074647 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -70,8 +70,8 @@ class LibrarySizeTest { ) testLinkedSizes( - expectedFastLinkSize = 150534, - expectedFullLinkSizeWithoutClosure = 131079, + expectedFastLinkSize = 150339, + expectedFullLinkSizeWithoutClosure = 130884, expectedFullLinkSizeWithClosure = 21394, classDefs, moduleInitializers = MainTestModuleInitializers 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 621910f9a7..2397717164 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 @@ -54,7 +54,6 @@ class PrintersTest { assertPrintEquals( """ |function test() { - | /**/ |} """, FunctionDef("test", Nil, None, Skip()) @@ -74,13 +73,11 @@ class PrintersTest { """ |class MyClass { | foo() { - | /**/ | } | get a() { | return 1; | } | set a(x) { - | /**/ | } |} """, @@ -105,7 +102,6 @@ class PrintersTest { assertPrintEquals( """ |for (let x = 1; (x < 15); x = (x + 1)) { - | /**/ |} """, For(Let("x", true, Some(IntLiteral(1))), @@ -119,7 +115,6 @@ class PrintersTest { assertPrintEquals( """ |for (var x in foo) { - | /**/ |} """, ForIn(VarDef("x", None), VarRef("foo"), Skip()) diff --git a/project/Build.scala b/project/Build.scala index da13fdd781..758975e8f0 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1967,15 +1967,15 @@ object Build { scalaVersion.value match { case `default212Version` => Some(ExpectedSizes( - fastLink = 775000 to 776000, + fastLink = 770000 to 771000, fullLink = 145000 to 146000, - fastLinkGz = 91000 to 92000, + fastLinkGz = 90000 to 91000, fullLinkGz = 35000 to 36000, )) case `default213Version` => Some(ExpectedSizes( - fastLink = 481000 to 483000, + fastLink = 479000 to 480000, fullLink = 102000 to 103000, fastLinkGz = 62000 to 63000, fullLinkGz = 27000 to 28000, From 7c3797d02d7194a051bc51c98299f09650134330 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 28 Dec 2023 18:22:15 +0100 Subject: [PATCH 07/65] Do not abuse a block to group instance tests This was forgotten in 1982a6b631a5b37ebbd4fe61655a45ad9b62bf93. --- .../org/scalajs/linker/backend/emitter/ClassEmitter.scala | 4 ++-- .../scala/org/scalajs/linker/backend/emitter/Emitter.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 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 0701d2fd84..ca58616785 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 @@ -683,12 +683,12 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { def genInstanceTests(className: ClassName, kind: ClassKind)( implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[js.Tree] = { + globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { for { single <- genSingleInstanceTests(className, kind) array <- genArrayInstanceTests(className) } yield { - js.Block(single ::: array) + single ::: array } } 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 d5b23e4525..d89de7ae4d 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 @@ -595,7 +595,7 @@ final class Emitter(config: Emitter.Config) { */ if (classEmitter.needInstanceTests(linkedClass)(classCache)) { - main += extractWithGlobals(classTreeCache.instanceTests.getOrElseUpdate( + main ++= extractWithGlobals(classTreeCache.instanceTests.getOrElseUpdate( classEmitter.genInstanceTests(className, kind)(moduleContext, classCache, linkedClass.pos))) } @@ -1035,7 +1035,7 @@ object Emitter { private final class DesugaredClassCache { val privateJSFields = new OneTimeCache[WithGlobals[List[js.Tree]]] - val instanceTests = new OneTimeCache[WithGlobals[js.Tree]] + val instanceTests = new OneTimeCache[WithGlobals[List[js.Tree]]] val typeData = new OneTimeCache[WithGlobals[List[js.Tree]]] val setTypeData = new OneTimeCache[js.Tree] val moduleAccessor = new OneTimeCache[WithGlobals[List[js.Tree]]] From bdea4d7ddb6ac6917bae270903c803ff5fdb3166 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 29 Dec 2023 15:55:20 +0100 Subject: [PATCH 08/65] Do not abuse a block to group const field export statements This was forgotten in 1982a6b631a5b37ebbd4fe61655a45ad9b62bf93. --- .../linker/backend/emitter/ClassEmitter.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 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 ca58616785..144672a471 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 @@ -1028,16 +1028,16 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { case e: TopLevelMethodExportDef => genTopLevelMethodExportDef(e) case e: TopLevelFieldExportDef => - genTopLevelFieldExportDef(topLevelExport.owningClass, e) + genTopLevelFieldExportDef(topLevelExport.owningClass, e).map(_ :: Nil) } } - WithGlobals.list(exportsWithGlobals) + WithGlobals.flatten(exportsWithGlobals) } private def genTopLevelMethodExportDef(tree: TopLevelMethodExportDef)( implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { + globalKnowledge: GlobalKnowledge): WithGlobals[List[js.Tree]] = { import TreeDSL._ val JSMethodDef(flags, StringLiteral(exportName), args, restParam, body) = @@ -1056,22 +1056,22 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { private def genConstValueExportDef(exportName: String, exportedValue: js.Tree)( - implicit pos: Position): WithGlobals[js.Tree] = { + implicit pos: Position): WithGlobals[List[js.Tree]] = { moduleKind match { case ModuleKind.NoModule => - genAssignToNoModuleExportVar(exportName, exportedValue) + genAssignToNoModuleExportVar(exportName, exportedValue).map(_ :: Nil) case ModuleKind.ESModule => val field = fileLevelVar(VarField.e, exportName) val let = js.Let(field.ident, mutable = true, Some(exportedValue)) val exportStat = js.Export((field.ident -> js.ExportName(exportName)) :: Nil) - WithGlobals(js.Block(let, exportStat)) + WithGlobals(List(let, exportStat)) case ModuleKind.CommonJSModule => globalRef("exports").map { exportsVarRef => js.Assign( genBracketSelect(exportsVarRef, js.StringLiteral(exportName)), - exportedValue) + exportedValue) :: Nil } } } From 135932afa2601f9f2b914c1fdea806f4d3903c25 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 28 Dec 2023 18:24:23 +0100 Subject: [PATCH 09/65] Simplify printTopLevelTree in JS printer Because it does not need to flatten blocks anymore, we can simply print it as a statement and add a trailing newline. --- .../linker/backend/javascript/Printers.scala | 15 ++------------- 1 file changed, 2 insertions(+), 13 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 adf58ee214..c50426a5d8 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 @@ -62,19 +62,8 @@ object Printers { } def printTopLevelTree(tree: Tree): Unit = { - tree match { - case Skip() => - // do not print anything - case tree: Block => - var rest = tree.stats - while (rest.nonEmpty) { - printTopLevelTree(rest.head) - rest = rest.tail - } - case _ => - printStat(tree) - println() - } + printStat(tree) + println() } protected def printRow(ts: List[Tree], start: Char, end: Char): Unit = { From de0d65c6df5a51a8b024363f1382723b245b1c20 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 29 Dec 2023 16:14:18 +0100 Subject: [PATCH 10/65] Restrict visibility of JS Printer methods They were unnecessarily protected. --- .../linker/backend/javascript/Printers.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 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 c50426a5d8..035f21ef0a 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 @@ -66,7 +66,7 @@ object Printers { println() } - protected def printRow(ts: List[Tree], start: Char, end: Char): Unit = { + private def printRow(ts: List[Tree], start: Char, end: Char): Unit = { print(start) var rest = ts while (rest.nonEmpty) { @@ -78,7 +78,7 @@ object Printers { print(end) } - protected def printBlock(tree: Tree): Unit = { + private def printBlock(tree: Tree): Unit = { print('{'); indent(); tree match { case Skip() => @@ -99,7 +99,7 @@ object Printers { undent(); println(); print('}') } - protected def printSig(args: List[ParamDef], restParam: Option[ParamDef]): Unit = { + private def printSig(args: List[ParamDef], restParam: Option[ParamDef]): Unit = { print("(") var rem = args while (rem.nonEmpty) { @@ -117,13 +117,13 @@ object Printers { print(") ") } - protected def printArgs(args: List[Tree]): Unit = + private def printArgs(args: List[Tree]): Unit = printRow(args, '(', ')') - protected def printStat(tree: Tree): Unit = + private def printStat(tree: Tree): Unit = printTree(tree, isStat = true) - protected def print(tree: Tree): Unit = + private def print(tree: Tree): Unit = printTree(tree, isStat = false) def printTree(tree: Tree, isStat: Boolean): Unit = { @@ -760,7 +760,7 @@ object Printers { print("]") } - protected def print(exportName: ExportName): Unit = + private def print(exportName: ExportName): Unit = printEscapeJS(exportName.name) /** Prints an ASCII string -- use for syntax strings, not for user strings. */ From bd58bbbb4e2e2cf3fb5fb2c7643f0dc0dd8940e0 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 29 Dec 2023 16:16:39 +0100 Subject: [PATCH 11/65] Print trailing newline as part of statement This is necessary so we can partially print substatements more easily (which we'll do in a subsequent PR). It will also allow us to fully remove the concept of "top-level" tree. --- .../linker/backend/javascript/Printers.scala | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 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 035f21ef0a..7d0235bed3 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 @@ -44,6 +44,9 @@ object Printers { protected def println(): Unit = { out.write('\n') + } + + protected def printIndent(): Unit = { val indentArray = this.indentArray val indentMargin = this.indentMargin val bigEnoughIndentArray = @@ -63,7 +66,6 @@ object Printers { def printTopLevelTree(tree: Tree): Unit = { printStat(tree) - println() } private def printRow(ts: List[Tree], start: Char, end: Char): Unit = { @@ -79,7 +81,7 @@ object Printers { } private def printBlock(tree: Tree): Unit = { - print('{'); indent(); + print('{'); indent(); println() tree match { case Skip() => // do not print anything @@ -87,16 +89,14 @@ object Printers { case tree: Block => var rest = tree.stats while (rest.nonEmpty) { - println() printStat(rest.head) rest = rest.tail } case _ => - println() printStat(tree) } - undent(); println(); print('}') + undent(); printIndent(); print('}') } private def printSig(args: List[ParamDef], restParam: Option[ParamDef]): Unit = { @@ -120,12 +120,22 @@ object Printers { private def printArgs(args: List[Tree]): Unit = printRow(args, '(', ')') - private def printStat(tree: Tree): Unit = + /** Prints a stat including leading indent and trailing newline. */ + private def printStat(tree: Tree): Unit = { + printIndent() printTree(tree, isStat = true) + println() + } private def print(tree: Tree): Unit = printTree(tree, isStat = false) + /** Print the "meat" of a tree. + * + * Even if it is a stat: + * - No leading indent. + * - No trailing newline. + */ def printTree(tree: Tree, isStat: Boolean): Unit = { def printSeparatorIfStat() = { if (isStat) @@ -144,12 +154,12 @@ object Printers { } else { print("/** ") print(lines.head) - println() + println(); printIndent() var rest = lines.tail while (rest.nonEmpty) { print(" * ") print(rest.head) - println() + println(); printIndent() rest = rest.tail } print(" */") @@ -326,7 +336,7 @@ object Printers { while (rest.nonEmpty) { val next = rest.head rest = rest.tail - println() + println(); printIndent() print("case ") print(next._1) print(':') @@ -339,13 +349,13 @@ object Printers { default match { case Skip() => case _ => - println() + println(); printIndent() print("default: ") printBlock(default) } undent() - println() + println(); printIndent() print('}') case Debugger() => @@ -509,16 +519,17 @@ object Printers { while (rest.nonEmpty) { val x = rest.head rest = rest.tail + printIndent() print(x._1) print(": ") print(x._2) if (rest.nonEmpty) { print(',') - println() } + println() } undent() - println() + printIndent() print('}') if (isStat) print(");") @@ -637,14 +648,13 @@ object Printers { print(" extends ") print(optParentClass.get) } - print(" {"); indent() + print(" {"); indent(); println() var rest = members while (rest.nonEmpty) { - println() printStat(rest.head) rest = rest.tail } - undent(); println(); print('}') + undent(); printIndent(); print('}') case MethodDef(static, name, params, restParam, body) => if (static) @@ -801,6 +811,13 @@ object Printers { override protected def println(): Unit = { super.println() sourceMap.nextLine() + column = 0 + } + + override protected def printIndent(): Unit = { + assert(column == 0) + + super.printIndent() column = this.getIndentMargin() } From cae909ad8d661eb6af63a6946236a5b63b9181d5 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 29 Dec 2023 16:21:36 +0100 Subject: [PATCH 12/65] Remove printTopLevelTree and replace it with printStat --- .../org/scalajs/linker/backend/BasicLinkerBackend.scala | 4 ++-- .../org/scalajs/linker/backend/javascript/Printers.scala | 6 +----- .../scalajs/linker/backend/javascript/PrintersTest.scala | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) 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 a1238e7433..564ebbb99c 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 @@ -283,7 +283,7 @@ private object BasicLinkerBackend { val jsCodeWriter = new ByteArrayWriter() val printer = new Printers.JSTreePrinter(jsCodeWriter) - printer.printTopLevelTree(tree) + printer.printStat(tree) new PrintedTree(jsCodeWriter.toByteArray(), SourceMapWriter.Fragment.Empty) } @@ -321,7 +321,7 @@ private object BasicLinkerBackend { val smFragmentBuilder = new SourceMapWriter.FragmentBuilder() val printer = new Printers.JSTreePrinterWithSourceMap(jsCodeWriter, smFragmentBuilder) - printer.printTopLevelTree(tree) + printer.printStat(tree) smFragmentBuilder.complete() new PrintedTree(jsCodeWriter.toByteArray(), smFragmentBuilder.result()) 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 7d0235bed3..9690946e69 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 @@ -64,10 +64,6 @@ object Printers { newIndentArray } - def printTopLevelTree(tree: Tree): Unit = { - printStat(tree) - } - private def printRow(ts: List[Tree], start: Char, end: Char): Unit = { print(start) var rest = ts @@ -121,7 +117,7 @@ object Printers { printRow(args, '(', ')') /** Prints a stat including leading indent and trailing newline. */ - private def printStat(tree: Tree): Unit = { + final def printStat(tree: Tree): Unit = { printIndent() printTree(tree, isStat = true) println() 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 2397717164..316f2c3907 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 @@ -33,7 +33,7 @@ class PrintersTest { private def assertPrintEquals(expected: String, tree: Tree): Unit = { val out = new ByteArrayWriter val printer = new Printers.JSTreePrinter(out) - printer.printTopLevelTree(tree) + printer.printStat(tree) assertEquals(expected.stripMargin.trim + "\n", new String(out.toByteArray(), StandardCharsets.UTF_8)) } From c12b25368dee2d5871ba1666277fa387f5b8f8e2 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 4 Nov 2023 13:56:54 +0100 Subject: [PATCH 13/65] Only pass isJSClass to ClassEmitter (instead of entire ClassKind) This allows us to invalidate generated exported members less often (and in following commits, also class members). --- .../main/scala/org/scalajs/ir/Version.scala | 10 ++++++ .../linker/backend/emitter/ClassEmitter.scala | 33 +++++++++---------- .../linker/backend/emitter/Emitter.scala | 24 +++++++++----- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Version.scala b/ir/shared/src/main/scala/org/scalajs/ir/Version.scala index f30be5f7ee..0228d63c86 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Version.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Version.scala @@ -80,6 +80,16 @@ object Version { def fromBytes(bytes: Array[Byte]): Version = make(Type.Ephemeral, bytes) + /** Create a non-hash version from a Byte. + * + * Strictly equivalent to (but potentially more efficient): + * {{{ + * fromBytes(Array[Byte](i)) + * }}} + */ + def fromByte(i: Byte): Version = + new Version(Array(Type.Ephemeral, i)) + /** Create a non-hash version from an Int. * * Strictly equivalent to (but potentially more efficient): 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 144672a471..34b69507ea 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 @@ -43,7 +43,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { import nameGen._ import varGen._ - def buildClass(className: ClassName, kind: ClassKind, jsClassCaptures: Option[List[ParamDef]], + def buildClass(className: ClassName, isJSClass: Boolean, jsClassCaptures: Option[List[ParamDef]], hasClassInitializer: Boolean, superClass: Option[ClassIdent], jsSuperClass: Option[Tree], useESClass: Boolean, ctorDefs: List[js.Tree], memberDefs: List[js.MethodDef], exportedDefs: List[js.Tree])( @@ -55,7 +55,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { def allES5Defs(classVar: js.Tree) = WithGlobals(ctorDefs ::: assignES5ClassMembers(classVar, memberDefs) ::: exportedDefs) - if (!kind.isJSClass) { + if (!isJSClass) { assert(jsSuperClass.isEmpty, className) if (useESClass) { @@ -534,7 +534,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } /** Generates a JS method. */ - private def genJSMethod(className: ClassName, kind: ClassKind, useESClass: Boolean, + private def genJSMethod(className: ClassName, isJSClass: Boolean, useESClass: Boolean, method: JSMethodDef)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { @@ -550,29 +550,29 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { if (useESClass) { js.MethodDef(static = namespace.isStatic, propName, methodFun.args, methodFun.restParam, methodFun.body) } else { - val targetObject = exportTargetES5(className, kind, namespace) + val targetObject = exportTargetES5(className, isJSClass, namespace) js.Assign(genPropSelect(targetObject, propName), methodFun) } } } /** Generates a property. */ - private def genJSProperty(className: ClassName, kind: ClassKind, useESClass: Boolean, + private def genJSProperty(className: ClassName, isJSClass: Boolean, useESClass: Boolean, property: JSPropertyDef)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge): WithGlobals[List[js.Tree]] = { if (useESClass) genJSPropertyES6(className, property) else - genJSPropertyES5(className, kind, property).map(_ :: Nil) + genJSPropertyES5(className, isJSClass, property).map(_ :: Nil) } - private def genJSPropertyES5(className: ClassName, kind: ClassKind, property: JSPropertyDef)( + private def genJSPropertyES5(className: ClassName, isJSClass: Boolean, property: JSPropertyDef)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { implicit val pos = property.pos - val targetObject = exportTargetES5(className, kind, property.flags.namespace) + val targetObject = exportTargetES5(className, isJSClass, property.flags.namespace) // optional getter definition val optGetterWithGlobals = property.getterBody map { body => @@ -627,13 +627,13 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } } - private def exportTargetES5(className: ClassName, kind: ClassKind, namespace: MemberNamespace)( + private def exportTargetES5(className: ClassName, isJSClass: Boolean, namespace: MemberNamespace)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): js.Tree = { import TreeDSL._ val classVarRef = - if (kind.isJSClass) fileLevelVar(VarField.b, genName(className)) + if (isJSClass) fileLevelVar(VarField.b, genName(className)) else globalVar(VarField.c, className) if (namespace.isStatic) classVarRef @@ -943,16 +943,13 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { globalVar(VarField.c, className).prototype DOT "$classData" := globalVar(VarField.d, className) } - def genModuleAccessor(className: ClassName, kind: ClassKind)( + def genModuleAccessor(className: ClassName, isJSClass: Boolean)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { import TreeDSL._ val tpe = ClassType(className) - require(kind.hasModuleAccessor, - s"genModuleAccessor called with non-module class: $className") - val moduleInstance = fileLevelVarIdent(VarField.n, genName(className)) val createModuleInstanceField = genEmptyMutableLet(moduleInstance) @@ -962,7 +959,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val assignModule = { moduleInstanceVar := { - if (kind == ClassKind.JSModuleClass) { + if (isJSClass) { js.New( genNonNativeJSClassConstructor(className), Nil) @@ -1002,12 +999,12 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { createAccessor.map(createModuleInstanceField :: _) } - def genExportedMember(className: ClassName, kind: ClassKind, useESClass: Boolean, member: JSMethodPropDef)( + def genExportedMember(className: ClassName, isJSClass: Boolean, useESClass: Boolean, member: JSMethodPropDef)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge): WithGlobals[List[js.Tree]] = { member match { - case m: JSMethodDef => genJSMethod(className, kind, useESClass, m).map(_ :: Nil) - case p: JSPropertyDef => genJSProperty(className, kind, useESClass, p) + case m: JSMethodDef => genJSMethod(className, isJSClass, useESClass, m).map(_ :: Nil) + case p: JSPropertyDef => genJSProperty(className, isJSClass, useESClass, p) } } 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 d89de7ae4d..28c20a4a66 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 @@ -412,8 +412,12 @@ final class Emitter(config: Emitter.Config) { } } + val isJSClass = kind.isJSClass + // Class definition if (linkedClass.hasInstances && kind.isAnyNonNativeClass) { + val isJSClassVersion = Version.fromByte(if (isJSClass) 1 else 0) + /* Is this class compiled as an ECMAScript `class`? * * See JSGen.useClassesForRegularClasses for the rationale here. @@ -430,13 +434,17 @@ final class Emitter(config: Emitter.Config) { * observable change; whereas rewiring Throwable to extend `Error` when * it does not actually directly extend `Object` would break everything, * so we need to be more careful there. + * + * For caching, isJSClassVersion can be used to guard use of `useESClass`: + * it is the only "dynamic" value it depends on. The rest is configuration + * or part of the cache key (ancestors). */ val useESClass = if (jsGen.useClassesForRegularClasses) { assert(jsGen.useClassesForJSClassesAndThrowables) true } else { jsGen.useClassesForJSClassesAndThrowables && - (kind.isJSClass || linkedClass.ancestors.contains(ThrowableClass)) + (isJSClass || linkedClass.ancestors.contains(ThrowableClass)) } // JS constructor @@ -448,7 +456,7 @@ final class Emitter(config: Emitter.Config) { */ val ctorCache = classCache.getConstructorCache() - if (linkedClass.kind.isJSClass) { + if (isJSClass) { assert(linkedInlineableInit.isEmpty) val jsConstructorDef = linkedClass.jsConstructorDef.getOrElse { @@ -533,13 +541,13 @@ final class Emitter(config: Emitter.Config) { (member, idx) <- linkedClass.exportedMembers.zipWithIndex } yield { val memberCache = classCache.getExportedMemberCache(idx) - val version = Version.combine(linkedClass.version, member.version) + val version = Version.combine(isJSClassVersion, member.version) memberCache.getOrElseUpdate(version, classEmitter.genExportedMember( className, // invalidated by overall class cache - kind, // invalidated by class verison - useESClass, // invalidated by class version (combined) - member // invalidated by version (combined) + isJSClass, // invalidated by isJSClassVersion + useESClass, // invalidated by isJSClassVersion + member // invalidated by version )(moduleContext, memberCache)) } @@ -561,7 +569,7 @@ final class Emitter(config: Emitter.Config) { exportedMembers <- WithGlobals.list(exportedMembersWithGlobals) clazz <- classEmitter.buildClass( className, // invalidated by overall class cache (part of ancestors) - linkedClass.kind, // invalidated by class version + isJSClass, // invalidated by class version linkedClass.jsClassCaptures, // invalidated by class version hasClassInitializer, // invalidated by class version (optimizer cannot remove it) linkedClass.superClass, // invalidated by class version @@ -618,7 +626,7 @@ final class Emitter(config: Emitter.Config) { if (linkedClass.kind.hasModuleAccessor && linkedClass.hasInstances) { main ++= extractWithGlobals(classTreeCache.moduleAccessor.getOrElseUpdate( - classEmitter.genModuleAccessor(className, kind)(moduleContext, classCache, linkedClass.pos))) + classEmitter.genModuleAccessor(className, isJSClass)(moduleContext, classCache, linkedClass.pos))) } // Static fields From 81a235d7468c40db12d65d1684b5d31bacc53f6d Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 2 Jan 2024 12:04:02 +0100 Subject: [PATCH 14/65] Use Version.fromByte in KnowledgeGuardian Now that we have it, we might as well save some memory. --- .../org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala index cd3bd4a687..2d7f18b19c 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala @@ -374,7 +374,7 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { */ private def computeFieldDefsVersion(linkedClass: LinkedClass): Version = { val hasAnyJSField = linkedClass.fields.exists(_.isInstanceOf[JSFieldDef]) - val hasAnyJSFieldVersion = Version.fromInt(if (hasAnyJSField) 1 else 0) + val hasAnyJSFieldVersion = Version.fromByte(if (hasAnyJSField) 1 else 0) val scalaFieldNamesVersion = linkedClass.fields.collect { case FieldDef(_, FieldIdent(name), _, _) => Version.fromUTF8String(name.encoded) } From 14144ac402c470cb65063e0d15ac0c2df0bbfea8 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 4 Nov 2023 13:59:25 +0100 Subject: [PATCH 15/65] Move member assignment from buildClass to genMember This simplifies buildClass. Thanks to the special version for isJSClass, this will lead to acceptable invalidations (only if the class kind changes, which should be rare). --- .../linker/backend/emitter/ClassEmitter.scala | 29 ++++++++++--------- .../linker/backend/emitter/CoreJSLib.scala | 9 ++++++ .../linker/backend/emitter/Emitter.scala | 22 ++++++++------ .../linker/backend/emitter/JSGen.scala | 12 -------- 4 files changed, 37 insertions(+), 35 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 34b69507ea..1a5a965e55 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 @@ -45,16 +45,11 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { def buildClass(className: ClassName, isJSClass: Boolean, jsClassCaptures: Option[List[ParamDef]], hasClassInitializer: Boolean, - superClass: Option[ClassIdent], jsSuperClass: Option[Tree], useESClass: Boolean, ctorDefs: List[js.Tree], - memberDefs: List[js.MethodDef], exportedDefs: List[js.Tree])( + superClass: Option[ClassIdent], jsSuperClass: Option[Tree], useESClass: Boolean, + members: List[js.Tree])( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { - def allES6Defs = ctorDefs ::: memberDefs ::: exportedDefs - - def allES5Defs(classVar: js.Tree) = - WithGlobals(ctorDefs ::: assignES5ClassMembers(classVar, memberDefs) ::: exportedDefs) - if (!isJSClass) { assert(jsSuperClass.isEmpty, className) @@ -66,10 +61,10 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } WithGlobals.option(parentVarWithGlobals).flatMap { parentVar => - globalClassDef(VarField.c, className, parentVar, allES6Defs) + globalClassDef(VarField.c, className, parentVar, members) } } else { - allES5Defs(globalVar(VarField.c, className)) + WithGlobals(members) } } else { // Wrap the entire class def in an accessor function @@ -87,10 +82,10 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val entireClassDefWithGlobals = if (useESClass) { genJSSuperCtor(superClass, jsSuperClass).map { jsSuperClass => - List(classValueVar := js.ClassDef(Some(classValueIdent), Some(jsSuperClass), allES6Defs)) + List(classValueVar := js.ClassDef(Some(classValueIdent), Some(jsSuperClass), members)) } } else { - allES5Defs(classValueVar) + WithGlobals(members) } val classDefStatsWithGlobals = for { @@ -470,9 +465,9 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } } - def genMemberMethod(className: ClassName, method: MethodDef)( + def genMemberMethod(className: ClassName, isJSClass: Boolean, useESClass: Boolean, method: MethodDef)( implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge): WithGlobals[js.MethodDef] = { + globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { assert(method.flags.namespace == MemberNamespace.Public) implicit val pos = method.pos @@ -481,7 +476,13 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { methodFun <- desugarToFunction(className, method.args, method.body.get, method.resultType) } yield { val jsMethodName = genMemberMethodIdent(method.name, method.originalName) - js.MethodDef(static = false, jsMethodName, methodFun.args, methodFun.restParam, methodFun.body) + + if (useESClass) { + js.MethodDef(static = false, jsMethodName, methodFun.args, methodFun.restParam, methodFun.body) + } else { + val targetObject = exportTargetES5(className, isJSClass, MemberNamespace.Public) + js.Assign(genPropSelect(targetObject, jsMethodName), methodFun) + } } } 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 8a04956ad8..def0d13434 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 @@ -2132,6 +2132,15 @@ private[emitter] object CoreJSLib { obj ::: prims } + private def assignES5ClassMembers(classRef: Tree, members: List[MethodDef]): List[Tree] = { + for { + MethodDef(static, name, args, restParam, body) <- members + } yield { + val target = if (static) classRef else classRef.prototype + genPropSelect(target, name) := Function(arrow = false, args, restParam, body) + } + } + private def defineFunction(name: VarField, args: List[ParamDef], body: Tree): List[Tree] = extractWithGlobals(globalFunctionDef(name, CoreVar, args, None, body)) 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 28c20a4a66..ea5e4b09bd 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 @@ -532,8 +532,14 @@ final class Emitter(config: Emitter.Config) { val methodCache = classCache.getMemberMethodCache(method.methodName) - methodCache.getOrElseUpdate(method.version, - classEmitter.genMemberMethod(className, method)(moduleContext, methodCache)) + val version = Version.combine(isJSClassVersion, method.version) + methodCache.getOrElseUpdate(version, + classEmitter.genMemberMethod( + className, // invalidated by overall class cache + isJSClass, // invalidated by isJSClassVersion + useESClass, // invalidated by isJSClassVersion + method // invalidated by method.version + )(moduleContext, methodCache)) } // Exported Members @@ -575,9 +581,7 @@ final class Emitter(config: Emitter.Config) { linkedClass.superClass, // invalidated by class version linkedClass.jsSuperClass, // invalidated by class version useESClass, // invalidated by class version (depends on kind, config and ancestry only) - ctor, // invalidated directly - memberMethods, // invalidated directly - exportedMembers.flatten // invalidated directly + ctor ::: memberMethods ::: exportedMembers.flatten // all 3 invalidated directly )(moduleContext, fullClassCache, linkedClass.pos) // pos invalidated by class version } yield { clazz @@ -770,7 +774,7 @@ final class Emitter(config: Emitter.Config) { Array.fill(MemberNamespace.Count)(mutable.Map.empty[MethodName, MethodCache[List[js.Tree]]]) private[this] val _memberMethodCache = - mutable.Map.empty[MethodName, MethodCache[js.MethodDef]] + mutable.Map.empty[MethodName, MethodCache[js.Tree]] private[this] var _constructorCache: Option[MethodCache[List[js.Tree]]] = None @@ -810,7 +814,7 @@ final class Emitter(config: Emitter.Config) { } def getMemberMethodCache( - methodName: MethodName): MethodCache[js.MethodDef] = { + methodName: MethodName): MethodCache[js.Tree] = { _memberMethodCache.getOrElseUpdate(methodName, new MethodCache) } @@ -897,7 +901,7 @@ final class Emitter(config: Emitter.Config) { private[this] var _tree: WithGlobals[List[js.Tree]] = null private[this] var _lastVersion: Version = Version.Unversioned private[this] var _lastCtor: WithGlobals[List[js.Tree]] = null - private[this] var _lastMemberMethods: List[WithGlobals[js.MethodDef]] = null + private[this] var _lastMemberMethods: List[WithGlobals[js.Tree]] = null private[this] var _lastExportedMembers: List[WithGlobals[List[js.Tree]]] = null private[this] var _cacheUsed = false @@ -913,7 +917,7 @@ final class Emitter(config: Emitter.Config) { def startRun(): Unit = _cacheUsed = false def getOrElseUpdate(version: Version, ctor: WithGlobals[List[js.Tree]], - memberMethods: List[WithGlobals[js.MethodDef]], exportedMembers: List[WithGlobals[List[js.Tree]]], + memberMethods: List[WithGlobals[js.Tree]], exportedMembers: List[WithGlobals[List[js.Tree]]], compute: => WithGlobals[List[js.Tree]]): WithGlobals[List[js.Tree]] = { @tailrec 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 c5e3aba380..3ed36197a5 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 @@ -156,18 +156,6 @@ private[emitter] final class JSGen(val config: Emitter.Config) { } } - def assignES5ClassMembers(classRef: Tree, members: List[MethodDef])( - implicit pos: Position): List[Tree] = { - import TreeDSL._ - - for { - MethodDef(static, name, args, restParam, body) <- members - } yield { - val target = if (static) classRef else classRef.prototype - genPropSelect(target, name) := Function(arrow = false, args, restParam, body) - } - } - def genIIFE(captures: List[(ParamDef, Tree)], body: Tree)( implicit pos: Position): Tree = { val (params, args) = captures.unzip From afef6bd76984772c4e5a1902ee307678059d8c49 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 17 Dec 2023 16:08:08 +0100 Subject: [PATCH 16/65] Cache (potential) jsSuperClass tree separately This removes the only desugarExpr call from buildClass. Since we'll stop caching buildClass going forward, this is important. --- .../linker/backend/emitter/ClassEmitter.scala | 31 ++++++++++--------- .../linker/backend/emitter/Emitter.scala | 12 +++++-- 2 files changed, 26 insertions(+), 17 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 1a5a965e55..08a3bcbf20 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 @@ -45,13 +45,13 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { def buildClass(className: ClassName, isJSClass: Boolean, jsClassCaptures: Option[List[ParamDef]], hasClassInitializer: Boolean, - superClass: Option[ClassIdent], jsSuperClass: Option[Tree], useESClass: Boolean, + superClass: Option[ClassIdent], storeJSSuperClass: Option[js.Tree], useESClass: Boolean, members: List[js.Tree])( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { if (!isJSClass) { - assert(jsSuperClass.isEmpty, className) + assert(storeJSSuperClass.isEmpty, className) if (useESClass) { val parentVarWithGlobals = for (parentIdent <- superClass) yield { @@ -70,18 +70,12 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { // Wrap the entire class def in an accessor function import TreeDSL._ - val genStoreJSSuperClass = jsSuperClass.map { jsSuperClass => - for (rhs <- desugarExpr(jsSuperClass, resultType = AnyType)) yield { - js.VarDef(fileLevelVar(VarField.superClass).ident, Some(rhs)) - } - } - val classValueIdent = fileLevelVarIdent(VarField.b, genName(className)) val classValueVar = js.VarRef(classValueIdent) val createClassValueVar = genEmptyMutableLet(classValueIdent) val entireClassDefWithGlobals = if (useESClass) { - genJSSuperCtor(superClass, jsSuperClass).map { jsSuperClass => + genJSSuperCtor(superClass, storeJSSuperClass.isDefined).map { jsSuperClass => List(classValueVar := js.ClassDef(Some(classValueIdent), Some(jsSuperClass), members)) } } else { @@ -89,11 +83,10 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } val classDefStatsWithGlobals = for { - optStoreJSSuperClass <- WithGlobals.option(genStoreJSSuperClass) entireClassDef <- entireClassDefWithGlobals createStaticFields <- genCreateStaticFieldsOfJSClass(className) } yield { - optStoreJSSuperClass.toList ::: entireClassDef ::: createStaticFields + storeJSSuperClass.toList ::: entireClassDef ::: createStaticFields } jsClassCaptures.fold { @@ -225,7 +218,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { /** Generates the JS constructor for a JS class. */ def genJSConstructor(className: ClassName, superClass: Option[ClassIdent], - jsSuperClass: Option[Tree], useESClass: Boolean, jsConstructorDef: JSConstructorDef)( + hasJSSuperClass: Boolean, useESClass: Boolean, jsConstructorDef: JSConstructorDef)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { @@ -240,7 +233,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } else { for { ctorFun <- ctorFunWithGlobals - superCtor <- genJSSuperCtor(superClass, jsSuperClass) + superCtor <- genJSSuperCtor(superClass, hasJSSuperClass) } yield { import TreeDSL._ @@ -254,16 +247,24 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } } - private def genJSSuperCtor(superClass: Option[ClassIdent], jsSuperClass: Option[Tree])( + private def genJSSuperCtor(superClass: Option[ClassIdent], hasJSSuperClass: Boolean)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[js.Tree] = { - if (jsSuperClass.isDefined) { + if (hasJSSuperClass) { WithGlobals(fileLevelVar(VarField.superClass)) } else { genJSClassConstructor(superClass.get.name, keepOnlyDangerousVarNames = true) } } + def genStoreJSSuperClass(jsSuperClass: Tree)( + implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, + pos: Position): WithGlobals[js.Tree] = { + for (rhs <- desugarExpr(jsSuperClass, resultType = AnyType)) yield { + js.VarDef(fileLevelVar(VarField.superClass).ident, Some(rhs)) + } + } + /** Generates the JavaScript constructor of a class, as a `js.Function`. * * For ECMAScript 2015, that `js.Function` must be decomposed and reformed 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 ea5e4b09bd..a5191cdf8c 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 @@ -447,6 +447,13 @@ final class Emitter(config: Emitter.Config) { (isJSClass || linkedClass.ancestors.contains(ThrowableClass)) } + val hasJSSuperClass = linkedClass.jsSuperClass.isDefined + + val storeJSSuperClass = linkedClass.jsSuperClass.map { jsSuperClass => + extractWithGlobals(classTreeCache.storeJSSuperClass.getOrElseUpdate( + classEmitter.genStoreJSSuperClass(jsSuperClass)(moduleContext, classCache, linkedClass.pos))) + } + // JS constructor val ctorWithGlobals = { /* The constructor depends both on the class version, and the version @@ -468,7 +475,7 @@ final class Emitter(config: Emitter.Config) { classEmitter.genJSConstructor( className, // invalidated by overall class cache (part of ancestors) linkedClass.superClass, // invalidated by class version - linkedClass.jsSuperClass, // invalidated by class version + hasJSSuperClass, // invalidated by class version useESClass, // invalidated by class version jsConstructorDef // part of ctor version )(moduleContext, ctorCache, linkedClass.pos)) @@ -579,7 +586,7 @@ final class Emitter(config: Emitter.Config) { linkedClass.jsClassCaptures, // invalidated by class version hasClassInitializer, // invalidated by class version (optimizer cannot remove it) linkedClass.superClass, // invalidated by class version - linkedClass.jsSuperClass, // invalidated by class version + storeJSSuperClass, // invalidated by class version useESClass, // invalidated by class version (depends on kind, config and ancestry only) ctor ::: memberMethods ::: exportedMembers.flatten // all 3 invalidated directly )(moduleContext, fullClassCache, linkedClass.pos) // pos invalidated by class version @@ -1047,6 +1054,7 @@ object Emitter { private final class DesugaredClassCache { val privateJSFields = new OneTimeCache[WithGlobals[List[js.Tree]]] + val storeJSSuperClass = new OneTimeCache[WithGlobals[js.Tree]] val instanceTests = new OneTimeCache[WithGlobals[List[js.Tree]]] val typeData = new OneTimeCache[WithGlobals[List[js.Tree]]] val setTypeData = new OneTimeCache[js.Tree] From 4b52fa070f3a1337639f55f0253aabcfaadd18f1 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 30 Dec 2023 11:11:06 +0100 Subject: [PATCH 17/65] Replace DocComment tree with dedicated JSDocConstructor tree This simplifies pretty much all usage sites. --- .../closure/ClosureAstTransformer.scala | 55 ++++++------------- .../linker/backend/emitter/ClassEmitter.scala | 14 ++--- .../linker/backend/javascript/Printers.scala | 26 ++------- .../linker/backend/javascript/Trees.scala | 4 +- .../backend/javascript/PrintersTest.scala | 8 ++- 5 files changed, 36 insertions(+), 71 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 fa759a4858..79ad4562ec 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 @@ -43,7 +43,8 @@ private class ClosureAstTransformer(featureSet: FeatureSet, def transformScript(topLevelTrees: List[Tree]): Node = { val script = setNodePosition(new Node(Token.SCRIPT), NoPosition) - transformBlockStats(topLevelTrees)(NoPosition).foreach(script.addChildToBack(_)) + for (stat <- topLevelTrees) + script.addChildToBack(transformStat(stat)(NoPosition)) script.putProp(Node.FEATURE_SET, featureSet) script } @@ -55,6 +56,20 @@ private class ClosureAstTransformer(featureSet: FeatureSet, implicit val pos = pos_in wrapTransform(tree) { + case JSDocConstructor(tree) => + val node = transformStat(tree) + // The @constructor must be propagated through an ExprResult node + val trg = + if (node.isExprResult()) node.getChildAtIndex(0) + else node + val ctorDoc = { + val b = JSDocInfo.builder() + b.recordConstructor() + b.build() + } + trg.setJSDocInfo(ctorDoc) + node + case VarDef(ident, optRhs) => val node = transformName(ident) optRhs.foreach(rhs => node.addChildToFront(transformExpr(rhs))) @@ -448,45 +463,11 @@ private class ClosureAstTransformer(featureSet: FeatureSet, def transformBlock(stats: List[Tree], blockPos: Position): Node = { val block = new Node(Token.BLOCK) - for (node <- transformBlockStats(stats)(blockPos)) - block.addChildToBack(node) + for (stat <- stats) + block.addChildToBack(transformStat(stat)(blockPos)) block } - def transformBlockStats(stats: List[Tree])( - implicit parentPos: Position): List[Node] = { - - @inline def ctorDoc(): JSDocInfo = { - val b = JSDocInfo.builder() - b.recordConstructor() - b.build() - } - - // The Rhino IR attaches DocComments to the following nodes (rather than - // having individual nodes). We preprocess these here. - @tailrec - def loop(ts: List[Tree], nextIsCtor: Boolean, acc: List[Node]): List[Node] = ts match { - case DocComment(text) :: tss => - loop(tss, nextIsCtor = text.startsWith("@constructor"), acc) - - case t :: tss => - val node = transformStat(t) - if (nextIsCtor) { - // The @constructor must be propagated through an ExprResult node - val trg = - if (node.isExprResult()) node.getChildAtIndex(0) - else node - trg.setJSDocInfo(ctorDoc()) - } - loop(tss, nextIsCtor = false, node :: acc) - - case Nil => - acc.reverse - } - - loop(stats, nextIsCtor = false, Nil) - } - @inline private def wrapTransform(tree: Tree)(body: Tree => Node)( implicit pos: Position): Node = { 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 144672a471..fc7054c1e6 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 @@ -214,14 +214,14 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } yield { ( // Real constructor - js.DocComment("@constructor") :: - realCtorDef ::: + js.JSDocConstructor(realCtorDef.head) :: + realCtorDef.tail ::: chainProto ::: (genIdentBracketSelect(ctorVar.prototype, "constructor") := ctorVar) :: // Inheritable constructor - js.DocComment("@constructor") :: - inheritableCtorDef ::: + js.JSDocConstructor(inheritableCtorDef.head) :: + inheritableCtorDef.tail ::: (globalVar(VarField.h, className).prototype := ctorVar.prototype) :: Nil ) } @@ -251,8 +251,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val ctorVar = fileLevelVar(VarField.b, genName(className)) - js.DocComment("@constructor") :: - (ctorVar := ctorFun) :: + js.JSDocConstructor(ctorVar := ctorFun) :: chainPrototypeWithLocalCtor(className, ctorVar, superCtor) ::: (genIdentBracketSelect(ctorVar.prototype, "constructor") := ctorVar) :: Nil } @@ -344,8 +343,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val dummyCtor = fileLevelVar(VarField.hh, genName(className)) List( - js.DocComment("@constructor"), - genConst(dummyCtor.ident, js.Function(false, Nil, None, js.Skip())), + js.JSDocConstructor(genConst(dummyCtor.ident, js.Function(false, Nil, None, js.Skip()))), dummyCtor.prototype := superCtor.prototype, ctorVar.prototype := js.New(dummyCtor, Nil) ) 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 9690946e69..4d675d4dec 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,27 +139,11 @@ object Printers { } tree match { - // Comments - - case DocComment(text) => - val lines = text.split("\n").toList - if (lines.tail.isEmpty) { - print("/** ") - print(lines.head) - print(" */") - } else { - print("/** ") - print(lines.head) - println(); printIndent() - var rest = lines.tail - while (rest.nonEmpty) { - print(" * ") - print(rest.head) - println(); printIndent() - rest = rest.tail - } - print(" */") - } + case JSDocConstructor(tree) => + print("/** @constructor */") + println(); printIndent() + // not printStat: we must not print the trailing newline. + printTree(tree, isStat = true) // Definitions 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 27da8e50c1..efcf98e609 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 @@ -39,9 +39,9 @@ object Trees { } } - // Comments + // Constructor comment / annotation. - sealed case class DocComment(text: String)(implicit val pos: Position) + sealed case class JSDocConstructor(tree: Tree)(implicit val pos: Position) extends Tree // Identifiers and properties 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 316f2c3907..86c9215f02 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 @@ -89,12 +89,14 @@ class PrintersTest { ) } - @Test def printDocComment(): Unit = { + @Test def printJSDocConstructor(): Unit = { assertPrintEquals( """ - | /** test */ + |/** @constructor */ + |ctor = (function() { + |}); """, - DocComment("test") + JSDocConstructor(Assign(VarRef("ctor"), Function(false, Nil, None, Skip()))) ) } From 6d072557808220d3eedb9209cc2b90d29b34357a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 25 Jan 2024 14:51:03 +0100 Subject: [PATCH 18/65] Do not put `O` (`jl.Object`) in the `ancestors` dictionaries. It is never read, because all the functions that would read it are special-cased, so these are wasted bytes. --- .../org/scalajs/linker/backend/emitter/ClassEmitter.scala | 2 +- .../org/scalajs/linker/backend/emitter/CoreJSLib.scala | 3 +-- .../test/scala/org/scalajs/linker/LibrarySizeTest.scala | 6 +++--- project/Build.scala | 8 ++++---- 4 files changed, 9 insertions(+), 10 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 144672a471..ad953d86ef 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 @@ -865,7 +865,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } val ancestorsRecord = js.ObjectConstr( - ancestors.map(ancestor => (js.Ident(genName(ancestor)), js.IntLiteral(1)))) + ancestors.withFilter(_ != ObjectClass).map(ancestor => (js.Ident(genName(ancestor)), js.IntLiteral(1)))) val isInstanceFunWithGlobals: WithGlobals[js.Tree] = { if (globalKnowledge.isAncestorOfHijackedClass(className)) { 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 8a04956ad8..6bfac4a983 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 @@ -1687,7 +1687,6 @@ private[emitter] object CoreJSLib { else Skip(), privateFieldSet("ancestors", ObjectConstr(List( - Ident(genName(ObjectClass)) -> 1, Ident(genName(CloneableClass)) -> 1, Ident(genName(SerializableClass)) -> 1 ))), @@ -2066,7 +2065,7 @@ private[emitter] object CoreJSLib { extractWithGlobals( globalVarDef(VarField.d, ObjectClass, New(globalVar(VarField.TypeData, CoreVar), Nil))) ::: List( - privateFieldSet("ancestors", ObjectConstr(List((Ident(genName(ObjectClass)) -> 1)))), + privateFieldSet("ancestors", ObjectConstr(Nil)), privateFieldSet("arrayEncodedName", str("L" + fullName + ";")), privateFieldSet("isAssignableFromFun", { genArrowFunction(paramList(that), { 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 9dcc074647..aa851ca438 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -70,9 +70,9 @@ class LibrarySizeTest { ) testLinkedSizes( - expectedFastLinkSize = 150339, - expectedFullLinkSizeWithoutClosure = 130884, - expectedFullLinkSizeWithClosure = 21394, + expectedFastLinkSize = 150063, + expectedFullLinkSizeWithoutClosure = 130664, + expectedFullLinkSizeWithClosure = 21325, classDefs, moduleInitializers = MainTestModuleInitializers ) diff --git a/project/Build.scala b/project/Build.scala index 758975e8f0..f9d72c08e0 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1967,16 +1967,16 @@ object Build { scalaVersion.value match { case `default212Version` => Some(ExpectedSizes( - fastLink = 770000 to 771000, - fullLink = 145000 to 146000, + fastLink = 768000 to 769000, + fullLink = 144000 to 145000, fastLinkGz = 90000 to 91000, fullLinkGz = 35000 to 36000, )) case `default213Version` => Some(ExpectedSizes( - fastLink = 479000 to 480000, - fullLink = 102000 to 103000, + fastLink = 478000 to 479000, + fullLink = 101000 to 102000, fastLinkGz = 62000 to 63000, fullLinkGz = 27000 to 28000, )) From 86c18be06bfff8c0245e1ab18b404bcb238a56ba Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 15 Oct 2023 20:05:42 +0200 Subject: [PATCH 19/65] Fuse emitting and printing of trees in the backend This allows us to use the Emitter's powerful caching mechanism to directly cache printed trees (as byte buffers) and not cache JavaScript trees anymore at all. This reduces in-between run memory usage on the test suite from 1.12 GB (not GiB) to 1.00 GB on my machine (roughly 10%). Runtime performance (both batch and incremental) is unaffected. It is worth pointing out, that due to how the Emitter caches trees, classes that end up being ES6 classes is performed will be held twice in memory (once the individual methods, once the entire class). On the test suite, this is the case for 710 cases out of 6538. --- .../closure/ClosureLinkerBackend.scala | 9 +- .../linker/backend/BasicLinkerBackend.scala | 169 ++++++-------- .../linker/backend/emitter/ClassEmitter.scala | 6 +- .../linker/backend/emitter/CoreJSLib.scala | 22 +- .../linker/backend/emitter/Emitter.scala | 213 +++++++++++------- .../linker/backend/javascript/Printers.scala | 30 ++- .../linker/backend/javascript/Trees.scala | 18 ++ .../linker/BasicLinkerBackendTest.scala | 30 +-- .../org/scalajs/linker/EmitterTest.scala | 94 ++++++++ .../backend/javascript/PrintersTest.scala | 24 +- 10 files changed, 383 insertions(+), 232 deletions(-) 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 003e873773..1f532767e2 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 @@ -60,7 +60,7 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) .withTrackAllGlobalRefs(true) .withInternalModulePattern(m => OutputPatternsImpl.moduleName(config.outputPatterns, m.id)) - new Emitter(emitterConfig) + new Emitter(emitterConfig, ClosureLinkerBackend.PostTransformer) } val symbolRequirements: SymbolRequirement = emitter.symbolRequirements @@ -295,4 +295,11 @@ 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 564ebbb99c..4faef57c0a 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 @@ -17,6 +17,8 @@ import scala.concurrent._ import java.nio.ByteBuffer import java.nio.charset.StandardCharsets +import java.util.concurrent.atomic.AtomicInteger + import org.scalajs.logging.Logger import org.scalajs.linker.interface.{IRFile, OutputDirectory, Report} @@ -36,12 +38,19 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) import BasicLinkerBackend._ + private[this] var totalModules = 0 + private[this] val rewrittenModules = new AtomicInteger(0) + private[this] val emitter = { val emitterConfig = Emitter.Config(config.commonConfig.coreSpec) .withJSHeader(config.jsHeader) .withInternalModulePattern(m => OutputPatternsImpl.moduleName(config.outputPatterns, m.id)) - new Emitter(emitterConfig) + val postTransformer = + if (config.sourceMap) PostTransformerWithSourceMap + else PostTransformerWithoutSourceMap + + new Emitter(emitterConfig, postTransformer) } val symbolRequirements: SymbolRequirement = emitter.symbolRequirements @@ -61,6 +70,11 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) implicit ec: ExecutionContext): Future[Report] = { verifyModuleSet(moduleSet) + // Reset stats. + + totalModules = moduleSet.modules.size + rewrittenModules.set(0) + val emitterResult = logger.time("Emitter") { emitter.emit(moduleSet, logger) } @@ -68,24 +82,25 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) val skipContentCheck = !isFirstRun isFirstRun = false - printedModuleSetCache.startRun(moduleSet) val allChanged = printedModuleSetCache.updateGlobal(emitterResult.header, emitterResult.footer) val writer = new OutputWriter(output, config, skipContentCheck) { protected def writeModuleWithoutSourceMap(moduleID: ModuleID, force: Boolean): Option[ByteBuffer] = { val cache = printedModuleSetCache.getModuleCache(moduleID) - val changed = cache.update(emitterResult.body(moduleID)) + val printedTrees = emitterResult.body(moduleID) + + val changed = cache.update(printedTrees) if (force || changed || allChanged) { - printedModuleSetCache.incRewrittenModules() + rewrittenModules.incrementAndGet() val jsFileWriter = new ByteArrayWriter(sizeHintFor(cache.getPreviousFinalJSFileSize())) jsFileWriter.write(printedModuleSetCache.headerBytes) jsFileWriter.writeASCIIString("'use strict';\n") - for (printedTree <- cache.printedTrees) + for (printedTree <- printedTrees) jsFileWriter.write(printedTree.jsCode) jsFileWriter.write(printedModuleSetCache.footerBytes) @@ -99,10 +114,12 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) protected def writeModuleWithSourceMap(moduleID: ModuleID, force: Boolean): Option[(ByteBuffer, ByteBuffer)] = { val cache = printedModuleSetCache.getModuleCache(moduleID) - val changed = cache.update(emitterResult.body(moduleID)) + val printedTrees = emitterResult.body(moduleID) + + val changed = cache.update(printedTrees) if (force || changed || allChanged) { - printedModuleSetCache.incRewrittenModules() + rewrittenModules.incrementAndGet() val jsFileWriter = new ByteArrayWriter(sizeHintFor(cache.getPreviousFinalJSFileSize())) val sourceMapWriter = new ByteArrayWriter(sizeHintFor(cache.getPreviousFinalSourceMapSize())) @@ -120,7 +137,7 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) jsFileWriter.writeASCIIString("'use strict';\n") smWriter.nextLine() - for (printedTree <- cache.printedTrees) { + for (printedTree <- printedTrees) { jsFileWriter.write(printedTree.jsCode) smWriter.insertFragment(printedTree.sourceMapFragment) } @@ -145,9 +162,15 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) writer.write(moduleSet) }.andThen { case _ => printedModuleSetCache.cleanAfterRun() - printedModuleSetCache.logStats(logger) + logStats(logger) } } + + private def logStats(logger: Logger): Unit = { + // Message extracted in BasicLinkerBackendTest + logger.debug( + s"BasicBackend: total modules: $totalModules; re-written: ${rewrittenModules.get()}") + } } private object BasicLinkerBackend { @@ -161,20 +184,6 @@ private object BasicLinkerBackend { private val modules = new java.util.concurrent.ConcurrentHashMap[ModuleID, PrintedModuleCache] - private var totalModules = 0 - private val rewrittenModules = new java.util.concurrent.atomic.AtomicInteger(0) - - private var totalTopLevelTrees = 0 - private var recomputedTopLevelTrees = 0 - - def startRun(moduleSet: ModuleSet): Unit = { - totalModules = moduleSet.modules.size - rewrittenModules.set(0) - - totalTopLevelTrees = 0 - recomputedTopLevelTrees = 0 - } - def updateGlobal(header: String, footer: String): Boolean = { if (header == lastHeader && footer == lastFooter) { false @@ -193,61 +202,32 @@ private object BasicLinkerBackend { def headerNewLineCount: Int = _headerNewLineCountCache def getModuleCache(moduleID: ModuleID): PrintedModuleCache = { - val result = modules.computeIfAbsent(moduleID, { _ => - if (withSourceMaps) new PrintedModuleCacheWithSourceMaps - else new PrintedModuleCache - }) - + val result = modules.computeIfAbsent(moduleID, _ => new PrintedModuleCache) result.startRun() result } - def incRewrittenModules(): Unit = - rewrittenModules.incrementAndGet() - def cleanAfterRun(): Unit = { val iter = modules.entrySet().iterator() while (iter.hasNext()) { val moduleCache = iter.next().getValue() - if (moduleCache.cleanAfterRun()) { - totalTopLevelTrees += moduleCache.getTotalTopLevelTrees - recomputedTopLevelTrees += moduleCache.getRecomputedTopLevelTrees - } else { + if (!moduleCache.cleanAfterRun()) { iter.remove() } } } - - def logStats(logger: Logger): Unit = { - /* These messages are extracted in BasicLinkerBackendTest to assert that - * we do not invalidate anything in a no-op second run. - */ - logger.debug( - s"BasicBackend: total top-level trees: $totalTopLevelTrees; re-computed: $recomputedTopLevelTrees") - logger.debug( - s"BasicBackend: total modules: $totalModules; re-written: ${rewrittenModules.get()}") - } - } - - private final class PrintedTree(val jsCode: Array[Byte], val sourceMapFragment: SourceMapWriter.Fragment) { - var cachedUsed: Boolean = false } private sealed class PrintedModuleCache { private var cacheUsed = false private var changed = false - private var lastJSTrees: List[js.Tree] = Nil - private var printedTreesCache: List[PrintedTree] = Nil - private val cache = new java.util.IdentityHashMap[js.Tree, PrintedTree] + private var lastPrintedTrees: List[js.PrintedTree] = Nil private var previousFinalJSFileSize: Int = 0 private var previousFinalSourceMapSize: Int = 0 - private var recomputedTopLevelTrees = 0 - def startRun(): Unit = { cacheUsed = true - recomputedTopLevelTrees = 0 } def getPreviousFinalJSFileSize(): Int = previousFinalJSFileSize @@ -259,72 +239,51 @@ private object BasicLinkerBackend { previousFinalSourceMapSize = finalSourceMapSize } - def update(newJSTrees: List[js.Tree]): Boolean = { - val changed = !newJSTrees.corresponds(lastJSTrees)(_ eq _) + def update(newPrintedTrees: List[js.PrintedTree]): Boolean = { + val changed = !newPrintedTrees.corresponds(lastPrintedTrees)(_ eq _) this.changed = changed if (changed) { - printedTreesCache = newJSTrees.map(getOrComputePrintedTree(_)) - lastJSTrees = newJSTrees + lastPrintedTrees = newPrintedTrees } changed } - private def getOrComputePrintedTree(tree: js.Tree): PrintedTree = { - val result = cache.computeIfAbsent(tree, { (tree: js.Tree) => - recomputedTopLevelTrees += 1 - computePrintedTree(tree) - }) - - result.cachedUsed = true - result - } - - protected def computePrintedTree(tree: js.Tree): PrintedTree = { - val jsCodeWriter = new ByteArrayWriter() - val printer = new Printers.JSTreePrinter(jsCodeWriter) - - printer.printStat(tree) - - new PrintedTree(jsCodeWriter.toByteArray(), SourceMapWriter.Fragment.Empty) + def cleanAfterRun(): Boolean = { + val wasUsed = cacheUsed + cacheUsed = false + wasUsed } + } - def printedTrees: List[PrintedTree] = printedTreesCache + private object PostTransformerWithoutSourceMap extends Emitter.PostTransformer[js.PrintedTree] { + def transformStats(trees: List[js.Tree], indent: Int): List[js.PrintedTree] = { + if (trees.isEmpty) { + Nil // Fast path + } else { + val jsCodeWriter = new ByteArrayWriter() + val printer = new Printers.JSTreePrinter(jsCodeWriter, indent) - def cleanAfterRun(): Boolean = { - if (cacheUsed) { - cacheUsed = false - - if (changed) { - val iter = cache.entrySet().iterator() - while (iter.hasNext()) { - val printedTree = iter.next().getValue() - if (printedTree.cachedUsed) - printedTree.cachedUsed = false - else - iter.remove() - } - } + trees.map(printer.printStat(_)) - true - } else { - false + js.PrintedTree(jsCodeWriter.toByteArray(), SourceMapWriter.Fragment.Empty) :: Nil } } - - def getTotalTopLevelTrees: Int = lastJSTrees.size - def getRecomputedTopLevelTrees: Int = recomputedTopLevelTrees } - private final class PrintedModuleCacheWithSourceMaps extends PrintedModuleCache { - override protected def computePrintedTree(tree: js.Tree): PrintedTree = { - val jsCodeWriter = new ByteArrayWriter() - val smFragmentBuilder = new SourceMapWriter.FragmentBuilder() - val printer = new Printers.JSTreePrinterWithSourceMap(jsCodeWriter, smFragmentBuilder) + private object PostTransformerWithSourceMap extends Emitter.PostTransformer[js.PrintedTree] { + def transformStats(trees: List[js.Tree], indent: Int): List[js.PrintedTree] = { + if (trees.isEmpty) { + Nil // Fast path + } else { + val jsCodeWriter = new ByteArrayWriter() + val smFragmentBuilder = new SourceMapWriter.FragmentBuilder() + val printer = new Printers.JSTreePrinterWithSourceMap(jsCodeWriter, smFragmentBuilder, indent) - printer.printStat(tree) - smFragmentBuilder.complete() + trees.map(printer.printStat(_)) + smFragmentBuilder.complete() - new PrintedTree(jsCodeWriter.toByteArray(), smFragmentBuilder.result()) + js.PrintedTree(jsCodeWriter.toByteArray(), smFragmentBuilder.result()) :: Nil + } } } } 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 424b962989..aade6c4b8a 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 @@ -45,7 +45,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { def buildClass(className: ClassName, isJSClass: Boolean, jsClassCaptures: Option[List[ParamDef]], hasClassInitializer: Boolean, - superClass: Option[ClassIdent], storeJSSuperClass: Option[js.Tree], useESClass: Boolean, + superClass: Option[ClassIdent], storeJSSuperClass: List[js.Tree], useESClass: Boolean, members: List[js.Tree])( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { @@ -75,7 +75,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val createClassValueVar = genEmptyMutableLet(classValueIdent) val entireClassDefWithGlobals = if (useESClass) { - genJSSuperCtor(superClass, storeJSSuperClass.isDefined).map { jsSuperClass => + genJSSuperCtor(superClass, storeJSSuperClass.nonEmpty).map { jsSuperClass => List(classValueVar := js.ClassDef(Some(classValueIdent), Some(jsSuperClass), members)) } } else { @@ -86,7 +86,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { entireClassDef <- entireClassDefWithGlobals createStaticFields <- genCreateStaticFieldsOfJSClass(className) } yield { - storeJSSuperClass.toList ::: entireClassDef ::: createStaticFields + storeJSSuperClass ::: entireClassDef ::: createStaticFields } jsClassCaptures.fold { 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 97330e7ccf..290bb7f362 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 @@ -32,9 +32,9 @@ import PolyfillableBuiltin._ private[emitter] object CoreJSLib { - def build(sjsGen: SJSGen, moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge): WithGlobals[Lib] = { - new CoreJSLibBuilder(sjsGen)(moduleContext, globalKnowledge).build() + def build[E](sjsGen: SJSGen, postTransform: List[Tree] => E, moduleContext: ModuleContext, + globalKnowledge: GlobalKnowledge): WithGlobals[Lib[E]] = { + new CoreJSLibBuilder(sjsGen)(moduleContext, globalKnowledge).build(postTransform) } /** A fully built CoreJSLib @@ -52,10 +52,10 @@ private[emitter] object CoreJSLib { * @param initialization Things that depend on Scala.js generated classes. * These must have class definitions (but not static fields) available. */ - final class Lib private[CoreJSLib] ( - val preObjectDefinitions: List[Tree], - val postObjectDefinitions: List[Tree], - val initialization: List[Tree]) + final class Lib[E] private[CoreJSLib] ( + val preObjectDefinitions: E, + val postObjectDefinitions: E, + val initialization: E) private class CoreJSLibBuilder(sjsGen: SJSGen)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge) { @@ -115,9 +115,11 @@ private[emitter] object CoreJSLib { private val specializedArrayTypeRefs: List[NonArrayTypeRef] = ClassRef(ObjectClass) :: orderedPrimRefsWithoutVoid - def build(): WithGlobals[Lib] = { - val lib = new Lib(buildPreObjectDefinitions(), - buildPostObjectDefinitions(), buildInitializations()) + def build[E](postTransform: List[Tree] => E): WithGlobals[Lib[E]] = { + val lib = new Lib( + postTransform(buildPreObjectDefinitions()), + postTransform(buildPostObjectDefinitions()), + postTransform(buildInitializations())) WithGlobals(lib, trackedGlobalRefs) } 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 a5191cdf8c..6035764aca 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 @@ -33,7 +33,8 @@ import EmitterNames._ import GlobalRefUtils._ /** Emits a desugared JS tree to a builder */ -final class Emitter(config: Emitter.Config) { +final class Emitter[E >: Null <: js.Tree]( + config: Emitter.Config, postTransformer: Emitter.PostTransformer[E]) { import Emitter._ import config._ @@ -71,13 +72,16 @@ final class Emitter(config: Emitter.Config) { private[this] var statsClassesInvalidated: Int = 0 private[this] var statsMethodsReused: Int = 0 private[this] var statsMethodsInvalidated: Int = 0 + private[this] var statsPostTransforms: Int = 0 + private[this] var statsNestedPostTransforms: Int = 0 + private[this] var statsNestedPostTransformsAvoided: Int = 0 val symbolRequirements: SymbolRequirement = Emitter.symbolRequirements(config) val injectedIRFiles: Seq[IRFile] = PrivateLibHolder.files - def emit(moduleSet: ModuleSet, logger: Logger): Result = { + def emit(moduleSet: ModuleSet, logger: Logger): Result[E] = { val WithGlobals(body, globalRefs) = emitInternal(moduleSet, logger) moduleKind match { @@ -108,12 +112,15 @@ final class Emitter(config: Emitter.Config) { } private def emitInternal(moduleSet: ModuleSet, - logger: Logger): WithGlobals[Map[ModuleID, List[js.Tree]]] = { + logger: Logger): WithGlobals[Map[ModuleID, List[E]]] = { // Reset caching stats. statsClassesReused = 0 statsClassesInvalidated = 0 statsMethodsReused = 0 statsMethodsInvalidated = 0 + statsPostTransforms = 0 + statsNestedPostTransforms = 0 + statsNestedPostTransformsAvoided = 0 // Update GlobalKnowledge. val invalidateAll = knowledgeGuardian.update(moduleSet) @@ -128,13 +135,17 @@ final class Emitter(config: Emitter.Config) { try { emitAvoidGlobalClash(moduleSet, logger, secondAttempt = false) } finally { - // Report caching stats. + // Report caching stats (extracted in EmitterTest). logger.debug( s"Emitter: Class tree cache stats: reused: $statsClassesReused -- "+ s"invalidated: $statsClassesInvalidated") logger.debug( s"Emitter: Method tree cache stats: reused: $statsMethodsReused -- "+ s"invalidated: $statsMethodsInvalidated") + logger.debug( + s"Emitter: Post transforms: total: $statsPostTransforms -- " + + s"nested: $statsNestedPostTransforms -- " + + s"nested avoided: $statsNestedPostTransformsAvoided") // Inform caches about run completion. state.moduleCaches.filterInPlace((_, c) => c.cleanAfterRun()) @@ -142,6 +153,14 @@ final class Emitter(config: Emitter.Config) { } } + private def postTransform(trees: List[js.Tree], indent: Int): List[E] = { + statsPostTransforms += 1 + postTransformer.transformStats(trees, indent) + } + + private def postTransform(tree: js.Tree, indent: Int): List[E] = + postTransform(tree :: Nil, indent) + /** Emits all JavaScript code avoiding clashes with global refs. * * If, at the end of the process, the set of accessed dangerous globals has @@ -150,7 +169,7 @@ final class Emitter(config: Emitter.Config) { */ @tailrec private def emitAvoidGlobalClash(moduleSet: ModuleSet, - logger: Logger, secondAttempt: Boolean): WithGlobals[Map[ModuleID, List[js.Tree]]] = { + logger: Logger, secondAttempt: Boolean): WithGlobals[Map[ModuleID, List[E]]] = { val result = emitOnce(moduleSet, logger) val mentionedDangerousGlobalRefs = @@ -175,7 +194,7 @@ final class Emitter(config: Emitter.Config) { } private def emitOnce(moduleSet: ModuleSet, - logger: Logger): WithGlobals[Map[ModuleID, List[js.Tree]]] = { + logger: Logger): WithGlobals[Map[ModuleID, List[E]]] = { // Genreate classes first so we can measure time separately. val generatedClasses = logger.time("Emitter: Generate Classes") { moduleSet.modules.map { module => @@ -200,7 +219,7 @@ final class Emitter(config: Emitter.Config) { val moduleImports = extractWithGlobals { moduleCache.getOrComputeImports(module.externalDependencies, module.internalDependencies) { - genModuleImports(module) + genModuleImports(module).map(postTransform(_, 0)) } } @@ -210,7 +229,7 @@ final class Emitter(config: Emitter.Config) { */ moduleCache.getOrComputeTopLevelExports(module.topLevelExports) { classEmitter.genTopLevelExports(module.topLevelExports)( - moduleContext, moduleCache) + moduleContext, moduleCache).map(postTransform(_, 0)) } } @@ -220,7 +239,7 @@ final class Emitter(config: Emitter.Config) { WithGlobals.list(initializers.map { initializer => classEmitter.genModuleInitializer(initializer)( moduleContext, moduleCache) - }) + }).map(postTransform(_, 0)) } } @@ -241,7 +260,7 @@ final class Emitter(config: Emitter.Config) { * requires consistency between the Analyzer and the Emitter. As such, * it is crucial that we verify it. */ - val defTrees: List[js.Tree] = ( + val defTrees: List[E] = ( /* The definitions of the CoreJSLib that come before the definition * of `j.l.Object`. They depend on nothing else. */ @@ -357,7 +376,7 @@ final class Emitter(config: Emitter.Config) { } private def genClass(linkedClass: LinkedClass, - moduleContext: ModuleContext): GeneratedClass = { + moduleContext: ModuleContext): GeneratedClass[E] = { val className = linkedClass.className val classCache = classCaches.getOrElseUpdate( @@ -379,7 +398,7 @@ final class Emitter(config: Emitter.Config) { // Main part - val main = List.newBuilder[js.Tree] + val main = List.newBuilder[E] val (linkedInlineableInit, linkedMethods) = classEmitter.extractInlineableInit(linkedClass)(classCache) @@ -388,7 +407,7 @@ final class Emitter(config: Emitter.Config) { if (kind.isJSClass) { val fieldDefs = classTreeCache.privateJSFields.getOrElseUpdate { classEmitter.genCreatePrivateJSFieldDefsOfJSClass(className)( - moduleContext, classCache) + moduleContext, classCache).map(postTransform(_, 0)) } main ++= extractWithGlobals(fieldDefs) } @@ -407,8 +426,10 @@ final class Emitter(config: Emitter.Config) { val methodCache = classCache.getStaticLikeMethodCache(namespace, methodDef.methodName) - main ++= extractWithGlobals(methodCache.getOrElseUpdate(methodDef.version, - classEmitter.genStaticLikeMethod(className, methodDef)(moduleContext, methodCache))) + main ++= extractWithGlobals(methodCache.getOrElseUpdate(methodDef.version, { + classEmitter.genStaticLikeMethod(className, methodDef)(moduleContext, methodCache) + .map(postTransform(_, 0)) + })) } } @@ -447,11 +468,21 @@ final class Emitter(config: Emitter.Config) { (isJSClass || linkedClass.ancestors.contains(ThrowableClass)) } + val memberIndent = { + (if (isJSClass) 1 else 0) + // accessor function + (if (useESClass) 1 else 0) // nesting from class + } + val hasJSSuperClass = linkedClass.jsSuperClass.isDefined - val storeJSSuperClass = linkedClass.jsSuperClass.map { jsSuperClass => - extractWithGlobals(classTreeCache.storeJSSuperClass.getOrElseUpdate( - classEmitter.genStoreJSSuperClass(jsSuperClass)(moduleContext, classCache, linkedClass.pos))) + val storeJSSuperClass = if (hasJSSuperClass) { + extractWithGlobals(classTreeCache.storeJSSuperClass.getOrElseUpdate({ + val jsSuperClass = linkedClass.jsSuperClass.get + classEmitter.genStoreJSSuperClass(jsSuperClass)(moduleContext, classCache, linkedClass.pos) + .map(postTransform(_, 1)) + })) + } else { + Nil } // JS constructor @@ -478,7 +509,7 @@ final class Emitter(config: Emitter.Config) { hasJSSuperClass, // invalidated by class version useESClass, // invalidated by class version jsConstructorDef // part of ctor version - )(moduleContext, ctorCache, linkedClass.pos)) + )(moduleContext, ctorCache, linkedClass.pos).map(postTransform(_, memberIndent))) } else { val ctorVersion = linkedInlineableInit.fold { Version.combine(linkedClass.version) @@ -492,7 +523,7 @@ final class Emitter(config: Emitter.Config) { linkedClass.superClass, // invalidated by class version useESClass, // invalidated by class version, linkedInlineableInit // part of ctor version - )(moduleContext, ctorCache, linkedClass.pos)) + )(moduleContext, ctorCache, linkedClass.pos).map(postTransform(_, memberIndent))) } } @@ -546,7 +577,7 @@ final class Emitter(config: Emitter.Config) { isJSClass, // invalidated by isJSClassVersion useESClass, // invalidated by isJSClassVersion method // invalidated by method.version - )(moduleContext, methodCache)) + )(moduleContext, methodCache).map(postTransform(_, memberIndent))) } // Exported Members @@ -561,7 +592,7 @@ final class Emitter(config: Emitter.Config) { isJSClass, // invalidated by isJSClassVersion useESClass, // invalidated by isJSClassVersion member // invalidated by version - )(moduleContext, memberCache)) + )(moduleContext, memberCache).map(postTransform(_, memberIndent))) } val hasClassInitializer: Boolean = { @@ -578,8 +609,9 @@ final class Emitter(config: Emitter.Config) { memberMethodsWithGlobals, exportedMembersWithGlobals, { for { ctor <- ctorWithGlobals - memberMethods <- WithGlobals.list(memberMethodsWithGlobals) - exportedMembers <- WithGlobals.list(exportedMembersWithGlobals) + memberMethods <- WithGlobals.flatten(memberMethodsWithGlobals) + exportedMembers <- WithGlobals.flatten(exportedMembersWithGlobals) + allMembers = ctor ::: memberMethods ::: exportedMembers clazz <- classEmitter.buildClass( className, // invalidated by overall class cache (part of ancestors) isJSClass, // invalidated by class version @@ -588,10 +620,17 @@ final class Emitter(config: Emitter.Config) { linkedClass.superClass, // invalidated by class version storeJSSuperClass, // invalidated by class version useESClass, // invalidated by class version (depends on kind, config and ancestry only) - ctor ::: memberMethods ::: exportedMembers.flatten // all 3 invalidated directly + allMembers // invalidated directly )(moduleContext, fullClassCache, linkedClass.pos) // pos invalidated by class version } yield { - clazz + // Avoid a nested post transform if we just got the original members back. + if (clazz eq allMembers) { + statsNestedPostTransformsAvoided += 1 + allMembers + } else { + statsNestedPostTransforms += 1 + postTransform(clazz, 0) + } } }) } @@ -614,8 +653,10 @@ final class Emitter(config: Emitter.Config) { */ if (classEmitter.needInstanceTests(linkedClass)(classCache)) { - main ++= extractWithGlobals(classTreeCache.instanceTests.getOrElseUpdate( - classEmitter.genInstanceTests(className, kind)(moduleContext, classCache, linkedClass.pos))) + main ++= extractWithGlobals(classTreeCache.instanceTests.getOrElseUpdate({ + classEmitter.genInstanceTests(className, kind)(moduleContext, classCache, linkedClass.pos) + .map(postTransform(_, 0)) + })) } if (linkedClass.hasRuntimeTypeInfo) { @@ -626,18 +667,22 @@ final class Emitter(config: Emitter.Config) { linkedClass.superClass, // invalidated by class version linkedClass.ancestors, // invalidated by overall class cache (identity) linkedClass.jsNativeLoadSpec // invalidated by class version - )(moduleContext, classCache, linkedClass.pos))) + )(moduleContext, classCache, linkedClass.pos).map(postTransform(_, 0)))) } if (linkedClass.hasInstances && kind.isClass && linkedClass.hasRuntimeTypeInfo) { - main += classTreeCache.setTypeData.getOrElseUpdate( - classEmitter.genSetTypeData(className)(moduleContext, classCache, linkedClass.pos)) + main ++= classTreeCache.setTypeData.getOrElseUpdate({ + val tree = classEmitter.genSetTypeData(className)(moduleContext, classCache, linkedClass.pos) + postTransform(tree, 0) + }) } } if (linkedClass.kind.hasModuleAccessor && linkedClass.hasInstances) { - main ++= extractWithGlobals(classTreeCache.moduleAccessor.getOrElseUpdate( - classEmitter.genModuleAccessor(className, isJSClass)(moduleContext, classCache, linkedClass.pos))) + main ++= extractWithGlobals(classTreeCache.moduleAccessor.getOrElseUpdate({ + classEmitter.genModuleAccessor(className, isJSClass)(moduleContext, classCache, linkedClass.pos) + .map(postTransform(_, 0)) + })) } // Static fields @@ -645,15 +690,19 @@ final class Emitter(config: Emitter.Config) { val staticFields = if (linkedClass.kind.isJSType) { Nil } else { - extractWithGlobals(classTreeCache.staticFields.getOrElseUpdate( - classEmitter.genCreateStaticFieldsOfScalaClass(className)(moduleContext, classCache))) + extractWithGlobals(classTreeCache.staticFields.getOrElseUpdate({ + classEmitter.genCreateStaticFieldsOfScalaClass(className)(moduleContext, classCache) + .map(postTransform(_, 0)) + })) } // Static initialization val staticInitialization = if (classEmitter.needStaticInitialization(linkedClass)) { - classTreeCache.staticInitialization.getOrElseUpdate( - classEmitter.genStaticInitialization(className)(moduleContext, classCache, linkedClass.pos)) + classTreeCache.staticInitialization.getOrElseUpdate({ + val tree = classEmitter.genStaticInitialization(className)(moduleContext, classCache, linkedClass.pos) + postTransform(tree, 0) + }) } else { Nil } @@ -674,14 +723,14 @@ final class Emitter(config: Emitter.Config) { private final class ModuleCache extends knowledgeGuardian.KnowledgeAccessor { private[this] var _cacheUsed: Boolean = false - private[this] var _importsCache: WithGlobals[List[js.Tree]] = WithGlobals.nil + private[this] var _importsCache: WithGlobals[List[E]] = WithGlobals.nil private[this] var _lastExternalDependencies: Set[String] = Set.empty private[this] var _lastInternalDependencies: Set[ModuleID] = Set.empty - private[this] var _topLevelExportsCache: WithGlobals[List[js.Tree]] = WithGlobals.nil + private[this] var _topLevelExportsCache: WithGlobals[List[E]] = WithGlobals.nil private[this] var _lastTopLevelExports: List[LinkedTopLevelExport] = Nil - private[this] var _initializersCache: WithGlobals[List[js.Tree]] = WithGlobals.nil + private[this] var _initializersCache: WithGlobals[List[E]] = WithGlobals.nil private[this] var _lastInitializers: List[ModuleInitializer.Initializer] = Nil override def invalidate(): Unit = { @@ -702,7 +751,7 @@ final class Emitter(config: Emitter.Config) { } def getOrComputeImports(externalDependencies: Set[String], internalDependencies: Set[ModuleID])( - compute: => WithGlobals[List[js.Tree]]): WithGlobals[List[js.Tree]] = { + compute: => WithGlobals[List[E]]): WithGlobals[List[E]] = { _cacheUsed = true @@ -715,7 +764,7 @@ final class Emitter(config: Emitter.Config) { } def getOrComputeTopLevelExports(topLevelExports: List[LinkedTopLevelExport])( - compute: => WithGlobals[List[js.Tree]]): WithGlobals[List[js.Tree]] = { + compute: => WithGlobals[List[E]]): WithGlobals[List[E]] = { _cacheUsed = true @@ -754,7 +803,7 @@ final class Emitter(config: Emitter.Config) { } def getOrComputeInitializers(initializers: List[ModuleInitializer.Initializer])( - compute: => WithGlobals[List[js.Tree]]): WithGlobals[List[js.Tree]] = { + compute: => WithGlobals[List[E]]): WithGlobals[List[E]] = { _cacheUsed = true @@ -773,20 +822,20 @@ final class Emitter(config: Emitter.Config) { } private final class ClassCache extends knowledgeGuardian.KnowledgeAccessor { - private[this] var _cache: DesugaredClassCache = null + private[this] var _cache: DesugaredClassCache[List[E]] = null private[this] var _lastVersion: Version = Version.Unversioned private[this] var _cacheUsed = false private[this] val _methodCaches = - Array.fill(MemberNamespace.Count)(mutable.Map.empty[MethodName, MethodCache[List[js.Tree]]]) + Array.fill(MemberNamespace.Count)(mutable.Map.empty[MethodName, MethodCache[List[E]]]) private[this] val _memberMethodCache = - mutable.Map.empty[MethodName, MethodCache[js.Tree]] + mutable.Map.empty[MethodName, MethodCache[List[E]]] - private[this] var _constructorCache: Option[MethodCache[List[js.Tree]]] = None + private[this] var _constructorCache: Option[MethodCache[List[E]]] = None private[this] val _exportedMembersCache = - mutable.Map.empty[Int, MethodCache[List[js.Tree]]] + mutable.Map.empty[Int, MethodCache[List[E]]] private[this] var _fullClassCache: Option[FullClassCache] = None @@ -807,12 +856,12 @@ final class Emitter(config: Emitter.Config) { _fullClassCache.foreach(_.startRun()) } - def getCache(version: Version): DesugaredClassCache = { + def getCache(version: Version): DesugaredClassCache[List[E]] = { if (_cache == null || !_lastVersion.sameVersion(version)) { invalidate() statsClassesInvalidated += 1 _lastVersion = version - _cache = new DesugaredClassCache + _cache = new DesugaredClassCache[List[E]] } else { statsClassesReused += 1 } @@ -821,25 +870,25 @@ final class Emitter(config: Emitter.Config) { } def getMemberMethodCache( - methodName: MethodName): MethodCache[js.Tree] = { + methodName: MethodName): MethodCache[List[E]] = { _memberMethodCache.getOrElseUpdate(methodName, new MethodCache) } def getStaticLikeMethodCache(namespace: MemberNamespace, - methodName: MethodName): MethodCache[List[js.Tree]] = { + methodName: MethodName): MethodCache[List[E]] = { _methodCaches(namespace.ordinal) .getOrElseUpdate(methodName, new MethodCache) } - def getConstructorCache(): MethodCache[List[js.Tree]] = { + def getConstructorCache(): MethodCache[List[E]] = { _constructorCache.getOrElse { - val cache = new MethodCache[List[js.Tree]] + val cache = new MethodCache[List[E]] _constructorCache = Some(cache) cache } } - def getExportedMemberCache(idx: Int): MethodCache[List[js.Tree]] = + def getExportedMemberCache(idx: Int): MethodCache[List[E]] = _exportedMembersCache.getOrElseUpdate(idx, new MethodCache) def getFullClassCache(): FullClassCache = { @@ -905,11 +954,11 @@ final class Emitter(config: Emitter.Config) { } private class FullClassCache extends knowledgeGuardian.KnowledgeAccessor { - private[this] var _tree: WithGlobals[List[js.Tree]] = null + private[this] var _tree: WithGlobals[List[E]] = null private[this] var _lastVersion: Version = Version.Unversioned - private[this] var _lastCtor: WithGlobals[List[js.Tree]] = null - private[this] var _lastMemberMethods: List[WithGlobals[js.Tree]] = null - private[this] var _lastExportedMembers: List[WithGlobals[List[js.Tree]]] = null + private[this] var _lastCtor: WithGlobals[List[E]] = null + private[this] var _lastMemberMethods: List[WithGlobals[List[E]]] = null + private[this] var _lastExportedMembers: List[WithGlobals[List[E]]] = null private[this] var _cacheUsed = false override def invalidate(): Unit = { @@ -923,9 +972,9 @@ final class Emitter(config: Emitter.Config) { def startRun(): Unit = _cacheUsed = false - def getOrElseUpdate(version: Version, ctor: WithGlobals[List[js.Tree]], - memberMethods: List[WithGlobals[js.Tree]], exportedMembers: List[WithGlobals[List[js.Tree]]], - compute: => WithGlobals[List[js.Tree]]): WithGlobals[List[js.Tree]] = { + def getOrElseUpdate(version: Version, ctor: WithGlobals[List[E]], + memberMethods: List[WithGlobals[List[E]]], exportedMembers: List[WithGlobals[List[E]]], + compute: => WithGlobals[List[E]]): WithGlobals[List[E]] = { @tailrec def allSame[A <: AnyRef](xs: List[A], ys: List[A]): Boolean = { @@ -960,11 +1009,11 @@ final class Emitter(config: Emitter.Config) { private class CoreJSLibCache extends knowledgeGuardian.KnowledgeAccessor { private[this] var _lastModuleContext: ModuleContext = _ - private[this] var _lib: WithGlobals[CoreJSLib.Lib] = _ + private[this] var _lib: WithGlobals[CoreJSLib.Lib[List[E]]] = _ - def build(moduleContext: ModuleContext): WithGlobals[CoreJSLib.Lib] = { + def build(moduleContext: ModuleContext): WithGlobals[CoreJSLib.Lib[List[E]]] = { if (_lib == null || _lastModuleContext != moduleContext) { - _lib = CoreJSLib.build(sjsGen, moduleContext, this) + _lib = CoreJSLib.build(sjsGen, postTransform(_, 0), moduleContext, this) _lastModuleContext = moduleContext } _lib @@ -979,9 +1028,9 @@ final class Emitter(config: Emitter.Config) { object Emitter { /** Result of an emitter run. */ - final class Result private[Emitter]( + final class Result[E] private[Emitter]( val header: String, - val body: Map[ModuleID, List[js.Tree]], + val body: Map[ModuleID, List[E]], val footer: String, val topLevelVarDecls: List[String], val globalRefs: Set[String] @@ -1052,22 +1101,26 @@ object Emitter { new Config(coreSpec.semantics, coreSpec.moduleKind, coreSpec.esFeatures) } - private final class DesugaredClassCache { - val privateJSFields = new OneTimeCache[WithGlobals[List[js.Tree]]] - val storeJSSuperClass = new OneTimeCache[WithGlobals[js.Tree]] - val instanceTests = new OneTimeCache[WithGlobals[List[js.Tree]]] - val typeData = new OneTimeCache[WithGlobals[List[js.Tree]]] - val setTypeData = new OneTimeCache[js.Tree] - val moduleAccessor = new OneTimeCache[WithGlobals[List[js.Tree]]] - val staticInitialization = new OneTimeCache[List[js.Tree]] - val staticFields = new OneTimeCache[WithGlobals[List[js.Tree]]] + trait PostTransformer[E] { + def transformStats(trees: List[js.Tree], indent: Int): List[E] + } + + private final class DesugaredClassCache[E >: Null] { + val privateJSFields = new OneTimeCache[WithGlobals[E]] + val storeJSSuperClass = new OneTimeCache[WithGlobals[E]] + val instanceTests = new OneTimeCache[WithGlobals[E]] + val typeData = new OneTimeCache[WithGlobals[E]] + val setTypeData = new OneTimeCache[E] + val moduleAccessor = new OneTimeCache[WithGlobals[E]] + val staticInitialization = new OneTimeCache[E] + val staticFields = new OneTimeCache[WithGlobals[E]] } - private final class GeneratedClass( + private final class GeneratedClass[E]( val className: ClassName, - val main: List[js.Tree], - val staticFields: List[js.Tree], - val staticInitialization: List[js.Tree], + val main: List[E], + val staticFields: List[E], + val staticInitialization: List[E], val trackedGlobalRefs: Set[String] ) 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 4d675d4dec..a6d632a1cd 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 @@ -12,6 +12,8 @@ package org.scalajs.linker.backend.javascript +import java.nio.charset.StandardCharsets + import scala.annotation.switch // Unimport default print and println to avoid invoking them by mistake @@ -31,10 +33,10 @@ import Trees._ object Printers { private val ReusableIndentArray = Array.fill(128)(' '.toByte) - class JSTreePrinter(protected val out: ByteArrayWriter) { + class JSTreePrinter(protected val out: ByteArrayWriter, initIndent: Int = 0) { private final val IndentStep = 2 - private var indentMargin = 0 + private var indentMargin = initIndent * IndentStep private var indentArray = ReusableIndentArray private def indent(): Unit = indentMargin += IndentStep @@ -117,10 +119,15 @@ object Printers { printRow(args, '(', ')') /** Prints a stat including leading indent and trailing newline. */ - final def printStat(tree: Tree): Unit = { - printIndent() - printTree(tree, isStat = true) - println() + final def printStat(tree: Tree): Unit = tree match { + case tree: PrintedTree => + // PrintedTree already contains indent and trailing newline. + print(tree) + + case _ => + printIndent() + printTree(tree, isStat = true) + println() } private def print(tree: Tree): Unit = @@ -750,6 +757,9 @@ object Printers { print("]") } + protected def print(printedTree: PrintedTree): Unit = + out.write(printedTree.jsCode) + private def print(exportName: ExportName): Unit = printEscapeJS(exportName.name) @@ -762,7 +772,8 @@ object Printers { } class JSTreePrinterWithSourceMap(_out: ByteArrayWriter, - sourceMap: SourceMapWriter.Builder) extends JSTreePrinter(_out) { + sourceMap: SourceMapWriter.Builder, initIndent: Int) + extends JSTreePrinter(_out, initIndent) { private var column = 0 @@ -788,6 +799,11 @@ object Printers { sourceMap.endNode(column) } + override protected def print(printedTree: PrintedTree): Unit = { + super.print(printedTree) + sourceMap.insertFragment(printedTree.sourceMapFragment) + } + override protected def println(): Unit = { super.println() sourceMap.nextLine() 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 efcf98e609..ec5b72e850 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 @@ -499,4 +499,22 @@ object Trees { from: StringLiteral)( implicit val pos: Position) extends Tree + + /** An already printed tree. + * + * This is a special purpose node to store partially transformed trees. + * + * A cleaner abstraction would be to have something like ir.Tree.Transient + * (for different output formats), but for now, we do not need this. + */ + sealed case class PrintedTree(jsCode: Array[Byte], + sourceMapFragment: SourceMapWriter.Fragment) extends Tree { + val pos: Position = Position.NoPosition + + override def show: String = new String(jsCode, StandardCharsets.UTF_8) + } + + object PrintedTree { + def empty: PrintedTree = PrintedTree(Array(), SourceMapWriter.Fragment.Empty) + } } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/BasicLinkerBackendTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/BasicLinkerBackendTest.scala index 1ce36b9153..2da5fe2324 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/BasicLinkerBackendTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/BasicLinkerBackendTest.scala @@ -20,6 +20,7 @@ import org.junit.Test import org.junit.Assert._ import org.scalajs.ir.Trees._ +import org.scalajs.ir.Version import org.scalajs.junit.async._ @@ -33,17 +34,17 @@ import org.scalajs.logging._ class BasicLinkerBackendTest { import scala.concurrent.ExecutionContext.Implicits.global - private val BackendInvalidatedTopLevelTreesStatsMessage = - raw"""BasicBackend: total top-level trees: (\d+); re-computed: (\d+)""".r + private val BackendInvalidatedPrintedTreesStatsMessage = + raw"""BasicBackend: total top-level printed trees: (\d+); re-computed: (\d+)""".r private val BackendInvalidatedModulesStatsMessage = raw"""BasicBackend: total modules: (\d+); re-written: (\d+)""".r /** Makes sure that linking a "substantial" program (using `println`) twice - * does not invalidate any top-level tree nor module in the second run. + * does not invalidate any module in the second run. */ @Test - def noInvalidatedTopLevelTreeOrModuleInSecondRun(): AsyncResult = await { + def noInvalidatedModuleInSecondRun(): AsyncResult = await { import ModuleSplitStyle._ val classDefs = List( @@ -60,7 +61,7 @@ class BasicLinkerBackendTest { .withModuleSplitStyle(splitStyle) val linker = StandardImpl.linker(config) - val classDefsFiles = classDefs.map(MemClassDefIRFile(_)) + val classDefsFiles = classDefs.map(MemClassDefIRFile(_, Version.fromInt(0))) val initializers = MainTestModuleInitializers val outputDir = MemOutputDirectory() @@ -74,25 +75,6 @@ class BasicLinkerBackendTest { val lines1 = logger1.allLogLines val lines2 = logger2.allLogLines - // Top-level trees - - val Seq(totalTrees1, recomputedTrees1) = - lines1.assertContainsMatch(BackendInvalidatedTopLevelTreesStatsMessage).map(_.toInt) - - val Seq(totalTrees2, recomputedTrees2) = - lines2.assertContainsMatch(BackendInvalidatedTopLevelTreesStatsMessage).map(_.toInt) - - // At the time of writing this test, totalTrees1 reports 382 trees - assertTrue( - s"Not enough total top-level trees (got $totalTrees1); extraction must have gone wrong", - totalTrees1 > 300) - - assertEquals("First run must invalidate every top-level tree", totalTrees1, recomputedTrees1) - assertEquals("Second run must have the same total top-level trees as first run", totalTrees1, totalTrees2) - assertEquals("Second run must not invalidate any top-level tree", 0, recomputedTrees2) - - // Modules - val Seq(totalModules1, rewrittenModules1) = lines1.assertContainsMatch(BackendInvalidatedModulesStatsMessage).map(_.toInt) 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 17512130bc..935c2a57ae 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala @@ -20,6 +20,7 @@ import org.junit.Test import org.junit.Assert._ import org.scalajs.ir.Trees._ +import org.scalajs.ir.Version import org.scalajs.junit.async._ @@ -128,6 +129,99 @@ class EmitterTest { logger.allLogLines.assertContains(EmitterSetOfDangerousGlobalRefsChangedMessage) } } + + private val EmitterClassTreeCacheStatsMessage = + raw"""Emitter: Class tree cache stats: reused: (\d+) -- invalidated: (\d+)""".r + + private val EmitterMethodTreeCacheStatsMessage = + raw"""Emitter: Method tree cache stats: reused: (\d+) -- invalidated: (\d+)""".r + + private val EmitterPostTransformStatsMessage = + raw"""Emitter: Post transforms: total: (\d+) -- nested: (\d+) -- nested avoided: (\d+)""".r + + /** Makes sure that linking a "substantial" program (using `println`) twice + * does not invalidate any cache or top-level tree in the second run. + */ + @Test + def noInvalidatedCacheOrTopLevelTreeInSecondRun(): AsyncResult = await { + val classDefs = List( + mainTestClassDef(systemOutPrintln(str("Hello world!"))) + ) + + val logger1 = new CapturingLogger + val logger2 = new CapturingLogger + + val config = StandardConfig() + .withCheckIR(true) + .withModuleKind(ModuleKind.ESModule) + + val linker = StandardImpl.linker(config) + val classDefsFiles = classDefs.map(MemClassDefIRFile(_, Version.fromInt(0))) + + val initializers = MainTestModuleInitializers + val outputDir = MemOutputDirectory() + + for { + javalib <- TestIRRepo.javalib + allIRFiles = javalib ++ classDefsFiles + _ <- linker.link(allIRFiles, initializers, outputDir, logger1) + _ <- linker.link(allIRFiles, initializers, outputDir, logger2) + } yield { + val lines1 = logger1.allLogLines + val lines2 = logger2.allLogLines + + // Class tree caches + + val Seq(classCacheReused1, classCacheInvalidated1) = + lines1.assertContainsMatch(EmitterClassTreeCacheStatsMessage).map(_.toInt) + + val Seq(classCacheReused2, classCacheInvalidated2) = + lines2.assertContainsMatch(EmitterClassTreeCacheStatsMessage).map(_.toInt) + + // At the time of writing this test, classCacheInvalidated1 reports 47 + assertTrue( + s"Not enough invalidated class caches (got $classCacheInvalidated1); extraction must have gone wrong", + classCacheInvalidated1 > 40) + + assertEquals("First run must not reuse any class cache", 0, classCacheReused1) + + assertEquals("Second run must reuse all class caches", classCacheReused2, classCacheInvalidated1) + assertEquals("Second run must not invalidate any class cache", 0, classCacheInvalidated2) + + // Method tree caches + + val Seq(methodCacheReused1, methodCacheInvalidated1) = + lines1.assertContainsMatch(EmitterMethodTreeCacheStatsMessage).map(_.toInt) + + val Seq(methodCacheReused2, methodCacheInvalidated2) = + lines2.assertContainsMatch(EmitterMethodTreeCacheStatsMessage).map(_.toInt) + + // At the time of writing this test, methodCacheInvalidated1 reports 107 + assertTrue( + s"Not enough invalidated method caches (got $methodCacheInvalidated1); extraction must have gone wrong", + methodCacheInvalidated1 > 100) + + assertEquals("First run must not reuse any method cache", 0, methodCacheReused1) + + assertEquals("Second run must reuse all method caches", methodCacheReused2, methodCacheInvalidated1) + assertEquals("Second run must not invalidate any method cache", 0, methodCacheInvalidated2) + + // Post transforms + + val Seq(postTransforms1, _, _) = + lines1.assertContainsMatch(EmitterPostTransformStatsMessage).map(_.toInt) + + val Seq(postTransforms2, _, _) = + lines2.assertContainsMatch(EmitterPostTransformStatsMessage).map(_.toInt) + + // At the time of writing this test, postTransformsTotal1 reports 216 + assertTrue( + s"Not enough post transforms (got $postTransforms1); extraction must have gone wrong", + postTransforms1 > 200) + + assertEquals("Second run must not have any post transforms", 0, postTransforms2) + } + } } object EmitterTest { 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 86c9215f02..ba4848f668 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 @@ -14,7 +14,7 @@ package org.scalajs.linker.backend.javascript import scala.language.implicitConversions -import java.nio.charset.StandardCharsets +import java.nio.charset.StandardCharsets.UTF_8 import org.junit.Test import org.junit.Assert._ @@ -35,7 +35,7 @@ class PrintersTest { val printer = new Printers.JSTreePrinter(out) printer.printStat(tree) assertEquals(expected.stripMargin.trim + "\n", - new String(out.toByteArray(), StandardCharsets.UTF_8)) + new String(out.toByteArray(), UTF_8)) } @Test def printFunctionDef(): Unit = { @@ -158,4 +158,24 @@ class PrintersTest { If(BooleanLiteral(true), IntLiteral(2), IntLiteral(3))) ) } + + @Test def showPrintedTree(): Unit = { + val tree = PrintedTree("test".getBytes(UTF_8), SourceMapWriter.Fragment.Empty) + + assertEquals("test", tree.show) + } + + @Test def showNestedPrintedTree(): Unit = { + val tree = PrintedTree(" test\n".getBytes(UTF_8), SourceMapWriter.Fragment.Empty) + + val str = While(BooleanLiteral(false), tree).show + assertEquals( + """ + |while (false) { + | test + |} + """.stripMargin.trim, + str + ) + } } From 5c56042c11adc6df0f830c71d35b053864bc0b66 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 17 Dec 2023 18:59:07 +0100 Subject: [PATCH 20/65] Track in Emitter whether a module changed in an incremental run In the next commit, we want to avoid caching entire classes because of the memory cost. However, the BasicLinkerBackend relies on the identity of the generated trees to detect changes: Since that identity will change if we stop caching them, we need to provide an explicit "changed" signal. --- .../closure/ClosureLinkerBackend.scala | 3 +- .../linker/backend/BasicLinkerBackend.scala | 19 +--- .../linker/backend/emitter/Emitter.scala | 98 ++++++++++++------- 3 files changed, 68 insertions(+), 52 deletions(-) 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 1f532767e2..64160204ac 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 @@ -106,7 +106,8 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) sjsModule <- moduleSet.modules.headOption } yield { val closureChunk = logger.time("Closure: Create trees)") { - buildChunk(emitterResult.body(sjsModule.id)) + val (trees, _) = emitterResult.body(sjsModule.id) + buildChunk(trees) } logger.time("Closure: Compiler pass") { 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 4faef57c0a..e07e31597b 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 @@ -88,9 +88,7 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) val writer = new OutputWriter(output, config, skipContentCheck) { protected def writeModuleWithoutSourceMap(moduleID: ModuleID, force: Boolean): Option[ByteBuffer] = { val cache = printedModuleSetCache.getModuleCache(moduleID) - val printedTrees = emitterResult.body(moduleID) - - val changed = cache.update(printedTrees) + val (printedTrees, changed) = emitterResult.body(moduleID) if (force || changed || allChanged) { rewrittenModules.incrementAndGet() @@ -114,9 +112,7 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) protected def writeModuleWithSourceMap(moduleID: ModuleID, force: Boolean): Option[(ByteBuffer, ByteBuffer)] = { val cache = printedModuleSetCache.getModuleCache(moduleID) - val printedTrees = emitterResult.body(moduleID) - - val changed = cache.update(printedTrees) + val (printedTrees, changed) = emitterResult.body(moduleID) if (force || changed || allChanged) { rewrittenModules.incrementAndGet() @@ -220,8 +216,6 @@ private object BasicLinkerBackend { private sealed class PrintedModuleCache { private var cacheUsed = false - private var changed = false - private var lastPrintedTrees: List[js.PrintedTree] = Nil private var previousFinalJSFileSize: Int = 0 private var previousFinalSourceMapSize: Int = 0 @@ -239,15 +233,6 @@ private object BasicLinkerBackend { previousFinalSourceMapSize = finalSourceMapSize } - def update(newPrintedTrees: List[js.PrintedTree]): Boolean = { - val changed = !newPrintedTrees.corresponds(lastPrintedTrees)(_ eq _) - this.changed = changed - if (changed) { - lastPrintedTrees = newPrintedTrees - } - changed - } - def cleanAfterRun(): Boolean = { val wasUsed = cacheUsed cacheUsed = false 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 6035764aca..8aafe7b745 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 @@ -112,7 +112,7 @@ final class Emitter[E >: Null <: js.Tree]( } private def emitInternal(moduleSet: ModuleSet, - logger: Logger): WithGlobals[Map[ModuleID, List[E]]] = { + logger: Logger): WithGlobals[Map[ModuleID, (List[E], Boolean)]] = { // Reset caching stats. statsClassesReused = 0 statsClassesInvalidated = 0 @@ -169,7 +169,7 @@ final class Emitter[E >: Null <: js.Tree]( */ @tailrec private def emitAvoidGlobalClash(moduleSet: ModuleSet, - logger: Logger, secondAttempt: Boolean): WithGlobals[Map[ModuleID, List[E]]] = { + logger: Logger, secondAttempt: Boolean): WithGlobals[Map[ModuleID, (List[E], Boolean)]] = { val result = emitOnce(moduleSet, logger) val mentionedDangerousGlobalRefs = @@ -194,7 +194,7 @@ final class Emitter[E >: Null <: js.Tree]( } private def emitOnce(moduleSet: ModuleSet, - logger: Logger): WithGlobals[Map[ModuleID, List[E]]] = { + logger: Logger): WithGlobals[Map[ModuleID, (List[E], Boolean)]] = { // Genreate classes first so we can measure time separately. val generatedClasses = logger.time("Emitter: Generate Classes") { moduleSet.modules.map { module => @@ -212,18 +212,26 @@ final class Emitter[E >: Null <: js.Tree]( val moduleTrees = logger.time("Emitter: Write trees") { moduleSet.modules.map { module => + var changed = false + def extractChangedAndWithGlobals[T](x: (WithGlobals[T], Boolean)): T = { + changed ||= x._2 + extractWithGlobals(x._1) + } + val moduleContext = ModuleContext.fromModule(module) val moduleCache = state.moduleCaches.getOrElseUpdate(module.id, new ModuleCache) val moduleClasses = generatedClasses(module.id) - val moduleImports = extractWithGlobals { + changed ||= moduleClasses.exists(_.changed) + + val moduleImports = extractChangedAndWithGlobals { moduleCache.getOrComputeImports(module.externalDependencies, module.internalDependencies) { genModuleImports(module).map(postTransform(_, 0)) } } - val topLevelExports = extractWithGlobals { + val topLevelExports = extractChangedAndWithGlobals { /* We cache top level exports all together, rather than individually, * since typically there are few. */ @@ -233,7 +241,7 @@ final class Emitter[E >: Null <: js.Tree]( } } - val moduleInitializers = extractWithGlobals { + val moduleInitializers = extractChangedAndWithGlobals { val initializers = module.initializers.toList moduleCache.getOrComputeInitializers(initializers) { WithGlobals.list(initializers.map { initializer => @@ -324,7 +332,7 @@ final class Emitter[E >: Null <: js.Tree]( trackedGlobalRefs = unionPreserveEmpty(trackedGlobalRefs, genClass.trackedGlobalRefs) } - module.id -> allTrees + module.id -> (allTrees, changed) } } @@ -382,8 +390,14 @@ final class Emitter[E >: Null <: js.Tree]( val classCache = classCaches.getOrElseUpdate( new ClassID(linkedClass.ancestors, moduleContext), new ClassCache) + var changed = false + def extractChanged[T](x: (T, Boolean)): T = { + changed ||= x._2 + x._1 + } + val classTreeCache = - classCache.getCache(linkedClass.version) + extractChanged(classCache.getCache(linkedClass.version)) val kind = linkedClass.kind @@ -396,6 +410,9 @@ final class Emitter[E >: Null <: js.Tree]( withGlobals.value } + def extractWithGlobalsAndChanged[T](x: (WithGlobals[T], Boolean)): T = + extractWithGlobals(extractChanged(x)) + // Main part val main = List.newBuilder[E] @@ -426,7 +443,7 @@ final class Emitter[E >: Null <: js.Tree]( val methodCache = classCache.getStaticLikeMethodCache(namespace, methodDef.methodName) - main ++= extractWithGlobals(methodCache.getOrElseUpdate(methodDef.version, { + main ++= extractWithGlobalsAndChanged(methodCache.getOrElseUpdate(methodDef.version, { classEmitter.genStaticLikeMethod(className, methodDef)(moduleContext, methodCache) .map(postTransform(_, 0)) })) @@ -486,7 +503,7 @@ final class Emitter[E >: Null <: js.Tree]( } // JS constructor - val ctorWithGlobals = { + val ctorWithGlobals = extractChanged { /* The constructor depends both on the class version, and the version * of the inlineable init, if there is one. * @@ -571,13 +588,13 @@ final class Emitter[E >: Null <: js.Tree]( classCache.getMemberMethodCache(method.methodName) val version = Version.combine(isJSClassVersion, method.version) - methodCache.getOrElseUpdate(version, + extractChanged(methodCache.getOrElseUpdate(version, classEmitter.genMemberMethod( className, // invalidated by overall class cache isJSClass, // invalidated by isJSClassVersion useESClass, // invalidated by isJSClassVersion method // invalidated by method.version - )(moduleContext, methodCache).map(postTransform(_, memberIndent))) + )(moduleContext, methodCache).map(postTransform(_, memberIndent)))) } // Exported Members @@ -586,13 +603,13 @@ final class Emitter[E >: Null <: js.Tree]( } yield { val memberCache = classCache.getExportedMemberCache(idx) val version = Version.combine(isJSClassVersion, member.version) - memberCache.getOrElseUpdate(version, + extractChanged(memberCache.getOrElseUpdate(version, classEmitter.genExportedMember( className, // invalidated by overall class cache isJSClass, // invalidated by isJSClassVersion useESClass, // invalidated by isJSClassVersion member // invalidated by version - )(moduleContext, memberCache).map(postTransform(_, memberIndent))) + )(moduleContext, memberCache).map(postTransform(_, memberIndent)))) } val hasClassInitializer: Boolean = { @@ -602,7 +619,7 @@ final class Emitter[E >: Null <: js.Tree]( } } - val fullClass = { + val fullClass = extractChanged { val fullClassCache = classCache.getFullClassCache() fullClassCache.getOrElseUpdate(linkedClass.version, ctorWithGlobals, @@ -714,7 +731,8 @@ final class Emitter[E >: Null <: js.Tree]( main.result(), staticFields, staticInitialization, - trackedGlobalRefs + trackedGlobalRefs, + changed ) } @@ -751,7 +769,7 @@ final class Emitter[E >: Null <: js.Tree]( } def getOrComputeImports(externalDependencies: Set[String], internalDependencies: Set[ModuleID])( - compute: => WithGlobals[List[E]]): WithGlobals[List[E]] = { + compute: => WithGlobals[List[E]]): (WithGlobals[List[E]], Boolean) = { _cacheUsed = true @@ -759,20 +777,25 @@ final class Emitter[E >: Null <: js.Tree]( _importsCache = compute _lastExternalDependencies = externalDependencies _lastInternalDependencies = internalDependencies + (_importsCache, true) + } else { + (_importsCache, false) } - _importsCache + } def getOrComputeTopLevelExports(topLevelExports: List[LinkedTopLevelExport])( - compute: => WithGlobals[List[E]]): WithGlobals[List[E]] = { + compute: => WithGlobals[List[E]]): (WithGlobals[List[E]], Boolean) = { _cacheUsed = true if (!sameTopLevelExports(topLevelExports, _lastTopLevelExports)) { _topLevelExportsCache = compute _lastTopLevelExports = topLevelExports + (_topLevelExportsCache, true) + } else { + (_topLevelExportsCache, false) } - _topLevelExportsCache } private def sameTopLevelExports(tles1: List[LinkedTopLevelExport], tles2: List[LinkedTopLevelExport]): Boolean = { @@ -803,15 +826,17 @@ final class Emitter[E >: Null <: js.Tree]( } def getOrComputeInitializers(initializers: List[ModuleInitializer.Initializer])( - compute: => WithGlobals[List[E]]): WithGlobals[List[E]] = { + compute: => WithGlobals[List[E]]): (WithGlobals[List[E]], Boolean) = { _cacheUsed = true if (initializers != _lastInitializers) { _initializersCache = compute _lastInitializers = initializers + (_initializersCache, true) + } else { + (_initializersCache, false) } - _initializersCache } def cleanAfterRun(): Boolean = { @@ -856,17 +881,18 @@ final class Emitter[E >: Null <: js.Tree]( _fullClassCache.foreach(_.startRun()) } - def getCache(version: Version): DesugaredClassCache[List[E]] = { + def getCache(version: Version): (DesugaredClassCache[List[E]], Boolean) = { + _cacheUsed = true if (_cache == null || !_lastVersion.sameVersion(version)) { invalidate() statsClassesInvalidated += 1 _lastVersion = version _cache = new DesugaredClassCache[List[E]] + (_cache, true) } else { statsClassesReused += 1 + (_cache, false) } - _cacheUsed = true - _cache } def getMemberMethodCache( @@ -932,17 +958,18 @@ final class Emitter[E >: Null <: js.Tree]( def startRun(): Unit = _cacheUsed = false def getOrElseUpdate(version: Version, - v: => WithGlobals[T]): WithGlobals[T] = { + v: => WithGlobals[T]): (WithGlobals[T], Boolean) = { + _cacheUsed = true if (_tree == null || !_lastVersion.sameVersion(version)) { invalidate() statsMethodsInvalidated += 1 _tree = v _lastVersion = version + (_tree, true) } else { statsMethodsReused += 1 + (_tree, false) } - _cacheUsed = true - _tree } def cleanAfterRun(): Boolean = { @@ -974,7 +1001,7 @@ final class Emitter[E >: Null <: js.Tree]( def getOrElseUpdate(version: Version, ctor: WithGlobals[List[E]], memberMethods: List[WithGlobals[List[E]]], exportedMembers: List[WithGlobals[List[E]]], - compute: => WithGlobals[List[E]]): WithGlobals[List[E]] = { + compute: => WithGlobals[List[E]]): (WithGlobals[List[E]], Boolean) = { @tailrec def allSame[A <: AnyRef](xs: List[A], ys: List[A]): Boolean = { @@ -984,6 +1011,8 @@ final class Emitter[E >: Null <: js.Tree]( } } + _cacheUsed = true + if (_tree == null || !version.sameVersion(_lastVersion) || (_lastCtor ne ctor) || !allSame(_lastMemberMethods, memberMethods) || !allSame(_lastExportedMembers, exportedMembers)) { @@ -993,10 +1022,10 @@ final class Emitter[E >: Null <: js.Tree]( _lastCtor = ctor _lastMemberMethods = memberMethods _lastExportedMembers = exportedMembers + (_tree, true) + } else { + (_tree, false) } - - _cacheUsed = true - _tree } def cleanAfterRun(): Boolean = { @@ -1030,7 +1059,7 @@ object Emitter { /** Result of an emitter run. */ final class Result[E] private[Emitter]( val header: String, - val body: Map[ModuleID, List[E]], + val body: Map[ModuleID, (List[E], Boolean)], val footer: String, val topLevelVarDecls: List[String], val globalRefs: Set[String] @@ -1121,7 +1150,8 @@ object Emitter { val main: List[E], val staticFields: List[E], val staticInitialization: List[E], - val trackedGlobalRefs: Set[String] + val trackedGlobalRefs: Set[String], + val changed: Boolean ) private final class OneTimeCache[A >: Null] { From 42efb2aa153b3feea58cda36ee0d25f896e52202 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 23 Dec 2023 18:07:04 +0100 Subject: [PATCH 21/65] Do not cache overall class This reduces some memory overhead for negligible performance cost. Residual (post link memory) benchmarks for the test suite: Baseline: 1.13 GB, new 1.01 GB --- .../linker/backend/emitter/Emitter.scala | 119 +++++++++--------- .../org/scalajs/linker/EmitterTest.scala | 11 +- 2 files changed, 70 insertions(+), 60 deletions(-) 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 8aafe7b745..1906ffe84f 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 @@ -619,37 +619,41 @@ final class Emitter[E >: Null <: js.Tree]( } } - val fullClass = extractChanged { - val fullClassCache = classCache.getFullClassCache() - - fullClassCache.getOrElseUpdate(linkedClass.version, ctorWithGlobals, - memberMethodsWithGlobals, exportedMembersWithGlobals, { - for { - ctor <- ctorWithGlobals - memberMethods <- WithGlobals.flatten(memberMethodsWithGlobals) - exportedMembers <- WithGlobals.flatten(exportedMembersWithGlobals) - allMembers = ctor ::: memberMethods ::: exportedMembers - clazz <- classEmitter.buildClass( - className, // invalidated by overall class cache (part of ancestors) - isJSClass, // invalidated by class version - linkedClass.jsClassCaptures, // invalidated by class version - hasClassInitializer, // invalidated by class version (optimizer cannot remove it) - linkedClass.superClass, // invalidated by class version - storeJSSuperClass, // invalidated by class version - useESClass, // invalidated by class version (depends on kind, config and ancestry only) - allMembers // invalidated directly - )(moduleContext, fullClassCache, linkedClass.pos) // pos invalidated by class version - } yield { - // Avoid a nested post transform if we just got the original members back. - if (clazz eq allMembers) { - statsNestedPostTransformsAvoided += 1 - allMembers - } else { - statsNestedPostTransforms += 1 - postTransform(clazz, 0) - } + val fullClass = { + val fullClassChangeTracker = classCache.getFullClassChangeTracker() + + // Put changed state into a val to avoid short circuiting behavior of ||. + val classChanged = fullClassChangeTracker.trackChanged( + linkedClass.version, ctorWithGlobals, + memberMethodsWithGlobals, exportedMembersWithGlobals) + + changed ||= classChanged + + for { + ctor <- ctorWithGlobals + memberMethods <- WithGlobals.flatten(memberMethodsWithGlobals) + exportedMembers <- WithGlobals.flatten(exportedMembersWithGlobals) + allMembers = ctor ::: memberMethods ::: exportedMembers + clazz <- classEmitter.buildClass( + className, // invalidated by overall class cache (part of ancestors) + isJSClass, // invalidated by class version + linkedClass.jsClassCaptures, // invalidated by class version + hasClassInitializer, // invalidated by class version (optimizer cannot remove it) + linkedClass.superClass, // invalidated by class version + storeJSSuperClass, // invalidated by class version + useESClass, // invalidated by class version (depends on kind, config and ancestry only) + allMembers // invalidated directly + )(moduleContext, fullClassChangeTracker, linkedClass.pos) // pos invalidated by class version + } yield { + // Avoid a nested post transform if we just got the original members back. + if (clazz eq allMembers) { + statsNestedPostTransformsAvoided += 1 + allMembers + } else { + statsNestedPostTransforms += 1 + postTransform(clazz, 0) } - }) + } } main ++= extractWithGlobals(fullClass) @@ -862,7 +866,7 @@ final class Emitter[E >: Null <: js.Tree]( private[this] val _exportedMembersCache = mutable.Map.empty[Int, MethodCache[List[E]]] - private[this] var _fullClassCache: Option[FullClassCache] = None + private[this] var _fullClassChangeTracker: Option[FullClassChangeTracker] = None override def invalidate(): Unit = { /* Do not invalidate contained methods, as they have their own @@ -878,7 +882,7 @@ final class Emitter[E >: Null <: js.Tree]( _methodCaches.foreach(_.valuesIterator.foreach(_.startRun())) _memberMethodCache.valuesIterator.foreach(_.startRun()) _constructorCache.foreach(_.startRun()) - _fullClassCache.foreach(_.startRun()) + _fullClassChangeTracker.foreach(_.startRun()) } def getCache(version: Version): (DesugaredClassCache[List[E]], Boolean) = { @@ -917,10 +921,10 @@ final class Emitter[E >: Null <: js.Tree]( def getExportedMemberCache(idx: Int): MethodCache[List[E]] = _exportedMembersCache.getOrElseUpdate(idx, new MethodCache) - def getFullClassCache(): FullClassCache = { - _fullClassCache.getOrElse { - val cache = new FullClassCache - _fullClassCache = Some(cache) + def getFullClassChangeTracker(): FullClassChangeTracker = { + _fullClassChangeTracker.getOrElse { + val cache = new FullClassChangeTracker + _fullClassChangeTracker = Some(cache) cache } } @@ -934,8 +938,8 @@ final class Emitter[E >: Null <: js.Tree]( _exportedMembersCache.filterInPlace((_, c) => c.cleanAfterRun()) - if (_fullClassCache.exists(!_.cleanAfterRun())) - _fullClassCache = None + if (_fullClassChangeTracker.exists(!_.cleanAfterRun())) + _fullClassChangeTracker = None if (!_cacheUsed) invalidate() @@ -980,28 +984,26 @@ final class Emitter[E >: Null <: js.Tree]( } } - private class FullClassCache extends knowledgeGuardian.KnowledgeAccessor { - private[this] var _tree: WithGlobals[List[E]] = null + private class FullClassChangeTracker extends knowledgeGuardian.KnowledgeAccessor { private[this] var _lastVersion: Version = Version.Unversioned private[this] var _lastCtor: WithGlobals[List[E]] = null private[this] var _lastMemberMethods: List[WithGlobals[List[E]]] = null private[this] var _lastExportedMembers: List[WithGlobals[List[E]]] = null - private[this] var _cacheUsed = false + private[this] var _trackerUsed = false override def invalidate(): Unit = { super.invalidate() - _tree = null _lastVersion = Version.Unversioned _lastCtor = null _lastMemberMethods = null _lastExportedMembers = null } - def startRun(): Unit = _cacheUsed = false + def startRun(): Unit = _trackerUsed = false - def getOrElseUpdate(version: Version, ctor: WithGlobals[List[E]], - memberMethods: List[WithGlobals[List[E]]], exportedMembers: List[WithGlobals[List[E]]], - compute: => WithGlobals[List[E]]): (WithGlobals[List[E]], Boolean) = { + def trackChanged(version: Version, ctor: WithGlobals[List[E]], + memberMethods: List[WithGlobals[List[E]]], + exportedMembers: List[WithGlobals[List[E]]]): Boolean = { @tailrec def allSame[A <: AnyRef](xs: List[A], ys: List[A]): Boolean = { @@ -1011,28 +1013,33 @@ final class Emitter[E >: Null <: js.Tree]( } } - _cacheUsed = true + _trackerUsed = true - if (_tree == null || !version.sameVersion(_lastVersion) || (_lastCtor ne ctor) || - !allSame(_lastMemberMethods, memberMethods) || - !allSame(_lastExportedMembers, exportedMembers)) { + val changed = { + !version.sameVersion(_lastVersion) || + (_lastCtor ne ctor) || + !allSame(_lastMemberMethods, memberMethods) || + !allSame(_lastExportedMembers, exportedMembers) + } + + if (changed) { + // Input has changed or we were invalidated. + // Clean knowledge tracking and re-track dependencies. invalidate() - _tree = compute _lastVersion = version _lastCtor = ctor _lastMemberMethods = memberMethods _lastExportedMembers = exportedMembers - (_tree, true) - } else { - (_tree, false) } + + changed } def cleanAfterRun(): Boolean = { - if (!_cacheUsed) + if (!_trackerUsed) invalidate() - _cacheUsed + _trackerUsed } } 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 935c2a57ae..1f7884c0f1 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala @@ -208,18 +208,21 @@ class EmitterTest { // Post transforms - val Seq(postTransforms1, _, _) = + val Seq(postTransforms1, nestedPostTransforms1, _) = lines1.assertContainsMatch(EmitterPostTransformStatsMessage).map(_.toInt) - val Seq(postTransforms2, _, _) = + val Seq(postTransforms2, nestedPostTransforms2, _) = lines2.assertContainsMatch(EmitterPostTransformStatsMessage).map(_.toInt) - // At the time of writing this test, postTransformsTotal1 reports 216 + // At the time of writing this test, postTransforms1 reports 216 assertTrue( s"Not enough post transforms (got $postTransforms1); extraction must have gone wrong", postTransforms1 > 200) - assertEquals("Second run must not have any post transforms", 0, postTransforms2) + assertEquals("Second run must only have nested post transforms", + nestedPostTransforms2, postTransforms2) + assertEquals("Both runs must have the same number of nested post transforms", + nestedPostTransforms1, nestedPostTransforms2) } } } From d5cef60d4f25e875d18f9b902038b67aa15daca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 31 Jan 2024 16:51:03 +0100 Subject: [PATCH 22/65] Fix #4934: Report errors in Analyzer only after all tasks are completed. Previously, as soon as one task completed with a Failure, we let the WorkTracker's main promise complete with that Failure. If other tasks were still running, they would leak and potentially cause worse errors down the line. In particular, they would cause `NullPointerException`s, which are UBEs on Scala.js. --- .../scalajs/linker/analyzer/Analyzer.scala | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala index 34a2be0d59..11a997ef27 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala @@ -1572,22 +1572,30 @@ private object AnalyzerRun { private class WorkTracker(implicit ec: ExecutionContext) { private val pending = new AtomicInteger(0) + private val failures = new AtomicReference[List[Throwable]](Nil) @volatile private var _allowComplete = false private val promise = Promise[Unit]() def track(fut: Future[Unit]): Unit = { pending.incrementAndGet() - fut.onComplete { - case Success(_) => - if (pending.decrementAndGet() == 0) - tryComplete() - - case Failure(t) => - promise.tryFailure(t) + fut.onComplete { result => + result match { + case Success(_) => () + case Failure(t) => addFailure(t) + } + if (pending.decrementAndGet() == 0) + tryComplete() } } + @tailrec + private def addFailure(t: Throwable): Unit = { + val prev = failures.get() + if (!failures.compareAndSet(prev, t :: prev)) + addFailure(t) + } + private def tryComplete(): Unit = { /* Note that after _allowComplete is true and pending == 0, we are sure * that no new task will be submitted concurrently: @@ -1596,7 +1604,14 @@ private object AnalyzerRun { * more tasks) are running anymore. */ if (_allowComplete && pending.get() == 0) { - promise.trySuccess(()) + failures.get() match { + case Nil => + promise.success(()) + case firstFailure :: moreFailures => + for (t <- moreFailures) + firstFailure.addSuppressed(t) + promise.failure(firstFailure) + } } } From 5de6c1d46a6e2d0aa53add1dcf1be7ebbe646693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 31 Jan 2024 12:11:44 +0100 Subject: [PATCH 23/65] Generalize the side-effect-free analysis of constructors to all classes. Previously, we only analyzed *module* class constructors for side-effect-freedom. This allowed us to get rid of unused `LoadModule`s. We now generalize the same analysis to all Scala classes. We take a little shortcut by bundling *all* the constructors of a class together as being side-effect-free or not. This is an over-approximation in theory, but in practice it is unlikely that it will make a difference. This shortcut allows our analysis to be straightforward even in the presence of constructor delegation chains. --- .../frontend/optimizer/IncOptimizer.scala | 81 ++++++++++--------- .../frontend/optimizer/OptimizerCore.scala | 13 +-- project/Build.scala | 8 +- .../testsuite/compiler/NullPointersTest.scala | 3 +- 4 files changed, 56 insertions(+), 49 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 8982107805..f6876b2a4e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -380,8 +380,9 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: var subclasses: collOps.ParIterable[Class] = collOps.emptyParIterable var isInstantiated: Boolean = linkedClass.hasInstances - private var hasElidableModuleAccessor: Boolean = computeHasElidableModuleAccessor(linkedClass) - private val hasElidableModuleAccessorAskers = collOps.emptyMap[Processable, Unit] + /** True if *all* constructors of this class are recursively elidable. */ + private var hasElidableConstructors: Boolean = computeHasElidableConstructors(linkedClass) + private val hasElidableConstructorsAskers = collOps.emptyMap[Processable, Unit] var fields: List[AnyFieldDef] = linkedClass.fields var fieldsRead: Set[FieldName] = linkedClass.fieldsRead @@ -507,12 +508,12 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: for (methodName <- methodAttributeChanges) myInterface.tagStaticCallersOf(namespace, methodName) - // Module class specifics - val newHasElidableModuleAccessor = computeHasElidableModuleAccessor(linkedClass) - if (hasElidableModuleAccessor != newHasElidableModuleAccessor) { - hasElidableModuleAccessor = newHasElidableModuleAccessor - hasElidableModuleAccessorAskers.keysIterator.foreach(_.tag()) - hasElidableModuleAccessorAskers.clear() + // Elidable constructors + val newHasElidableConstructors = computeHasElidableConstructors(linkedClass) + if (hasElidableConstructors != newHasElidableConstructors) { + hasElidableConstructors = newHasElidableConstructors + hasElidableConstructorsAskers.keysIterator.foreach(_.tag()) + hasElidableConstructorsAskers.clear() } // Inlineable class @@ -543,25 +544,21 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: subclasses = collOps.finishAdd(subclassAcc) } - def askHasElidableModuleAccessor(asker: Processable): Boolean = { - hasElidableModuleAccessorAskers.put(asker, ()) + def askHasElidableConstructors(asker: Processable): Boolean = { + hasElidableConstructorsAskers.put(asker, ()) asker.registerTo(this) - hasElidableModuleAccessor + hasElidableConstructors } /** UPDATE PASS ONLY. */ - private def computeHasElidableModuleAccessor(linkedClass: LinkedClass): Boolean = { - def lookupModuleConstructor: Option[MethodImpl] = { - myInterface - .staticLike(MemberNamespace.Constructor) - .methods - .get(NoArgConstructorName) + private def computeHasElidableConstructors(linkedClass: LinkedClass): Boolean = { + if (isAdHocElidableConstructors(className)) { + true + } else { + superClass.forall(_.hasElidableConstructors) && // this was always updated before myself + myInterface.staticLike(MemberNamespace.Constructor) + .methods.valuesIterator.forall(computeIsElidableConstructor) } - - val isModuleClass = linkedClass.kind == ClassKind.ModuleClass - - isAdHocElidableModuleAccessor(className) || - (isModuleClass && lookupModuleConstructor.exists(isElidableModuleConstructor)) } /** UPDATE PASS ONLY. */ @@ -624,16 +621,17 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } /** UPDATE PASS ONLY. */ - private def isElidableModuleConstructor(impl: MethodImpl): Boolean = { + private def computeIsElidableConstructor(impl: MethodImpl): Boolean = { def isTriviallySideEffectFree(tree: Tree): Boolean = tree match { case _:VarRef | _:Literal | _:This | _:Skip => true case _ => false } + def isElidableStat(tree: Tree): Boolean = tree match { case Block(stats) => stats.forall(isElidableStat) case Assign(Select(This(), _, _), rhs) => isTriviallySideEffectFree(rhs) - // Mixin constructor + // Mixin constructor -- test whether its body is entirely empty case ApplyStatically(flags, This(), className, methodName, Nil) if !flags.isPrivate && !classes.contains(className) => // Since className is not in classes, it must be a default method call. @@ -646,22 +644,27 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } } - // Delegation to another constructor (super or in the same class) - case ApplyStatically(flags, This(), className, methodName, args) - if flags.isConstructor => - args.forall(isTriviallySideEffectFree) && { - getInterface(className) - .staticLike(MemberNamespace.Constructor) - .methods - .get(methodName.name) - .exists(isElidableModuleConstructor) - } + /* Delegation to another constructor (super or in the same class) + * + * - for super constructor calls, we have already checked before getting + * here that the super class has elidable constructors, so by + * construction they are elidable and we do not need to test them + * - for other constructors in the same class, we will collectively + * treat them as all-elidable or non-elidable; therefore, we do not + * need to check them either at this point. + * + * We only need to check the arguments to the constructor, not their + * bodies. + */ + case ApplyStatically(flags, This(), _, _, args) if flags.isConstructor => + args.forall(isTriviallySideEffectFree) case StoreModule(_, _) => true case _ => isTriviallySideEffectFree(tree) } + impl.originalDef.body.fold { - throw new AssertionError("Module constructor cannot be abstract") + throw new AssertionError("Constructor cannot be abstract") } { body => isElidableStat(body) } @@ -692,7 +695,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } def unregisterDependee(dependee: Processable): Unit = { - hasElidableModuleAccessorAskers.remove(dependee) + hasElidableConstructorsAskers.remove(dependee) } } @@ -1349,8 +1352,8 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: protected def getAncestorsOf(intfName: ClassName): List[ClassName] = getInterface(intfName).askAncestors(asker) - protected def hasElidableModuleAccessor(moduleClassName: ClassName): Boolean = - classes(moduleClassName).askHasElidableModuleAccessor(asker) + protected def hasElidableConstructors(className: ClassName): Boolean = + classes(className).askHasElidableConstructors(asker) protected def tryNewInlineableClass( className: ClassName): Option[OptimizerCore.InlineableClassStructure] = { @@ -1380,6 +1383,6 @@ object IncOptimizer { def apply(config: CommonPhaseConfig): IncOptimizer = new IncOptimizer(config, SeqCollOps) - private val isAdHocElidableModuleAccessor: Set[ClassName] = + private val isAdHocElidableConstructors: Set[ClassName] = Set(ClassName("scala.Predef$")) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index d2454ea80f..6b65697fe8 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -66,11 +66,11 @@ private[optimizer] abstract class OptimizerCore( /** Returns the list of ancestors of a class or interface. */ protected def getAncestorsOf(className: ClassName): List[ClassName] - /** Tests whether the given module class has an elidable accessor. - * In other words, whether it is safe to discard a LoadModule of that - * module class which is not used. + /** Tests whether *all* the constructors of the given class are elidable. + * In other words, whether it is safe to discard a New or LoadModule of that + * class which is not used. */ - protected def hasElidableModuleAccessor(moduleClassName: ClassName): Boolean + protected def hasElidableConstructors(className: ClassName): Boolean /** Tests whether the given class is inlineable. * @@ -1578,8 +1578,11 @@ private[optimizer] abstract class OptimizerCore( case Skip() => keepOnlySideEffects(Block(init)(stat.pos)) case lastEffects => Block(init, lastEffects)(stat.pos) } + case New(className, _, args) => + if (hasElidableConstructors(className)) Block(args.map(keepOnlySideEffects(_)))(stat.pos) + else stat case LoadModule(moduleClassName) => - if (hasElidableModuleAccessor(moduleClassName)) Skip()(stat.pos) + if (hasElidableConstructors(moduleClassName)) Skip()(stat.pos) else stat case NewArray(_, lengths) if lengths.forall(isNonNegativeIntLiteral(_)) => Skip()(stat.pos) diff --git a/project/Build.scala b/project/Build.scala index f9d72c08e0..9ff7b777a7 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1967,17 +1967,17 @@ object Build { scalaVersion.value match { case `default212Version` => Some(ExpectedSizes( - fastLink = 768000 to 769000, + fastLink = 682000 to 683000, fullLink = 144000 to 145000, - fastLinkGz = 90000 to 91000, + fastLinkGz = 82000 to 83000, fullLinkGz = 35000 to 36000, )) case `default213Version` => Some(ExpectedSizes( - fastLink = 478000 to 479000, + fastLink = 476000 to 477000, fullLink = 101000 to 102000, - fastLinkGz = 62000 to 63000, + fastLinkGz = 61000 to 62000, fullLinkGz = 27000 to 28000, )) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/NullPointersTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/NullPointersTest.scala index d76225c466..4ff13defd1 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/NullPointersTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/NullPointersTest.scala @@ -23,7 +23,8 @@ class NullPointersTest { import NullPointersTest._ // Instantiate Tester somewhere, otherwise plenty of tests are moot - new Tester(0) + @noinline def keep(x: Any): Unit = () + keep(new Tester(0)) @noinline private def nullOf[T >: Null]: T = null From 7814252c8869cae75652c4dad1e0bee4e48611ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 2 Feb 2024 11:28:14 +0100 Subject: [PATCH 24/65] Better codegen for the null check on outer pointers in constructors. When introducing outer pointers, scalac generates a null check in the constructor of nested classes. The null check looks like this: if ($outer.eq(null)) throw null else this.$outer = $outer This is a bad shape for our optimizations, notably because the explicit null check cannot be considered UB at the IR level if we compile it as is, although in the original *language*, that would clearly fall into UB. Therefore, we now intercept that shape and rewrite it as follows instead: ($outer) // null check subject to UB this.$outer = $outer // the `else` branch in general --- .../org/scalajs/nscplugin/GenJSCode.scala | 49 ++++++++++++++++--- .../frontend/optimizer/OptimizerCore.scala | 4 +- project/Build.scala | 6 +-- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index e4c9ecadd9..2cf95451db 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -2270,13 +2270,50 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) toIRType(sym.tpe), sym.isMutable, rhsTree) } - case If(cond, thenp, elsep) => - val tpe = - if (isStat) jstpe.NoType - else toIRType(tree.tpe) + case tree @ If(cond, thenp, elsep) => + def default: js.Tree = { + val tpe = + if (isStat) jstpe.NoType + else toIRType(tree.tpe) + + js.If(genExpr(cond), genStatOrExpr(thenp, isStat), + genStatOrExpr(elsep, isStat))(tpe) + } - js.If(genExpr(cond), genStatOrExpr(thenp, isStat), - genStatOrExpr(elsep, isStat))(tpe) + if (isStat && currentMethodSym.isClassConstructor) { + /* Nested classes that need an outer pointer have a weird shape for + * assigning it, with an explicit null check. It looks like this: + * + * if ($outer.eq(null)) + * throw null + * else + * this.$outer = $outer + * + * This is a bad shape for our optimizations, notably because the + * explicit null check cannot be considered UB at the IR level if + * we compile it as is, although in the original *language*, that + * would clearly fall into UB. + * + * Therefore, we intercept that shape and rewrite it as follows + * instead: + * + * ($outer) // null check subject to UB + * this.$outer = $outer // the `else` branch in general + */ + tree match { + case If(Apply(fun @ Select(outer: Ident, nme.eq), Literal(Constant(null)) :: Nil), + Throw(Literal(Constant(null))), elsep) + if outer.symbol.isOuterParam && fun.symbol == definitions.Object_eq => + js.Block( + js.GetClass(genExpr(outer)), // null check + genStat(elsep) + ) + case _ => + default + } + } else { + default + } case Return(expr) => js.Return(toIRType(expr.tpe) match { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 6b65697fe8..534fd345be 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -2791,7 +2791,9 @@ private[optimizer] abstract class OptimizerCore( * if (cond) throw e * else value * - * Typical shape of initialization of outer pointer of inner classes. + * Typical shape of initialization of outer pointer of inner classes + * coming from Scala.js < 1.15.1 (since 1.15.1, we intercept that shape + * already in the compiler back-end). */ case If(cond, th: Throw, Assign(Select(This(), _, _), value)) :: rest => // work around a bug of the compiler (these should be @-bindings) diff --git a/project/Build.scala b/project/Build.scala index 9ff7b777a7..31c63099fb 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1967,15 +1967,15 @@ object Build { scalaVersion.value match { case `default212Version` => Some(ExpectedSizes( - fastLink = 682000 to 683000, - fullLink = 144000 to 145000, + fastLink = 681000 to 682000, + fullLink = 142000 to 143000, fastLinkGz = 82000 to 83000, fullLinkGz = 35000 to 36000, )) case `default213Version` => Some(ExpectedSizes( - fastLink = 476000 to 477000, + fastLink = 475000 to 476000, fullLink = 101000 to 102000, fastLinkGz = 61000 to 62000, fullLinkGz = 27000 to 28000, From fea064dd526433181a37ae6a967866a2131f19db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 2 Feb 2024 13:10:23 +0100 Subject: [PATCH 25/65] Handle `GetClass` in the side-effect-free analysis of constructors. When NPEs are unchecked, `GetClass` is side-effect-free. This helps enormously the analysis, as it now allows many inner classes to be considered to have elidable constructors. --- .../linker/frontend/optimizer/IncOptimizer.scala | 13 ++++++++++--- project/Build.scala | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index f6876b2a4e..2ed19a5b19 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -29,7 +29,7 @@ import org.scalajs.logging._ import org.scalajs.linker._ import org.scalajs.linker.backend.emitter.LongImpl import org.scalajs.linker.frontend.LinkingUnit -import org.scalajs.linker.interface.ModuleKind +import org.scalajs.linker.interface.{CheckedBehavior, ModuleKind} import org.scalajs.linker.standard._ import org.scalajs.linker.CollectionsCompat._ @@ -623,8 +623,15 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: /** UPDATE PASS ONLY. */ private def computeIsElidableConstructor(impl: MethodImpl): Boolean = { def isTriviallySideEffectFree(tree: Tree): Boolean = tree match { - case _:VarRef | _:Literal | _:This | _:Skip => true - case _ => false + case _:VarRef | _:Literal | _:This | _:Skip => + true + + case GetClass(expr) => + config.coreSpec.semantics.nullPointers == CheckedBehavior.Unchecked && + isTriviallySideEffectFree(expr) + + case _ => + false } def isElidableStat(tree: Tree): Boolean = tree match { diff --git a/project/Build.scala b/project/Build.scala index 31c63099fb..85ad9026ee 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1968,9 +1968,9 @@ object Build { case `default212Version` => Some(ExpectedSizes( fastLink = 681000 to 682000, - fullLink = 142000 to 143000, + fullLink = 111000 to 112000, fastLinkGz = 82000 to 83000, - fullLinkGz = 35000 to 36000, + fullLinkGz = 28000 to 29000, )) case `default213Version` => From a99abdef340a9fdd236644c232716a81e92c0873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 2 Feb 2024 14:29:29 +0100 Subject: [PATCH 26/65] Handle `Closure` in the side-effect-free analysis of constructors. --- .../org/scalajs/linker/frontend/optimizer/IncOptimizer.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 2ed19a5b19..9c433fb62c 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -626,6 +626,9 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: case _:VarRef | _:Literal | _:This | _:Skip => true + case Closure(_, _, _, _, _, captureValues) => + captureValues.forall(isTriviallySideEffectFree(_)) + case GetClass(expr) => config.coreSpec.semantics.nullPointers == CheckedBehavior.Unchecked && isTriviallySideEffectFree(expr) From e47c278ed9b7dac0850587e02daefa46cd58093d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 31 Jan 2024 18:01:38 +0100 Subject: [PATCH 27/65] Make the side-effect-free analysis of constructors graph-aware. Previously, as soon as a class A's constructor instantiated another class B (including accessing module instances), A's constructors would be considered non-elidable. That is even if B's constructors are elidable. In this commit, we implement a graph-based analysis that solves the above limitation. First, during the incremental pass of the linker (the UDPATE pass), we compute the "elidable constructor infos" of every class: - whether any of its constructors contains potentially side-effecting code, *other than* instantiating other classes; if yes, it is `NotElidable`; - otherwise, the set of classes that its constructors collectively instantiate, which is then `DependentOn`. We then insert a third pass in the `IncOptimizer`: the ELIDABLE CTORS pass. This pass computes the transitive closure of `NotElidable` by following the reverse `DependentOn` relationships. This pass is not incremental nor parallel. However, it is only linear in the number of classes (`Class`es, `ModuleClass`es and `HijackedClass`es). That makes it very fast regardless (on order of 10 ms for our test suite). Therefore, it remains acceptable even for `fastLink`. --- .../frontend/optimizer/IncOptimizer.scala | 129 ++++++++++++++++-- project/Build.scala | 14 +- 2 files changed, 122 insertions(+), 21 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 9c433fb62c..6ba6bca95b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -83,6 +83,11 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: updateAndTagEverything(unit) } + logger.time("Optimizer: Elidable constructors") { + /** ELIDABLE CTORS PASS */ + updateElidableConstructors() + } + logger.time("Optimizer: Optimizer part") { /* PROCESS PASS */ processAllTaggedMethods(logger) @@ -259,6 +264,47 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } + /** Elidable constructors: compute the fix point of hasElidableConstructors. + * + * ELIDABLE CTORS PASS ONLY. (This IS the elidable ctors pass). + */ + private def updateElidableConstructors(): Unit = { + import ElidableConstructorsInfo._ + + /* Invariant: when something is in the stack, its + * elidableConstructorsInfo was set to NotElidable. + */ + val toProcessStack = mutable.ArrayBuffer.empty[Class] + + // Build the graph and initial stack from the infos + for (cls <- classes.valuesIterator) { + cls.elidableConstructorsInfo match { + case NotElidable => + toProcessStack += cls + case DependentOn(dependencies) => + for (dependency <- dependencies) + classes(dependency).elidableConstructorsDependents += cls + } + } + + // Propagate + while (toProcessStack.nonEmpty) { + val cls = toProcessStack.remove(toProcessStack.size - 1) + + for (dependent <- cls.elidableConstructorsDependents) { + if (dependent.elidableConstructorsInfo != NotElidable) { + dependent.elidableConstructorsInfo = NotElidable + toProcessStack += dependent + } + } + } + + // Set the final value of hasElidableConstructors + for (cls <- classes.valuesIterator) { + cls.setHasElidableConstructors(cls.elidableConstructorsInfo != NotElidable) + } + } + /** Optimizer part: process all methods that need reoptimizing. * PROCESS PASS ONLY. (This IS the process pass). */ @@ -380,8 +426,14 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: var subclasses: collOps.ParIterable[Class] = collOps.emptyParIterable var isInstantiated: Boolean = linkedClass.hasInstances + // Temporary information used to eventually derive `hasElidableConstructors` + var elidableConstructorsInfo: ElidableConstructorsInfo = + computeElidableConstructorsInfo(linkedClass) + val elidableConstructorsDependents: mutable.ArrayBuffer[Class] = mutable.ArrayBuffer.empty + /** True if *all* constructors of this class are recursively elidable. */ - private var hasElidableConstructors: Boolean = computeHasElidableConstructors(linkedClass) + private var hasElidableConstructors: Boolean = + elidableConstructorsInfo != ElidableConstructorsInfo.NotElidable // initial educated guess private val hasElidableConstructorsAskers = collOps.emptyMap[Processable, Unit] var fields: List[AnyFieldDef] = linkedClass.fields @@ -509,12 +561,8 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: myInterface.tagStaticCallersOf(namespace, methodName) // Elidable constructors - val newHasElidableConstructors = computeHasElidableConstructors(linkedClass) - if (hasElidableConstructors != newHasElidableConstructors) { - hasElidableConstructors = newHasElidableConstructors - hasElidableConstructorsAskers.keysIterator.foreach(_.tag()) - hasElidableConstructorsAskers.clear() - } + elidableConstructorsInfo = computeElidableConstructorsInfo(linkedClass) + elidableConstructorsDependents.clear() // Inlineable class if (updateTryNewInlineable(linkedClass)) { @@ -528,6 +576,15 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } } + /** ELIDABLE CTORS PASS ONLY. */ + def setHasElidableConstructors(newHasElidableConstructors: Boolean): Unit = { + if (hasElidableConstructors != newHasElidableConstructors) { + hasElidableConstructors = newHasElidableConstructors + hasElidableConstructorsAskers.keysIterator.foreach(_.tag()) + hasElidableConstructorsAskers.clear() + } + } + /** UPDATE PASS ONLY. */ def walkForAdditions( getNewChildren: ClassName => collOps.ParIterable[LinkedClass]): Unit = { @@ -551,13 +608,25 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } /** UPDATE PASS ONLY. */ - private def computeHasElidableConstructors(linkedClass: LinkedClass): Boolean = { + private def computeElidableConstructorsInfo(linkedClass: LinkedClass): ElidableConstructorsInfo = { + import ElidableConstructorsInfo._ + if (isAdHocElidableConstructors(className)) { - true + AlwaysElidable } else { - superClass.forall(_.hasElidableConstructors) && // this was always updated before myself - myInterface.staticLike(MemberNamespace.Constructor) - .methods.valuesIterator.forall(computeIsElidableConstructor) + // It's OK to look at the superClass like this because it will always be updated before myself + var result = superClass.fold(ElidableConstructorsInfo.AlwaysElidable)(_.elidableConstructorsInfo) + + if (result == NotElidable) { + // fast path + result + } else { + val ctorIterator = myInterface.staticLike(MemberNamespace.Constructor).methods.valuesIterator + while (result != NotElidable && ctorIterator.hasNext) { + result = result.mergeWith(computeCtorElidableInfo(ctorIterator.next())) + } + result + } } } @@ -621,7 +690,9 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } /** UPDATE PASS ONLY. */ - private def computeIsElidableConstructor(impl: MethodImpl): Boolean = { + private def computeCtorElidableInfo(impl: MethodImpl): ElidableConstructorsInfo = { + val dependenciesBuilder = Set.newBuilder[ClassName] + def isTriviallySideEffectFree(tree: Tree): Boolean = tree match { case _:VarRef | _:Literal | _:This | _:Skip => true @@ -633,6 +704,14 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: config.coreSpec.semantics.nullPointers == CheckedBehavior.Unchecked && isTriviallySideEffectFree(expr) + case New(className, _, args) => + dependenciesBuilder += className + args.forall(isTriviallySideEffectFree(_)) + + case LoadModule(className) => + dependenciesBuilder += className + true + case _ => false } @@ -676,7 +755,10 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: impl.originalDef.body.fold { throw new AssertionError("Constructor cannot be abstract") } { body => - isElidableStat(body) + if (isElidableStat(body)) + ElidableConstructorsInfo.DependentOn(dependenciesBuilder.result()) + else + ElidableConstructorsInfo.NotElidable } } @@ -1395,4 +1477,23 @@ object IncOptimizer { private val isAdHocElidableConstructors: Set[ClassName] = Set(ClassName("scala.Predef$")) + + sealed abstract class ElidableConstructorsInfo { + import ElidableConstructorsInfo._ + + final def mergeWith(that: ElidableConstructorsInfo): ElidableConstructorsInfo = (this, that) match { + case (DependentOn(deps1), DependentOn(deps2)) => + DependentOn(deps1 ++ deps2) + case _ => + NotElidable + } + } + + object ElidableConstructorsInfo { + case object NotElidable extends ElidableConstructorsInfo + + final case class DependentOn(dependencies: Set[ClassName]) extends ElidableConstructorsInfo + + val AlwaysElidable: ElidableConstructorsInfo = DependentOn(Set.empty) + } } diff --git a/project/Build.scala b/project/Build.scala index 85ad9026ee..9bc1560729 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1967,18 +1967,18 @@ object Build { scalaVersion.value match { case `default212Version` => Some(ExpectedSizes( - fastLink = 681000 to 682000, - fullLink = 111000 to 112000, + fastLink = 676000 to 677000, + fullLink = 103000 to 104000, fastLinkGz = 82000 to 83000, - fullLinkGz = 28000 to 29000, + fullLinkGz = 26000 to 27000, )) case `default213Version` => Some(ExpectedSizes( - fastLink = 475000 to 476000, - fullLink = 101000 to 102000, - fastLinkGz = 61000 to 62000, - fullLinkGz = 27000 to 28000, + fastLink = 468000 to 469000, + fullLink = 100000 to 101000, + fastLinkGz = 60000 to 61000, + fullLinkGz = 26000 to 27000, )) case _ => From 5f3323dc16ce9adb456dc02224be370791398625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 2 Feb 2024 18:42:12 +0100 Subject: [PATCH 28/65] Hard-code `scala.package$` as having elidable constructors. Like `scala.Predef$`. --- .../scalajs/linker/frontend/optimizer/IncOptimizer.scala | 2 +- project/Build.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 6ba6bca95b..e8db94edb1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -1476,7 +1476,7 @@ object IncOptimizer { new IncOptimizer(config, SeqCollOps) private val isAdHocElidableConstructors: Set[ClassName] = - Set(ClassName("scala.Predef$")) + Set(ClassName("scala.Predef$"), ClassName("scala.package$")) sealed abstract class ElidableConstructorsInfo { import ElidableConstructorsInfo._ diff --git a/project/Build.scala b/project/Build.scala index 9bc1560729..263683ee52 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1967,9 +1967,9 @@ object Build { scalaVersion.value match { case `default212Version` => Some(ExpectedSizes( - fastLink = 676000 to 677000, - fullLink = 103000 to 104000, - fastLinkGz = 82000 to 83000, + fastLink = 642000 to 643000, + fullLink = 102000 to 103000, + fastLinkGz = 77000 to 78000, fullLinkGz = 26000 to 27000, )) From 26300676c8acf6bdacf1b89a12dfef1be66a49dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 2 Feb 2024 14:24:09 +0100 Subject: [PATCH 29/65] Use `def`s for `hashCode` in 2.13.x Manifests, like in 2.12.x. Originally we made the change from the upstream `val`s to `def`s in 7865a5d48776198fa205d882de48f930ade43e41. We then accidentally reverted that change in the 2.13.x in 1a7f013641edd77dd427e05a50f9d38462fbd154. This commit aligns the 2.13.x overrides with 2.12.x again. --- scalalib/overrides-2.13/scala/reflect/Manifest.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scalalib/overrides-2.13/scala/reflect/Manifest.scala b/scalalib/overrides-2.13/scala/reflect/Manifest.scala index 4f84b5e639..053b0c485d 100644 --- a/scalalib/overrides-2.13/scala/reflect/Manifest.scala +++ b/scalalib/overrides-2.13/scala/reflect/Manifest.scala @@ -152,8 +152,7 @@ abstract class AnyValManifest[T <: AnyVal](override val toString: String) extend case _ => false } override def equals(that: Any): Boolean = this eq that.asInstanceOf[AnyRef] - @transient - override val hashCode = System.identityHashCode(this) + override def hashCode = System.identityHashCode(this) } /** `ManifestFactory` defines factory methods for manifests. @@ -402,8 +401,7 @@ object ManifestFactory { private abstract class PhantomManifest[T](_runtimeClass: Predef.Class[_], override val toString: String) extends ClassTypeManifest[T](None, _runtimeClass, Nil) { override def equals(that: Any): Boolean = this eq that.asInstanceOf[AnyRef] - @transient - override val hashCode = System.identityHashCode(this) + override def hashCode = System.identityHashCode(this) } /** Manifest for the class type `clazz[args]`, where `clazz` is From 2e4594f0739cc58b74f1de7d5c4cc51b72a1371a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 5 Feb 2024 14:06:14 +0100 Subject: [PATCH 30/65] Fix #4929: Fix logic for moving early assignements in JS ctors. Previously, we moved all statements in the constructors after the super constructor call. However, it turns out that there are statements that must be kept before, notably local `val`s generated for default arguments to the super constructor. We now keep statements where they are by default. We only move statements of the form `C.this.field = ident;`, which are the only ones that require access to `this`. This requires only a little bit of special-casing for outer pointer null checks. Fortunately, we already had some logic to identify and decompose those, which we reuse here. --- .../org/scalajs/nscplugin/GenJSCode.scala | 120 +++++++++++++++--- .../jsinterop/NonNativeJSTypeTestScala2.scala | 43 +++++++ .../jsinterop/NonNativeJSTypeTest.scala | 36 ++++++ 3 files changed, 184 insertions(+), 15 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 2cf95451db..5d46b0617b 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -1472,18 +1472,78 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val sym = dd.symbol assert(sym.isPrimaryConstructor, s"called with non-primary ctor: $sym") + var preSuperStats = List.newBuilder[js.Tree] var jsSuperCall: Option[js.JSSuperConstructorCall] = None - val jsStats = List.newBuilder[js.Tree] + val postSuperStats = List.newBuilder[js.Tree] - /* Move all statements after the super constructor call since JS - * cannot access `this` before the super constructor call. + /* Move param accessor initializers and early initializers after the + * super constructor call since JS cannot access `this` before the super + * constructor call. * * scalac inserts statements before the super constructor call for early * initializers and param accessor initializers (including val's and var's - * declared in the params). We move those after the super constructor - * call, and are therefore executed later than for a Scala class. + * declared in the params). Those statements include temporary local `val` + * definitions (for true early initializers only) and the assignments, + * whose rhs'es are always simple Idents (either constructor params or the + * temporary local `val`s). + * + * There can also be local `val`s before the super constructor call for + * default arguments to the super constructor. These must remain before. + * + * Our strategy is therefore to move only the field assignments after the + * super constructor call. They are therefore executed later than for a + * Scala class (as specified for non-native JS classes semantics). + * However, side effects and evaluation order of all the other + * computations remains unchanged. + * + * For a somewhat extreme example of the shapes we can get here, consider + * the source code: + * + * class Parent(output: Any = "output", callbackObject: Any = "callbackObject") extends js.Object { + * println(s"Parent constructor; $output; $callbackObject") + * } + * + * class Child(val foo: Int, callbackObject: Any, val bar: Int) extends { + * val xyz = foo + bar + * val yz = { println(xyz); xyz + 2 } + * } with Parent(callbackObject = { println(foo); xyz + bar }) { + * println("Child constructor") + * println(xyz) + * } + * + * At this phase, for the constructor of `Child`, we receive the following + * scalac Tree: + * + * def (foo: Int, callbackObject: Object, bar: Int): helloworld.Child = { + * Child.this.foo = foo; // param accessor assignment, moved + * Child.this.bar = bar; // param accessor assignment, moved + * val xyz: Int = foo.+(bar); // note that these still use the ctor params, not the fields + * Child.this.xyz = xyz; // early initializer, moved + * val yz: Int = { + * scala.Predef.println(scala.Int.box(xyz)); // note that this uses the local val, not the field + * xyz.+(2) + * }; + * Child.this.yz = yz; // early initializer, moved + * { + * val x$1: Int = { + * scala.Predef.println(scala.Int.box(foo)); + * xyz.+(bar) // here as well, we use the local vals, not the fields + * }; + * val x$2: Object = helloworld.this.Parent.$default$1(); + * Child.super.(x$2, scala.Int.box(x$1)) + * }; + * scala.Predef.println("Child constructor"); + * scala.Predef.println(scala.Int.box(Child.this.xyz())); + * () + * } + * */ withPerMethodBodyState(sym) { + def isThisFieldAssignment(tree: Tree): Boolean = tree match { + case Assign(Select(ths: This, _), Ident(_)) => ths.symbol == currentClassSym.get + case _ => false + } + flatStats(stats).foreach { case tree @ Apply(fun @ Select(Super(This(_), _), _), args) if fun.symbol.isClassConstructor => @@ -1491,14 +1551,27 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) implicit val pos = tree.pos jsSuperCall = Some(js.JSSuperConstructorCall(genPrimitiveJSArgs(fun.symbol, args))) - case stat => - val jsStat = genStat(stat) + case tree if jsSuperCall.isDefined => + // Once we're past the super constructor call, everything goes after. + postSuperStats += genStat(tree) - assert(jsSuperCall.isDefined || !jsStat.isInstanceOf[js.VarDef], - "Trying to move a local VarDef after the super constructor call " + - s"of a non-native JS class at ${dd.pos}") + case tree if isThisFieldAssignment(tree) => + /* If that shape appears before the jsSuperCall, it is an early + * initializer or param accessor initializer. We move it. + */ + postSuperStats += genStat(tree) + + case tree @ OuterPointerNullCheck(outer, assign) if isThisFieldAssignment(assign) => + /* Variant of the above with an outer pointer null check. The actual + * null check remains before the super call, while the associated + * assignment is moved after. + */ + preSuperStats += js.GetClass(genExpr(outer))(tree.pos) + postSuperStats += genStat(assign) - jsStats += jsStat + case stat => + // Other statements are left before. + preSuperStats += genStat(stat) } } @@ -1506,7 +1579,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) s"construtor at ${dd.pos}") new PrimaryJSCtor(sym, genParamsAndInfo(sym, vparamss), - js.JSConstructorBody(Nil, jsSuperCall.get, jsStats.result())(dd.pos)) + js.JSConstructorBody(preSuperStats.result(), jsSuperCall.get, postSuperStats.result())(dd.pos)) } private def genSecondaryJSClassCtor(dd: DefDef): SplitSecondaryJSCtor = { @@ -2301,9 +2374,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * this.$outer = $outer // the `else` branch in general */ tree match { - case If(Apply(fun @ Select(outer: Ident, nme.eq), Literal(Constant(null)) :: Nil), - Throw(Literal(Constant(null))), elsep) - if outer.symbol.isOuterParam && fun.symbol == definitions.Object_eq => + case OuterPointerNullCheck(outer, elsep) => js.Block( js.GetClass(genExpr(outer)), // null check genStat(elsep) @@ -2566,6 +2637,25 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } // end of GenJSCode.genExpr() + /** Extractor for the shape of outer pointer null check. + * + * See the comment in `case If(...) =>` of `genExpr`. + * + * When successful, returns the pair `(outer, elsep)` where `outer` is the + * `Ident` of the outer constructor parameter, and `elsep` is the else + * branch of the condition. + */ + private object OuterPointerNullCheck { + def unapply(tree: If): Option[(Ident, Tree)] = tree match { + case If(Apply(fun @ Select(outer: Ident, nme.eq), Literal(Constant(null)) :: Nil), + Throw(Literal(Constant(null))), elsep) + if outer.symbol.isOuterParam && fun.symbol == definitions.Object_eq => + Some((outer, elsep)) + case _ => + None + } + } + /** Gen JS this of the current class. * Normally encoded straightforwardly as a JS this. * But must be replaced by the tail-jump-this local variable if there diff --git a/test-suite/js/src/test/require-scala2/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTestScala2.scala b/test-suite/js/src/test/require-scala2/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTestScala2.scala index ea1c4c3f90..c3fcc9d69b 100644 --- a/test-suite/js/src/test/require-scala2/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTestScala2.scala +++ b/test-suite/js/src/test/require-scala2/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTestScala2.scala @@ -12,6 +12,8 @@ package org.scalajs.testsuite.jsinterop +import scala.collection.mutable + import scala.scalajs.js import scala.scalajs.js.annotation._ @@ -36,6 +38,29 @@ class NonNativeJSTypeTestScala2 { assertNull(obj.asInstanceOf[js.Dynamic].valueClass) } + @Test def callSuperConstructorWithDefaultParamsAndEarlyInitializers_Issue4929(): Unit = { + import ConstructorSuperCallWithDefaultParamsAndEarlyInitializers._ + + sideEffects.clear() + + val child = new Child(4, "hello", 23) + assertEquals(4, child.foo) + assertEquals(23, child.bar) + assertEquals(27, child.xyz) + assertEquals(29, child.yz) + + assertEquals( + List( + "27", + "4", + "Parent constructor; param1, 27, param1-27", + "Child constructor; 4, hello, 23", + "27, 29" + ), + sideEffects.toList + ) + } + } object NonNativeJSTypeTestScala2 { @@ -46,4 +71,22 @@ object NonNativeJSTypeTestScala2 { class SomeValueClass(val i: Int) extends AnyVal + object ConstructorSuperCallWithDefaultParamsAndEarlyInitializers { + val sideEffects = mutable.ListBuffer.empty[String] + + class Parent(parentParam1: Any = "param1", parentParam2: Any = "param2")( + dependentParam: String = s"$parentParam1-$parentParam2") + extends js.Object { + sideEffects += s"Parent constructor; $parentParam1, $parentParam2, $dependentParam" + } + + class Child(val foo: Int, parentParam2: Any, val bar: Int) extends { + val xyz = foo + bar + val yz = { sideEffects += xyz.toString(); xyz + 2 } + } with Parent(parentParam2 = { sideEffects += foo.toString(); foo + bar })() { + sideEffects += s"Child constructor; $foo, $parentParam2, $bar" + sideEffects += s"$xyz, $yz" + } + } + } diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTest.scala index 94cdf52847..0417489023 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTest.scala @@ -12,6 +12,8 @@ package org.scalajs.testsuite.jsinterop +import scala.collection.mutable + import scala.scalajs.js import scala.scalajs.js.annotation._ @@ -1306,6 +1308,25 @@ class NonNativeJSTypeTest { assertEquals(js.undefined, foo4.description) } + @Test def callSuperConstructorWithDefaultParams_Issue4929(): Unit = { + import ConstructorSuperCallWithDefaultParams._ + + sideEffects.clear() + + val child = new Child(4, "hello", 23) + assertEquals(4, child.foo) + assertEquals(23, child.bar) + + assertEquals( + List( + "4", + "Parent constructor; param1, 27, param1-27", + "Child constructor; 4, hello, 23" + ), + sideEffects.toList + ) + } + @Test def callSuperConstructorWithColonAsterisk(): Unit = { class CallSuperCtorWithSpread(x: Int, y: Int, z: Int) extends NativeParentClassWithVarargs(x, Seq(y, z): _*) @@ -2040,6 +2061,21 @@ object NonNativeJSTypeTest { def this(x: Int, y: Int) = this(x)(y.toString, js.undefined) } + object ConstructorSuperCallWithDefaultParams { + val sideEffects = mutable.ListBuffer.empty[String] + + class Parent(parentParam1: Any = "param1", parentParam2: Any = "param2")( + dependentParam: String = s"$parentParam1-$parentParam2") + extends js.Object { + sideEffects += s"Parent constructor; $parentParam1, $parentParam2, $dependentParam" + } + + class Child(val foo: Int, parentParam2: Any, val bar: Int) + extends Parent(parentParam2 = { sideEffects += foo.toString(); foo + bar })() { + sideEffects += s"Child constructor; $foo, $parentParam2, $bar" + } + } + class OverloadedConstructorParamNumber(val foo: Int) extends js.Object { def this(x: Int, y: Int) = this(x + y) def this(x: Int, y: Int, z: Int) = this(x + y, z) From db3b3ab640910b09a5a333a437c9aa02286e2ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 24 Jan 2024 13:42:20 +0100 Subject: [PATCH 31/65] Use `genSelect` in the intrinsics that access `jl.Class.data`. Instead of hard-coding the `"jl_Class__f_data"` string. --- .../org/scalajs/linker/backend/emitter/EmitterNames.scala | 1 + .../scalajs/linker/backend/emitter/FunctionEmitter.scala | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterNames.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterNames.scala index 17c03b75dc..4c1bcebc2b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterNames.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterNames.scala @@ -26,6 +26,7 @@ private[emitter] object EmitterNames { // Field names + val dataFieldName = FieldName("data") val exceptionFieldName = FieldName("exception") // Method names 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 c299d8fdd7..3f8dc28427 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 @@ -2733,7 +2733,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Transient(ZeroOf(runtimeClass)) => js.DotSelect( - js.DotSelect(transformExprNoChar(checkNotNull(runtimeClass)), js.Ident("jl_Class__f_data")), + genSelect(transformExprNoChar(checkNotNull(runtimeClass)), + ClassClass, FieldIdent(dataFieldName)), js.Ident("zero")) case Transient(NativeArrayWrapper(elemClass, nativeArray)) => @@ -2744,9 +2745,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { extractWithGlobals( genNativeArrayWrapper(arrayTypeRef, newNativeArray)) case _ => - val elemClassData = js.DotSelect( + val elemClassData = genSelect( transformExprNoChar(checkNotNull(elemClass)), - js.Ident("jl_Class__f_data")) + ClassClass, FieldIdent(dataFieldName)) val arrayClassData = js.Apply( js.DotSelect(elemClassData, js.Ident("getArrayOf")), Nil) js.Apply(arrayClassData DOT "wrapArray", newNativeArray :: Nil) From 96980516b4e8f477ca54a70f6d076678f8f94513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 24 Jan 2024 13:46:51 +0100 Subject: [PATCH 32/65] Go through SJSGen to generate Scala method names. This centralizes the decision on how to emit method names in one place, like we already had for fields. --- .../linker/backend/emitter/ClassEmitter.scala | 2 +- .../linker/backend/emitter/CoreJSLib.scala | 14 ++--- .../backend/emitter/FunctionEmitter.scala | 54 +++++++++---------- .../linker/backend/emitter/SJSGen.scala | 17 +++++- 4 files changed, 47 insertions(+), 40 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 aade6c4b8a..4a502b6331 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 @@ -670,7 +670,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { private def genMemberMethodIdent(ident: MethodIdent, originalName: OriginalName): js.Ident = { - val jsName = genName(ident.name) + val jsName = genMethodName(ident.name) js.Ident(jsName, genOriginalName(ident.name, originalName, jsName))( ident.pos) } 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 290bb7f362..6aff172312 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 @@ -717,7 +717,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"), - Apply(instance DOT genName(cloneMethodName), Nil), + genApply(instance, cloneMethodName, Nil), genCallHelper(VarField.objectClone, instance))) } ) @@ -767,7 +767,7 @@ private[emitter] object CoreJSLib { ), { If(instance === Null(), { if (nullPointers == CheckedBehavior.Unchecked) - Return(Apply(instance DOT genName(getClassMethodName), Nil)) + Return(genApply(instance, getClassMethodName, Nil)) else genCallHelper(VarField.throwNullPointerException) }, { @@ -813,7 +813,7 @@ private[emitter] object CoreJSLib { instance => genIdentBracketSelect(instance DOT classData, "name"), { if (nullPointers == CheckedBehavior.Unchecked) - Apply(Null() DOT genName(getNameMethodName), Nil) + genApply(Null(), getNameMethodName, Nil) else genCallHelper(VarField.throwNullPointerException) } @@ -862,7 +862,7 @@ private[emitter] object CoreJSLib { } def genBodyNoSwitch(hijackedClasses: List[ClassName]): Tree = { - val normalCall = Return(Apply(instance DOT genName(methodName), args)) + val normalCall = Return(genApply(instance, methodName, args)) def hijackedDispatch(default: Tree) = { hijackedClasses.foldRight(default) { (className, next) => @@ -874,7 +874,7 @@ private[emitter] object CoreJSLib { if (implementedInObject) { val staticObjectCall: Tree = { - val fun = globalVar(VarField.c, ObjectClass).prototype DOT genName(methodName) + val fun = globalVar(VarField.c, ObjectClass).prototype DOT genMethodName(methodName) Return(Apply(fun DOT "call", instance :: args)) } @@ -1498,7 +1498,7 @@ private[emitter] object CoreJSLib { Nil } - val clone = MethodDef(static = false, Ident(genName(cloneMethodName)), Nil, None, { + val clone = MethodDef(static = false, Ident(genMethodName(cloneMethodName)), Nil, None, { Return(New(ArrayClass, Apply(genIdentBracketSelect(This().u, "slice"), Nil) :: Nil)) }) @@ -1803,7 +1803,7 @@ private[emitter] object CoreJSLib { Nil } - val clone = MethodDef(static = false, Ident(genName(cloneMethodName)), Nil, None, { + val clone = MethodDef(static = false, Ident(genMethodName(cloneMethodName)), 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 3f8dc28427..9ee6f42ecd 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 @@ -2352,7 +2352,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { if (useBigIntForLongs) js.Apply(genGlobalVarRef("Number"), List(wrapBigInt32(newLhs))) else - genLongMethodApply(newLhs, LongImpl.toInt) + genApply(newLhs, LongImpl.toInt) case DoubleToInt => genCallHelper(VarField.doubleToInt, newLhs) case DoubleToFloat => @@ -2363,7 +2363,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { if (useBigIntForLongs) js.Apply(genGlobalVarRef("Number"), List(newLhs)) else - genLongMethodApply(newLhs, LongImpl.toDouble) + genApply(newLhs, LongImpl.toDouble) case DoubleToLong => if (useBigIntForLongs) genCallHelper(VarField.doubleToLong, newLhs) @@ -2375,7 +2375,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { if (useBigIntForLongs) genCallHelper(VarField.longToFloat, newLhs) else - genLongMethodApply(newLhs, LongImpl.toFloat) + genApply(newLhs, LongImpl.toFloat) // String.length case String_length => @@ -2488,25 +2488,25 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { if (useBigIntForLongs) wrapBigInt64(js.BinaryOp(JSBinaryOp.+, newLhs, newRhs)) else - genLongMethodApply(newLhs, LongImpl.+, newRhs) + genApply(newLhs, LongImpl.+, newRhs) case Long_- => lhs match { case LongLiteral(0L) => if (useBigIntForLongs) wrapBigInt64(js.UnaryOp(JSUnaryOp.-, newRhs)) else - genLongMethodApply(newRhs, LongImpl.UNARY_-) + genApply(newRhs, LongImpl.UNARY_-) case _ => if (useBigIntForLongs) wrapBigInt64(js.BinaryOp(JSBinaryOp.-, newLhs, newRhs)) else - genLongMethodApply(newLhs, LongImpl.-, newRhs) + genApply(newLhs, LongImpl.-, newRhs) } case Long_* => if (useBigIntForLongs) wrapBigInt64(js.BinaryOp(JSBinaryOp.*, newLhs, newRhs)) else - genLongMethodApply(newLhs, LongImpl.*, newRhs) + genApply(newLhs, LongImpl.*, newRhs) case Long_/ => if (useBigIntForLongs) { rhs match { @@ -2516,7 +2516,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genCallHelper(VarField.longDiv, newLhs, newRhs) } } else { - genLongMethodApply(newLhs, LongImpl./, newRhs) + genApply(newLhs, LongImpl./, newRhs) } case Long_% => if (useBigIntForLongs) { @@ -2527,78 +2527,78 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genCallHelper(VarField.longMod, newLhs, newRhs) } } else { - genLongMethodApply(newLhs, LongImpl.%, newRhs) + genApply(newLhs, LongImpl.%, newRhs) } case Long_| => if (useBigIntForLongs) wrapBigInt64(js.BinaryOp(JSBinaryOp.|, newLhs, newRhs)) else - genLongMethodApply(newLhs, LongImpl.|, newRhs) + genApply(newLhs, LongImpl.|, newRhs) case Long_& => if (useBigIntForLongs) wrapBigInt64(js.BinaryOp(JSBinaryOp.&, newLhs, newRhs)) else - genLongMethodApply(newLhs, LongImpl.&, newRhs) + genApply(newLhs, LongImpl.&, newRhs) case Long_^ => lhs match { case LongLiteral(-1L) => if (useBigIntForLongs) wrapBigInt64(js.UnaryOp(JSUnaryOp.~, newRhs)) else - genLongMethodApply(newRhs, LongImpl.UNARY_~) + genApply(newRhs, LongImpl.UNARY_~) case _ => if (useBigIntForLongs) wrapBigInt64(js.BinaryOp(JSBinaryOp.^, newLhs, newRhs)) else - genLongMethodApply(newLhs, LongImpl.^, newRhs) + genApply(newLhs, LongImpl.^, newRhs) } case Long_<< => if (useBigIntForLongs) wrapBigInt64(js.BinaryOp(JSBinaryOp.<<, newLhs, bigIntShiftRhs(newRhs))) else - genLongMethodApply(newLhs, LongImpl.<<, newRhs) + genApply(newLhs, LongImpl.<<, newRhs) case Long_>>> => if (useBigIntForLongs) wrapBigInt64(js.BinaryOp(JSBinaryOp.>>, wrapBigIntU64(newLhs), bigIntShiftRhs(newRhs))) else - genLongMethodApply(newLhs, LongImpl.>>>, newRhs) + genApply(newLhs, LongImpl.>>>, newRhs) case Long_>> => if (useBigIntForLongs) wrapBigInt64(js.BinaryOp(JSBinaryOp.>>, newLhs, bigIntShiftRhs(newRhs))) else - genLongMethodApply(newLhs, LongImpl.>>, newRhs) + genApply(newLhs, LongImpl.>>, newRhs) case Long_== => if (useBigIntForLongs) js.BinaryOp(JSBinaryOp.===, newLhs, newRhs) else - genLongMethodApply(newLhs, LongImpl.===, newRhs) + genApply(newLhs, LongImpl.===, newRhs) case Long_!= => if (useBigIntForLongs) js.BinaryOp(JSBinaryOp.!==, newLhs, newRhs) else - genLongMethodApply(newLhs, LongImpl.!==, newRhs) + genApply(newLhs, LongImpl.!==, newRhs) case Long_< => if (useBigIntForLongs) js.BinaryOp(JSBinaryOp.<, newLhs, newRhs) else - genLongMethodApply(newLhs, LongImpl.<, newRhs) + genApply(newLhs, LongImpl.<, newRhs) case Long_<= => if (useBigIntForLongs) js.BinaryOp(JSBinaryOp.<=, newLhs, newRhs) else - genLongMethodApply(newLhs, LongImpl.<=, newRhs) + genApply(newLhs, LongImpl.<=, newRhs) case Long_> => if (useBigIntForLongs) js.BinaryOp(JSBinaryOp.>, newLhs, newRhs) else - genLongMethodApply(newLhs, LongImpl.>, newRhs) + genApply(newLhs, LongImpl.>, newRhs) case Long_>= => if (useBigIntForLongs) js.BinaryOp(JSBinaryOp.>=, newLhs, newRhs) else - genLongMethodApply(newLhs, LongImpl.>=, newRhs) + genApply(newLhs, LongImpl.>=, newRhs) case Float_+ => genFround(js.BinaryOp(JSBinaryOp.+, newLhs, newRhs)) case Float_- => genFround(js.BinaryOp(JSBinaryOp.-, newLhs, newRhs)) @@ -2684,7 +2684,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { * those cases, leaving a `Clone()` node an array. */ case _: ArrayType => - js.Apply(newExpr DOT genName(cloneMethodName), Nil) + genApply(newExpr, cloneMethodName, Nil) /* Otherwise, if it might be an array, use the full dispatcher. * In theory, only the `CloneableClass` case is required, since @@ -3201,7 +3201,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.Ident(genName(ident.name))(ident.pos) private def transformMethodIdent(ident: MethodIdent): js.Ident = - js.Ident(genName(ident.name))(ident.pos) + js.Ident(genMethodName(ident.name))(ident.pos) private def transformLocalVarIdent(ident: LocalIdent): js.Ident = js.Ident(transformLocalName(ident.name))(ident.pos) @@ -3268,12 +3268,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.Apply(genIdentBracketSelect(genGlobalVarRef("BigInt"), "asUintN"), List(js.IntLiteral(n), tree)) } - - private def genLongMethodApply(receiver: js.Tree, methodName: MethodName, - args: js.Tree*)(implicit pos: Position): js.Tree = { - import TreeDSL._ - js.Apply(receiver DOT genName(methodName), args.toList) - } } } 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 0449e0ed92..6100df00eb 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 @@ -107,8 +107,8 @@ private[emitter] final class SJSGen( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { import TreeDSL._ - Apply( - genLoadModule(LongImpl.RuntimeLongModuleClass) DOT genName(methodName), + genApply( + genLoadModule(LongImpl.RuntimeLongModuleClass), methodName, args.toList) } @@ -172,6 +172,19 @@ private[emitter] final class SJSGen( private def genFieldJSName(className: ClassName, field: irt.FieldIdent): String = genName(className) + "__f_" + genName(field.name) + def genApply(receiver: Tree, methodName: MethodName, args: List[Tree])( + implicit pos: Position): Tree = { + Apply(DotSelect(receiver, Ident(genMethodName(methodName))), args) + } + + def genApply(receiver: Tree, methodName: MethodName, args: Tree*)( + implicit pos: Position): Tree = { + genApply(receiver, methodName, args.toList) + } + + def genMethodName(methodName: MethodName): String = + genName(methodName) + def genJSPrivateSelect(receiver: Tree, className: ClassName, field: irt.FieldIdent)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, From 73ad25c7be8c8a723c8f59fd018e198b5c86a44d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 24 Jan 2024 14:24:05 +0100 Subject: [PATCH 33/65] Transform and traverse the `name` subtree of `JSMethodPropDef`s. --- ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala | 4 ++-- ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 99a72aedb9..879864c047 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -267,7 +267,7 @@ object Transformers { case JSPropertyDef(flags, name, getterBody, setterArgAndBody) => JSPropertyDef( flags, - name, + transformExpr(name), getterBody.map(transformStat), setterArgAndBody map { case (arg, body) => (arg, transformStat(body)) @@ -277,7 +277,7 @@ object Transformers { def transformJSMethodDef(jsMethodDef: JSMethodDef): JSMethodDef = { val JSMethodDef(flags, name, args, restParam, body) = jsMethodDef - JSMethodDef(flags, name, args, restParam, transformExpr(body))( + JSMethodDef(flags, transformExpr(name), args, restParam, transformExpr(body))( jsMethodDef.optimizerHints, Unversioned)(jsMethodDef.pos) } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala index 7a4f5d9756..be26304b96 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -246,10 +246,12 @@ object Traversers { def traverseJSMethodPropDef(jsMethodPropDef: JSMethodPropDef): Unit = { jsMethodPropDef match { - case JSMethodDef(_, _, _, _, body) => + case JSMethodDef(_, name, _, _, body) => + traverse(name) traverse(body) - case JSPropertyDef(_, _, getterBody, setterArgAndBody) => + case JSPropertyDef(_, name, getterBody, setterArgAndBody) => + traverse(name) getterBody.foreach(traverse) setterArgAndBody.foreach(argAndBody => traverse(argAndBody._2)) } From 374b13e6e82cfe7f5f380d7de6b8be7f9245d966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 24 Jan 2024 17:30:11 +0100 Subject: [PATCH 34/65] Use implicits to be more explicit about global refs tracking. Normally, unless we track all global refs for the benefit of GCC, we prune away non-dangerous global refs as soon as possible, to preserve empty sets. This is done in two ways: * Within `FunctionEmitter`, we track everything for local purposes, but then prune the non-dangerous global refs when escaping back to `ClassEmitter`. * Everywhere else, we don't track non-dangerous global refs in the first place. This policy was broken in two places, because of helper code that is called both from inside and outside `FunctionEmitter`. In general, the way we applied that policy was very fragile, notably because it was not explicit what methods could be called in what context. We now use an `implicit GlobalRefTracking` in methods that generate global refs. The `GlobalRefTracking` indicates which set of global refs need to be tracked in the current context. Ironically, these implicits make it more explicit when we need to track what. --- .../linker/backend/emitter/ClassEmitter.scala | 20 +++--- .../linker/backend/emitter/CoreJSLib.scala | 5 +- .../linker/backend/emitter/Emitter.scala | 14 ++++- .../backend/emitter/FunctionEmitter.scala | 55 +++++++++-------- .../backend/emitter/GlobalRefTracking.scala | 47 ++++++++++++++ .../linker/backend/emitter/JSGen.scala | 16 +++-- .../linker/backend/emitter/SJSGen.scala | 61 +++++++++---------- .../linker/backend/emitter/VarGen.scala | 20 +++--- 8 files changed, 147 insertions(+), 91 deletions(-) create mode 100644 linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/GlobalRefTracking.scala 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 4a502b6331..dff91a1fd1 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 @@ -43,6 +43,9 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { import nameGen._ import varGen._ + private implicit val globalRefTracking: GlobalRefTracking = + topLevelGlobalRefTracking + def buildClass(className: ClassName, isJSClass: Boolean, jsClassCaptures: Option[List[ParamDef]], hasClassInitializer: Boolean, superClass: Option[ClassIdent], storeJSSuperClass: List[js.Tree], useESClass: Boolean, @@ -56,7 +59,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { if (useESClass) { val parentVarWithGlobals = for (parentIdent <- superClass) yield { implicit val pos = parentIdent.pos - if (shouldExtendJSError(className, superClass)) untrackedGlobalRef("Error") + if (shouldExtendJSError(className, superClass)) globalRef("Error") else WithGlobals(globalVar(VarField.c, parentIdent.name)) } @@ -186,7 +189,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { WithGlobals.nil case Some(_) if shouldExtendJSError(className, superClass) => - untrackedGlobalRef("Error").map(chainPrototypeWithLocalCtor(className, ctorVar, _)) + globalRef("Error").map(chainPrototypeWithLocalCtor(className, ctorVar, _)) case Some(parentIdent) => WithGlobals(List(ctorVar.prototype := js.New(globalVar(VarField.h, parentIdent.name), Nil))) @@ -252,7 +255,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { if (hasJSSuperClass) { WithGlobals(fileLevelVar(VarField.superClass)) } else { - genJSClassConstructor(superClass.get.name, keepOnlyDangerousVarNames = true) + genJSClassConstructor(superClass.get.name) } } @@ -899,8 +902,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { })) } else { for { - jsCtor <- genJSClassConstructor(className, jsNativeLoadSpec, - keepOnlyDangerousVarNames = true) + jsCtor <- genJSClassConstructor(className, jsNativeLoadSpec) } yield { genArrowFunction(List(js.ParamDef(js.Ident("x"))), None, js.Return { js.VarRef(js.Ident("x")) instanceof jsCtor @@ -1075,12 +1077,8 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { private def genAssignToNoModuleExportVar(exportName: String, rhs: js.Tree)( implicit pos: Position): WithGlobals[js.Tree] = { - val dangerousGlobalRefs: Set[String] = - if (GlobalRefUtils.isDangerousGlobalRef(exportName)) Set(exportName) - else Set.empty - WithGlobals( - js.Assign(js.VarRef(js.Ident(exportName)), rhs), - dangerousGlobalRefs) + for (exportVar <- globalRef(exportName)) yield + js.Assign(exportVar, rhs) } private def genTopLevelFieldExportDef(className: ClassName, 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 6aff172312..b36e35783d 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 @@ -71,6 +71,9 @@ private[emitter] object CoreJSLib { implicit private val noPosition: Position = Position.NoPosition + private implicit val globalRefTracking: GlobalRefTracking = + topLevelGlobalRefTracking + private var trackedGlobalRefs = Set.empty[String] private def globalRef(name: String): VarRef = { @@ -81,7 +84,7 @@ private[emitter] object CoreJSLib { private def trackGlobalRef(name: String): Unit = { // We never access dangerous global refs from the core JS lib assert(!GlobalRefUtils.isDangerousGlobalRef(name)) - if (trackAllGlobalRefs) + if (globalRefTracking.shouldTrack(name)) trackedGlobalRefs += name } 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 1906ffe84f..1a8b9bc517 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._ + private implicit val globalRefTracking: GlobalRefTracking = + config.topLevelGlobalRefTracking + private val knowledgeGuardian = new KnowledgeGuardian(config) private val uncachedKnowledge = new knowledgeGuardian.KnowledgeAccessor {} @@ -173,15 +176,16 @@ final class Emitter[E >: Null <: js.Tree]( val result = emitOnce(moduleSet, logger) val mentionedDangerousGlobalRefs = - if (!trackAllGlobalRefs) result.globalVarNames - else GlobalRefUtils.keepOnlyDangerousGlobalRefs(result.globalVarNames) + GlobalRefTracking.Dangerous.refineFrom(topLevelGlobalRefTracking, result.globalVarNames) if (mentionedDangerousGlobalRefs == state.lastMentionedDangerousGlobalRefs) { result } else { assert(!secondAttempt, "Uh oh! The second attempt gave a different set of dangerous " + - "global refs than the first one.") + "global refs than the first one.\n" + + "Before:" + state.lastMentionedDangerousGlobalRefs.toList.sorted.mkString("\n ", "\n ", "\n") + + "After:" + mentionedDangerousGlobalRefs.toList.sorted.mkString("\n ", "\n ", "")) // !!! This log message is tested in EmitterTest logger.debug( @@ -1096,6 +1100,10 @@ object Emitter { trackAllGlobalRefs = false) } + private[emitter] val topLevelGlobalRefTracking: GlobalRefTracking = + if (trackAllGlobalRefs) GlobalRefTracking.All + else GlobalRefTracking.Dangerous + def withSemantics(f: Semantics => Semantics): Config = copy(semantics = f(semantics)) 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 9ee6f42ecd..d2f83eb4a3 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 @@ -259,7 +259,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def desugarToFunction(enclosingClassName: ClassName, params: List[ParamDef], body: Tree, resultType: Type)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[js.Function] = { + globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[js.Function] = { desugarToFunction(enclosingClassName, params, restParam = None, body, resultType) } @@ -269,10 +269,10 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def desugarToFunction(enclosingClassName: ClassName, params: List[ParamDef], restParam: Option[ParamDef], body: JSConstructorBody)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[js.Function] = { + globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[js.Function] = { val bodyBlock = Block(body.allStats)(body.pos) - new JSDesugar().desugarToFunction(params, restParam, bodyBlock, - isStat = false, + new JSDesugar(globalRefTracking).desugarToFunction( + params, restParam, bodyBlock, isStat = false, Env.empty(AnyType).withEnclosingClassName(Some(enclosingClassName))) } @@ -281,9 +281,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def desugarToFunction(enclosingClassName: ClassName, params: List[ParamDef], restParam: Option[ParamDef], body: Tree, resultType: Type)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[js.Function] = { - new JSDesugar().desugarToFunction(params, restParam, body, - isStat = resultType == NoType, + globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[js.Function] = { + new JSDesugar(globalRefTracking).desugarToFunction( + params, restParam, body, isStat = resultType == NoType, Env.empty(resultType).withEnclosingClassName(Some(enclosingClassName))) } @@ -293,9 +293,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def desugarToFunctionWithExplicitThis(enclosingClassName: ClassName, params: List[ParamDef], body: Tree, resultType: Type)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[js.Function] = { - new JSDesugar().desugarToFunctionWithExplicitThis(params, body, - isStat = resultType == NoType, + globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[js.Function] = { + new JSDesugar(globalRefTracking).desugarToFunctionWithExplicitThis( + params, body, isStat = resultType == NoType, Env.empty(resultType).withEnclosingClassName(Some(enclosingClassName))) } @@ -304,15 +304,16 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def desugarToFunction(params: List[ParamDef], restParam: Option[ParamDef], body: Tree, resultType: Type)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[js.Function] = { - new JSDesugar().desugarToFunction(params, restParam, body, - isStat = resultType == NoType, Env.empty(resultType)) + globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[js.Function] = { + new JSDesugar(globalRefTracking).desugarToFunction( + params, restParam, body, isStat = resultType == NoType, + Env.empty(resultType)) } /** Desugars a class-level expression. */ def desugarExpr(expr: Tree, resultType: Type)( - implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { + implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, + globalRefTracking: GlobalRefTracking): WithGlobals[js.Tree] = { implicit val pos = expr.pos for (fun <- desugarToFunction(Nil, None, expr, resultType)) yield { @@ -326,9 +327,13 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { } } - private class JSDesugar()( + private class JSDesugar(outerGlobalRefTracking: GlobalRefTracking)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge) { + // Inside JSDesugar, we always track everything + private implicit val globalRefTracking: GlobalRefTracking = + GlobalRefTracking.All + // For convenience private val es2015 = esFeatures.esVersion >= ESVersion.ES2015 @@ -410,7 +415,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { val result = body if (!isOptimisticNamingRun || !globalVarNames.exists(localVarNames)) { /* At this point, filter out the global refs that do not need to be - * tracked across functions and classes. + * tracked in the outer context. * * By default, only dangerous global refs need to be tracked outside of * functions, to power `mentionedDangerousGlobalRefs` In that case, the @@ -422,11 +427,10 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { * need to track all global variables across functions and classes. This is * slower, but running GCC will take most of the time anyway in that case. */ - val globalRefs = - if (trackAllGlobalRefs) globalVarNames.toSet - else GlobalRefUtils.keepOnlyDangerousGlobalRefs(globalVarNames.toSet) + val outerGlobalRefs = + outerGlobalRefTracking.refineFrom(globalRefTracking, globalVarNames.toSet) - WithGlobals(result, globalRefs) + WithGlobals(result, outerGlobalRefs) } else { /* Clear the local var names, but *not* the global var names. * In the pessimistic run, we will use the knowledge gathered during @@ -2214,8 +2218,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case SelectJSNativeMember(className, member) => val jsNativeLoadSpec = globalKnowledge.getJSNativeLoadSpec(className, member.name) - extractWithGlobals(genLoadJSFromSpec( - jsNativeLoadSpec, keepOnlyDangerousVarNames = false)) + extractWithGlobals(genLoadJSFromSpec(jsNativeLoadSpec)) case Apply(_, receiver, method, args) => val methodName = method.name @@ -2863,8 +2866,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genLoadModule(className) case Some(spec) => - extractWithGlobals( - genLoadJSFromSpec(spec, keepOnlyDangerousVarNames = false)) + extractWithGlobals(genLoadJSFromSpec(spec)) } case JSUnaryOp(op, lhs) => @@ -3229,8 +3231,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { */ private def genJSClassConstructor(className: ClassName)( implicit pos: Position): WithGlobals[js.Tree] = { - sjsGen.genJSClassConstructor(className, - keepOnlyDangerousVarNames = false) + sjsGen.genJSClassConstructor(className) } private def genApplyStaticLike(field: VarField, className: ClassName, diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/GlobalRefTracking.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/GlobalRefTracking.scala new file mode 100644 index 0000000000..13f32d5ebc --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/GlobalRefTracking.scala @@ -0,0 +1,47 @@ +/* + * 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 + +/** Amount of global ref tracking that we need to do in a given context. + * + * - When using GCC, we always track all global refs; + * - Otherwise, inside the body of a function, in `FunctionEmitter`, we track + * all global refs so that we can identify collisions with locally allocated + * variable names; + * - Otherwise, we only track dangerous global refs. + */ +private[emitter] sealed abstract class GlobalRefTracking { + import GlobalRefTracking._ + + def shouldTrack(globalRef: String): Boolean = this match { + case All => true + case Dangerous => GlobalRefUtils.isDangerousGlobalRef(globalRef) + } + + /** Given a set of global refs tracked under the rules of `fromTracking`, + * keep only the ones needed according to `this`. + */ + def refineFrom(fromTracking: GlobalRefTracking, globalRefs: Set[String]): Set[String] = { + if (this == fromTracking) + globalRefs + else if (this == Dangerous) + GlobalRefUtils.keepOnlyDangerousGlobalRefs(globalRefs) + else + throw new AssertionError(s"Cannot refine set of global refs from $fromTracking to $this") + } +} + +private[emitter] object GlobalRefTracking { + case object All extends GlobalRefTracking + case object Dangerous extends GlobalRefTracking +} 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 3ed36197a5..9d0150c2c9 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 @@ -127,7 +127,7 @@ private[emitter] final class JSGen(val config: Emitter.Config) { } def genDefineProperty(obj: Tree, prop: Tree, descriptor: List[(String, Tree)])( - implicit pos: Position): WithGlobals[Tree] = { + implicit tracking: GlobalRefTracking, pos: Position): WithGlobals[Tree] = { val descriptorTree = ObjectConstr(descriptor.map(x => StringLiteral(x._1) -> x._2)) @@ -137,14 +137,12 @@ private[emitter] final class JSGen(val config: Emitter.Config) { } } - def globalRef(name: String)(implicit pos: Position): WithGlobals[VarRef] = - WithGlobals(VarRef(Ident(name)), Set(name)) - - def untrackedGlobalRef(name: String)(implicit pos: Position): WithGlobals[VarRef] = { - assert(!GlobalRefUtils.isDangerousGlobalRef(name)) - - if (trackAllGlobalRefs) globalRef(name) - else WithGlobals(VarRef(Ident(name))) + def globalRef(name: String)( + implicit tracking: GlobalRefTracking, pos: Position): WithGlobals[VarRef] = { + val trackedSet: Set[String] = + if (tracking.shouldTrack(name)) Set(name) + else Set.empty + WithGlobals(VarRef(Ident(name)), trackedSet) } def genPropSelect(qual: Tree, item: PropertyName)( 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 6100df00eb..4cc050d33c 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 @@ -112,11 +112,18 @@ private[emitter] final class SJSGen( args.toList) } - def usesUnderlyingTypedArray(elemTypeRef: NonArrayTypeRef): Boolean = - getArrayUnderlyingTypedArrayClassRef(elemTypeRef)(Position.NoPosition).nonEmpty + def usesUnderlyingTypedArray(elemTypeRef: NonArrayTypeRef): Boolean = { + /* We are only interested in whether `getArrayUnderlyingTypedArrayClassRef` + * returns a `Some` or not. We do not keep the result, so the `Position` + * and the `GlobalRefTracking` are irrelevant. + */ + implicit val dontCareGlobalRefTracking = GlobalRefTracking.Dangerous + implicit val dontCarePosition = Position.NoPosition + getArrayUnderlyingTypedArrayClassRef(elemTypeRef).nonEmpty + } def getArrayUnderlyingTypedArrayClassRef(elemTypeRef: NonArrayTypeRef)( - implicit pos: Position): Option[WithGlobals[VarRef]] = { + implicit tracking: GlobalRefTracking, pos: Position): Option[WithGlobals[VarRef]] = { elemTypeRef match { case _ if esFeatures.esVersion < ESVersion.ES2015 => None case primRef: PrimRef => typedArrayRef(primRef) @@ -125,7 +132,7 @@ private[emitter] final class SJSGen( } def typedArrayRef(primRef: PrimRef)( - implicit pos: Position): Option[WithGlobals[VarRef]] = { + implicit tracking: GlobalRefTracking, pos: Position): Option[WithGlobals[VarRef]] = { def some(name: String) = Some(globalRef(name)) primRef match { @@ -295,7 +302,7 @@ private[emitter] final class SJSGen( def genAsInstanceOf(expr: Tree, tpe: Type)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[Tree] = { + tracking: GlobalRefTracking, pos: Position): WithGlobals[Tree] = { import TreeDSL._ // Local short-hand of WithGlobals(...) @@ -407,7 +414,7 @@ private[emitter] final class SJSGen( def genCallPolyfillableBuiltin(builtin: PolyfillableBuiltin, args: Tree*)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[Tree] = { + tracking: GlobalRefTracking, pos: Position): WithGlobals[Tree] = { if (esFeatures.esVersion >= builtin.availableInESVersion) { builtin match { case builtin: GlobalVarBuiltin => @@ -441,28 +448,25 @@ private[emitter] final class SJSGen( } } - def genJSClassConstructor(className: ClassName, - keepOnlyDangerousVarNames: Boolean)( + def genJSClassConstructor(className: ClassName)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[Tree] = { + tracking: GlobalRefTracking, pos: Position): WithGlobals[Tree] = { genJSClassConstructor(className, - globalKnowledge.getJSNativeLoadSpec(className), - keepOnlyDangerousVarNames) + globalKnowledge.getJSNativeLoadSpec(className)) } def genJSClassConstructor(className: ClassName, - spec: Option[irt.JSNativeLoadSpec], - keepOnlyDangerousVarNames: Boolean)( + spec: Option[irt.JSNativeLoadSpec])( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[Tree] = { + tracking: GlobalRefTracking, pos: Position): WithGlobals[Tree] = { spec match { case None => // This is a non-native JS class WithGlobals(genNonNativeJSClassConstructor(className)) case Some(spec) => - genLoadJSFromSpec(spec, keepOnlyDangerousVarNames) + genLoadJSFromSpec(spec) } } @@ -472,10 +476,9 @@ private[emitter] final class SJSGen( Apply(globalVar(VarField.a, className), Nil) } - def genLoadJSFromSpec(spec: irt.JSNativeLoadSpec, - keepOnlyDangerousVarNames: Boolean)( + def genLoadJSFromSpec(spec: irt.JSNativeLoadSpec)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[Tree] = { + tracking: GlobalRefTracking, pos: Position): WithGlobals[Tree] = { def pathSelection(from: Tree, path: List[String]): Tree = { path.foldLeft(from) { @@ -484,17 +487,9 @@ private[emitter] final class SJSGen( } spec match { - case irt.JSNativeLoadSpec.Global(globalRef, path) => - val globalVarRef = VarRef(Ident(globalRef)) - val globalVarNames = { - if (keepOnlyDangerousVarNames && !trackAllGlobalRefs && - !GlobalRefUtils.isDangerousGlobalRef(globalRef)) { - Set.empty[String] - } else { - Set(globalRef) - } - } - WithGlobals(pathSelection(globalVarRef, path), globalVarNames) + case irt.JSNativeLoadSpec.Global(globalRefName, path) => + for (globalVarRef <- globalRef(globalRefName)) yield + pathSelection(globalVarRef, path) case irt.JSNativeLoadSpec.Import(module, path) => val moduleValue = VarRef(externalModuleFieldIdent(module)) @@ -509,9 +504,9 @@ private[emitter] final class SJSGen( case irt.JSNativeLoadSpec.ImportWithGlobalFallback(importSpec, globalSpec) => moduleKind match { case ModuleKind.NoModule => - genLoadJSFromSpec(globalSpec, keepOnlyDangerousVarNames) + genLoadJSFromSpec(globalSpec) case ModuleKind.ESModule | ModuleKind.CommonJSModule => - genLoadJSFromSpec(importSpec, keepOnlyDangerousVarNames) + genLoadJSFromSpec(importSpec) } } } @@ -533,13 +528,13 @@ private[emitter] final class SJSGen( def genArrayValue(arrayTypeRef: ArrayTypeRef, elems: List[Tree])( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[Tree] = { + tracking: GlobalRefTracking, pos: Position): WithGlobals[Tree] = { genNativeArrayWrapper(arrayTypeRef, ArrayConstr(elems)) } def genNativeArrayWrapper(arrayTypeRef: ArrayTypeRef, nativeArray: Tree)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[Tree] = { + tracking: GlobalRefTracking, pos: Position): WithGlobals[Tree] = { val argWithGlobals = arrayTypeRef match { case ArrayTypeRef(elemTypeRef, 1) => getArrayUnderlyingTypedArrayClassRef(elemTypeRef) match { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala index b58ac77235..7b7f87859d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala @@ -54,7 +54,8 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, def globalClassDef[T: Scope](field: VarField, scope: T, parentClass: Option[Tree], members: List[Tree], origName: OriginalName = NoOriginalName)( - implicit moduleContext: ModuleContext, pos: Position): WithGlobals[List[Tree]] = { + implicit moduleContext: ModuleContext, + globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[List[Tree]] = { val ident = globalVarIdent(field, scope, origName) maybeExport(ident, ClassDef(Some(ident), parentClass, members), mutable = false) } @@ -62,14 +63,16 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, def globalFunctionDef[T: Scope](field: VarField, scope: T, args: List[ParamDef], restParam: Option[ParamDef], body: Tree, origName: OriginalName = NoOriginalName)( - implicit moduleContext: ModuleContext, pos: Position): WithGlobals[List[Tree]] = { + implicit moduleContext: ModuleContext, + globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[List[Tree]] = { val ident = globalVarIdent(field, scope, origName) maybeExport(ident, FunctionDef(ident, args, restParam, body), mutable = false) } def globalVarDef[T: Scope](field: VarField, scope: T, value: Tree, origName: OriginalName = NoOriginalName)( - implicit moduleContext: ModuleContext, pos: Position): WithGlobals[List[Tree]] = { + implicit moduleContext: ModuleContext, + globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[List[Tree]] = { val ident = globalVarIdent(field, scope, origName) maybeExport(ident, genConst(ident, value), mutable = false) } @@ -77,7 +80,8 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, /** Attention: A globalVarDecl may only be modified from the module it was declared in. */ def globalVarDecl[T: Scope](field: VarField, scope: T, origName: OriginalName = NoOriginalName)( - implicit moduleContext: ModuleContext, pos: Position): WithGlobals[List[Tree]] = { + implicit moduleContext: ModuleContext, + globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[List[Tree]] = { val ident = globalVarIdent(field, scope, origName) maybeExport(ident, genEmptyMutableLet(ident), mutable = true) } @@ -88,7 +92,8 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, */ def globallyMutableVarDef[T: Scope](field: VarField, setterField: VarField, scope: T, value: Tree, origName: OriginalName = NoOriginalName)( - implicit moduleContext: ModuleContext, pos: Position): WithGlobals[List[Tree]] = { + implicit moduleContext: ModuleContext, + globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[List[Tree]] = { val ident = globalVarIdent(field, scope, origName) val varDef = genLet(ident, mutable = true, value) @@ -135,7 +140,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, /** Apply the provided body to a dynamically loaded global var */ def withDynamicGlobalVar[T: Scope](field: VarField, scope: T)(body: Tree => Tree)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): WithGlobals[Tree] = { + globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[Tree] = { val ident = globalVarIdent(field, scope) val module = fileLevelVarIdent(VarField.module) @@ -269,7 +274,8 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, } private def maybeExport(ident: Ident, tree: Tree, mutable: Boolean)( - implicit moduleContext: ModuleContext, pos: Position): WithGlobals[List[Tree]] = { + implicit moduleContext: ModuleContext, + globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[List[Tree]] = { if (moduleContext.public) { WithGlobals(tree :: Nil) } else { From 7b1fece0fb665a40aa2ed05445b8df6ec2cdc7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 12 Feb 2024 11:41:47 +0100 Subject: [PATCH 35/65] Bump the version to 1.16.0-SNAPSHOT for the upcoming changes. As well as the IR version to 1.16-SNAPSHOT. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index 8dfbb764e2..91d113055f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,8 +17,8 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.15.1-SNAPSHOT", - binaryEmitted = "1.13" + current = "1.16.0-SNAPSHOT", + binaryEmitted = "1.16-SNAPSHOT" ) /** Helper class to allow for testing of logic. */ From 659d51808b94e46a13efd7599d6119d23ea07dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 12 Feb 2024 13:12:20 +0100 Subject: [PATCH 36/65] Remove the parameters to `StoreModule` IR nodes. `StoreModule` was always implicitly assumed to be used only in the constructor of the corresponding module class, and with a `This()` rhs. We now include that assumption as a core truth of `StoreModule()`, and therefore remove its two arguments. A deserialization hack reads the old arguments and throws if they do not conform to the above assumption, before discarding them. --- .../org/scalajs/nscplugin/GenJSCode.scala | 5 +- .../main/scala/org/scalajs/ir/Hashers.scala | 4 +- .../main/scala/org/scalajs/ir/Printers.scala | 7 +-- .../scala/org/scalajs/ir/Serializers.scala | 22 +++++++- .../scala/org/scalajs/ir/Transformers.scala | 10 ++-- .../scala/org/scalajs/ir/Traversers.scala | 10 ++-- .../src/main/scala/org/scalajs/ir/Trees.scala | 3 +- .../scala/org/scalajs/ir/PrintersTest.scala | 3 +- .../backend/emitter/FunctionEmitter.scala | 9 +-- .../linker/checker/ClassDefChecker.scala | 38 ++++++++++--- .../scalajs/linker/checker/IRChecker.scala | 11 +--- .../frontend/optimizer/IncOptimizer.scala | 4 +- .../frontend/optimizer/OptimizerCore.scala | 9 +-- .../linker/checker/ClassDefCheckerTest.scala | 56 +++++++++++++++++++ project/BinaryIncompatibilities.scala | 3 + 15 files changed, 135 insertions(+), 59 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 5d46b0617b..2c2c0478ea 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -3191,10 +3191,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (isStaticModule(currentClassSym) && !isModuleInitialized.value && currentMethodSym.isClassConstructor) { isModuleInitialized.value = true - val className = encodeClassName(currentClassSym) - val initModule = - js.StoreModule(className, js.This()(jstpe.ClassType(className))) - js.Block(superCall, initModule) + js.Block(superCall, js.StoreModule()) } else { superCall } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index 9246cc6874..e363efccfb 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -259,10 +259,8 @@ object Hashers { mixTag(TagLoadModule) mixName(className) - case StoreModule(className, value) => + case StoreModule() => mixTag(TagStoreModule) - mixName(className) - mixTree(value) case Select(qualifier, className, field) => mixTag(TagSelect) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index f3efeb3d52..deb6ded863 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -296,11 +296,8 @@ object Printers { print("mod:") print(className) - case StoreModule(className, value) => - print("mod:") - print(className) - print("<-") - print(value) + case StoreModule() => + print("") case Select(qualifier, className, field) => print(qualifier) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index a2eb58cd91..f4c02e8760 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -318,9 +318,8 @@ object Serializers { writeTagAndPos(TagLoadModule) writeName(className) - case StoreModule(className, value) => + case StoreModule() => writeTagAndPos(TagStoreModule) - writeName(className); writeTree(value) case Select(qualifier, className, field) => writeTagAndPos(TagSelect) @@ -1012,6 +1011,7 @@ object Serializers { private[this] var lastPosition: Position = Position.NoPosition + private[this] var enclosingClassName: ClassName = _ private[this] var thisTypeForHack8: Type = NoType def deserializeEntryPointsInfo(): EntryPointsInfo = { @@ -1156,7 +1156,18 @@ object Serializers { case TagNew => New(readClassName(), readMethodIdent(), readTrees()) case TagLoadModule => LoadModule(readClassName()) - case TagStoreModule => StoreModule(readClassName(), readTree()) + + case TagStoreModule => + if (hacks.use13) { + val cls = readClassName() + val rhs = readTree() + if (cls != enclosingClassName || !rhs.isInstanceOf[This]) { + throw new IOException( + s"Illegal legacy StoreModule(${cls.nameString}, $rhs) " + + s"found in class ${enclosingClassName.nameString}") + } + } + StoreModule() case TagSelect => val qualifier = readTree() @@ -1359,8 +1370,11 @@ object Serializers { def readClassDef(): ClassDef = { implicit val pos = readPosition() + val name = readClassIdent() val cls = name.name + enclosingClassName = cls + val originalName = readOriginalName() val kind = ClassKind.fromByte(readByte()) @@ -2073,6 +2087,8 @@ object Serializers { private val use11: Boolean = use8 || sourceVersion == "1.11" val use12: Boolean = use11 || sourceVersion == "1.12" + + val use13: Boolean = use12 || sourceVersion == "1.13" } /** Names needed for hacks. */ diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 879864c047..415db46f33 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -91,9 +91,6 @@ object Transformers { case New(className, ctor, args) => New(className, ctor, args map transformExpr) - case StoreModule(className, value) => - StoreModule(className, transformExpr(value)) - case Select(qualifier, className, field) => Select(transformExpr(qualifier), className, field)(tree.tpe) @@ -223,9 +220,10 @@ object Transformers { // Trees that need not be transformed - case _:Skip | _:Debugger | _:LoadModule | _:SelectStatic | _:SelectJSNativeMember | - _:LoadJSConstructor | _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | - _:JSLinkingInfo | _:Literal | _:VarRef | _:This | _:JSGlobalRef => + case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | + _:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor | + _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | _:JSLinkingInfo | + _:Literal | _:VarRef | _:This | _:JSGlobalRef => tree } } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala index be26304b96..2040341a74 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -77,9 +77,6 @@ object Traversers { case New(_, _, args) => args foreach traverse - case StoreModule(_, value) => - traverse(value) - case Select(qualifier, _, _) => traverse(qualifier) @@ -222,9 +219,10 @@ object Traversers { // Trees that need not be traversed - case _:Skip | _:Debugger | _:LoadModule | _:SelectStatic | _:SelectJSNativeMember | - _:LoadJSConstructor | _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | - _:JSLinkingInfo | _:Literal | _:VarRef | _:This | _:JSGlobalRef => + case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | + _:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor | + _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | _:JSLinkingInfo | + _:Literal | _:VarRef | _:This | _:JSGlobalRef => } def traverseClassDef(tree: ClassDef): Unit = { diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 25b1c54575..631e39642a 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -237,8 +237,7 @@ object Trees { val tpe = ClassType(className) } - sealed case class StoreModule(className: ClassName, value: Tree)( - implicit val pos: Position) extends Tree { + sealed case class StoreModule()(implicit val pos: Position) extends Tree { val tpe = NoType // cannot be in expression position } diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index b8af4c7fe0..03c1628f16 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -302,8 +302,7 @@ class PrintersTest { } @Test def printStoreModule(): Unit = { - assertPrintEquals("mod:scala.Predef$<-this", - StoreModule("scala.Predef$", This()("scala.Predef$"))) + assertPrintEquals("", StoreModule()) } @Test def printSelect(): Unit = { 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 d2f83eb4a3..128e8ebeed 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 @@ -693,11 +693,12 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { pushLhsInto(Lhs.Assign(lhs), rhs, tailPosLabels) } - case StoreModule(className, value) => - unnest(value) { (newValue, env0) => - implicit val env = env0 - js.Assign(globalVar(VarField.n, className), transformExprNoChar(newValue)) + case StoreModule() => + val enclosingClassName = env.enclosingClassName.getOrElse { + throw new AssertionError( + "Need enclosing class for StoreModule().") } + js.Assign(globalVar(VarField.n, enclosingClassName), js.This()) case While(cond, body) => val loopEnv = env.withInLoopForVarCapture(true) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index 9ce0e7175a..ba64a3b9b1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -292,7 +292,10 @@ private final class ClassDefChecker(classDef: ClassDef, // Body val thisType = if (static) NoType else instanceThisType - body.foreach(checkTree(_, Env.fromParams(params).withThisType(thisType))) + val bodyEnv = Env.fromParams(params) + .withThisType(thisType) + .withInConstructor(isConstructor) + body.foreach(checkTree(_, bodyEnv)) } private def checkJSConstructorDef(ctorDef: JSConstructorDef): Unit = withPerMethodState { @@ -311,6 +314,7 @@ private final class ClassDefChecker(classDef: ClassDef, val startEnv = Env.fromParams(classDef.jsClassCaptures.getOrElse(Nil) ++ params ++ restParam) .withHasNewTarget(true) + .withInConstructor(true) val envJustBeforeSuper = body.beforeSuper.foldLeft(startEnv) { (prevEnv, stat) => checkTree(stat, prevEnv) @@ -608,8 +612,13 @@ private final class ClassDefChecker(classDef: ClassDef, case _: LoadModule => - case StoreModule(_, value) => - checkTree(value, env) + case StoreModule() => + if (!classDef.kind.hasModuleAccessor) + reportError(i"Illegal StoreModule inside class of kind ${classDef.kind}") + if (!env.inConstructor) + reportError(i"Illegal StoreModule outside of constructor") + if (env.thisType == NoType) // can happen before JSSuperConstructorCall in JSModuleClass + reportError(i"Cannot find `this` in scope for StoreModule()") case Select(qualifier, _, _) => checkTree(qualifier, env) @@ -922,7 +931,9 @@ object ClassDefChecker { /** Local variables in scope (including through closures). */ val locals: Map[LocalName, LocalDef], /** Return types by label. */ - val returnLabels: Set[LabelName] + val returnLabels: Set[LabelName], + /** Whether we are in a constructor of the class. */ + val inConstructor: Boolean ) { import Env._ @@ -938,25 +949,36 @@ object ClassDefChecker { def withLabel(label: LabelName): Env = copy(returnLabels = returnLabels + label) + def withInConstructor(inConstructor: Boolean): Env = + copy(inConstructor = inConstructor) + private def copy( hasNewTarget: Boolean = hasNewTarget, thisType: Type = thisType, locals: Map[LocalName, LocalDef] = locals, - returnLabels: Set[LabelName] = returnLabels + returnLabels: Set[LabelName] = returnLabels, + inConstructor: Boolean = inConstructor ): Env = { - new Env(hasNewTarget, thisType, locals, returnLabels) + new Env(hasNewTarget, thisType, locals, returnLabels, inConstructor) } } private object Env { val empty: Env = - new Env(hasNewTarget = false, thisType = NoType, Map.empty, Set.empty) + new Env(hasNewTarget = false, thisType = NoType, Map.empty, Set.empty, inConstructor = false) def fromParams(params: List[ParamDef]): Env = { val paramLocalDefs = for (p @ ParamDef(ident, _, tpe, mutable) <- params) yield ident.name -> LocalDef(ident.name, tpe, mutable) - new Env(hasNewTarget = false, thisType = NoType, paramLocalDefs.toMap, Set.empty) + + new Env( + hasNewTarget = false, + thisType = NoType, + paramLocalDefs.toMap, + Set.empty, + inConstructor = false + ) } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 2e3678a6d4..418629d55b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -319,14 +319,9 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { if (clazz.kind != ClassKind.ModuleClass) reportError("LoadModule of non-module class $className") - case StoreModule(className, value) => - val clazz = lookupClass(className) - if (!clazz.kind.hasModuleAccessor) - reportError("StoreModule of non-module class $className") - val expectedType = - if (clazz.kind == ClassKind.JSModuleClass) AnyType - else ClassType(className) - typecheckExpect(value, env, expectedType) + case StoreModule() => + // Nothing to check; everything is checked in ClassDefChecker + () case Select(qualifier, className, FieldIdent(item)) => val c = lookupClass(className) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index e8db94edb1..05763942ff 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -748,8 +748,8 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: case ApplyStatically(flags, This(), _, _, args) if flags.isConstructor => args.forall(isTriviallySideEffectFree) - case StoreModule(_, _) => true - case _ => isTriviallySideEffectFree(tree) + case StoreModule() => true + case _ => isTriviallySideEffectFree(tree) } impl.originalDef.body.fold { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 534fd345be..a417440b22 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -171,7 +171,7 @@ private[optimizer] abstract class OptimizerCore( private def tryElimStoreModule(body: Tree): Tree = { implicit val pos = body.pos body match { - case StoreModule(_, _) => + case StoreModule() => Skip() case Block(stats) => val (before, from) = stats.span(!_.isInstanceOf[StoreModule]) @@ -458,9 +458,6 @@ private[optimizer] abstract class OptimizerCore( case New(className, ctor, args) => New(className, ctor, args map transformExpr) - case StoreModule(className, value) => - StoreModule(className, transformExpr(value)) - case tree: Select => trampoline { pretransformSelectCommon(tree, isLhsOfAssign = false)( @@ -656,8 +653,8 @@ private[optimizer] abstract class OptimizerCore( // Trees that need not be transformed - case _:Skip | _:Debugger | _:LoadModule | _:SelectStatic | - _:JSNewTarget | _:JSImportMeta | _:JSLinkingInfo | + case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | + _:SelectStatic | _:JSNewTarget | _:JSImportMeta | _:JSLinkingInfo | _:JSGlobalRef | _:JSTypeOfGlobalRef | _:Literal => tree diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index d092a04ee4..75717babd3 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -305,6 +305,62 @@ class ClassDefCheckerTest { Closure(arrow = false, Nil, Nil, None, This()(ClassType("Foo")), Nil), "`this` of type any typed as Foo") } + + @Test + def storeModule(): Unit = { + val ctorFlags = EMF.withNamespace(MemberNamespace.Constructor) + + val superCtorCall = ApplyStatically(EAF.withConstructor(true), This()(ClassType("Foo")), + ObjectClass, NoArgConstructorName, Nil)(NoType) + + assertError( + classDef( + "Foo", + kind = ClassKind.Class, + superClass = Some(ObjectClass), + methods = List( + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, Some { + Block( + superCtorCall, + StoreModule() + ) + })(EOH, UNV) + ) + ), + "Illegal StoreModule inside class of kind Class" + ) + + assertError( + classDef( + "Foo", + kind = ClassKind.ModuleClass, + superClass = Some(ObjectClass), + methods = List( + trivialCtor("Foo"), + MethodDef(EMF, MethodName("foo", Nil, VoidRef), NON, Nil, NoType, Some { + Block( + StoreModule() + ) + })(EOH, UNV) + ) + ), + "Illegal StoreModule outside of constructor" + ) + + assertError( + classDef( + "Foo", + kind = ClassKind.JSModuleClass, + superClass = Some("scala.scalajs.js.Object"), + jsConstructor = Some( + JSConstructorDef(JSCtorFlags, Nil, None, + JSConstructorBody(StoreModule() :: Nil, JSSuperConstructorCall(Nil), Undefined() :: Nil))( + EOH, UNV) + ) + ), + "Cannot find `this` in scope for StoreModule()" + ) + } } private object ClassDefCheckerTest { diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 4713fe6bf8..0144862b7b 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -5,6 +5,9 @@ import com.typesafe.tools.mima.core.ProblemFilters._ object BinaryIncompatibilities { val IR = Seq( + // !!! Breaking, OK in minor release + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Trees#StoreModule.*"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#StoreModule.unapply"), ) val Linker = Seq( From 723663b76a4dc2775dbab12f3c268e33990b6b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 17 Feb 2024 11:21:05 +0100 Subject: [PATCH 37/65] Refactor: Make FieldName a composite of ClassName and SimpleFieldName. Previously, `FieldName` only represented the *simple* name of a field. It was complemented everywhere with the enclosing `ClassName`, for namespacing purposes. We now make `FieldName` a composite, like `MethodName`. It contains a `ClassName` and a `SimpleFieldName`. Structurally, `SimpleFieldName` is the same as the old `FieldName`. This removes the need to pass additional, out-of-band `ClassName`s everywhere a `FieldName` or `FieldIdent` was used. In addition to the readability improvements, this might improve performance. We previously often had to create (temporary) pairs of `(ClassName, FieldName)` as keys of maps. Now, we can directly use the `FieldName`s instead. While the IR names, types and trees are significantly impacted by this change, the `.sjsir` format is unchanged. --- .../org/scalajs/nscplugin/GenJSCode.scala | 20 +-- .../org/scalajs/nscplugin/JSEncoding.scala | 7 +- .../main/scala/org/scalajs/ir/Hashers.scala | 20 ++- .../src/main/scala/org/scalajs/ir/Names.scala | 84 +++++++-- .../scala/org/scalajs/ir/OriginalName.scala | 8 + .../main/scala/org/scalajs/ir/Printers.scala | 29 +-- .../scala/org/scalajs/ir/Serializers.scala | 88 ++++++--- .../scala/org/scalajs/ir/Transformers.scala | 8 +- .../scala/org/scalajs/ir/Traversers.scala | 4 +- .../src/main/scala/org/scalajs/ir/Trees.scala | 15 +- .../src/main/scala/org/scalajs/ir/Types.scala | 4 +- .../scala/org/scalajs/ir/PrintersTest.scala | 34 ++-- .../scala/org/scalajs/ir/TestIRBuilder.scala | 10 +- .../org/scalajs/linker/analyzer/Infos.scala | 48 ++--- .../linker/backend/emitter/ClassEmitter.scala | 29 ++- .../linker/backend/emitter/EmitterNames.scala | 4 +- .../backend/emitter/FunctionEmitter.scala | 65 ++++--- .../backend/emitter/GlobalKnowledge.scala | 5 +- .../backend/emitter/KnowledgeGuardian.scala | 17 +- .../linker/backend/emitter/NameGen.scala | 14 +- .../linker/backend/emitter/SJSGen.scala | 16 +- .../linker/backend/emitter/VarGen.scala | 8 +- .../linker/checker/ClassDefChecker.scala | 6 +- .../linker/checker/ErrorReporter.scala | 1 + .../scalajs/linker/checker/IRChecker.scala | 27 +-- .../frontend/optimizer/IncOptimizer.scala | 14 +- .../frontend/optimizer/OptimizerCore.scala | 169 ++++++++---------- .../org/scalajs/linker/OptimizerTest.scala | 18 +- .../linker/checker/ClassDefCheckerTest.scala | 38 +++- .../linker/testutils/TestIRBuilder.scala | 8 +- project/BinaryIncompatibilities.scala | 13 ++ project/JavalibIRCleaner.scala | 5 +- 32 files changed, 479 insertions(+), 357 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 2c2c0478ea..6ad055b5ee 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -27,7 +27,7 @@ import scala.reflect.internal.Flags import org.scalajs.ir import org.scalajs.ir.{Trees => js, Types => jstpe, ClassKind, Hashers, OriginalName} -import org.scalajs.ir.Names.{LocalName, FieldName, SimpleMethodName, MethodName, ClassName} +import org.scalajs.ir.Names.{LocalName, SimpleFieldName, FieldName, SimpleMethodName, MethodName, ClassName} import org.scalajs.ir.OriginalName.NoOriginalName import org.scalajs.ir.Trees.OptimizerHints import org.scalajs.ir.Version.Unversioned @@ -6337,7 +6337,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val classType = jstpe.ClassType(className) // val f: Any - val fFieldIdent = js.FieldIdent(FieldName("f")) + val fFieldIdent = js.FieldIdent(FieldName(className, SimpleFieldName("f"))) val fFieldDef = js.FieldDef(js.MemberFlags.empty, fFieldIdent, NoOriginalName, jstpe.AnyType) @@ -6353,8 +6353,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) jstpe.NoType, Some(js.Block(List( js.Assign( - js.Select(js.This()(classType), className, fFieldIdent)( - jstpe.AnyType), + js.Select(js.This()(classType), fFieldIdent)(jstpe.AnyType), fParamDef.ref), js.ApplyStatically(js.ApplyFlags.empty.withConstructor(true), js.This()(classType), @@ -6405,7 +6404,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) }.map((ensureBoxed _).tupled) val call = js.JSFunctionApply( - js.Select(js.This()(classType), className, fFieldIdent)(jstpe.AnyType), + js.Select(js.This()(classType), fFieldIdent)(jstpe.AnyType), actualParams) val body = fromAny(call, enteringPhase(currentRun.posterasurePhase) { @@ -6746,14 +6745,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.JSSelect(qual, genPrivateFieldsSymbol()), encodeFieldSymAsStringLiteral(sym)) } else { - js.JSPrivateSelect(qual, encodeClassName(sym.owner), - encodeFieldSym(sym)) + js.JSPrivateSelect(qual, encodeFieldSym(sym)) } (f, true) } else if (jsInterop.topLevelExportsOf(sym).nonEmpty) { - val f = js.SelectStatic(encodeClassName(sym.owner), - encodeFieldSym(sym))(jstpe.AnyType) + val f = js.SelectStatic(encodeFieldSym(sym))(jstpe.AnyType) (f, true) } else if (jsInterop.staticExportsOf(sym).nonEmpty) { val exportInfo = jsInterop.staticExportsOf(sym).head @@ -6764,7 +6761,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) (f, true) } else { - val className = encodeClassName(sym.owner) val fieldIdent = encodeFieldSym(sym) /* #4370 Fields cannot have type NothingType, so we box them as @@ -6774,11 +6770,11 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) */ toIRType(sym.tpe) match { case jstpe.NothingType => - val f = js.Select(qual, className, fieldIdent)( + val f = js.Select(qual, fieldIdent)( encodeClassType(RuntimeNothingClass)) (f, true) case ftpe => - val f = js.Select(qual, className, fieldIdent)(ftpe) + val f = js.Select(qual, fieldIdent)(ftpe) (f, false) } } diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala index 432abaaa7a..56f089bf1e 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala @@ -18,7 +18,7 @@ import scala.tools.nsc._ import org.scalajs.ir import org.scalajs.ir.{Trees => js, Types => jstpe} -import org.scalajs.ir.Names.{LocalName, LabelName, FieldName, SimpleMethodName, MethodName, ClassName} +import org.scalajs.ir.Names.{LocalName, LabelName, SimpleFieldName, FieldName, SimpleMethodName, MethodName, ClassName} import org.scalajs.ir.OriginalName import org.scalajs.ir.OriginalName.NoOriginalName import org.scalajs.ir.UTF8String @@ -178,8 +178,9 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent { def encodeFieldSym(sym: Symbol)(implicit pos: Position): js.FieldIdent = { requireSymIsField(sym) - val name = sym.name.dropLocal - js.FieldIdent(FieldName(name.toString())) + val className = encodeClassName(sym.owner) + val simpleName = SimpleFieldName(sym.name.dropLocal.toString()) + js.FieldIdent(FieldName(className, simpleName)) } def encodeFieldSymAsStringLiteral(sym: Symbol)( diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index e363efccfb..bbf0b85409 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -262,16 +262,14 @@ object Hashers { case StoreModule() => mixTag(TagStoreModule) - case Select(qualifier, className, field) => + case Select(qualifier, field) => mixTag(TagSelect) mixTree(qualifier) - mixName(className) mixFieldIdent(field) mixType(tree.tpe) - case SelectStatic(className, field) => + case SelectStatic(field) => mixTag(TagSelectStatic) - mixName(className) mixFieldIdent(field) mixType(tree.tpe) @@ -351,7 +349,7 @@ object Hashers { case RecordSelect(record, field) => mixTag(TagRecordSelect) mixTree(record) - mixFieldIdent(field) + mixSimpleFieldIdent(field) mixType(tree.tpe) case IsInstanceOf(expr, testType) => @@ -389,10 +387,9 @@ object Hashers { mixTree(ctor) mixTreeOrJSSpreads(args) - case JSPrivateSelect(qualifier, className, field) => + case JSPrivateSelect(qualifier, field) => mixTag(TagJSPrivateSelect) mixTree(qualifier) - mixName(className) mixFieldIdent(field) case JSSelect(qualifier, item) => @@ -651,11 +648,18 @@ object Hashers { mixName(ident.name) } - def mixFieldIdent(ident: FieldIdent): Unit = { + def mixSimpleFieldIdent(ident: SimpleFieldIdent): Unit = { mixPos(ident.pos) mixName(ident.name) } + def mixFieldIdent(ident: FieldIdent): Unit = { + // For historical reasons, the className comes *before* the position + mixName(ident.name.className) + mixPos(ident.pos) + mixName(ident.name.simpleName) + } + def mixMethodIdent(ident: MethodIdent): Unit = { mixPos(ident.pos) mixMethodName(ident.name) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Names.scala b/ir/shared/src/main/scala/org/scalajs/ir/Names.scala index cee7f057cb..3e4a429e2a 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Names.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Names.scala @@ -99,7 +99,7 @@ object Names { def apply(name: String): LocalName = LocalName(UTF8String(name)) - private[Names] def fromFieldName(name: FieldName): LocalName = + private[Names] def fromSimpleFieldName(name: SimpleFieldName): LocalName = new LocalName(name.encoded) } @@ -137,38 +137,90 @@ object Names { LabelName(UTF8String(name)) } - /** The name of a field. + /** The simple name of a field (excluding its enclosing class). * * Field names must be non-empty, and can contain any Unicode code point * except `/ . ; [`. */ - final class FieldName private (encoded: UTF8String) - extends Name(encoded) with Comparable[FieldName] { + final class SimpleFieldName private (encoded: UTF8String) + extends Name(encoded) with Comparable[SimpleFieldName] { - type ThisName = FieldName + type ThisName = SimpleFieldName override def equals(that: Any): Boolean = { (this eq that.asInstanceOf[AnyRef]) || (that match { - case that: FieldName => equalsName(that) - case _ => false + case that: SimpleFieldName => equalsName(that) + case _ => false }) } - protected def stringPrefix: String = "FieldName" + protected def stringPrefix: String = "SimpleFieldName" - final def withSuffix(suffix: String): FieldName = - FieldName(this.encoded ++ UTF8String(suffix)) + final def withSuffix(suffix: String): SimpleFieldName = + SimpleFieldName(this.encoded ++ UTF8String(suffix)) final def toLocalName: LocalName = - LocalName.fromFieldName(this) + LocalName.fromSimpleFieldName(this) } - object FieldName { - def apply(name: UTF8String): FieldName = - new FieldName(validateSimpleEncodedName(name)) + object SimpleFieldName { + def apply(name: UTF8String): SimpleFieldName = + new SimpleFieldName(validateSimpleEncodedName(name)) + + def apply(name: String): SimpleFieldName = + SimpleFieldName(UTF8String(name)) + } - def apply(name: String): FieldName = - FieldName(UTF8String(name)) + /** The full name of a field, including its simple name and its enclosing + * class name. + */ + final class FieldName private ( + val className: ClassName, val simpleName: SimpleFieldName) + extends Comparable[FieldName] { + + import FieldName._ + + private val _hashCode: Int = { + import scala.util.hashing.MurmurHash3._ + var acc = -1025990011 // "FieldName".hashCode() + acc = mix(acc, className.##) + acc = mix(acc, simpleName.##) + finalizeHash(acc, 2) + } + + override def equals(that: Any): Boolean = { + (this eq that.asInstanceOf[AnyRef]) || (that match { + case that: FieldName => + this._hashCode == that._hashCode && // fail fast on different hash codes + this.className == that.className && + this.simpleName == that.simpleName + case _ => + false + }) + } + + override def hashCode(): Int = _hashCode + + def compareTo(that: FieldName): Int = { + val classNameCmp = this.className.compareTo(that.className) + if (classNameCmp != 0) + classNameCmp + else + this.simpleName.compareTo(that.simpleName) + } + + protected def stringPrefix: String = "FieldName" + + def nameString: String = + className.nameString + "::" + simpleName.nameString + + override def toString(): String = + "FieldName<" + nameString + ">" + } + + object FieldName { + def apply(className: ClassName, simpleName: SimpleFieldName): FieldName = + new FieldName(className, simpleName) } /** The simple name of a method (excluding its signature). diff --git a/ir/shared/src/main/scala/org/scalajs/ir/OriginalName.scala b/ir/shared/src/main/scala/org/scalajs/ir/OriginalName.scala index d2211095d5..7ae6745e53 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/OriginalName.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/OriginalName.scala @@ -53,6 +53,10 @@ final class OriginalName private (private val bytes: Array[Byte]) if (isDefined) this else OriginalName(name) + // new in 1.16.0; added as last overload to preserve binary compatibility + def orElse(name: FieldName): OriginalName = + orElse(name.simpleName) + def getOrElse(name: Name): UTF8String = getOrElse(name.encoded) @@ -71,6 +75,10 @@ final class OriginalName private (private val bytes: Array[Byte]) else UTF8String(name) } + // new in 1.16.0; added as last overload to preserve binary compatibility + def getOrElse(name: FieldName): UTF8String = + getOrElse(name.simpleName) + override def toString(): String = if (isDefined) s"OriginalName($unsafeGet)" else "NoOriginalName" diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index deb6ded863..875a4e4c0b 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -123,6 +123,7 @@ object Printers { node match { case node: LocalIdent => print(node) case node: LabelIdent => print(node) + case node: SimpleFieldIdent => print(node) case node: FieldIdent => print(node) case node: MethodIdent => print(node) case node: ClassIdent => print(node) @@ -299,16 +300,12 @@ object Printers { case StoreModule() => print("") - case Select(qualifier, className, field) => + case Select(qualifier, field) => print(qualifier) print('.') - print(className) - print("::") print(field) - case SelectStatic(className, field) => - print(className) - print("::") + case SelectStatic(field) => print(field) case SelectJSNativeMember(className, member) => @@ -572,11 +569,11 @@ object Printers { case JSNew(ctor, args) => def containsOnlySelectsFromAtom(tree: Tree): Boolean = tree match { - case JSPrivateSelect(qual, _, _) => containsOnlySelectsFromAtom(qual) - case JSSelect(qual, _) => containsOnlySelectsFromAtom(qual) - case VarRef(_) => true - case This() => true - case _ => false // in particular, Apply + case JSPrivateSelect(qual, _) => containsOnlySelectsFromAtom(qual) + case JSSelect(qual, _) => containsOnlySelectsFromAtom(qual) + case VarRef(_) => true + case This() => true + case _ => false // in particular, Apply } if (containsOnlySelectsFromAtom(ctor)) { print("new ") @@ -588,11 +585,9 @@ object Printers { } printArgs(args) - case JSPrivateSelect(qualifier, className, field) => + case JSPrivateSelect(qualifier, field) => print(qualifier) print('.') - print(className) - print("::") print(field) case JSSelect(qualifier, item) => @@ -1113,6 +1108,9 @@ object Printers { def print(ident: LabelIdent): Unit = print(ident.name) + def print(ident: SimpleFieldIdent): Unit = + print(ident.name) + def print(ident: FieldIdent): Unit = print(ident.name) @@ -1125,6 +1123,9 @@ object Printers { def print(name: Name): Unit = printEscapeJS(name.nameString, out) + def print(name: FieldName): Unit = + printEscapeJS(name.nameString, out) + def print(name: MethodName): Unit = printEscapeJS(name.nameString, out) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index f4c02e8760..9be664598a 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -321,14 +321,14 @@ object Serializers { case StoreModule() => writeTagAndPos(TagStoreModule) - case Select(qualifier, className, field) => + case Select(qualifier, field) => writeTagAndPos(TagSelect) - writeTree(qualifier); writeName(className); writeFieldIdent(field) + writeTree(qualifier); writeFieldIdent(field) writeType(tree.tpe) - case SelectStatic(className, field) => + case SelectStatic(field) => writeTagAndPos(TagSelectStatic) - writeName(className); writeFieldIdent(field) + writeFieldIdent(field) writeType(tree.tpe) case SelectJSNativeMember(className, member) => @@ -385,7 +385,7 @@ object Serializers { case RecordSelect(record, field) => writeTagAndPos(TagRecordSelect) - writeTree(record); writeFieldIdent(field) + writeTree(record); writeSimpleFieldIdent(field) writeType(tree.tpe) case IsInstanceOf(expr, testType) => @@ -420,9 +420,9 @@ object Serializers { writeTagAndPos(TagJSNew) writeTree(ctor); writeTreeOrJSSpreads(args) - case JSPrivateSelect(qualifier, className, field) => + case JSPrivateSelect(qualifier, field) => writeTagAndPos(TagJSPrivateSelect) - writeTree(qualifier); writeName(className); writeFieldIdent(field) + writeTree(qualifier); writeFieldIdent(field) case JSSelect(qualifier, item) => writeTagAndPos(TagJSSelect) @@ -632,7 +632,7 @@ object Serializers { case FieldDef(flags, name, originalName, ftpe) => writeByte(TagFieldDef) writeInt(MemberFlags.toBits(flags)) - writeFieldIdent(name) + writeFieldIdentForEnclosingClass(name) writeOriginalName(originalName) writeType(ftpe) @@ -762,7 +762,7 @@ object Serializers { case TopLevelFieldExportDef(moduleID, exportName, field) => writeByte(TagTopLevelFieldExportDef) - writeString(moduleID); writeString(exportName); writeFieldIdent(field) + writeString(moduleID); writeString(exportName); writeFieldIdentForEnclosingClass(field) } } @@ -782,11 +782,23 @@ object Serializers { writeName(ident.name) } - def writeFieldIdent(ident: FieldIdent): Unit = { + def writeSimpleFieldIdent(ident: SimpleFieldIdent): Unit = { writePosition(ident.pos) writeName(ident.name) } + def writeFieldIdent(ident: FieldIdent): Unit = { + // For historical reasons, the className comes *before* the position + writeName(ident.name.className) + writePosition(ident.pos) + writeName(ident.name.simpleName) + } + + def writeFieldIdentForEnclosingClass(ident: FieldIdent): Unit = { + writePosition(ident.pos) + writeName(ident.name.simpleName) + } + def writeMethodIdent(ident: MethodIdent): Unit = { writePosition(ident.pos) writeMethodName(ident.name) @@ -1003,12 +1015,23 @@ object Serializers { private[this] var encodedNames: Array[UTF8String] = _ private[this] var localNames: Array[LocalName] = _ private[this] var labelNames: Array[LabelName] = _ - private[this] var fieldNames: Array[FieldName] = _ + private[this] var simpleFieldNames: Array[SimpleFieldName] = _ private[this] var simpleMethodNames: Array[SimpleMethodName] = _ private[this] var classNames: Array[ClassName] = _ private[this] var methodNames: Array[MethodName] = _ private[this] var strings: Array[String] = _ + /** Uniqueness cache for FieldName's. + * + * For historical reasons, the `ClassName` and `SimpleFieldName` + * components of `FieldName`s are store separately in the `.sjsir` format. + * Since most if not all occurrences of any particular `FieldName` + * typically come from a single `.sjsir` file, we use a uniqueness cache + * to make them all `eq`, consuming less memory and speeding up equality + * tests. + */ + private[this] val uniqueFieldNames = mutable.AnyRefMap.empty[FieldName, FieldName] + private[this] var lastPosition: Position = Position.NoPosition private[this] var enclosingClassName: ClassName = _ @@ -1031,7 +1054,7 @@ object Serializers { } localNames = new Array(encodedNames.length) labelNames = new Array(encodedNames.length) - fieldNames = new Array(encodedNames.length) + simpleFieldNames = new Array(encodedNames.length) simpleMethodNames = new Array(encodedNames.length) classNames = new Array(encodedNames.length) methodNames = Array.fill(readInt()) { @@ -1171,7 +1194,6 @@ object Serializers { case TagSelect => val qualifier = readTree() - val className = readClassName() val field = readFieldIdent() val tpe = readType() @@ -1179,12 +1201,12 @@ object Serializers { /* Note [Nothing FieldDef rewrite] * qual.field[nothing] --> throw qual.field[null] */ - Throw(Select(qualifier, className, field)(NullType)) + Throw(Select(qualifier, field)(NullType)) } else { - Select(qualifier, className, field)(tpe) + Select(qualifier, field)(tpe) } - case TagSelectStatic => SelectStatic(readClassName(), readFieldIdent())(readType()) + case TagSelectStatic => SelectStatic(readFieldIdent())(readType()) case TagSelectJSNativeMember => SelectJSNativeMember(readClassName(), readMethodIdent()) case TagApply => @@ -1219,7 +1241,7 @@ object Serializers { UnwrapFromThrowable(readTree()) case TagJSNew => JSNew(readTree(), readTreeOrJSSpreads()) - case TagJSPrivateSelect => JSPrivateSelect(readTree(), readClassName(), readFieldIdent()) + case TagJSPrivateSelect => JSPrivateSelect(readTree(), readFieldIdent()) case TagJSSelect => JSSelect(readTree(), readTree()) case TagJSFunctionApply => JSFunctionApply(readTree(), readTreeOrJSSpreads()) case TagJSMethodApply => JSMethodApply(readTree(), readTree(), readTreeOrJSSpreads()) @@ -1495,7 +1517,7 @@ object Serializers { private def readFieldDef()(implicit pos: Position): FieldDef = { val flags = MemberFlags.fromBits(readInt()) - val name = readFieldIdent() + val name = readFieldIdentForEnclosingClass() val originalName = readOriginalName() val ftpe0 = readType() @@ -1721,7 +1743,9 @@ object Serializers { case TagTopLevelJSClassExportDef => TopLevelJSClassExportDef(readModuleID(), readString()) case TagTopLevelModuleExportDef => TopLevelModuleExportDef(readModuleID(), readString()) case TagTopLevelMethodExportDef => TopLevelMethodExportDef(readModuleID(), readJSMethodDef()) - case TagTopLevelFieldExportDef => TopLevelFieldExportDef(readModuleID(), readString(), readFieldIdent()) + + case TagTopLevelFieldExportDef => + TopLevelFieldExportDef(readModuleID(), readString(), readFieldIdentForEnclosingClass()) } } @@ -1739,8 +1763,22 @@ object Serializers { } def readFieldIdent(): FieldIdent = { + // For historical reasons, the className comes *before* the position + val className = readClassName() + implicit val pos = readPosition() + val simpleName = readSimpleFieldName() + FieldIdent(makeFieldName(className, simpleName)) + } + + def readFieldIdentForEnclosingClass(): FieldIdent = { implicit val pos = readPosition() - FieldIdent(readFieldName()) + val simpleName = readSimpleFieldName() + FieldIdent(makeFieldName(enclosingClassName, simpleName)) + } + + private def makeFieldName(className: ClassName, simpleName: SimpleFieldName): FieldName = { + val newFieldName = FieldName(className, simpleName) + uniqueFieldNames.getOrElseUpdate(newFieldName, newFieldName) } def readMethodIdent(): MethodIdent = { @@ -1826,7 +1864,7 @@ object Serializers { case TagRecordType => RecordType(List.fill(readInt()) { - val name = readFieldName() + val name = readSimpleFieldName() val originalName = readString() val tpe = readType() val mutable = readBoolean() @@ -1959,14 +1997,14 @@ object Serializers { } } - private def readFieldName(): FieldName = { + private def readSimpleFieldName(): SimpleFieldName = { val i = readInt() - val existing = fieldNames(i) + val existing = simpleFieldNames(i) if (existing ne null) { existing } else { - val result = FieldName(encodedNames(i)) - fieldNames(i) = result + val result = SimpleFieldName(encodedNames(i)) + simpleFieldNames(i) = result result } } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 415db46f33..6d30327786 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -91,8 +91,8 @@ object Transformers { case New(className, ctor, args) => New(className, ctor, args map transformExpr) - case Select(qualifier, className, field) => - Select(transformExpr(qualifier), className, field)(tree.tpe) + case Select(qualifier, field) => + Select(transformExpr(qualifier), field)(tree.tpe) case Apply(flags, receiver, method, args) => Apply(flags, transformExpr(receiver), method, @@ -158,8 +158,8 @@ object Transformers { case JSNew(ctor, args) => JSNew(transformExpr(ctor), args.map(transformExprOrJSSpread)) - case JSPrivateSelect(qualifier, className, field) => - JSPrivateSelect(transformExpr(qualifier), className, field) + case JSPrivateSelect(qualifier, field) => + JSPrivateSelect(transformExpr(qualifier), field) case JSSelect(qualifier, item) => JSSelect(transformExpr(qualifier), transformExpr(item)) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala index 2040341a74..8a8909cdce 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -77,7 +77,7 @@ object Traversers { case New(_, _, args) => args foreach traverse - case Select(qualifier, _, _) => + case Select(qualifier, _) => traverse(qualifier) case Apply(_, receiver, _, args) => @@ -147,7 +147,7 @@ object Traversers { traverse(ctor) args.foreach(traverseTreeOrJSSpread) - case JSPrivateSelect(qualifier, _, _) => + case JSPrivateSelect(qualifier, _) => traverse(qualifier) case JSSelect(qualifier, item) => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 631e39642a..c3d206624a 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -59,6 +59,9 @@ object Trees { sealed case class LabelIdent(name: LabelName)(implicit val pos: Position) extends IRNode + sealed case class SimpleFieldIdent(name: SimpleFieldName)(implicit val pos: Position) + extends IRNode + sealed case class FieldIdent(name: FieldName)(implicit val pos: Position) extends IRNode @@ -241,13 +244,10 @@ object Trees { val tpe = NoType // cannot be in expression position } - sealed case class Select(qualifier: Tree, className: ClassName, - field: FieldIdent)( - val tpe: Type)( + sealed case class Select(qualifier: Tree, field: FieldIdent)(val tpe: Type)( implicit val pos: Position) extends AssignLhs - sealed case class SelectStatic(className: ClassName, field: FieldIdent)( - val tpe: Type)( + sealed case class SelectStatic(field: FieldIdent)(val tpe: Type)( implicit val pos: Position) extends AssignLhs sealed case class SelectJSNativeMember(className: ClassName, member: MethodIdent)( @@ -465,7 +465,7 @@ object Trees { sealed case class RecordValue(tpe: RecordType, elems: List[Tree])( implicit val pos: Position) extends Tree - sealed case class RecordSelect(record: Tree, field: FieldIdent)( + sealed case class RecordSelect(record: Tree, field: SimpleFieldIdent)( val tpe: Type)( implicit val pos: Position) extends AssignLhs @@ -512,8 +512,7 @@ object Trees { val tpe = AnyType } - sealed case class JSPrivateSelect(qualifier: Tree, className: ClassName, - field: FieldIdent)( + sealed case class JSPrivateSelect(qualifier: Tree, field: FieldIdent)( implicit val pos: Position) extends AssignLhs { val tpe = AnyType } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Types.scala b/ir/shared/src/main/scala/org/scalajs/ir/Types.scala index 459f42f457..4f91fd3319 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Types.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Types.scala @@ -143,12 +143,12 @@ object Types { * The compiler itself never generates record types. */ final case class RecordType(fields: List[RecordType.Field]) extends Type { - def findField(name: FieldName): RecordType.Field = + def findField(name: SimpleFieldName): RecordType.Field = fields.find(_.name == name).get } object RecordType { - final case class Field(name: FieldName, originalName: OriginalName, + final case class Field(name: SimpleFieldName, originalName: OriginalName, tpe: Type, mutable: Boolean) } diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 03c1628f16..ab3c6be098 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -307,12 +307,12 @@ class PrintersTest { @Test def printSelect(): Unit = { assertPrintEquals("x.test.Test::f", - Select(ref("x", "test.Test"), "test.Test", "f")(IntType)) + Select(ref("x", "test.Test"), FieldName("test.Test", "f"))(IntType)) } @Test def printSelectStatic(): Unit = { assertPrintEquals("test.Test::f", - SelectStatic("test.Test", "f")(IntType)) + SelectStatic(FieldName("test.Test", "f"))(IntType)) } @Test def printApply(): Unit = { @@ -606,21 +606,21 @@ class PrintersTest { assertPrintEquals("new C()", JSNew(ref("C", AnyType), Nil)) assertPrintEquals("new C(4, 5)", JSNew(ref("C", AnyType), List(i(4), i(5)))) assertPrintEquals("new x.test.Test::C(4, 5)", - JSNew(JSPrivateSelect(ref("x", AnyType), "test.Test", "C"), List(i(4), i(5)))) + JSNew(JSPrivateSelect(ref("x", AnyType), FieldName("test.Test", "C")), List(i(4), i(5)))) assertPrintEquals("""new x["C"]()""", JSNew(JSSelect(ref("x", AnyType), StringLiteral("C")), Nil)) val fApplied = JSFunctionApply(ref("f", AnyType), Nil) assertPrintEquals("new (f())()", JSNew(fApplied, Nil)) assertPrintEquals("new (f().test.Test::C)(4, 5)", - JSNew(JSPrivateSelect(fApplied, "test.Test", "C"), List(i(4), i(5)))) + JSNew(JSPrivateSelect(fApplied, FieldName("test.Test", "C")), List(i(4), i(5)))) assertPrintEquals("""new (f()["C"])()""", JSNew(JSSelect(fApplied, StringLiteral("C")), Nil)) } @Test def printJSPrivateSelect(): Unit = { assertPrintEquals("x.test.Test::f", - JSPrivateSelect(ref("x", AnyType), "test.Test", "f")) + JSPrivateSelect(ref("x", AnyType), FieldName("test.Test", "f"))) } @Test def printJSSelect(): Unit = { @@ -634,12 +634,12 @@ class PrintersTest { JSFunctionApply(ref("f", AnyType), List(i(3), i(4)))) assertPrintEquals("(0, x.test.Test::f)()", - JSFunctionApply(JSPrivateSelect(ref("x", AnyType), "test.Test", "f"), Nil)) + JSFunctionApply(JSPrivateSelect(ref("x", AnyType), FieldName("test.Test", "f")), Nil)) assertPrintEquals("""(0, x["f"])()""", JSFunctionApply(JSSelect(ref("x", AnyType), StringLiteral("f")), Nil)) assertPrintEquals("(0, x.test.Test::f)()", - JSFunctionApply(Select(ref("x", "test.Test"), "test.Test", "f")(AnyType), + JSFunctionApply(Select(ref("x", "test.Test"), FieldName("test.Test", "f"))(AnyType), Nil)) } @@ -1137,7 +1137,7 @@ class PrintersTest { assertPrintEquals( """ |module class Test extends java.lang.Object { - | val x: int + | val Test::x: int | def m;I(): int = | constructor def constructor(): any = { | super() @@ -1151,7 +1151,7 @@ class PrintersTest { """, ClassDef("Test", NON, ClassKind.ModuleClass, None, Some(ObjectClass), Nil, None, None, - List(FieldDef(MemberFlags.empty, "x", NON, IntType)), + List(FieldDef(MemberFlags.empty, FieldName("Test", "x"), NON, IntType)), List(MethodDef(MemberFlags.empty, MethodName("m", Nil, I), NON, Nil, IntType, None)(NoOptHints, UNV)), Some(JSConstructorDef(MemberFlags.empty.withNamespace(Constructor), Nil, None, JSConstructorBody(Nil, JSSuperConstructorCall(Nil), Nil))(NoOptHints, UNV)), @@ -1163,12 +1163,12 @@ class PrintersTest { } @Test def printFieldDef(): Unit = { - assertPrintEquals("val x: int", - FieldDef(MemberFlags.empty, "x", NON, IntType)) - assertPrintEquals("var y: any", - FieldDef(MemberFlags.empty.withMutable(true), "y", NON, AnyType)) - assertPrintEquals("val x{orig name}: int", - FieldDef(MemberFlags.empty, "x", TestON, IntType)) + assertPrintEquals("val Test::x: int", + FieldDef(MemberFlags.empty, FieldName("Test", "x"), NON, IntType)) + assertPrintEquals("var Test::y: any", + FieldDef(MemberFlags.empty.withMutable(true), FieldName("Test", "y"), NON, AnyType)) + assertPrintEquals("val Test::x{orig name}: int", + FieldDef(MemberFlags.empty, FieldName("Test", "x"), TestON, IntType)) } @Test def printJSFieldDef(): Unit = { @@ -1428,8 +1428,8 @@ class PrintersTest { @Test def printTopLevelFieldExportDef(): Unit = { assertPrintEquals( """ - |export top[moduleID="main"] static field x$1 as "x" + |export top[moduleID="main"] static field Test::x$1 as "x" """, - TopLevelFieldExportDef("main", "x", "x$1")) + TopLevelFieldExportDef("main", "x", FieldName("Test", "x$1"))) } } diff --git a/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala b/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala index 83255c1ca2..3cc7058b8f 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala @@ -37,8 +37,8 @@ object TestIRBuilder { val NoOptHints = OptimizerHints.empty // String -> Name conversions - implicit def string2fieldName(name: String): FieldName = - FieldName(name) + implicit def string2simpleFieldName(name: String): SimpleFieldName = + SimpleFieldName(name) implicit def string2className(name: String): ClassName = ClassName(name) @@ -47,8 +47,8 @@ object TestIRBuilder { LocalIdent(LocalName(name)) implicit def string2labelIdent(name: String): LabelIdent = LabelIdent(LabelName(name)) - implicit def string2fieldIdent(name: String): FieldIdent = - FieldIdent(FieldName(name)) + implicit def string2simpleFieldIdent(name: String): SimpleFieldIdent = + SimpleFieldIdent(SimpleFieldName(name)) implicit def string2classIdent(name: String): ClassIdent = ClassIdent(ClassName(name)) @@ -59,6 +59,8 @@ object TestIRBuilder { ClassRef(ClassName(className)) // Name -> Ident conversions + implicit def fieldName2fieldIdent(name: FieldName): FieldIdent = + FieldIdent(name) implicit def methodName2methodIdent(name: MethodName): MethodIdent = MethodIdent(name) implicit def className2classRef(className: ClassName): ClassRef = diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala index d063cd5b6a..fc57860d25 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala @@ -194,23 +194,23 @@ object Infos { private def forClass(cls: ClassName): ReachabilityInfoInClassBuilder = byClass.getOrElseUpdate(cls, new ReachabilityInfoInClassBuilder(cls)) - def addFieldRead(cls: ClassName, field: FieldName): this.type = { - forClass(cls).addFieldRead(field) + def addFieldRead(field: FieldName): this.type = { + forClass(field.className).addFieldRead(field) this } - def addFieldWritten(cls: ClassName, field: FieldName): this.type = { - forClass(cls).addFieldWritten(field) + def addFieldWritten(field: FieldName): this.type = { + forClass(field.className).addFieldWritten(field) this } - def addStaticFieldRead(cls: ClassName, field: FieldName): this.type = { - forClass(cls).addStaticFieldRead(field) + def addStaticFieldRead(field: FieldName): this.type = { + forClass(field.className).addStaticFieldRead(field) this } - def addStaticFieldWritten(cls: ClassName, field: FieldName): this.type = { - forClass(cls).addStaticFieldWritten(field) + def addStaticFieldWritten(field: FieldName): this.type = { + forClass(field.className).addStaticFieldWritten(field) this } @@ -560,8 +560,8 @@ object Infos { case topLevelFieldExport: TopLevelFieldExportDef => val field = topLevelFieldExport.field.name - builder.addStaticFieldRead(enclosingClass, field) - builder.addStaticFieldWritten(enclosingClass, field) + builder.addStaticFieldRead(field) + builder.addStaticFieldWritten(field) } builder.result() @@ -576,14 +576,14 @@ object Infos { */ case Assign(lhs, rhs) => lhs match { - case Select(qualifier, className, field) => - builder.addFieldWritten(className, field.name) + case Select(qualifier, field) => + builder.addFieldWritten(field.name) traverse(qualifier) - case SelectStatic(className, field) => - builder.addStaticFieldWritten(className, field.name) - case JSPrivateSelect(qualifier, className, field) => - builder.addStaticallyReferencedClass(className) // for the private name of the field - builder.addFieldWritten(className, field.name) + case SelectStatic(field) => + builder.addStaticFieldWritten(field.name) + case JSPrivateSelect(qualifier, field) => + builder.addStaticallyReferencedClass(field.name.className) // for the private name of the field + builder.addFieldWritten(field.name) traverse(qualifier) case _ => traverse(lhs) @@ -596,10 +596,10 @@ object Infos { case New(className, ctor, _) => builder.addInstantiatedClass(className, ctor.name) - case Select(_, className, field) => - builder.addFieldRead(className, field.name) - case SelectStatic(className, field) => - builder.addStaticFieldRead(className, field.name) + case Select(_, field) => + builder.addFieldRead(field.name) + case SelectStatic(field) => + builder.addStaticFieldRead(field.name) case SelectJSNativeMember(className, member) => builder.addJSNativeMemberUsed(className, member.name) @@ -687,9 +687,9 @@ object Infos { case UnwrapFromThrowable(_) => builder.addUsedInstanceTest(JavaScriptExceptionClass) - case JSPrivateSelect(_, className, field) => - builder.addStaticallyReferencedClass(className) // for the private name of the field - builder.addFieldRead(className, field.name) + case JSPrivateSelect(_, field) => + builder.addStaticallyReferencedClass(field.name.className) // for the private name of the field + builder.addFieldRead(field.name) case JSNewTarget() => builder.addAccessNewTarget() 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 dff91a1fd1..211b335e29 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 @@ -298,18 +298,15 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[js.Function] = { val superCtorCallAndFieldDefs = if (forESClass) { - val fieldDefs = genFieldDefsOfScalaClass(className, + val fieldDefs = genFieldDefsOfScalaClass( globalKnowledge.getFieldDefs(className)) if (superClass.isEmpty) fieldDefs else js.Apply(js.Super(), Nil) :: fieldDefs } else { - val allFields = - globalKnowledge.getAllScalaClassFieldDefs(className) - allFields.flatMap { classAndFields => - genFieldDefsOfScalaClass(classAndFields._1, classAndFields._2) - } + val allFields = globalKnowledge.getAllScalaClassFieldDefs(className) + genFieldDefsOfScalaClass(allFields) } initToInline.fold { @@ -349,8 +346,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } /** Generates the creation of fields for a Scala class. */ - private def genFieldDefsOfScalaClass(className: ClassName, - fields: List[AnyFieldDef])( + private def genFieldDefsOfScalaClass(fields: List[AnyFieldDef])( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge): List[js.Tree] = { for { @@ -359,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(), className, field.name, field.originalName), + js.Assign(genSelect(js.This(), field.name, field.originalName), genZeroOf(field.ftpe)) } } @@ -374,13 +370,12 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } yield { implicit val pos = field.pos - val varScope = (className, name) val value = genZeroOf(ftpe) if (flags.isMutable) - globallyMutableVarDef(VarField.t, VarField.u, varScope, value, origName.orElse(name)) + globallyMutableVarDef(VarField.t, VarField.u, name, value, origName.orElse(name)) else - globalVarDef(VarField.t, varScope, value, origName.orElse(name)) + globalVarDef(VarField.t, name, value, origName.orElse(name)) } WithGlobals.flatten(defs) @@ -407,7 +402,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } symbolValueWithGlobals.flatMap { symbolValue => - globalVarDef(VarField.r, (className, name), symbolValue, origName.orElse(name)) + globalVarDef(VarField.r, name, symbolValue, origName.orElse(name)) } } @@ -1091,17 +1086,15 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { implicit val pos = tree.pos - val varScope = (className, field.name) - moduleKind match { case ModuleKind.NoModule => /* Initial value of the export. Updates are taken care of explicitly * when we assign to the static field. */ - genAssignToNoModuleExportVar(exportName, globalVar(VarField.t, varScope)) + genAssignToNoModuleExportVar(exportName, globalVar(VarField.t, field.name)) case ModuleKind.ESModule => - WithGlobals(globalVarExport(VarField.t, varScope, js.ExportName(exportName))) + WithGlobals(globalVarExport(VarField.t, field.name, js.ExportName(exportName))) case ModuleKind.CommonJSModule => globalRef("exports").flatMap { exportsVarRef => @@ -1110,7 +1103,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { js.StringLiteral(exportName), List( "get" -> js.Function(arrow = false, Nil, None, { - js.Return(globalVar(VarField.t, varScope)) + js.Return(globalVar(VarField.t, field.name)) }), "configurable" -> js.BooleanLiteral(true) ) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterNames.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterNames.scala index 4c1bcebc2b..ba31cd7019 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterNames.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterNames.scala @@ -26,8 +26,8 @@ private[emitter] object EmitterNames { // Field names - val dataFieldName = FieldName("data") - val exceptionFieldName = FieldName("exception") + val dataFieldName = FieldName(ClassClass, SimpleFieldName("data")) + val exceptionFieldName = FieldName(JavaScriptExceptionClass, SimpleFieldName("exception")) // Method names 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 128e8ebeed..32b8baca4f 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 @@ -459,11 +459,11 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { } def makeRecordFieldIdent(recIdent: js.Ident, - fieldName: FieldName, fieldOrigName: OriginalName)( + fieldName: SimpleFieldName, fieldOrigName: OriginalName)( implicit pos: Position): js.Ident = { /* "__" is a safe separator for generated names because JSGen avoids it - * when generating `LocalName`s and `FieldName`s. + * when generating `LocalName`s and `SimpleFieldName`s. */ val name = recIdent.name + "__" + genName(fieldName) val originalName = OriginalName( @@ -607,11 +607,11 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Assign(lhs, rhs) => lhs match { - case Select(qualifier, className, field) => + case Select(qualifier, field) => unnest(checkNotNull(qualifier), rhs) { (newQualifier, newRhs, env0) => implicit val env = env0 js.Assign( - genSelect(transformExprNoChar(newQualifier), className, field)(lhs.pos), + genSelect(transformExprNoChar(newQualifier), field)(lhs.pos), transformExpr(newRhs, lhs.tpe)) } @@ -648,12 +648,12 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { mutable = true)(lhs.tpe)) pushLhsInto(Lhs.Assign(newLhs), rhs, tailPosLabels) - case JSPrivateSelect(qualifier, className, field) => + case JSPrivateSelect(qualifier, field) => unnest(qualifier, rhs) { (newQualifier, newRhs, env0) => implicit val env = env0 js.Assign( - genJSPrivateSelect(transformExprNoChar(newQualifier), - className, field)(moduleContext, globalKnowledge, lhs.pos), + genJSPrivateSelect(transformExprNoChar(newQualifier), field)( + moduleContext, globalKnowledge, lhs.pos), transformExprNoChar(newRhs)) } @@ -676,13 +676,11 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { transformExprNoChar(rhs)) } - case SelectStatic(className, item) => - val scope = (className, item.name) - - if (needToUseGloballyMutableVarSetter(scope)) { + case SelectStatic(item) => + if (needToUseGloballyMutableVarSetter(item.name)) { unnest(rhs) { (rhs, env0) => implicit val env = env0 - js.Apply(globalVar(VarField.u, scope), transformExpr(rhs, lhs.tpe) :: Nil) + js.Apply(globalVar(VarField.u, item.name), transformExpr(rhs, lhs.tpe) :: Nil) } } else { // Assign normally. @@ -837,7 +835,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { field match { case FieldDef(_, name, _, _) => js.Assign( - genJSPrivateSelect(js.This(), enclosingClassName, name), + genJSPrivateSelect(js.This(), name), zero) case JSFieldDef(_, name, _) => @@ -1066,8 +1064,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case New(className, constr, args) if noExtractYet => New(className, constr, recs(args)) - case Select(qualifier, className, item) if noExtractYet => - Select(rec(qualifier), className, item)(arg.tpe) + case Select(qualifier, item) if noExtractYet => + Select(rec(qualifier), item)(arg.tpe) case Apply(flags, receiver, method, args) if noExtractYet => val newArgs = recs(args) Apply(flags, rec(receiver), method, newArgs)(arg.tpe) @@ -1292,9 +1290,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { testNPE(obj) // Expressions preserving side-effect freedom (modulo NPE) - case Select(qualifier, _, _) => + case Select(qualifier, _) => allowUnpure && testNPE(qualifier) - case SelectStatic(_, _) => + case SelectStatic(_) => allowUnpure case ArrayValue(tpe, elems) => allowUnpure && (elems forall test) @@ -1354,7 +1352,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { allowSideEffects && test(fun) && (args.forall(testJSArg)) case Transient(JSNewVararg(ctor, argArray)) => allowSideEffects && test(ctor) && test(argArray) - case JSPrivateSelect(qualifier, _, _) => + case JSPrivateSelect(qualifier, _) => allowSideEffects && test(qualifier) case JSSelect(qualifier, item) => allowSideEffects && test(qualifier) && test(item) @@ -1467,10 +1465,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { val base = js.Assign(transformExpr(lhs, preserveChar = true), transformExpr(rhs, lhs.tpe)) lhs match { - case SelectStatic(className, FieldIdent(field)) + case SelectStatic(FieldIdent(field)) if moduleKind == ModuleKind.NoModule => - val mirrors = - globalKnowledge.getStaticFieldMirrors(className, field) + val mirrors = globalKnowledge.getStaticFieldMirrors(field) mirrors.foldLeft(base) { (prev, mirror) => js.Assign(genGlobalVarRef(mirror), prev) } @@ -1716,9 +1713,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { redo(New(className, ctor, newArgs))(env) } - case Select(qualifier, className, item) => + case Select(qualifier, item) => unnest(qualifier) { (newQualifier, env) => - redo(Select(newQualifier, className, item)(rhs.tpe))(env) + redo(Select(newQualifier, item)(rhs.tpe))(env) } case Apply(flags, receiver, method, args) => @@ -1924,9 +1921,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { redo(JSImportCall(newArg))(env) } - case JSPrivateSelect(qualifier, className, field) => + case JSPrivateSelect(qualifier, field) => unnest(qualifier) { (newQualifier, env) => - redo(JSPrivateSelect(newQualifier, className, field))(env) + redo(JSPrivateSelect(newQualifier, field))(env) } case JSSelect(qualifier, item) => @@ -2210,11 +2207,11 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case LoadModule(className) => genLoadModule(className) - case Select(qualifier, className, field) => - genSelect(transformExprNoChar(checkNotNull(qualifier)), className, field) + case Select(qualifier, field) => + genSelect(transformExprNoChar(checkNotNull(qualifier)), field) - case SelectStatic(className, item) => - globalVar(VarField.t, (className, item.name)) + case SelectStatic(item) => + globalVar(VarField.t, item.name) case SelectJSNativeMember(className, member) => val jsNativeLoadSpec = @@ -2725,7 +2722,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { val newExpr = transformExprNoChar(expr).asInstanceOf[js.VarRef] js.If( genIsInstanceOfClass(newExpr, JavaScriptExceptionClass), - genSelect(newExpr, JavaScriptExceptionClass, FieldIdent(exceptionFieldName)), + genSelect(newExpr, FieldIdent(exceptionFieldName)), genCheckNotNull(newExpr)) // Transients @@ -2738,7 +2735,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Transient(ZeroOf(runtimeClass)) => js.DotSelect( genSelect(transformExprNoChar(checkNotNull(runtimeClass)), - ClassClass, FieldIdent(dataFieldName)), + FieldIdent(dataFieldName)), js.Ident("zero")) case Transient(NativeArrayWrapper(elemClass, nativeArray)) => @@ -2751,7 +2748,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case _ => val elemClassData = genSelect( transformExprNoChar(checkNotNull(elemClass)), - ClassClass, FieldIdent(dataFieldName)) + FieldIdent(dataFieldName)) val arrayClassData = js.Apply( js.DotSelect(elemClassData, js.Ident("getArrayOf")), Nil) js.Apply(arrayClassData DOT "wrapArray", newNativeArray :: Nil) @@ -2808,8 +2805,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genCallHelper(VarField.newJSObjectWithVarargs, transformExprNoChar(constr), transformExprNoChar(argsArray)) - case JSPrivateSelect(qualifier, className, field) => - genJSPrivateSelect(transformExprNoChar(qualifier), className, field) + case JSPrivateSelect(qualifier, field) => + genJSPrivateSelect(transformExprNoChar(qualifier), field) case JSSelect(qualifier, item) => genBracketSelect(transformExprNoChar(qualifier), diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/GlobalKnowledge.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/GlobalKnowledge.scala index 1122c4b935..84592f0ec1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/GlobalKnowledge.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/GlobalKnowledge.scala @@ -33,8 +33,7 @@ private[emitter] trait GlobalKnowledge { * It is invalid to call this method with anything but a `Class` or * `ModuleClass`. */ - def getAllScalaClassFieldDefs( - className: ClassName): List[(ClassName, List[AnyFieldDef])] + def getAllScalaClassFieldDefs(className: ClassName): List[AnyFieldDef] /** Tests whether the specified class uses an inlineable init. * @@ -82,7 +81,7 @@ private[emitter] trait GlobalKnowledge { def getFieldDefs(className: ClassName): List[AnyFieldDef] /** The global variables that mirror a given static field. */ - def getStaticFieldMirrors(className: ClassName, field: FieldName): List[String] + def getStaticFieldMirrors(field: FieldName): List[String] /** The module containing this class definition. * diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala index 2d7f18b19c..1e9f26e285 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala @@ -178,7 +178,7 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { def isInterface(className: ClassName): Boolean = classes(className).askIsInterface(this) - def getAllScalaClassFieldDefs(className: ClassName): List[(ClassName, List[AnyFieldDef])] = + def getAllScalaClassFieldDefs(className: ClassName): List[AnyFieldDef] = classes(className).askAllScalaClassFieldDefs(this) def hasInlineableInit(className: ClassName): Boolean = @@ -205,8 +205,8 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { def getFieldDefs(className: ClassName): List[AnyFieldDef] = classes(className).askFieldDefs(this) - def getStaticFieldMirrors(className: ClassName, field: FieldName): List[String] = - classes(className).askStaticFieldMirrors(this, field) + def getStaticFieldMirrors(field: FieldName): List[String] = + classes(field.className).askStaticFieldMirrors(this, field) def getModule(className: ClassName): ModuleID = classes(className).askModule(this) @@ -366,8 +366,8 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { * which will change every time the reachability analysis of the * `JSFieldDef`s changes (because we either keep all or none of * them), and - * - the list of names of the `FieldDef`s, which will change every time - * the reachability analysis of the `FieldDef`s changes. + * - the list of simple names of the `FieldDef`s, which will change every + * time the reachability analysis of the `FieldDef`s changes. * * We do not try to use the names of `JSFieldDef`s because they are * `Tree`s, which are not efficiently comparable nor versionable here. @@ -376,7 +376,7 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { val hasAnyJSField = linkedClass.fields.exists(_.isInstanceOf[JSFieldDef]) val hasAnyJSFieldVersion = Version.fromByte(if (hasAnyJSField) 1 else 0) val scalaFieldNamesVersion = linkedClass.fields.collect { - case FieldDef(_, FieldIdent(name), _, _) => Version.fromUTF8String(name.encoded) + case FieldDef(_, FieldIdent(name), _, _) => Version.fromUTF8String(name.simpleName.encoded) } Version.combine((linkedClass.version :: hasAnyJSFieldVersion :: scalaFieldNamesVersion): _*) } @@ -396,15 +396,14 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { isInterface } - def askAllScalaClassFieldDefs( - invalidatable: Invalidatable): List[(ClassName, List[AnyFieldDef])] = { + def askAllScalaClassFieldDefs(invalidatable: Invalidatable): List[AnyFieldDef] = { invalidatable.registeredTo(this) superClassAskers += invalidatable fieldDefsAskers += invalidatable val inheritedFieldDefs = if (superClass == null) Nil else classes(superClass).askAllScalaClassFieldDefs(invalidatable) - inheritedFieldDefs :+ (className -> fieldDefs) + inheritedFieldDefs ::: fieldDefs } def askHasInlineableInit(invalidatable: Invalidatable): Boolean = { 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 3f21ea8adb..552dd545bd 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 @@ -47,8 +47,8 @@ private[emitter] final class NameGen { cache } - private val genFieldNameCache = - mutable.Map.empty[FieldName, String] + private val genSimpleFieldNameCache = + mutable.Map.empty[SimpleFieldName, String] private val genMethodNameCache = mutable.Map.empty[MethodName, String] @@ -107,7 +107,10 @@ private[emitter] final class NameGen { } def genName(name: LabelName): String = genNameGeneric(name, genLabelNameCache) - def genName(name: FieldName): String = genNameGeneric(name, genFieldNameCache) + def genName(name: SimpleFieldName): String = genNameGeneric(name, genSimpleFieldNameCache) + + def genName(name: FieldName): String = + genName(name.className) + "__f_" + genName(name.simpleName) def genName(name: MethodName): String = { genMethodNameCache.getOrElseUpdate(name, { @@ -210,6 +213,11 @@ private[emitter] final class NameGen { genOriginalName(name.encoded, originalName, jsName) } + def genOriginalName(name: FieldName, originalName: OriginalName, + jsName: String): OriginalName = { + genOriginalName(name.simpleName, originalName, jsName) + } + def genOriginalName(name: MethodName, originalName: OriginalName, jsName: String): OriginalName = { genOriginalName(name.simpleName, originalName, jsName) 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 4cc050d33c..4541d3a292 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 @@ -163,22 +163,19 @@ private[emitter] final class SJSGen( genCallHelper(VarField.systemArraycopy, args: _*) } - def genSelect(receiver: Tree, className: ClassName, field: irt.FieldIdent)( + def genSelect(receiver: Tree, field: irt.FieldIdent)( implicit pos: Position): Tree = { - DotSelect(receiver, Ident(genFieldJSName(className, field))(field.pos)) + DotSelect(receiver, Ident(genName(field.name))(field.pos)) } - def genSelect(receiver: Tree, className: ClassName, field: irt.FieldIdent, + def genSelect(receiver: Tree, field: irt.FieldIdent, originalName: OriginalName)( implicit pos: Position): Tree = { - val jsName = genFieldJSName(className, field) + val jsName = genName(field.name) val jsOrigName = genOriginalName(field.name, originalName, jsName) DotSelect(receiver, Ident(jsName, jsOrigName)(field.pos)) } - private def genFieldJSName(className: ClassName, field: irt.FieldIdent): String = - genName(className) + "__f_" + genName(field.name) - def genApply(receiver: Tree, methodName: MethodName, args: List[Tree])( implicit pos: Position): Tree = { Apply(DotSelect(receiver, Ident(genMethodName(methodName))), args) @@ -192,13 +189,12 @@ private[emitter] final class SJSGen( def genMethodName(methodName: MethodName): String = genName(methodName) - def genJSPrivateSelect(receiver: Tree, className: ClassName, - field: irt.FieldIdent)( + def genJSPrivateSelect(receiver: Tree, field: irt.FieldIdent)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { val fieldName = { implicit val pos = field.pos - globalVar(VarField.r, (className, field.name)) + globalVar(VarField.r, field.name) } BracketSelect(receiver, fieldName) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala index 7b7f87859d..11d93244d9 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala @@ -350,11 +350,11 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, def reprClass(x: ClassName): ClassName = x } - implicit object FieldScope extends Scope[(ClassName, FieldName)] { - def subField(x: (ClassName, FieldName)): String = - genName(x._1) + "__" + genName(x._2) + implicit object FieldScope extends Scope[FieldName] { + def subField(x: FieldName): String = + genName(x.className) + "__" + genName(x.simpleName) - def reprClass(x: (ClassName, FieldName)): ClassName = x._1 + def reprClass(x: FieldName): ClassName = x.className } implicit object MethodScope extends Scope[(ClassName, MethodName)] { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index ba64a3b9b1..981d065512 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -214,6 +214,8 @@ private final class ClassDefChecker(classDef: ClassDef, case FieldDef(_, FieldIdent(name), _, ftpe) => if (!classDef.kind.isAnyNonNativeClass) reportError("illegal FieldDef (only non native classes may contain fields)") + if (name.className != classDef.className) + reportError(i"illegal FieldDef with name $name in class ${classDef.className}") if (fields(namespace.ordinal).put(name, ftpe).isDefined) reportError(i"duplicate ${namespace.prefixString}field '$name'") @@ -620,7 +622,7 @@ private final class ClassDefChecker(classDef: ClassDef, if (env.thisType == NoType) // can happen before JSSuperConstructorCall in JSModuleClass reportError(i"Cannot find `this` in scope for StoreModule()") - case Select(qualifier, _, _) => + case Select(qualifier, _) => checkTree(qualifier, env) case _: SelectStatic => @@ -714,7 +716,7 @@ private final class ClassDefChecker(classDef: ClassDef, checkTree(ctor, env) checkTreeOrSpreads(args, env) - case JSPrivateSelect(qualifier, className, field) => + case JSPrivateSelect(qualifier, _) => checkTree(qualifier, env) case JSSelect(qualifier, item) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ErrorReporter.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ErrorReporter.scala index 35cef51bf6..18c6527d13 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ErrorReporter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ErrorReporter.scala @@ -36,6 +36,7 @@ private[checker] object ErrorReporter { private def format(arg: Any): String = { arg match { case arg: Name => arg.nameString + case arg: FieldName => arg.nameString case arg: MethodName => arg.displayName case arg: IRNode => arg.show case arg: TypeRef => arg.displayName diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 418629d55b..8f399fa984 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -225,23 +225,23 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { typecheckExpect(body, env.withLabeledReturnType(label.name, tpe), tpe) case Assign(lhs, rhs) => - def checkNonStaticField(receiver: Tree, className: ClassName, name: FieldName): Unit = { + def checkNonStaticField(receiver: Tree, name: FieldName): Unit = { receiver match { - case This() if env.inConstructorOf == Some(className) => + case This() if env.inConstructorOf == Some(name.className) => // ok case _ => - if (lookupClass(className).lookupField(name).exists(!_.flags.isMutable)) + if (lookupClass(name.className).lookupField(name).exists(!_.flags.isMutable)) reportError(i"Assignment to immutable field $name.") } } lhs match { - case Select(receiver, className, FieldIdent(name)) => - checkNonStaticField(receiver, className, name) - case JSPrivateSelect(receiver, className, FieldIdent(name)) => - checkNonStaticField(receiver, className, name) - case SelectStatic(className, FieldIdent(name)) => - val c = lookupClass(className) + case Select(receiver, FieldIdent(name)) => + checkNonStaticField(receiver, name) + case JSPrivateSelect(receiver, FieldIdent(name)) => + checkNonStaticField(receiver, name) + case SelectStatic(FieldIdent(name)) => + val c = lookupClass(name.className) for { f <- c.lookupStaticField(name) if !f.flags.isMutable @@ -323,7 +323,8 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { // Nothing to check; everything is checked in ClassDefChecker () - case Select(qualifier, className, FieldIdent(item)) => + case Select(qualifier, FieldIdent(item)) => + val className = item.className val c = lookupClass(className) val kind = c.kind if (!kind.isClass) { @@ -354,7 +355,8 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { } } - case SelectStatic(className, FieldIdent(item)) => + case SelectStatic(FieldIdent(item)) => + val className = item.className val checkedClass = lookupClass(className) if (checkedClass.kind.isJSType) { reportError(i"Cannot select static $item of JS type $className") @@ -530,8 +532,9 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { for (arg <- args) typecheckExprOrSpread(arg, env) - case JSPrivateSelect(qualifier, className, field) => + case JSPrivateSelect(qualifier, field) => typecheckExpr(qualifier, env) + val className = field.name.className val checkedClass = lookupClass(className) if (!checkedClass.kind.isJSClass && checkedClass.kind != ClassKind.AbstractJSType) { reportError(i"Cannot select JS private field $field of non-JS class $className") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 05763942ff..6d8cc24f28 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -645,7 +645,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: field = anyField.asInstanceOf[FieldDef] if parent.fieldsRead.contains(field.name.name) } yield { - parent.className -> field + field } Some(new OptimizerCore.InlineableClassStructure(allFields)) @@ -717,8 +717,8 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } def isElidableStat(tree: Tree): Boolean = tree match { - case Block(stats) => stats.forall(isElidableStat) - case Assign(Select(This(), _, _), rhs) => isTriviallySideEffectFree(rhs) + case Block(stats) => stats.forall(isElidableStat) + case Assign(Select(This(), _), rhs) => isTriviallySideEffectFree(rhs) // Mixin constructor -- test whether its body is entirely empty case ApplyStatically(flags, This(), className, methodName, Nil) @@ -1462,11 +1462,11 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } } - protected def isFieldRead(className: ClassName, fieldName: FieldName): Boolean = - getInterface(className).askFieldRead(fieldName, asker) + protected def isFieldRead(fieldName: FieldName): Boolean = + getInterface(fieldName.className).askFieldRead(fieldName, asker) - protected def isStaticFieldRead(className: ClassName, fieldName: FieldName): Boolean = - getInterface(className).askStaticFieldRead(fieldName, asker) + protected def isStaticFieldRead(fieldName: FieldName): Boolean = + getInterface(fieldName.className).askStaticFieldRead(fieldName, asker) } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index a417440b22..2dca9842bd 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -88,10 +88,10 @@ private[optimizer] abstract class OptimizerCore( target: ImportTarget): Option[JSNativeLoadSpec.Import] /** Returns true if the given (non-static) field is ever read. */ - protected def isFieldRead(className: ClassName, fieldName: FieldName): Boolean + protected def isFieldRead(fieldName: FieldName): Boolean /** Returns true if the given static field is ever read. */ - protected def isStaticFieldRead(className: ClassName, fieldName: FieldName): Boolean + protected def isStaticFieldRead(fieldName: FieldName): Boolean private val localNameAllocator = new FreshNameAllocator.Local @@ -180,9 +180,9 @@ private[optimizer] abstract class OptimizerCore( } else { val after = from.tail val afterIsTrivial = after.forall { - case Assign(Select(This(), _, _), _:Literal | _:VarRef) => + case Assign(Select(This(), _), _:Literal | _:VarRef) => true - case Assign(SelectStatic(_, _), _:Literal | _:VarRef) => + case Assign(SelectStatic(_), _:Literal | _:VarRef) => true case _ => false @@ -334,15 +334,15 @@ private[optimizer] abstract class OptimizerCore( } lhs match { - case Select(qualifier, className, FieldIdent(name)) if !isFieldRead(className, name) => + case Select(qualifier, FieldIdent(name)) if !isFieldRead(name) => // Field is never read. Drop assign, keep side effects only. Block(transformStat(qualifier), transformStat(rhs)) - case SelectStatic(className, FieldIdent(name)) if !isStaticFieldRead(className, name) => + case SelectStatic(FieldIdent(name)) if !isStaticFieldRead(name) => // Field is never read. Drop assign, keep side effects only. transformStat(rhs) - case JSPrivateSelect(qualifier, className, FieldIdent(name)) if !isFieldRead(className, name) => + case JSPrivateSelect(qualifier, FieldIdent(name)) if !isFieldRead(name) => // Field is never read. Drop assign, keep side effects only. Block(transformStat(qualifier), transformStat(rhs)) @@ -574,8 +574,8 @@ private[optimizer] abstract class OptimizerCore( case JSNew(ctor, args) => JSNew(transformExpr(ctor), transformExprsOrSpreads(args)) - case JSPrivateSelect(qualifier, className, field) => - JSPrivateSelect(transformExpr(qualifier), className, field) + case JSPrivateSelect(qualifier, field) => + JSPrivateSelect(transformExpr(qualifier), field) case tree: JSSelect => trampoline { @@ -967,7 +967,7 @@ private[optimizer] abstract class OptimizerCore( } else if (baseTpe == NullType) { cont(checkNotNull(texpr)) } else if (isSubtype(baseTpe, JavaScriptExceptionClassType)) { - pretransformSelectCommon(AnyType, texpr, JavaScriptExceptionClass, + pretransformSelectCommon(AnyType, texpr, FieldIdent(exceptionFieldName), isLhsOfAssign = false)(cont) } else { if (texpr.tpe.isExact || !isSubtype(JavaScriptExceptionClassType, baseTpe)) @@ -1171,16 +1171,15 @@ private[optimizer] abstract class OptimizerCore( private def pretransformSelectCommon(tree: Select, isLhsOfAssign: Boolean)( cont: PreTransCont)( implicit scope: Scope): TailRec[Tree] = { - val Select(qualifier, className, field) = tree + val Select(qualifier, field) = tree pretransformExpr(qualifier) { preTransQual => - pretransformSelectCommon(tree.tpe, preTransQual, className, field, - isLhsOfAssign)(cont)(scope, tree.pos) + pretransformSelectCommon(tree.tpe, preTransQual, field, isLhsOfAssign)( + cont)(scope, tree.pos) } } private def pretransformSelectCommon(expectedType: Type, - preTransQual: PreTransform, className: ClassName, field: FieldIdent, - isLhsOfAssign: Boolean)( + preTransQual: PreTransform, field: FieldIdent, isLhsOfAssign: Boolean)( cont: PreTransCont)( implicit scope: Scope, pos: Position): TailRec[Tree] = { /* Note: Callers are expected to have already removed writes to fields that @@ -1190,7 +1189,7 @@ private[optimizer] abstract class OptimizerCore( preTransQual match { case PreTransLocalDef(LocalDef(_, _, InlineClassBeingConstructedReplacement(_, fieldLocalDefs, cancelFun))) => - val fieldLocalDef = fieldLocalDefs(FieldID(className, field)) + val fieldLocalDef = fieldLocalDefs(field.name) if (!isLhsOfAssign || fieldLocalDef.mutable) { cont(fieldLocalDef.toPreTransform) } else { @@ -1205,18 +1204,18 @@ private[optimizer] abstract class OptimizerCore( case PreTransLocalDef(LocalDef(_, _, InlineClassInstanceReplacement(_, fieldLocalDefs, cancelFun))) => - val fieldLocalDef = fieldLocalDefs(FieldID(className, field)) + val fieldLocalDef = fieldLocalDefs(field.name) assert(!isLhsOfAssign || fieldLocalDef.mutable, s"assign to immutable field at $pos") cont(fieldLocalDef.toPreTransform) // Select the lo or hi "field" of a Long literal case PreTransLit(LongLiteral(value)) if useRuntimeLong => val itemName = field.name - assert(itemName == inlinedRTLongLoField || - itemName == inlinedRTLongHiField) + assert(itemName.simpleName == inlinedRTLongLoField || + itemName.simpleName == inlinedRTLongHiField) assert(expectedType == IntType) val resultValue = - if (itemName == inlinedRTLongLoField) value.toInt + if (itemName.simpleName == inlinedRTLongLoField) value.toInt else (value >>> 32).toInt cont(PreTransLit(IntLiteral(resultValue))) @@ -1224,8 +1223,13 @@ private[optimizer] abstract class OptimizerCore( resolveLocalDef(preTransQual) match { case PreTransRecordTree(newQual, origType, cancelFun) => val recordType = newQual.tpe.asInstanceOf[RecordType] - val recordField = recordType.findField(field.name) - val sel = RecordSelect(newQual, field)(recordField.tpe) + /* FIXME How come this lookup requires only the `simpleName`? + * The `recordType` is created at `InlineableClassStructure.recordType`, + * where it uses an allocator. Something fishy is going on here. + * (And no, this is not dead code.) + */ + val recordField = recordType.findField(field.name.simpleName) + val sel = RecordSelect(newQual, SimpleFieldIdent(recordField.name))(recordField.tpe) sel.tpe match { case _: RecordType => cont(PreTransRecordTree(sel, RefinedType(expectedType), cancelFun)) @@ -1235,7 +1239,7 @@ private[optimizer] abstract class OptimizerCore( case PreTransTree(newQual, newQualType) => val newQual1 = maybeAssumeNotNull(newQual, newQualType) - cont(PreTransTree(Select(newQual1, className, field)(expectedType), + cont(PreTransTree(Select(newQual1, field)(expectedType), RefinedType(expectedType))) } } @@ -1332,7 +1336,7 @@ private[optimizer] abstract class OptimizerCore( if (!isImmutableType(recordType)) cancelFun() PreTransRecordTree( - RecordValue(recordType, structure.fieldIDs.map( + RecordValue(recordType, structure.fieldNames.map( id => fieldLocalDefs(id).newReplacement)), tpe, cancelFun) @@ -1591,7 +1595,7 @@ private[optimizer] abstract class OptimizerCore( checkNotNullStatement(array)(stat.pos) case ArraySelect(array, index) if semantics.arrayIndexOutOfBounds == CheckedBehavior.Unchecked => Block(checkNotNullStatement(array)(stat.pos), keepOnlySideEffects(index))(stat.pos) - case Select(qualifier, _, _) => + case Select(qualifier, _) => checkNotNullStatement(qualifier)(stat.pos) case Closure(_, _, _, _, _, captureValues) => Block(captureValues.map(keepOnlySideEffects))(stat.pos) @@ -2221,13 +2225,13 @@ private[optimizer] abstract class OptimizerCore( "There was a This(), there should be a receiver") cont(checkNotNull(optReceiver.get._2)) - case Select(This(), className, field) if formals.isEmpty => + case Select(This(), field) if formals.isEmpty => assert(optReceiver.isDefined, "There was a This(), there should be a receiver") - pretransformSelectCommon(body.tpe, optReceiver.get._2, className, field, + pretransformSelectCommon(body.tpe, optReceiver.get._2, field, isLhsOfAssign = false)(cont) - case Assign(lhs @ Select(This(), className, field), VarRef(LocalIdent(rhsName))) + case Assign(lhs @ Select(This(), field), VarRef(LocalIdent(rhsName))) if formals.size == 1 && formals.head.name.name == rhsName => assert(isStat, "Found Assign in expression position") assert(optReceiver.isDefined, @@ -2236,11 +2240,11 @@ private[optimizer] abstract class OptimizerCore( val treceiver = optReceiver.get._2 val trhs = args.head - if (!isFieldRead(className, field.name)) { + if (!isFieldRead(field.name)) { // Field is never read, discard assign, keep side effects only. cont(PreTransTree(finishTransformArgsAsStat(), RefinedType.NoRefinedType)) } else { - pretransformSelectCommon(lhs.tpe, treceiver, className, field, + pretransformSelectCommon(lhs.tpe, treceiver, field, isLhsOfAssign = true) { tlhs => pretransformAssign(tlhs, args.head)(cont) } @@ -2582,7 +2586,7 @@ private[optimizer] abstract class OptimizerCore( elemLocalDef match { case LocalDef(RefinedType(ClassType(Tuple2Class), _, _), false, InlineClassInstanceReplacement(structure, tupleFields, _)) => - val List(key, value) = structure.fieldIDs.map(tupleFields) + val List(key, value) = structure.fieldNames.map(tupleFields) (key.newReplacement, value.newReplacement) case _ => @@ -2658,7 +2662,7 @@ private[optimizer] abstract class OptimizerCore( withNewLocalDefs(initialFieldBindings) { (initialFieldLocalDefList, cont1) => val initialFieldLocalDefs = - structure.fieldIDs.zip(initialFieldLocalDefList).toMap + structure.fieldNames.zip(initialFieldLocalDefList).toMap inlineClassConstructorBody(allocationSite, structure, initialFieldLocalDefs, className, className, ctor, args, cancelFun) { (finalFieldLocalDefs, cont2) => @@ -2674,10 +2678,10 @@ private[optimizer] abstract class OptimizerCore( private def inlineClassConstructorBody( allocationSite: AllocationSite, structure: InlineableClassStructure, - inputFieldsLocalDefs: Map[FieldID, LocalDef], className: ClassName, + inputFieldsLocalDefs: Map[FieldName, LocalDef], className: ClassName, ctorClass: ClassName, ctor: MethodIdent, args: List[PreTransform], cancelFun: CancelFun)( - buildInner: (Map[FieldID, LocalDef], PreTransCont) => TailRec[Tree])( + buildInner: (Map[FieldName, LocalDef], PreTransCont) => TailRec[Tree])( cont: PreTransCont)( implicit scope: Scope): TailRec[Tree] = tailcall { @@ -2714,9 +2718,9 @@ private[optimizer] abstract class OptimizerCore( private def inlineClassConstructorBodyList( allocationSite: AllocationSite, structure: InlineableClassStructure, - thisLocalDef: LocalDef, inputFieldsLocalDefs: Map[FieldID, LocalDef], + thisLocalDef: LocalDef, inputFieldsLocalDefs: Map[FieldName, LocalDef], className: ClassName, stats: List[Tree], cancelFun: CancelFun)( - buildInner: (Map[FieldID, LocalDef], PreTransCont) => TailRec[Tree])( + buildInner: (Map[FieldName, LocalDef], PreTransCont) => TailRec[Tree])( cont: PreTransCont)( implicit scope: Scope): TailRec[Tree] = { @@ -2745,18 +2749,17 @@ private[optimizer] abstract class OptimizerCore( inlineClassConstructorBodyList(allocationSite, structure, thisLocalDef, inputFieldsLocalDefs, className, rest, cancelFun)(buildInner)(cont) - case Assign(s @ Select(ths: This, className, field), value) :: rest - if !inputFieldsLocalDefs.contains(FieldID(className, field)) => + case Assign(s @ Select(ths: This, field), value) :: rest + if !inputFieldsLocalDefs.contains(field.name) => // Field is being optimized away. Only keep side effects of the write. withStat(value, rest) - case Assign(s @ Select(ths: This, className, field), value) :: rest - if !inputFieldsLocalDefs(FieldID(className, field)).mutable => + case Assign(s @ Select(ths: This, field), value) :: rest + if !inputFieldsLocalDefs(field.name).mutable => pretransformExpr(value) { tvalue => - val fieldID = FieldID(className, field) - val originalName = structure.fieldOriginalName(fieldID) + val originalName = structure.fieldOriginalName(field.name) val binding = Binding( - Binding.Local(field.name.toLocalName, originalName), + Binding.Local(field.name.simpleName.toLocalName, originalName), s.tpe, false, tvalue) withNewLocalDef(binding) { (localDef, cont1) => if (localDef.contains(thisLocalDef)) { @@ -2766,7 +2769,7 @@ private[optimizer] abstract class OptimizerCore( cancelFun() } val newFieldsLocalDefs = - inputFieldsLocalDefs.updated(fieldID, localDef) + inputFieldsLocalDefs.updated(field.name, localDef) val newThisLocalDef = LocalDef(thisLocalDef.tpe, false, InlineClassBeingConstructedReplacement(structure, newFieldsLocalDefs, cancelFun)) val restScope = @@ -2792,7 +2795,7 @@ private[optimizer] abstract class OptimizerCore( * coming from Scala.js < 1.15.1 (since 1.15.1, we intercept that shape * already in the compiler back-end). */ - case If(cond, th: Throw, Assign(Select(This(), _, _), value)) :: rest => + case If(cond, th: Throw, Assign(Select(This(), _), value)) :: rest => // work around a bug of the compiler (these should be @-bindings) val stat = stats.head.asInstanceOf[If] val ass = stat.elsep.asInstanceOf[Assign] @@ -5083,7 +5086,8 @@ private[optimizer] object OptimizerCore { private val JavaScriptExceptionClassType = ClassType(JavaScriptExceptionClass) private val ThrowableClassType = ClassType(ThrowableClass) - private val exceptionFieldName = FieldName("exception") + private val exceptionFieldName = + FieldName(JavaScriptExceptionClass, SimpleFieldName("exception")) private val AnyArgConstructorName = MethodName.constructor(List(ClassRef(ObjectClass))) @@ -5092,34 +5096,31 @@ private[optimizer] object OptimizerCore { private val ClassTagApplyMethodName = MethodName("apply", List(ClassRef(ClassClass)), ClassRef(ClassName("scala.reflect.ClassTag"))) - final class InlineableClassStructure( - /** `List[ownerClassName -> fieldDef]`. */ - private val allFields: List[(ClassName, FieldDef)]) { - - private[OptimizerCore] val fieldIDs: List[FieldID] = - allFields.map(field => FieldID(field._1, field._2)) + final class InlineableClassStructure(private val allFields: List[FieldDef]) { + private[OptimizerCore] val fieldNames: List[FieldName] = + allFields.map(_.name.name) private[OptimizerCore] val recordType: RecordType = { val allocator = new FreshNameAllocator.Field val recordFields = for { - (className, f @ FieldDef(flags, FieldIdent(name), originalName, ftpe)) <- allFields + f @ FieldDef(flags, FieldIdent(name), originalName, ftpe) <- allFields } yield { assert(!flags.namespace.isStatic, s"unexpected static field in InlineableClassStructure at ${f.pos}") - RecordType.Field(allocator.freshName(name), originalName, ftpe, + RecordType.Field(allocator.freshName(name.simpleName), originalName, ftpe, flags.isMutable) } RecordType(recordFields) } - private val recordFieldNames: Map[FieldID, RecordType.Field] = { - val elems = for (((className, fieldDef), recordField) <- allFields.zip(recordType.fields)) - yield FieldID(className, fieldDef) -> recordField + private val recordFieldNames: Map[FieldName, RecordType.Field] = { + val elems = for ((fieldDef, recordField) <- allFields.zip(recordType.fields)) + yield fieldDef.name.name -> recordField elems.toMap } - private[OptimizerCore] def fieldOriginalName(fieldID: FieldID): OriginalName = - recordFieldNames(fieldID).originalName + private[OptimizerCore] def fieldOriginalName(fieldName: FieldName): OriginalName = + recordFieldNames(fieldName).originalName override def equals(that: Any): Boolean = that match { case that: InlineableClassStructure => @@ -5132,7 +5133,7 @@ private[optimizer] object OptimizerCore { override def toString(): String = { allFields - .map(f => s"${f._1.nameString}::${f._2.name.name.nameString}: ${f._2.ftpe}") + .map(f => s"${f.name.name.nameString}: ${f.ftpe}") .mkString("InlineableClassStructure(", ", ", ")") } } @@ -5276,7 +5277,7 @@ private[optimizer] object OptimizerCore { */ case InlineClassInstanceReplacement(structure, fieldLocalDefs, _) if tpe.base == ClassType(LongImpl.RuntimeLongClass) => - val List(loField, hiField) = structure.fieldIDs + val List(loField, hiField) = structure.fieldNames val lo = fieldLocalDefs(loField).newReplacement val hi = fieldLocalDefs(hiField).newReplacement createNewLong(lo, hi) @@ -5341,12 +5342,12 @@ private[optimizer] object OptimizerCore { private final case class InlineClassBeingConstructedReplacement( structure: InlineableClassStructure, - fieldLocalDefs: Map[FieldID, LocalDef], + fieldLocalDefs: Map[FieldName, LocalDef], cancelFun: CancelFun) extends LocalDefReplacement private final case class InlineClassInstanceReplacement( structure: InlineableClassStructure, - fieldLocalDefs: Map[FieldID, LocalDef], + fieldLocalDefs: Map[FieldName, LocalDef], cancelFun: CancelFun) extends LocalDefReplacement private final case class InlineJSArrayReplacement( @@ -5798,8 +5799,8 @@ private[optimizer] object OptimizerCore { val RecordType(List(loField, hiField)) = recordVarRef.tpe createNewLong( - RecordSelect(recordVarRef, FieldIdent(loField.name))(IntType), - RecordSelect(recordVarRef, FieldIdent(hiField.name))(IntType)) + RecordSelect(recordVarRef, SimpleFieldIdent(loField.name))(IntType), + RecordSelect(recordVarRef, SimpleFieldIdent(hiField.name))(IntType)) } /** Creates a new instance of `RuntimeLong` from its `lo` and `hi` parts. */ @@ -6056,9 +6057,9 @@ private[optimizer] object OptimizerCore { true // Shape of accessors - case Select(This(), _, _) if params.isEmpty => + case Select(This(), _) if params.isEmpty => true - case Assign(Select(This(), _, _), VarRef(_)) if params.size == 1 => + case Assign(Select(This(), _), VarRef(_)) if params.size == 1 => true // Shape of trivial call-super constructors @@ -6150,7 +6151,7 @@ private[optimizer] object OptimizerCore { */ private def isSmallTree(tree: TreeOrJSSpread): Boolean = tree match { case _:VarRef | _:Literal => true - case Select(This(), _, _) => true + case Select(This(), _) => true case UnaryOp(_, lhs) => isSmallTree(lhs) case BinaryOp(_, lhs, rhs) => isSmallTree(lhs) && isSmallTree(rhs) case JSUnaryOp(_, lhs) => isSmallTree(lhs) @@ -6165,7 +6166,7 @@ private[optimizer] object OptimizerCore { case Apply(_, receiver, _, args) => areSimpleArgs(receiver :: args) case ApplyStatically(_, receiver, _, _, args) => areSimpleArgs(receiver :: args) case ApplyStatic(_, _, _, args) => areSimpleArgs(args) - case Select(qual, _, _) => isSimpleArg(qual) + case Select(qual, _) => isSimpleArg(qual) case IsInstanceOf(inner, _) => isSimpleArg(inner) case Block(List(inner, Undefined())) => @@ -6319,11 +6320,11 @@ private[optimizer] object OptimizerCore { name.withSuffix(suffix) } - private val InitialFieldMap: Map[FieldName, Int] = + private val InitialFieldMap: Map[SimpleFieldName, Int] = Map.empty - final class Field extends FreshNameAllocator[FieldName](InitialFieldMap) { - protected def nameWithSuffix(name: FieldName, suffix: String): FieldName = + final class Field extends FreshNameAllocator[SimpleFieldName](InitialFieldMap) { + protected def nameWithSuffix(name: SimpleFieldName, suffix: String): SimpleFieldName = name.withSuffix(suffix) } @@ -6337,30 +6338,6 @@ private[optimizer] object OptimizerCore { else OriginalName(base) } - final class FieldID private (val ownerClassName: ClassName, val name: FieldName) { - override def equals(that: Any): Boolean = that match { - case that: FieldID => - this.ownerClassName == that.ownerClassName && - this.name == that.name - case _ => - false - } - - override def hashCode(): Int = - ownerClassName.## ^ name.## - - override def toString(): String = - s"FieldID($ownerClassName, $name)" - } - - object FieldID { - def apply(ownerClassName: ClassName, field: FieldIdent): FieldID = - new FieldID(ownerClassName, field.name) - - def apply(ownerClassName: ClassName, fieldDef: FieldDef): FieldID = - new FieldID(ownerClassName, fieldDef.name.name) - } - private sealed abstract class IsUsed { def isUsed: Boolean } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala index 608005203e..441b146638 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala @@ -203,14 +203,14 @@ class OptimizerTest { fields = List( // static var foo: java.lang.String FieldDef(EMF.withNamespace(PublicStatic).withMutable(true), - "foo", NON, StringType) + FieldName(MainTestClassName, "foo"), NON, StringType) ), methods = List( trivialCtor(MainTestClassName), // static def foo(): java.lang.String = Test::foo MethodDef(EMF.withNamespace(MemberNamespace.PublicStatic), fooGetter, NON, Nil, StringType, Some({ - SelectStatic(MainTestClassName, "foo")(StringType) + SelectStatic(FieldName(MainTestClassName, "foo"))(StringType) }))(EOH, UNV), // static def main(args: String[]) { println(Test::foo()) } mainMethodDef({ @@ -223,7 +223,7 @@ class OptimizerTest { for (moduleSet <- linkToModuleSet(classDefs, MainTestModuleInitializers)) yield { val mainClassDef = findClass(moduleSet, MainTestClassName).get assertTrue(mainClassDef.fields.exists { - case FieldDef(_, FieldIdent(name), _, _) => name == FieldName("foo") + case FieldDef(_, FieldIdent(name), _, _) => name == FieldName(MainTestClassName, "foo") case _ => false }) } @@ -491,10 +491,10 @@ class OptimizerTest { classDef("Foo", kind = ClassKind.Class, superClass = Some(ObjectClass), fields = List( // x: Witness - FieldDef(EMF.withMutable(witnessMutable), "x", NON, witnessType), + FieldDef(EMF.withMutable(witnessMutable), FieldName("Foo", "x"), NON, witnessType), // y: Int - FieldDef(EMF, "y", NON, IntType) + FieldDef(EMF, FieldName("Foo", "y"), NON, IntType) ), methods = List( // def this() = { @@ -502,13 +502,13 @@ class OptimizerTest { // this.y = 5 // } MethodDef(EMF.withNamespace(Constructor), NoArgConstructorName, NON, Nil, NoType, Some(Block( - Assign(Select(This()(ClassType("Foo")), "Foo", "x")(witnessType), Null()), - Assign(Select(This()(ClassType("Foo")), "Foo", "y")(IntType), int(5)) + Assign(Select(This()(ClassType("Foo")), FieldName("Foo", "x"))(witnessType), Null()), + Assign(Select(This()(ClassType("Foo")), FieldName("Foo", "y"))(IntType), int(5)) )))(EOH, UNV), // def method(): Int = this.y MethodDef(EMF, methodName, NON, Nil, IntType, Some { - Select(This()(ClassType("Foo")), "Foo", "y")(IntType) + Select(This()(ClassType("Foo")), FieldName("Foo", "y"))(IntType) })(EOH, UNV) ), optimizerHints = EOH.withInline(classInline) @@ -527,7 +527,7 @@ class OptimizerTest { moduleSet <- linkToModuleSet(classDefs, MainTestModuleInitializers) } yield { findClass(moduleSet, "Foo").get.fields match { - case List(FieldDef(_, FieldIdent(name), _, _)) if name == FieldName("y") => + case List(FieldDef(_, FieldIdent(name), _, _)) if name == FieldName("Foo", "y") => // ok case fields => diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index 75717babd3..e9f6ba2306 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -122,15 +122,47 @@ class ClassDefCheckerTest { "interfaces may not have a superClass") } + @Test + def fieldDefClassName(): Unit = { + assertError( + classDef( + "A", + superClass = Some(ObjectClass), + fields = List( + FieldDef(EMF, FieldName("B", "foo"), NON, IntType) + ), + methods = List(trivialCtor("A")) + ), + "illegal FieldDef with name B::foo in class A" + ) + + // evidence that we do not need an explicit check for top-level field exports + assertError( + classDef( + "A", + kind = ClassKind.ModuleClass, + superClass = Some(ObjectClass), + fields = List( + FieldDef(EMF.withNamespace(MemberNamespace.PublicStatic), FieldName("A", "foo"), NON, IntType) + ), + methods = List(trivialCtor("A")), + topLevelExportDefs = List( + TopLevelFieldExportDef("main", "foo", FieldName("B", "foo")) + ) + ), + "Cannot export non-existent static field 'B::foo'" + ) + } + @Test def noDuplicateFields(): Unit = { assertError( classDef("A", superClass = Some(ObjectClass), fields = List( - FieldDef(EMF, "foobar", NON, IntType), - FieldDef(EMF, "foobar", NON, BooleanType) + FieldDef(EMF, FieldName("A", "foobar"), NON, IntType), + FieldDef(EMF, FieldName("A", "foobar"), NON, BooleanType) )), - "duplicate field 'foobar'") + "duplicate field 'A::foobar'") } @Test diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala index 8b73664dc8..be6dbb33a9 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala @@ -155,8 +155,8 @@ object TestIRBuilder { LocalName(name) implicit def string2LabelName(name: String): LabelName = LabelName(name) - implicit def string2FieldName(name: String): FieldName = - FieldName(name) + implicit def string2SimpleFieldName(name: String): SimpleFieldName = + SimpleFieldName(name) implicit def string2ClassName(name: String): ClassName = ClassName(name) @@ -164,13 +164,13 @@ object TestIRBuilder { LocalIdent(LocalName(name)) implicit def string2LabelIdent(name: String): LabelIdent = LabelIdent(LabelName(name)) - implicit def string2FieldIdent(name: String): FieldIdent = - FieldIdent(FieldName(name)) implicit def string2ClassIdent(name: String): ClassIdent = ClassIdent(ClassName(name)) implicit def localName2LocalIdent(name: LocalName): LocalIdent = LocalIdent(name) + implicit def fieldName2FieldIdent(name: FieldName): FieldIdent = + FieldIdent(name) implicit def methodName2MethodIdent(name: MethodName): MethodIdent = MethodIdent(name) diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 0144862b7b..6d2c642453 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -6,8 +6,21 @@ import com.typesafe.tools.mima.core.ProblemFilters._ object BinaryIncompatibilities { val IR = Seq( // !!! Breaking, OK in minor release + + ProblemFilters.exclude[MissingTypesProblem]("org.scalajs.ir.Names$FieldName"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Names#FieldName.*"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Names#LocalName.fromFieldName"), + + ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.ir.Types#RecordType.findField"), + ProblemFilters.exclude[MemberProblem]("org.scalajs.ir.Types#RecordType#Field.*"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Trees#StoreModule.*"), ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#StoreModule.unapply"), + + ProblemFilters.exclude[MemberProblem]("org.scalajs.ir.Trees#JSPrivateSelect.*"), + ProblemFilters.exclude[MemberProblem]("org.scalajs.ir.Trees#RecordSelect.*"), + ProblemFilters.exclude[MemberProblem]("org.scalajs.ir.Trees#Select.*"), + ProblemFilters.exclude[MemberProblem]("org.scalajs.ir.Trees#SelectStatic.*"), ) val Linker = Seq( diff --git a/project/JavalibIRCleaner.scala b/project/JavalibIRCleaner.scala index 58fdc14a8a..305fc74212 100644 --- a/project/JavalibIRCleaner.scala +++ b/project/JavalibIRCleaner.scala @@ -439,8 +439,9 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { case New(className, ctor, args) => New(transformNonJSClassName(className), transformMethodIdent(ctor), args) - case Select(qualifier, className, field) => - Select(qualifier, transformNonJSClassName(className), field)(transformType(tree.tpe)) + case Select(qualifier, field @ FieldIdent(fieldName)) => + val newFieldName = FieldName(transformNonJSClassName(fieldName.className), fieldName.simpleName) + Select(qualifier, FieldIdent(newFieldName)(field.pos))(transformType(tree.tpe)) case t: Apply => Apply(t.flags, t.receiver, transformMethodIdent(t.method), t.args)( From 0854041850154a62a5a265b4f14ebe1b9b2445fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 12 Feb 2024 18:20:01 +0100 Subject: [PATCH 38/65] Codegen: Replace LoadModule(myClass) by This() after StoreModule(). As the comment explains, this will help the elidable constructors analysis to give stronger guarantees. --- .../org/scalajs/nscplugin/GenJSCode.scala | 24 +++++++++++++++---- .../nscplugin/test/OptimizationTest.scala | 22 +++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 6ad055b5ee..a093b718da 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -6613,11 +6613,25 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (sym.hasAnnotation(JSGlobalScopeAnnotation)) { MaybeGlobalScope.GlobalScope(pos) } else { - val className = encodeClassName(sym) - val tree = - if (isJSType(sym)) js.LoadJSModule(className) - else js.LoadModule(className) - MaybeGlobalScope.NotGlobalScope(tree) + if (sym == currentClassSym.get && isModuleInitialized.get != null && isModuleInitialized.value) { + /* This is a LoadModule(myClass) after the StoreModule(). It is + * guaranteed to always return the `this` value. We eagerly replace + * it by a `This()` node to help the elidable constructors analysis + * of the linker. If we don't do this, then the analysis must + * tolerate `LoadModule(myClass)` after `StoreModule()` to be + * side-effect-free, but that would weaken the guarantees resulting + * from the analysis. In particular, it cannot guarantee that the + * result of a `LoadModule()` of a module with elidable constructors + * is always fully initialized. + */ + MaybeGlobalScope.NotGlobalScope(genThis()) + } else { + val className = encodeClassName(sym) + val tree = + if (isJSType(sym)) js.LoadJSModule(className) + else js.LoadModule(className) + MaybeGlobalScope.NotGlobalScope(tree) + } } } } diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala index f3a5ab9ac9..7e140ebd38 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala @@ -560,6 +560,28 @@ class OptimizationTest extends JSASTTest { assertTrue(flags.inline) } + + @Test + def loadModuleAfterStoreModuleIsThis: Unit = { + val testName = ClassName("Test$") + + """ + object Test { + private val selfPair = (Test, Test) + } + """.hasNot("LoadModule") { + case js.LoadModule(_) => + } + + // Confidence check + """ + object Test { + private def selfPair = (Test, Test) + } + """.hasExactly(2, "LoadModule") { + case js.LoadModule(`testName`) => + } + } } object OptimizationTest { From b6db0f5b9d9444fc25af5ecb00378121a7e98b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 12 Feb 2024 19:24:52 +0100 Subject: [PATCH 39/65] New side-effect-free analysis of ctors that also requires acyclic init. Previously, the inter-class side-effect-free analysis of constructors allowed cycles in the initialization graph. For example, `A` and `B` would be considered to have elidable constructors in object A { B } object B { A } To do that, the actual algorithm started from the known-`NotElidable` classes and propagated that to the classes depending on them. Now, we instead also require that the initialization graph be *acyclic* for a class to have elidable constructors. To do that, we reverse the algorithm: we start from known-`AcyclicElidable` classes and propagate that to classes that *only* depend on them. Now, why would we impose *more* requirements for a class to have elidable constructors? Because it guarantees that the result of `LoadModule` is actually non-null *and* fully initialized. That means we can also follow `Select`ions and `Apply`s of getter-shaped methods on these. Counter-intuitively, that means *more* classes can be considered to have elidable constructors. The additional guarantee of being fully initialized will also be used in the following commit. --- .../frontend/optimizer/IncOptimizer.scala | 157 +++++++++++++++--- project/Build.scala | 4 +- 2 files changed, 135 insertions(+), 26 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 6d8cc24f28..6118b1153e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -271,37 +271,93 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: private def updateElidableConstructors(): Unit = { import ElidableConstructorsInfo._ - /* Invariant: when something is in the stack, its - * elidableConstructorsInfo was set to NotElidable. - */ val toProcessStack = mutable.ArrayBuffer.empty[Class] - // Build the graph and initial stack from the infos + /* Invariants for this algo: + * - when a class is in the stack, its elidableConstructorsInfo was set + * to AcyclicElidable, + * - when a class has `DependentOn` as its info, its + * `elidableConstructorsRemainingDependenciesCount` is the number of + * classes in its `dependencies` that have not yet been *processed* as + * AcyclicElidable. + * + * During this algorithm, the info can transition from DependentOn to + * + * - NotElidable, if its getter dependencies were not satisfied. + * - AcyclicElidable. + * + * Other transitions are not possible. + */ + + def isGetter(classAndMethodName: (ClassName, MethodName)): Boolean = { + val (className, methodName) = classAndMethodName + classes(className).lookupMethod(methodName).exists { m => + m.originalDef.body match { + case Some(Select(This(), _)) => true + case _ => false + } + } + } + + /* Initialization: + * - Prune classes with unsatisfied getter dependencies + * - Build reverse dependencies + * - Initialize `elidableConstructorsRemainingDependenciesCount` for `DependentOn` classes + * - Initialize the stack with dependency-free classes + */ for (cls <- classes.valuesIterator) { cls.elidableConstructorsInfo match { - case NotElidable => + case DependentOn(deps, getterDeps) => + if (!getterDeps.forall(isGetter(_))) { + cls.elidableConstructorsInfo = NotElidable + } else { + if (deps.isEmpty) { + cls.elidableConstructorsInfo = AcyclicElidable + toProcessStack += cls + } else { + cls.elidableConstructorsRemainingDependenciesCount = deps.size + deps.foreach(dep => classes(dep).elidableConstructorsDependents += cls) + } + } + case AcyclicElidable => toProcessStack += cls - case DependentOn(dependencies) => - for (dependency <- dependencies) - classes(dependency).elidableConstructorsDependents += cls + case NotElidable => + () } } - // Propagate + /* Propagate AcyclicElidable + * When a class `cls` is on the stack, it is known to be AcyclicElidable. + * Go to all its dependents and decrement their count of remaining + * dependencies. If the count reaches 0, then all the dependencies of the + * class are known to be AcyclicElidable, and so the new class is known to + * be AcyclicElidable. + */ while (toProcessStack.nonEmpty) { val cls = toProcessStack.remove(toProcessStack.size - 1) for (dependent <- cls.elidableConstructorsDependents) { - if (dependent.elidableConstructorsInfo != NotElidable) { - dependent.elidableConstructorsInfo = NotElidable - toProcessStack += dependent + dependent.elidableConstructorsInfo match { + case DependentOn(_, _) => + dependent.elidableConstructorsRemainingDependenciesCount -= 1 + if (dependent.elidableConstructorsRemainingDependenciesCount == 0) { + dependent.elidableConstructorsInfo = AcyclicElidable + toProcessStack += dependent + } + case NotElidable => + () + case AcyclicElidable => + throw new AssertionError( + s"Unexpected dependent link from class ${cls.className.nameString} " + + s"to ${dependent.className.nameString} which is AcyclicElidable" + ) } } } // Set the final value of hasElidableConstructors for (cls <- classes.valuesIterator) { - cls.setHasElidableConstructors(cls.elidableConstructorsInfo != NotElidable) + cls.setHasElidableConstructors() } } @@ -430,6 +486,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: var elidableConstructorsInfo: ElidableConstructorsInfo = computeElidableConstructorsInfo(linkedClass) val elidableConstructorsDependents: mutable.ArrayBuffer[Class] = mutable.ArrayBuffer.empty + var elidableConstructorsRemainingDependenciesCount: Int = 0 /** True if *all* constructors of this class are recursively elidable. */ private var hasElidableConstructors: Boolean = @@ -562,7 +619,6 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: // Elidable constructors elidableConstructorsInfo = computeElidableConstructorsInfo(linkedClass) - elidableConstructorsDependents.clear() // Inlineable class if (updateTryNewInlineable(linkedClass)) { @@ -577,12 +633,21 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } /** ELIDABLE CTORS PASS ONLY. */ - def setHasElidableConstructors(newHasElidableConstructors: Boolean): Unit = { + def setHasElidableConstructors(): Unit = { + import ElidableConstructorsInfo._ + + val newHasElidableConstructors = elidableConstructorsInfo == AcyclicElidable + if (hasElidableConstructors != newHasElidableConstructors) { hasElidableConstructors = newHasElidableConstructors hasElidableConstructorsAskers.keysIterator.foreach(_.tag()) hasElidableConstructorsAskers.clear() } + + // Release memory that we won't use anymore + if (!newHasElidableConstructors) + elidableConstructorsInfo = NotElidable // get rid of DependentOn + elidableConstructorsDependents.clear() // also resets state for next run } /** UPDATE PASS ONLY. */ @@ -612,10 +677,10 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: import ElidableConstructorsInfo._ if (isAdHocElidableConstructors(className)) { - AlwaysElidable + AcyclicElidable } else { // It's OK to look at the superClass like this because it will always be updated before myself - var result = superClass.fold(ElidableConstructorsInfo.AlwaysElidable)(_.elidableConstructorsInfo) + var result = superClass.fold[ElidableConstructorsInfo](AcyclicElidable)(_.elidableConstructorsInfo) if (result == NotElidable) { // fast path @@ -691,8 +756,17 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: /** UPDATE PASS ONLY. */ private def computeCtorElidableInfo(impl: MethodImpl): ElidableConstructorsInfo = { + /* Dependencies on other classes to have acyclic elidable constructors + * It is possible for the enclosing class name to be added to this set, + * if the constructor depends on its own class. In that case, the + * analysis will naturally treat it as a cycle and will conclude that the + * class does not have elidable constructors. + */ val dependenciesBuilder = Set.newBuilder[ClassName] + // Dependencies on certain methods to be getters + val getterDependenciesBuilder = Set.newBuilder[(ClassName, MethodName)] + def isTriviallySideEffectFree(tree: Tree): Boolean = tree match { case _:VarRef | _:Literal | _:This | _:Skip => true @@ -712,6 +786,28 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: dependenciesBuilder += className true + case Select(LoadModule(className), _) => + /* If the given module can be loaded without cycles, it is guaranteed + * to be non-null, and therefore the Select is side-effect-free. + */ + dependenciesBuilder += className + true + + case Select(This(), _) => + true + + case Apply(_, LoadModule(className), MethodIdent(methodName), Nil) + if !methodName.isReflectiveProxy => + // For a getter-like call, we need the method to actually be a getter. + dependenciesBuilder += className + getterDependenciesBuilder += ((className, methodName)) + true + + case Apply(_, This(), MethodIdent(methodName), Nil) + if !methodName.isReflectiveProxy => + getterDependenciesBuilder += ((className, methodName)) + true + case _ => false } @@ -755,10 +851,16 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: impl.originalDef.body.fold { throw new AssertionError("Constructor cannot be abstract") } { body => - if (isElidableStat(body)) - ElidableConstructorsInfo.DependentOn(dependenciesBuilder.result()) - else + if (isElidableStat(body)) { + val dependencies = dependenciesBuilder.result() + val getterDependencies = getterDependenciesBuilder.result() + if (dependencies.isEmpty && getterDependencies.isEmpty) + ElidableConstructorsInfo.AcyclicElidable + else + ElidableConstructorsInfo.DependentOn(dependencies, getterDependencies) + } else { ElidableConstructorsInfo.NotElidable + } } } @@ -1482,8 +1584,12 @@ object IncOptimizer { import ElidableConstructorsInfo._ final def mergeWith(that: ElidableConstructorsInfo): ElidableConstructorsInfo = (this, that) match { - case (DependentOn(deps1), DependentOn(deps2)) => - DependentOn(deps1 ++ deps2) + case (DependentOn(deps1, getterDeps1), DependentOn(deps2, getterDeps2)) => + DependentOn(deps1 ++ deps2, getterDeps1 ++ getterDeps2) + case (AcyclicElidable, _) => + that + case (_, AcyclicElidable) => + this case _ => NotElidable } @@ -1492,8 +1598,11 @@ object IncOptimizer { object ElidableConstructorsInfo { case object NotElidable extends ElidableConstructorsInfo - final case class DependentOn(dependencies: Set[ClassName]) extends ElidableConstructorsInfo + case object AcyclicElidable extends ElidableConstructorsInfo - val AlwaysElidable: ElidableConstructorsInfo = DependentOn(Set.empty) + final case class DependentOn( + dependencies: Set[ClassName], + getterDependencies: Set[(ClassName, MethodName)] + ) extends ElidableConstructorsInfo } } diff --git a/project/Build.scala b/project/Build.scala index 263683ee52..f3c9cbedac 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1975,8 +1975,8 @@ object Build { case `default213Version` => Some(ExpectedSizes( - fastLink = 468000 to 469000, - fullLink = 100000 to 101000, + fastLink = 463000 to 464000, + fullLink = 99000 to 100000, fastLinkGz = 60000 to 61000, fullLinkGz = 26000 to 27000, )) From 3348716c2c22f379815eaa83459dc87bb89eec6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 9 Feb 2024 15:31:24 +0100 Subject: [PATCH 40/65] Inline the "body" of fields of modules when possible. If a module `M` has elidable constructors, and an immutable field `f` of `M` is initialized with an "easy" value, we can replace all occurrences of `M.f` by that value. Easy values are literals, loads of other modules, or accesses to fields/getters of other modules. We can do this because having elidable constructors implies that the initialization of the module is acyclic. So once it is fully initialized, all fields have indeed been initialized in a predictable way. This is particularly effective for all the forwarders declared in `scala.Predef$` in `scala.package$`. They contain a series of vals like val Nil = scala.collection.immutable.Nil When typical Scala code refers to `Nil`, it means `scala.Nil` rather than `sci.Nil` We are now able to "inline" those references instead of going through the alias. In addition to generating less code, this tends to give a better type to the result. For example `sci.Nil` is known to be non-nullable, whereas `scala.Nil` could be null. --- .../src/main/scala/org/scalajs/ir/Trees.scala | 20 +- .../frontend/optimizer/IncOptimizer.scala | 241 ++++++++++++++++-- .../frontend/optimizer/OptimizerCore.scala | 155 +++++++++-- project/Build.scala | 2 +- 4 files changed, 373 insertions(+), 45 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index c3d206624a..2dd8e43d36 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -904,7 +904,11 @@ object Trees { // Literals - /** Marker for literals. Literals are always pure. */ + /** Marker for literals. Literals are always pure. + * + * All `Literal`s can be compared for equality. The equality does not take + * the `pos` into account. + */ sealed trait Literal extends Tree /** Marker for literals that can be used in a [[Match]] case. @@ -960,11 +964,25 @@ object Trees { sealed case class FloatLiteral(value: Float)( implicit val pos: Position) extends Literal { val tpe = FloatType + + override def equals(that: Any): Boolean = that match { + case that: FloatLiteral => java.lang.Float.compare(this.value, that.value) == 0 + case _ => false + } + + override def hashCode(): Int = java.lang.Float.hashCode(value) } sealed case class DoubleLiteral(value: Double)( implicit val pos: Position) extends Literal { val tpe = DoubleType + + override def equals(that: Any): Boolean = that match { + case that: DoubleLiteral => java.lang.Double.compare(this.value, that.value) == 0 + case _ => false + } + + override def hashCode(): Int = java.lang.Double.hashCode(value) } sealed case class StringLiteral(value: String)( diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 6118b1153e..d092397b51 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -33,6 +33,8 @@ import org.scalajs.linker.interface.{CheckedBehavior, ModuleKind} import org.scalajs.linker.standard._ import org.scalajs.linker.CollectionsCompat._ +import OptimizerCore.InlineableFieldBodies.FieldBody + /** Incremental optimizer. * * An incremental optimizer optimizes a [[LinkingUnit]] @@ -497,6 +499,13 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: var fieldsRead: Set[FieldName] = linkedClass.fieldsRead var tryNewInlineable: Option[OptimizerCore.InlineableClassStructure] = None + /** The "bodies" of fields that can "inlined", *provided that* the enclosing + * module class has elidable constructors. + */ + private var inlineableFieldBodies: OptimizerCore.InlineableFieldBodies = + computeInlineableFieldBodies(linkedClass) + private val inlineableFieldBodiesAskers = collOps.emptyMap[Processable, Unit] + setupAfterCreation(linkedClass) override def toString(): String = @@ -620,6 +629,14 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: // Elidable constructors elidableConstructorsInfo = computeElidableConstructorsInfo(linkedClass) + // Inlineable field bodies + val newInlineableFieldBodies = computeInlineableFieldBodies(linkedClass) + if (inlineableFieldBodies != newInlineableFieldBodies) { + inlineableFieldBodies = newInlineableFieldBodies + inlineableFieldBodiesAskers.keysIterator.foreach(_.tag()) + inlineableFieldBodiesAskers.clear() + } + // Inlineable class if (updateTryNewInlineable(linkedClass)) { for (method <- methods.values; if method.methodName.isConstructor) @@ -672,6 +689,22 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: hasElidableConstructors } + def askInlineableFieldBodies(asker: Processable): OptimizerCore.InlineableFieldBodies = { + inlineableFieldBodiesAskers.put(asker, ()) + + if (inlineableFieldBodies.isEmpty) { + // Avoid asking for `hasInlineableConstructors`; we always get here for non-ModuleClass'es + asker.registerTo(this) + inlineableFieldBodies + } else { + // No need for asker.registerTo(this) in this branch; it is done anyway in askHasElidableConstructors + if (askHasElidableConstructors(asker)) + inlineableFieldBodies + else + OptimizerCore.InlineableFieldBodies.Empty + } + } + /** UPDATE PASS ONLY. */ private def computeElidableConstructorsInfo(linkedClass: LinkedClass): ElidableConstructorsInfo = { import ElidableConstructorsInfo._ @@ -695,6 +728,39 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } } + /** UPDATE PASS ONLY. */ + private def computeInlineableFieldBodies( + linkedClass: LinkedClass): OptimizerCore.InlineableFieldBodies = { + import OptimizerCore.InlineableFieldBodies + + if (linkedClass.kind != ClassKind.ModuleClass) { + InlineableFieldBodies.Empty + } else { + myInterface.staticLike(MemberNamespace.Constructor).methods.get(NoArgConstructorName) match { + case None => + InlineableFieldBodies.Empty + + case Some(ctor) => + val initFieldBodies = for { + fieldDef <- computeAllInstanceFieldDefs() + if !fieldDef.flags.isMutable + } yield { + // the zero value is always a Literal because the ftpe is not a RecordType + val zeroValue = zeroOf(fieldDef.ftpe)(NoPosition).asInstanceOf[Literal] + fieldDef.name.name -> FieldBody.Literal(zeroValue) + } + + if (initFieldBodies.isEmpty) { + // fast path + InlineableFieldBodies.Empty + } else { + val finalFieldBodies = interpretConstructor(ctor, initFieldBodies.toMap, Nil) + new InlineableFieldBodies(finalFieldBodies) + } + } + } + } + /** UPDATE PASS ONLY. */ def updateTryNewInlineable(linkedClass: LinkedClass): Boolean = { val oldTryNewInlineable = tryNewInlineable @@ -702,23 +768,27 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: tryNewInlineable = if (!linkedClass.optimizerHints.inline) { None } else { - val allFields = for { - parent <- reverseParentChain - anyField <- parent.fields - if !anyField.flags.namespace.isStatic - // non-JS class may only contain FieldDefs (no JSFieldDef) - field = anyField.asInstanceOf[FieldDef] - if parent.fieldsRead.contains(field.name.name) - } yield { - field - } - + val allFields = computeAllInstanceFieldDefs() Some(new OptimizerCore.InlineableClassStructure(allFields)) } tryNewInlineable != oldTryNewInlineable } + /** UPDATE PASS ONLY, used by `computeInlineableFieldBodies` and `updateTryNewInlineable`. */ + private def computeAllInstanceFieldDefs(): List[FieldDef] = { + for { + parent <- reverseParentChain + anyField <- parent.fields + if !anyField.flags.namespace.isStatic + // non-JS class may only contain FieldDefs (no JSFieldDef) + field = anyField.asInstanceOf[FieldDef] + if parent.fieldsRead.contains(field.name.name) + } yield { + field + } + } + /** UPDATE PASS ONLY. */ private[this] def setupAfterCreation(linkedClass: LinkedClass): Unit = { if (batchMode) { @@ -864,6 +934,128 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } } + /** UPDATE PASS ONLY. */ + private def interpretConstructor(impl: MethodImpl, + fieldBodies: Map[FieldName, FieldBody], + paramBodies: List[Option[FieldBody]]): Map[FieldName, FieldBody] = { + + /* This method performs a kind of abstract intepretation of the given + * given constructor `impl`. It *assumes* that the enclosing class ends + * up having elidable constructors. If that is not the case, the + * computation must not crash but its result can be arbitrary. + */ + + type FieldBodyMap = Map[FieldName, FieldBody] + type ParamBodyMap = Map[LocalName, FieldBody] + + def interpretSelfGetter(methodName: MethodName, + fieldBodies: FieldBodyMap): Option[FieldBody] = { + methods.get(methodName).flatMap { impl => + impl.originalDef.body match { + case Some(Select(This(), FieldIdent(fieldName))) => + fieldBodies.get(fieldName) + + case _ => + /* If we get here, it means the class won't have elidable + * constructors, and therefore we can return arbitrary values + * from the whole method, so we don't care. + */ + None + } + } + } + + def interpretExpr(tree: Tree, fieldBodies: FieldBodyMap, + paramBodies: ParamBodyMap): Option[FieldBody] = { + tree match { + case lit: Literal => + Some(FieldBody.Literal(lit)) + + case VarRef(LocalIdent(valName)) => + paramBodies.get(valName) + + case LoadModule(moduleClassName) => + Some(FieldBody.LoadModule(moduleClassName, tree.pos)) + + case Select(qual @ LoadModule(moduleClassName), FieldIdent(fieldName)) => + val moduleBody = FieldBody.LoadModule(moduleClassName, qual.pos) + Some(FieldBody.ModuleSelect(moduleBody, fieldName, tree.tpe, tree.pos)) + + case Select(This(), FieldIdent(fieldName)) => + fieldBodies.get(fieldName) + + case Apply(_, receiver @ LoadModule(moduleClassName), MethodIdent(methodName), Nil) + if !methodName.isReflectiveProxy => + val moduleBody = FieldBody.LoadModule(moduleClassName, receiver.pos) + Some(FieldBody.ModuleGetter(moduleBody, methodName, tree.tpe, tree.pos)) + + case Apply(_, This(), MethodIdent(methodName), Nil) + if !methodName.isReflectiveProxy => + interpretSelfGetter(methodName, fieldBodies) + + case _ => + None + } + } + + def interpretBody(body: Tree, fieldBodies: FieldBodyMap, + paramBodies: ParamBodyMap): FieldBodyMap = { + body match { + case Block(stats) => + stats.foldLeft(fieldBodies) { (prev, stat) => + interpretBody(stat, prev, paramBodies) + } + + case Skip() => + fieldBodies + + case Assign(Select(This(), FieldIdent(fieldName)), rhs) => + if (!fieldBodies.contains(fieldName)) { + // It is a mutable field or it is dce'ed as never read, don't track it + fieldBodies + } else { + interpretExpr(rhs, fieldBodies, paramBodies) match { + case Some(newFieldBody) => + fieldBodies.updated(fieldName, newFieldBody) + case None => + // Unknown value; don't track it anymore + fieldBodies - fieldName + } + } + + // Mixin constructor -- assume it is empty + case ApplyStatically(flags, This(), className, methodName, Nil) + if !flags.isPrivate && !classes.contains(className) => + fieldBodies + + // Delegation to another constructor + case ApplyStatically(flags, This(), className, MethodIdent(methodName), args) if flags.isConstructor => + val argBodies = args.map(interpretExpr(_, fieldBodies, paramBodies)) + val ctorImpl = getInterface(className) + .staticLike(MemberNamespace.Constructor) + .methods(methodName) + interpretConstructor(ctorImpl, fieldBodies, argBodies) + + case _ => + /* Other statements cannot affect the eventual fieldBodies + * (assuming the class ends up having elidable constructors, as always). + */ + fieldBodies + } + } + + impl.originalDef.body.fold { + throw new AssertionError(s"Constructor $impl cannot be abstract") + } { body => + val paramBodiesMap: ParamBodyMap = + impl.originalDef.args.zip(paramBodies).collect { + case (paramDef, Some(paramBody)) => paramDef.name.name -> paramBody + }.toMap + + interpretBody(body, fieldBodies, paramBodiesMap) + } + } + /** All the methods of this class, including inherited ones. * It has () so we remember this is an expensive operation. * UPDATE PASS ONLY. @@ -890,6 +1082,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: def unregisterDependee(dependee: Processable): Unit = { hasElidableConstructorsAskers.remove(dependee) + inlineableFieldBodiesAskers.remove(dependee) } } @@ -1421,8 +1614,8 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: throw new AssertionError("Methods to optimize must be concrete") } - val (newParams, newBody) = new Optimizer(this, this.toString()).optimize( - Some(this), owner.untrackedThisType, params, jsClassCaptures = Nil, + val (newParams, newBody) = new Optimizer(this, Some(this), this.toString()).optimize( + owner.untrackedThisType, params, jsClassCaptures = Nil, resultType, body, isNoArgCtor = name.name == NoArgConstructorName) MethodDef(static, name, originalName, @@ -1446,8 +1639,8 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: case originalDef @ JSMethodDef(flags, name, params, restParam, body) => val thisType = owner.untrackedThisType(flags.namespace) - val (newParamsAndRest, newBody) = new Optimizer(this, this.toString()).optimize( - None, thisType, params ++ restParam.toList, owner.untrackedJSClassCaptures, + val (newParamsAndRest, newBody) = new Optimizer(this, None, this.toString()).optimize( + thisType, params ++ restParam.toList, owner.untrackedJSClassCaptures, AnyType, body, isNoArgCtor = false) val (newParams, newRestParam) = @@ -1462,14 +1655,14 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: val jsClassCaptures = owner.untrackedJSClassCaptures val newGetterBody = getterBody.map { body => - val (_, newBody) = new Optimizer(this, "get " + this.toString()).optimize( - None, thisType, Nil, jsClassCaptures, AnyType, body, isNoArgCtor = false) + val (_, newBody) = new Optimizer(this, None, "get " + this.toString()).optimize( + thisType, Nil, jsClassCaptures, AnyType, body, isNoArgCtor = false) newBody } val newSetterArgAndBody = setterArgAndBody.map { case (param, body) => - val (List(newParam), newBody) = new Optimizer(this, "set " + this.toString()).optimize( - None, thisType, List(param), jsClassCaptures, AnyType, body, + val (List(newParam), newBody) = new Optimizer(this, None, "set " + this.toString()).optimize( + thisType, List(param), jsClassCaptures, AnyType, body, isNoArgCtor = false) (newParam, newBody) } @@ -1494,8 +1687,8 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: val thisType = owner.untrackedThisType(flags.namespace) - val (newParamsAndRest, newRawBody) = new Optimizer(this, this.toString()).optimize( - None, thisType, params ++ restParam.toList, owner.untrackedJSClassCaptures, AnyType, + val (newParamsAndRest, newRawBody) = new Optimizer(this, None, this.toString()).optimize( + thisType, params ++ restParam.toList, owner.untrackedJSClassCaptures, AnyType, Block(body.allStats)(body.pos), isNoArgCtor = false) val (newParams, newRestParam) = @@ -1522,7 +1715,8 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: * * All methods are PROCESS PASS ONLY */ - private final class Optimizer(asker: Processable, debugID: String) + private final class Optimizer( + asker: Processable, protected val myself: Option[MethodImpl], debugID: String) extends OptimizerCore(config, debugID) { import OptimizerCore.ImportTarget @@ -1549,6 +1743,9 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: protected def hasElidableConstructors(className: ClassName): Boolean = classes(className).askHasElidableConstructors(asker) + protected def inlineableFieldBodies(className: ClassName): OptimizerCore.InlineableFieldBodies = + classes.get(className).fold(OptimizerCore.InlineableFieldBodies.Empty)(_.askInlineableFieldBodies(asker)) + protected def tryNewInlineableClass( className: ClassName): Option[OptimizerCore.InlineableClassStructure] = { classes(className).tryNewInlineable diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 2dca9842bd..d9c57e3b9a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -46,6 +46,8 @@ private[optimizer] abstract class OptimizerCore( type MethodID <: AbstractMethodID + protected val myself: Option[MethodID] + private def semantics: Semantics = config.coreSpec.semantics // Uncomment and adapt to print debug messages only during one method @@ -72,6 +74,13 @@ private[optimizer] abstract class OptimizerCore( */ protected def hasElidableConstructors(className: ClassName): Boolean + /** Returns the inlineable field bodies of this module class. + * + * If the class is not a module class, or if it does not have inlineable + * accessors, the resulting `InlineableFieldBodies` is always empty. + */ + protected def inlineableFieldBodies(className: ClassName): InlineableFieldBodies + /** Tests whether the given class is inlineable. * * @return @@ -141,7 +150,7 @@ private[optimizer] abstract class OptimizerCore( private val intrinsics = Intrinsics.buildIntrinsics(config.coreSpec.esFeatures) - def optimize(myself: Option[MethodID], thisType: Type, params: List[ParamDef], + def optimize(thisType: Type, params: List[ParamDef], jsClassCaptures: List[ParamDef], resultType: Type, body: Tree, isNoArgCtor: Boolean): (List[ParamDef], Tree) = { try { @@ -1220,28 +1229,72 @@ private[optimizer] abstract class OptimizerCore( cont(PreTransLit(IntLiteral(resultValue))) case _ => - resolveLocalDef(preTransQual) match { - case PreTransRecordTree(newQual, origType, cancelFun) => - val recordType = newQual.tpe.asInstanceOf[RecordType] - /* FIXME How come this lookup requires only the `simpleName`? - * The `recordType` is created at `InlineableClassStructure.recordType`, - * where it uses an allocator. Something fishy is going on here. - * (And no, this is not dead code.) - */ - val recordField = recordType.findField(field.name.simpleName) - val sel = RecordSelect(newQual, SimpleFieldIdent(recordField.name))(recordField.tpe) - sel.tpe match { - case _: RecordType => - cont(PreTransRecordTree(sel, RefinedType(expectedType), cancelFun)) - case _ => - cont(PreTransTree(sel, RefinedType(sel.tpe))) - } + def default: TailRec[Tree] = { + resolveLocalDef(preTransQual) match { + case PreTransRecordTree(newQual, origType, cancelFun) => + val recordType = newQual.tpe.asInstanceOf[RecordType] + /* FIXME How come this lookup requires only the `simpleName`? + * The `recordType` is created at `InlineableClassStructure.recordType`, + * where it uses an allocator. Something fishy is going on here. + * (And no, this is not dead code.) + */ + val recordField = recordType.findField(field.name.simpleName) + val sel = RecordSelect(newQual, SimpleFieldIdent(recordField.name))(recordField.tpe) + sel.tpe match { + case _: RecordType => + cont(PreTransRecordTree(sel, RefinedType(expectedType), cancelFun)) + case _ => + cont(PreTransTree(sel, RefinedType(sel.tpe))) + } - case PreTransTree(newQual, newQualType) => - val newQual1 = maybeAssumeNotNull(newQual, newQualType) - cont(PreTransTree(Select(newQual1, field)(expectedType), - RefinedType(expectedType))) + case PreTransTree(newQual, newQualType) => + val newQual1 = maybeAssumeNotNull(newQual, newQualType) + cont(PreTransTree(Select(newQual1, field)(expectedType), + RefinedType(expectedType))) + } } + + preTransQual.tpe match { + // Try to inline an inlineable field body + case RefinedType(ClassType(qualClassName), _, _) if !isLhsOfAssign => + if (myself.exists(m => m.enclosingClassName == qualClassName && m.methodName.isConstructor)) { + /* Within the constructor of a class, we cannot trust the + * inlineable field bodies of that class, since they only reflect + * the values of fields when the instance is fully initialized. + */ + default + } else { + inlineableFieldBodies(qualClassName).fieldBodies.get(field.name) match { + case None => + default + case Some(fieldBody) => + val qualSideEffects = checkNotNullStatement(preTransQual) + val fieldBodyTree = fieldBodyToTree(fieldBody) + pretransformExpr(fieldBodyTree) { preTransFieldBody => + cont(PreTransBlock(qualSideEffects, preTransFieldBody)) + } + } + } + case _ => + default + } + } + } + + private def fieldBodyToTree(fieldBody: InlineableFieldBodies.FieldBody): Tree = { + import InlineableFieldBodies.FieldBody + + implicit val pos = fieldBody.pos + + fieldBody match { + case FieldBody.Literal(literal, _) => + literal + case FieldBody.LoadModule(moduleClassName, _) => + LoadModule(moduleClassName) + case FieldBody.ModuleSelect(qualifier, fieldName, tpe, _) => + Select(fieldBodyToTree(qualifier), FieldIdent(fieldName))(tpe) + case FieldBody.ModuleGetter(qualifier, methodName, tpe, _) => + Apply(ApplyFlags.empty, fieldBodyToTree(qualifier), MethodIdent(methodName), Nil)(tpe) } } @@ -5138,6 +5191,66 @@ private[optimizer] object OptimizerCore { } } + final class InlineableFieldBodies( + val fieldBodies: Map[FieldName, InlineableFieldBodies.FieldBody] + ) { + def isEmpty: Boolean = fieldBodies.isEmpty + + override def equals(that: Any): Boolean = that match { + case that: InlineableFieldBodies => + this.fieldBodies == that.fieldBodies + case _ => + false + } + + override def hashCode(): Int = fieldBodies.## + + override def toString(): String = { + fieldBodies + .map(f => s"${f._1.nameString}: ${f._2}") + .mkString("InlineableFieldBodies(", ", ", ")") + } + } + + object InlineableFieldBodies { + /** The body of field that we can inline. + * + * This hierarchy mirrors the small subset of `Tree`s that we need to + * represent field bodies that we can inline. + * + * Unlike `Tree`, `FieldBody` guarantees a comprehensive equality test + * representing its full structure. It is generally not safe to compare + * `Tree`s for equality, but for `FieldBody` it is safe. We use equality + * in `IncOptimizer` to detect changes. + * + * This is also why the members of the hierarchy contain an explicit + * `Position` in their primary parameter list. + */ + sealed abstract class FieldBody { + val pos: Position + } + + object FieldBody { + final case class Literal(literal: Trees.Literal, pos: Position) extends FieldBody { + require(pos == literal.pos, s"TreeBody.Literal.pos must be the same as its Literal") + } + + object Literal { + def apply(literal: Trees.Literal): Literal = + Literal(literal, literal.pos) + } + + final case class LoadModule(moduleClassName: ClassName, pos: Position) + extends FieldBody + final case class ModuleSelect(qualifier: LoadModule, + fieldName: FieldName, tpe: Type, pos: Position) extends FieldBody + final case class ModuleGetter(qualifier: LoadModule, + methodName: MethodName, tpe: Type, pos: Position) extends FieldBody + } + + val Empty: InlineableFieldBodies = new InlineableFieldBodies(Map.empty) + } + private final val MaxRollbacksPerMethod = 256 private final class TooManyRollbacksException diff --git a/project/Build.scala b/project/Build.scala index f3c9cbedac..25eec2454a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1975,7 +1975,7 @@ object Build { case `default213Version` => Some(ExpectedSizes( - fastLink = 463000 to 464000, + fastLink = 462000 to 463000, fullLink = 99000 to 100000, fastLinkGz = 60000 to 61000, fullLinkGz = 26000 to 27000, From 31452d4b2f81ab253adb2bdfb6761ec7411fd55e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 9 Feb 2024 15:56:53 +0100 Subject: [PATCH 41/65] Turn fields of scala.reflect.* back into `val`s. They are `val`s in the original version of the files. We previously turned them into `def`s because it allowed our inliner to do a better job. However, now that we can "inline" the fields of modules with elidable constructors, that is no longer necessary. Interestingly, this allows `scala.reflect.package$` and `scala.reflect.ClassManifestFactory$` to have elidable constructors, which they did not have before. That is because they referred to the `def`s in their constructor, which now have a getter shape and therefore can be understood by the side-effect-free analysis. --- project/Build.scala | 4 +- .../scala/reflect/ClassTag.scala | 30 ++++----- .../scala/reflect/Manifest.scala | 30 ++++----- .../scala/reflect/ClassTag.scala | 30 ++++----- .../scala/reflect/Manifest.scala | 62 +++++++++---------- 5 files changed, 78 insertions(+), 78 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 25eec2454a..a14202f7f9 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1967,8 +1967,8 @@ object Build { scalaVersion.value match { case `default212Version` => Some(ExpectedSizes( - fastLink = 642000 to 643000, - fullLink = 102000 to 103000, + fastLink = 640000 to 641000, + fullLink = 101000 to 102000, fastLinkGz = 77000 to 78000, fullLinkGz = 26000 to 27000, )) diff --git a/scalalib/overrides-2.12/scala/reflect/ClassTag.scala b/scalalib/overrides-2.12/scala/reflect/ClassTag.scala index f9dc5dd716..20fac6a392 100644 --- a/scalalib/overrides-2.12/scala/reflect/ClassTag.scala +++ b/scalalib/overrides-2.12/scala/reflect/ClassTag.scala @@ -133,21 +133,21 @@ trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serial * Class tags corresponding to primitive types and constructor/extractor for ClassTags. */ object ClassTag { - def Byte : ClassTag[scala.Byte] = ManifestFactory.Byte - def Short : ClassTag[scala.Short] = ManifestFactory.Short - def Char : ClassTag[scala.Char] = ManifestFactory.Char - def Int : ClassTag[scala.Int] = ManifestFactory.Int - def Long : ClassTag[scala.Long] = ManifestFactory.Long - def Float : ClassTag[scala.Float] = ManifestFactory.Float - def Double : ClassTag[scala.Double] = ManifestFactory.Double - def Boolean : ClassTag[scala.Boolean] = ManifestFactory.Boolean - def Unit : ClassTag[scala.Unit] = ManifestFactory.Unit - def Any : ClassTag[scala.Any] = ManifestFactory.Any - def Object : ClassTag[java.lang.Object] = ManifestFactory.Object - def AnyVal : ClassTag[scala.AnyVal] = ManifestFactory.AnyVal - def AnyRef : ClassTag[scala.AnyRef] = ManifestFactory.AnyRef - def Nothing : ClassTag[scala.Nothing] = ManifestFactory.Nothing - def Null : ClassTag[scala.Null] = ManifestFactory.Null + val Byte : ClassTag[scala.Byte] = ManifestFactory.Byte + val Short : ClassTag[scala.Short] = ManifestFactory.Short + val Char : ClassTag[scala.Char] = ManifestFactory.Char + val Int : ClassTag[scala.Int] = ManifestFactory.Int + val Long : ClassTag[scala.Long] = ManifestFactory.Long + val Float : ClassTag[scala.Float] = ManifestFactory.Float + val Double : ClassTag[scala.Double] = ManifestFactory.Double + val Boolean : ClassTag[scala.Boolean] = ManifestFactory.Boolean + val Unit : ClassTag[scala.Unit] = ManifestFactory.Unit + val Any : ClassTag[scala.Any] = ManifestFactory.Any + val Object : ClassTag[java.lang.Object] = ManifestFactory.Object + val AnyVal : ClassTag[scala.AnyVal] = ManifestFactory.AnyVal + val AnyRef : ClassTag[scala.AnyRef] = ManifestFactory.AnyRef + val Nothing : ClassTag[scala.Nothing] = ManifestFactory.Nothing + val Null : ClassTag[scala.Null] = ManifestFactory.Null @inline private class GenericClassTag[T](val runtimeClass: jClass[_]) extends ClassTag[T] diff --git a/scalalib/overrides-2.12/scala/reflect/Manifest.scala b/scalalib/overrides-2.12/scala/reflect/Manifest.scala index f38ce59e4d..a7b8169481 100644 --- a/scalalib/overrides-2.12/scala/reflect/Manifest.scala +++ b/scalalib/overrides-2.12/scala/reflect/Manifest.scala @@ -93,7 +93,7 @@ object ManifestFactory { override def newArrayBuilder(): ArrayBuilder[Byte] = new ArrayBuilder.ofByte() private def readResolve(): Any = Manifest.Byte } - def Byte: AnyValManifest[Byte] = ByteManifest + val Byte: AnyValManifest[Byte] = ByteManifest private object ShortManifest extends AnyValManifest[scala.Short]("Short") { def runtimeClass = java.lang.Short.TYPE @@ -102,7 +102,7 @@ object ManifestFactory { override def newArrayBuilder(): ArrayBuilder[Short] = new ArrayBuilder.ofShort() private def readResolve(): Any = Manifest.Short } - def Short: AnyValManifest[Short] = ShortManifest + val Short: AnyValManifest[Short] = ShortManifest private object CharManifest extends AnyValManifest[scala.Char]("Char") { def runtimeClass = java.lang.Character.TYPE @@ -111,7 +111,7 @@ object ManifestFactory { override def newArrayBuilder(): ArrayBuilder[Char] = new ArrayBuilder.ofChar() private def readResolve(): Any = Manifest.Char } - def Char: AnyValManifest[Char] = CharManifest + val Char: AnyValManifest[Char] = CharManifest private object IntManifest extends AnyValManifest[scala.Int]("Int") { def runtimeClass = java.lang.Integer.TYPE @@ -120,7 +120,7 @@ object ManifestFactory { override def newArrayBuilder(): ArrayBuilder[Int] = new ArrayBuilder.ofInt() private def readResolve(): Any = Manifest.Int } - def Int: AnyValManifest[Int] = IntManifest + val Int: AnyValManifest[Int] = IntManifest private object LongManifest extends AnyValManifest[scala.Long]("Long") { def runtimeClass = java.lang.Long.TYPE @@ -129,7 +129,7 @@ object ManifestFactory { override def newArrayBuilder(): ArrayBuilder[Long] = new ArrayBuilder.ofLong() private def readResolve(): Any = Manifest.Long } - def Long: AnyValManifest[Long] = LongManifest + val Long: AnyValManifest[Long] = LongManifest private object FloatManifest extends AnyValManifest[scala.Float]("Float") { def runtimeClass = java.lang.Float.TYPE @@ -138,7 +138,7 @@ object ManifestFactory { override def newArrayBuilder(): ArrayBuilder[Float] = new ArrayBuilder.ofFloat() private def readResolve(): Any = Manifest.Float } - def Float: AnyValManifest[Float] = FloatManifest + val Float: AnyValManifest[Float] = FloatManifest private object DoubleManifest extends AnyValManifest[scala.Double]("Double") { def runtimeClass = java.lang.Double.TYPE @@ -147,7 +147,7 @@ object ManifestFactory { override def newArrayBuilder(): ArrayBuilder[Double] = new ArrayBuilder.ofDouble() private def readResolve(): Any = Manifest.Double } - def Double: AnyValManifest[Double] = DoubleManifest + val Double: AnyValManifest[Double] = DoubleManifest private object BooleanManifest extends AnyValManifest[scala.Boolean]("Boolean") { def runtimeClass = java.lang.Boolean.TYPE @@ -156,7 +156,7 @@ object ManifestFactory { override def newArrayBuilder(): ArrayBuilder[Boolean] = new ArrayBuilder.ofBoolean() private def readResolve(): Any = Manifest.Boolean } - def Boolean: AnyValManifest[Boolean] = BooleanManifest + val Boolean: AnyValManifest[Boolean] = BooleanManifest private object UnitManifest extends AnyValManifest[scala.Unit]("Unit") { def runtimeClass = java.lang.Void.TYPE @@ -168,7 +168,7 @@ object ManifestFactory { else super.arrayClass(tp) private def readResolve(): Any = Manifest.Unit } - def Unit: AnyValManifest[Unit] = UnitManifest + val Unit: AnyValManifest[Unit] = UnitManifest private object AnyManifest extends PhantomManifest[scala.Any](classOf[java.lang.Object], "Any") { override def runtimeClass = classOf[java.lang.Object] @@ -176,7 +176,7 @@ object ManifestFactory { override def <:<(that: ClassManifest[_]): Boolean = (that eq this) private def readResolve(): Any = Manifest.Any } - def Any: Manifest[scala.Any] = AnyManifest + val Any: Manifest[scala.Any] = AnyManifest private object ObjectManifest extends PhantomManifest[java.lang.Object](classOf[java.lang.Object], "Object") { override def runtimeClass = classOf[java.lang.Object] @@ -184,9 +184,9 @@ object ManifestFactory { override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Any) private def readResolve(): Any = Manifest.Object } - def Object: Manifest[java.lang.Object] = ObjectManifest + val Object: Manifest[java.lang.Object] = ObjectManifest - def AnyRef: Manifest[scala.AnyRef] = Object.asInstanceOf[Manifest[scala.AnyRef]] + val AnyRef: Manifest[scala.AnyRef] = Object private object AnyValManifest extends PhantomManifest[scala.AnyVal](classOf[java.lang.Object], "AnyVal") { override def runtimeClass = classOf[java.lang.Object] @@ -194,7 +194,7 @@ object ManifestFactory { override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Any) private def readResolve(): Any = Manifest.AnyVal } - def AnyVal: Manifest[scala.AnyVal] = AnyValManifest + val AnyVal: Manifest[scala.AnyVal] = AnyValManifest private object NullManifest extends PhantomManifest[scala.Null](classOf[scala.runtime.Null$], "Null") { override def runtimeClass = classOf[scala.runtime.Null$] @@ -203,7 +203,7 @@ object ManifestFactory { (that ne null) && (that ne Nothing) && !(that <:< AnyVal) private def readResolve(): Any = Manifest.Null } - def Null: Manifest[scala.Null] = NullManifest + val Null: Manifest[scala.Null] = NullManifest private object NothingManifest extends PhantomManifest[scala.Nothing](classOf[scala.runtime.Nothing$], "Nothing") { override def runtimeClass = classOf[scala.runtime.Nothing$] @@ -211,7 +211,7 @@ object ManifestFactory { override def <:<(that: ClassManifest[_]): Boolean = (that ne null) private def readResolve(): Any = Manifest.Nothing } - def Nothing: Manifest[scala.Nothing] = NothingManifest + val Nothing: Manifest[scala.Nothing] = NothingManifest private class SingletonTypeManifest[T <: AnyRef](value: AnyRef) extends Manifest[T] { lazy val runtimeClass = value.getClass diff --git a/scalalib/overrides-2.13/scala/reflect/ClassTag.scala b/scalalib/overrides-2.13/scala/reflect/ClassTag.scala index 253d55cefb..a66a1a6a8e 100644 --- a/scalalib/overrides-2.13/scala/reflect/ClassTag.scala +++ b/scalalib/overrides-2.13/scala/reflect/ClassTag.scala @@ -96,21 +96,21 @@ trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serial object ClassTag { import ManifestFactory._ - @uncheckedStable def Byte : ByteManifest = ManifestFactory.Byte - @uncheckedStable def Short : ShortManifest = ManifestFactory.Short - @uncheckedStable def Char : CharManifest = ManifestFactory.Char - @uncheckedStable def Int : IntManifest = ManifestFactory.Int - @uncheckedStable def Long : LongManifest = ManifestFactory.Long - @uncheckedStable def Float : FloatManifest = ManifestFactory.Float - @uncheckedStable def Double : DoubleManifest = ManifestFactory.Double - @uncheckedStable def Boolean : BooleanManifest = ManifestFactory.Boolean - @uncheckedStable def Unit : UnitManifest = ManifestFactory.Unit - @uncheckedStable def Any : ClassTag[scala.Any] = ManifestFactory.Any - @uncheckedStable def Object : ClassTag[java.lang.Object] = ManifestFactory.Object - @uncheckedStable def AnyVal : ClassTag[scala.AnyVal] = ManifestFactory.AnyVal - @uncheckedStable def AnyRef : ClassTag[scala.AnyRef] = ManifestFactory.AnyRef - @uncheckedStable def Nothing : ClassTag[scala.Nothing] = ManifestFactory.Nothing - @uncheckedStable def Null : ClassTag[scala.Null] = ManifestFactory.Null + val Byte : ByteManifest = ManifestFactory.Byte + val Short : ShortManifest = ManifestFactory.Short + val Char : CharManifest = ManifestFactory.Char + val Int : IntManifest = ManifestFactory.Int + val Long : LongManifest = ManifestFactory.Long + val Float : FloatManifest = ManifestFactory.Float + val Double : DoubleManifest = ManifestFactory.Double + val Boolean : BooleanManifest = ManifestFactory.Boolean + val Unit : UnitManifest = ManifestFactory.Unit + val Any : ClassTag[scala.Any] = ManifestFactory.Any + val Object : ClassTag[java.lang.Object] = ManifestFactory.Object + val AnyVal : ClassTag[scala.AnyVal] = ManifestFactory.AnyVal + val AnyRef : ClassTag[scala.AnyRef] = ManifestFactory.AnyRef + val Nothing : ClassTag[scala.Nothing] = ManifestFactory.Nothing + val Null : ClassTag[scala.Null] = ManifestFactory.Null @inline @SerialVersionUID(1L) diff --git a/scalalib/overrides-2.13/scala/reflect/Manifest.scala b/scalalib/overrides-2.13/scala/reflect/Manifest.scala index 053b0c485d..faa2fe6f6e 100644 --- a/scalalib/overrides-2.13/scala/reflect/Manifest.scala +++ b/scalalib/overrides-2.13/scala/reflect/Manifest.scala @@ -80,22 +80,22 @@ object Manifest { def valueManifests: List[AnyValManifest[_]] = ManifestFactory.valueManifests - def Byte: ManifestFactory.ByteManifest = ManifestFactory.Byte - def Short: ManifestFactory.ShortManifest = ManifestFactory.Short - def Char: ManifestFactory.CharManifest = ManifestFactory.Char - def Int: ManifestFactory.IntManifest = ManifestFactory.Int - def Long: ManifestFactory.LongManifest = ManifestFactory.Long - def Float: ManifestFactory.FloatManifest = ManifestFactory.Float - def Double: ManifestFactory.DoubleManifest = ManifestFactory.Double - def Boolean: ManifestFactory.BooleanManifest = ManifestFactory.Boolean - def Unit: ManifestFactory.UnitManifest = ManifestFactory.Unit - - def Any: Manifest[scala.Any] = ManifestFactory.Any - def Object: Manifest[java.lang.Object] = ManifestFactory.Object - def AnyRef: Manifest[scala.AnyRef] = ManifestFactory.AnyRef - def AnyVal: Manifest[scala.AnyVal] = ManifestFactory.AnyVal - def Null: Manifest[scala.Null] = ManifestFactory.Null - def Nothing: Manifest[scala.Nothing] = ManifestFactory.Nothing + val Byte: ManifestFactory.ByteManifest = ManifestFactory.Byte + val Short: ManifestFactory.ShortManifest = ManifestFactory.Short + val Char: ManifestFactory.CharManifest = ManifestFactory.Char + val Int: ManifestFactory.IntManifest = ManifestFactory.Int + val Long: ManifestFactory.LongManifest = ManifestFactory.Long + val Float: ManifestFactory.FloatManifest = ManifestFactory.Float + val Double: ManifestFactory.DoubleManifest = ManifestFactory.Double + val Boolean: ManifestFactory.BooleanManifest = ManifestFactory.Boolean + val Unit: ManifestFactory.UnitManifest = ManifestFactory.Unit + + val Any: Manifest[scala.Any] = ManifestFactory.Any + val Object: Manifest[java.lang.Object] = ManifestFactory.Object + val AnyRef: Manifest[scala.AnyRef] = ManifestFactory.AnyRef + val AnyVal: Manifest[scala.AnyVal] = ManifestFactory.AnyVal + val Null: Manifest[scala.Null] = ManifestFactory.Null + val Nothing: Manifest[scala.Nothing] = ManifestFactory.Nothing /** Manifest for the singleton type `value.type`. */ def singleType[T <: AnyRef](value: AnyRef): Manifest[T] = @@ -181,7 +181,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Byte } private object ByteManifest extends ByteManifest - def Byte: ByteManifest = ByteManifest + val Byte: ByteManifest = ByteManifest @SerialVersionUID(1L) private[reflect] class ShortManifest extends AnyValManifest[scala.Short]("Short") { @@ -198,7 +198,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Short } private object ShortManifest extends ShortManifest - def Short: ShortManifest = ShortManifest + val Short: ShortManifest = ShortManifest @SerialVersionUID(1L) private[reflect] class CharManifest extends AnyValManifest[scala.Char]("Char") { @@ -215,7 +215,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Char } private object CharManifest extends CharManifest - def Char: CharManifest = CharManifest + val Char: CharManifest = CharManifest @SerialVersionUID(1L) private[reflect] class IntManifest extends AnyValManifest[scala.Int]("Int") { @@ -232,7 +232,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Int } private object IntManifest extends IntManifest - def Int: IntManifest = IntManifest + val Int: IntManifest = IntManifest @SerialVersionUID(1L) private[reflect] class LongManifest extends AnyValManifest[scala.Long]("Long") { @@ -249,7 +249,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Long } private object LongManifest extends LongManifest - def Long: LongManifest = LongManifest + val Long: LongManifest = LongManifest @SerialVersionUID(1L) private[reflect] class FloatManifest extends AnyValManifest[scala.Float]("Float") { @@ -266,7 +266,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Float } private object FloatManifest extends FloatManifest - def Float: FloatManifest = FloatManifest + val Float: FloatManifest = FloatManifest @SerialVersionUID(1L) private[reflect] class DoubleManifest extends AnyValManifest[scala.Double]("Double") { @@ -284,7 +284,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Double } private object DoubleManifest extends DoubleManifest - def Double: DoubleManifest = DoubleManifest + val Double: DoubleManifest = DoubleManifest @SerialVersionUID(1L) private[reflect] class BooleanManifest extends AnyValManifest[scala.Boolean]("Boolean") { @@ -301,7 +301,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Boolean } private object BooleanManifest extends BooleanManifest - def Boolean: BooleanManifest = BooleanManifest + val Boolean: BooleanManifest = BooleanManifest @SerialVersionUID(1L) private[reflect] class UnitManifest extends AnyValManifest[scala.Unit]("Unit") { @@ -321,7 +321,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Unit } private object UnitManifest extends UnitManifest - def Unit: UnitManifest = UnitManifest + val Unit: UnitManifest = UnitManifest private object AnyManifest extends PhantomManifest[scala.Any](classOf[java.lang.Object], "Any") { override def runtimeClass = classOf[java.lang.Object] @@ -329,7 +329,7 @@ object ManifestFactory { override def <:<(that: ClassManifest[_]): Boolean = (that eq this) private def readResolve(): Any = Manifest.Any } - def Any: Manifest[scala.Any] = AnyManifest + val Any: Manifest[scala.Any] = AnyManifest private object ObjectManifest extends PhantomManifest[java.lang.Object](classOf[java.lang.Object], "Object") { override def runtimeClass = classOf[java.lang.Object] @@ -337,9 +337,9 @@ object ManifestFactory { override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Any) private def readResolve(): Any = Manifest.Object } - def Object: Manifest[java.lang.Object] = ObjectManifest + val Object: Manifest[java.lang.Object] = ObjectManifest - def AnyRef: Manifest[scala.AnyRef] = Object.asInstanceOf[Manifest[scala.AnyRef]] + val AnyRef: Manifest[scala.AnyRef] = Object private object AnyValManifest extends PhantomManifest[scala.AnyVal](classOf[java.lang.Object], "AnyVal") { override def runtimeClass = classOf[java.lang.Object] @@ -347,7 +347,7 @@ object ManifestFactory { override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Any) private def readResolve(): Any = Manifest.AnyVal } - def AnyVal: Manifest[scala.AnyVal] = AnyValManifest + val AnyVal: Manifest[scala.AnyVal] = AnyValManifest private object NullManifest extends PhantomManifest[scala.Null](classOf[scala.runtime.Null$], "Null") { override def runtimeClass = classOf[scala.runtime.Null$] @@ -356,7 +356,7 @@ object ManifestFactory { (that ne null) && (that ne Nothing) && !(that <:< AnyVal) private def readResolve(): Any = Manifest.Null } - def Null: Manifest[scala.Null] = NullManifest + val Null: Manifest[scala.Null] = NullManifest private object NothingManifest extends PhantomManifest[scala.Nothing](classOf[scala.runtime.Nothing$], "Nothing") { override def runtimeClass = classOf[scala.runtime.Nothing$] @@ -364,7 +364,7 @@ object ManifestFactory { override def <:<(that: ClassManifest[_]): Boolean = (that ne null) private def readResolve(): Any = Manifest.Nothing } - def Nothing: Manifest[scala.Nothing] = NothingManifest + val Nothing: Manifest[scala.Nothing] = NothingManifest @SerialVersionUID(1L) private class SingletonTypeManifest[T <: AnyRef](value: AnyRef) extends Manifest[T] { 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 42/65] 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 ec2a24e7dbc75094d122d3077fb65106db105fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 26 Feb 2024 16:01:18 +0100 Subject: [PATCH 43/65] Fix #4954: Count `!_allowComplete` as a fake pending task of WorkTracker. --- .../scalajs/linker/analyzer/Analyzer.scala | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala index 11a997ef27..534a30ef85 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala @@ -1571,9 +1571,11 @@ private object AnalyzerRun { MethodName("getSuperclass", Nil, ClassRef(ClassClass)) private class WorkTracker(implicit ec: ExecutionContext) { - private val pending = new AtomicInteger(0) + /** The number of tasks that have started but not completed, `+ 1` until + * `allowComplete()` gets called. + */ + private val pending = new AtomicInteger(1) private val failures = new AtomicReference[List[Throwable]](Nil) - @volatile private var _allowComplete = false private val promise = Promise[Unit]() def track(fut: Future[Unit]): Unit = { @@ -1584,8 +1586,7 @@ private object AnalyzerRun { case Success(_) => () case Failure(t) => addFailure(t) } - if (pending.decrementAndGet() == 0) - tryComplete() + decrementPending() } } @@ -1596,28 +1597,33 @@ private object AnalyzerRun { addFailure(t) } - private def tryComplete(): Unit = { - /* Note that after _allowComplete is true and pending == 0, we are sure - * that no new task will be submitted concurrently: - * - _allowComplete guarantees us that no external task will be added anymore - * - pending == 0 guarantees us that no internal task (which might create - * more tasks) are running anymore. + private def decrementPending(): Unit = { + /* When `pending` reaches 0, we are sure that all started tasks have + * completed, and that `allowComplete()` was called. Therefore, no + * further task can be concurrently added, and we are done. */ - if (_allowComplete && pending.get() == 0) { - failures.get() match { - case Nil => - promise.success(()) - case firstFailure :: moreFailures => - for (t <- moreFailures) - firstFailure.addSuppressed(t) - promise.failure(firstFailure) - } + if (pending.decrementAndGet() == 0) + complete() + } + + private def complete(): Unit = { + failures.get() match { + case Nil => + promise.success(()) + case firstFailure :: moreFailures => + for (t <- moreFailures) + firstFailure.addSuppressed(t) + promise.failure(firstFailure) } } + /** Signals that no new top-level tasks will be started, and that it is + * therefore OK to complete the tracker once all ongoing tasks have finished. + * + * `allowComplete()` must not be called more than once. + */ def allowComplete(): Future[Unit] = { - _allowComplete = true - tryComplete() + decrementPending() promise.future } } From 18e1862b09bf82a2f621e4ae274aab8baf823abd Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Sat, 2 Mar 2024 01:36:52 +0900 Subject: [PATCH 44/65] Fix #4957: Fix error message for ConflictingTopLevelExport fix https://github.com/scala-js/scala-js/issues/4957 --- .../src/main/scala/org/scalajs/linker/analyzer/Analysis.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala index 1ad5f24785..6d0a02e83c 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala @@ -242,7 +242,7 @@ object Analysis { "is not a valid JavaScript identifier " + "(did you want to emit a module instead?)" case ConflictingTopLevelExport(moduleID, exportName, infos) => - s"Conflicting top level exports for module $moduleID, name $exportName " + s"Conflicting top level exports for module $moduleID, name $exportName " + "involving " + infos.map(_.owningClass.nameString).mkString(", ") case ImportWithoutModuleSupport(module, info, None, _) => s"${info.displayName} needs to be imported from module " + 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 45/65] 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 46/65] 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 47/65] 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 48/65] 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 49/65] 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 50/65] 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, )) From 1afad42e64cf801c7cc90a55afaf942e1a5aa25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 24 Feb 2024 10:07:41 +0100 Subject: [PATCH 51/65] Fix #4949: Always wrap object literals with `()`. --- .../linker/backend/javascript/Printers.scala | 21 ++++++++++--------- .../org/scalajs/linker/LibrarySizeTest.scala | 4 ++-- project/Build.scala | 10 ++++----- .../testsuite/jsinterop/DynamicTest.scala | 14 ++++++++++--- 4 files changed, 29 insertions(+), 20 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 09a3b8648a..33ec9cc020 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 @@ -491,15 +491,17 @@ object Printers { printSeparatorIfStat() case ObjectConstr(Nil) => - if (isStat) - print("({});") // force expression position for the object literal - else - print("{}") + /* #4949 Always wrap object literals with () in case they end up at + * the start of an `ExpressionStatement`. + */ + print("({})") + printSeparatorIfStat() case ObjectConstr(fields) => - if (isStat) - print('(') // force expression position for the object literal - print('{') + /* #4949 Always wrap object literals with () in case they end up at + * the start of an `ExpressionStatement`. + */ + print("({") indent() println() var rest = fields @@ -517,9 +519,8 @@ object Printers { } undent() printIndent() - print('}') - if (isStat) - print(");") + print("})") + printSeparatorIfStat() // Literals 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 dca2b88b47..fbc5b93948 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -70,8 +70,8 @@ class LibrarySizeTest { ) testLinkedSizes( - expectedFastLinkSize = 150063, - expectedFullLinkSizeWithoutClosure = 92648, + expectedFastLinkSize = 150205, + expectedFullLinkSizeWithoutClosure = 92762, expectedFullLinkSizeWithClosure = 21325, classDefs, moduleInitializers = MainTestModuleInitializers diff --git a/project/Build.scala b/project/Build.scala index c6ae6168e5..f6d3d596ab 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1998,14 +1998,14 @@ object Build { case `default212Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 640000 to 641000, + fastLink = 641000 to 642000, fullLink = 101000 to 102000, fastLinkGz = 77000 to 78000, fullLinkGz = 26000 to 27000, )) } else { Some(ExpectedSizes( - fastLink = 494000 to 495000, + fastLink = 495000 to 496000, fullLink = 337000 to 338000, fastLinkGz = 69000 to 70000, fullLinkGz = 50000 to 51000, @@ -2015,15 +2015,15 @@ object Build { case `default213Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 462000 to 463000, + fastLink = 463000 to 464000, fullLink = 99000 to 100000, fastLinkGz = 60000 to 61000, fullLinkGz = 26000 to 27000, )) } else { Some(ExpectedSizes( - fastLink = 347000 to 348000, - fullLink = 307000 to 308000, + fastLink = 348000 to 349000, + fullLink = 308000 to 309000, fastLinkGz = 54000 to 55000, fullLinkGz = 49000 to 50000, )) diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/DynamicTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/DynamicTest.scala index 27b7f224c1..bd23946f2e 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/DynamicTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/DynamicTest.scala @@ -109,11 +109,19 @@ class DynamicTest { assertJSUndefined(obj_anything) } - @Test def objectLiteralInStatementPosition_Issue1627(): Unit = { - // Just make sure it does not cause a SyntaxError + @Test def objectLiteralInStatementPosition_Issue1627_Issue4949(): Unit = { + @noinline def dynProp(): String = "foo" + + // Just make sure those statements do not cause a SyntaxError js.Dynamic.literal(foo = "bar") - // and also test the case without param (different code path in Printers) js.Dynamic.literal() + js.Dynamic.literal(foo = "bar").foo + js.Dynamic.literal(foo = () => "bar").foo() + js.Dynamic.literal(foo = "bar").foo = "babar" + js.Dynamic.literal(foo = "foo").selectDynamic(dynProp()) + js.Dynamic.literal(foo = "foo").updateDynamic(dynProp())("babar") + js.Dynamic.literal(foo = () => "bar").applyDynamic(dynProp())() + js.Dynamic.literal(foo = "bar") + js.Dynamic.literal(foobar = "babar") } @Test def objectLiteralConstructionWithDynamicNaming(): Unit = { From e8b7dd7b9f59ed47eee939b5bd8d395327b10102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 25 Feb 2024 11:26:58 +0100 Subject: [PATCH 52/65] Add some tests for printing of `ObjectConstr` nodes. --- .../backend/javascript/PrintersTest.scala | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) 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 a2a8108fa8..2c5de62dd1 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 @@ -163,6 +163,34 @@ class PrintersTest { ) } + @Test def printObjectLiteral(): Unit = { + assertPrintEquals("({});", ObjectConstr(Nil)) + + assertPrintEquals( + """ + |({ + | "foo": 1 + |}); + """, + ObjectConstr(List(StringLiteral("foo") -> IntLiteral(1))) + ) + + assertPrintEquals( + """ + |({ + | "foo": 1, + | ["bar"]: 2, + | baz: 3 + |}); + """, + ObjectConstr(List( + StringLiteral("foo") -> IntLiteral(1), + ComputedName(StringLiteral("bar")) -> IntLiteral(2), + Ident("baz") -> IntLiteral(3) + )) + ) + } + @Test def delayedIdentPrintVersusShow(): Unit = { locally { object resolver extends DelayedIdent.Resolver { From 8b772e219c29f1bbc0336da71b8b40c9d4d22afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 26 Jan 2024 17:43:51 +0100 Subject: [PATCH 53/65] Under Minify, assign prototypes temporarily through `$p`. When minifying, a susprisingly large amount of bytes in the resulting .js file are caused by the `prototype`s in: C.prototype.f = function(...) { ... }; C.prototype.g = function(...) { ... }; We can get rid of them by assigning `C.prototype` once to a temporary variable, then reusing it many times: $p = C.prototype; $p.f = function(...) { ... }; $p.f = function(...) { ... }; This commit implements that strategy when the `minify` config is on. --- .../linker/backend/emitter/ClassEmitter.scala | 20 +++++------ .../linker/backend/emitter/CoreJSLib.scala | 27 +++++++++------ .../linker/backend/emitter/Emitter.scala | 10 ++++++ .../linker/backend/emitter/SJSGen.scala | 33 +++++++++++++++++++ .../linker/backend/emitter/VarField.scala | 5 ++- .../org/scalajs/linker/LibrarySizeTest.scala | 2 +- project/Build.scala | 16 ++++----- 7 files changed, 83 insertions(+), 30 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 11f8c3df33..8b50efda5e 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 @@ -186,13 +186,13 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val chainProtoWithGlobals = superClass match { case None => - WithGlobals.nil + WithGlobals(setPrototypeVar(ctorVar)) case Some(_) if shouldExtendJSError(className, superClass) => - globalRef("Error").map(chainPrototypeWithLocalCtor(className, ctorVar, _)) + globalRef("Error").map(chainPrototypeWithLocalCtor(className, ctorVar, _, localDeclPrototypeVar = false)) case Some(parentIdent) => - WithGlobals(List(ctorVar.prototype := js.New(globalVar(VarField.h, parentIdent.name), Nil))) + WithGlobals(List(genAssignPrototype(ctorVar, js.New(globalVar(VarField.h, parentIdent.name), Nil)))) } for { @@ -208,12 +208,12 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { js.JSDocConstructor(realCtorDef.head) :: realCtorDef.tail ::: chainProto ::: - (genIdentBracketSelect(ctorVar.prototype, "constructor") := ctorVar) :: + (genIdentBracketSelect(prototypeFor(ctorVar), "constructor") := ctorVar) :: // Inheritable constructor js.JSDocConstructor(inheritableCtorDef.head) :: inheritableCtorDef.tail ::: - (globalVar(VarField.h, className).prototype := ctorVar.prototype) :: Nil + (globalVar(VarField.h, className).prototype := prototypeFor(ctorVar)) :: Nil ) } } @@ -243,8 +243,8 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val ctorVar = fileLevelVar(VarField.b, genName(className)) js.JSDocConstructor(ctorVar := ctorFun) :: - chainPrototypeWithLocalCtor(className, ctorVar, superCtor) ::: - (genIdentBracketSelect(ctorVar.prototype, "constructor") := ctorVar) :: Nil + chainPrototypeWithLocalCtor(className, ctorVar, superCtor, localDeclPrototypeVar = true) ::: + (genIdentBracketSelect(prototypeFor(ctorVar), "constructor") := ctorVar) :: Nil } } } @@ -333,7 +333,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } private def chainPrototypeWithLocalCtor(className: ClassName, ctorVar: js.Tree, - superCtor: js.Tree)(implicit pos: Position): List[js.Tree] = { + superCtor: js.Tree, localDeclPrototypeVar: Boolean)(implicit pos: Position): List[js.Tree] = { import TreeDSL._ val dummyCtor = fileLevelVar(VarField.hh, genName(className)) @@ -341,7 +341,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { List( js.JSDocConstructor(genConst(dummyCtor.ident, js.Function(false, Nil, None, js.Skip()))), dummyCtor.prototype := superCtor.prototype, - ctorVar.prototype := js.New(dummyCtor, Nil) + genAssignPrototype(ctorVar, js.New(dummyCtor, Nil), localDeclPrototypeVar) ) } @@ -638,7 +638,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { else globalVar(VarField.c, className) if (namespace.isStatic) classVarRef - else classVarRef.prototype + else prototypeFor(classVarRef) } def genMemberNameTree(name: Tree)( 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 6dc814cd87..43d58795ad 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 @@ -562,6 +562,7 @@ private[emitter] object CoreJSLib { extractWithGlobals(globalClassDef(VarField.Char, CoreVar, None, ctor :: toStr :: Nil)) } else { defineFunction(VarField.Char, ctor.args, ctor.body) ::: + setPrototypeVar(globalVar(VarField.Char, CoreVar)) ::: assignES5ClassMembers(globalVar(VarField.Char, CoreVar), List(toStr)) } } @@ -1528,8 +1529,8 @@ private[emitter] object CoreJSLib { val clsDef = { extractWithGlobals(globalFunctionDef(VarField.ac, componentTypeRef, ctor.args, ctor.restParam, ctor.body)) ::: - (ArrayClass.prototype := New(globalVar(VarField.h, ObjectClass), Nil)) :: - (ArrayClass.prototype DOT "constructor" := ArrayClass) :: + genAssignPrototype(ArrayClass, New(globalVar(VarField.h, ObjectClass), Nil)) :: + (prototypeFor(ArrayClass) DOT "constructor" := ArrayClass) :: assignES5ClassMembers(ArrayClass, members) } @@ -1537,7 +1538,7 @@ private[emitter] object CoreJSLib { case _: ClassRef => clsDef ::: extractWithGlobals(globalFunctionDef(VarField.ah, ObjectClass, Nil, None, Skip())) ::: - (globalVar(VarField.ah, ObjectClass).prototype := ArrayClass.prototype) :: Nil + (globalVar(VarField.ah, ObjectClass).prototype := prototypeFor(ArrayClass)) :: Nil case _: PrimRef => clsDef } @@ -1697,7 +1698,6 @@ private[emitter] object CoreJSLib { val name = varRef("name") Block( - arrayClass.prototype DOT classData := This(), const(name, str("[") + (componentData DOT cpn.arrayEncodedName)), privateFieldSet(cpn.constr, arrayClass), if (globalKnowledge.isParentDataAccessed) @@ -1729,6 +1729,7 @@ private[emitter] object CoreJSLib { MethodDef(static = false, Ident(cpn.initSpecializedArray), paramList(componentData, arrayClass, typedArrayClass, isAssignableFromFun), None, { Block( + arrayClass.prototype DOT classData := This(), initArrayCommonBody(arrayClass, componentData, componentData, 1), const(self, This()), // capture `this` for use in arrow fun privateFieldSet(cpn.isAssignableFromFun, isAssignableFromFun || { @@ -1833,14 +1834,19 @@ private[emitter] object CoreJSLib { val members = set ::: copyTo ::: clone :: Nil if (useClassesForRegularClasses) { - ClassDef(Some(ArrayClass.ident), Some(globalVar(VarField.ac, ObjectClass)), - ctor :: members) + Block( + ClassDef(Some(ArrayClass.ident), Some(globalVar(VarField.ac, ObjectClass)), + ctor :: members), + ArrayClass.prototype DOT cpn.classData := This() + ) } else { Block( FunctionDef(ArrayClass.ident, ctor.args, ctor.restParam, ctor.body) :: - (ArrayClass.prototype := New(globalVar(VarField.ah, ObjectClass), Nil)) :: - (ArrayClass.prototype DOT "constructor" := ArrayClass) :: - assignES5ClassMembers(ArrayClass, members) + genAssignPrototype(ArrayClass, New(globalVar(VarField.ah, ObjectClass), Nil), localDecl = true) :: + (prototypeFor(ArrayClass) DOT "constructor" := ArrayClass) :: + assignES5ClassMembers(ArrayClass, members) ::: + (prototypeFor(ArrayClass) DOT cpn.classData := This()) :: + Nil ) } } @@ -2000,6 +2006,7 @@ private[emitter] object CoreJSLib { extractWithGlobals(globalClassDef(VarField.TypeData, CoreVar, None, ctor :: members)) } else { defineFunction(VarField.TypeData, ctor.args, ctor.body) ::: + setPrototypeVar(globalVar(VarField.TypeData, CoreVar)) ::: assignES5ClassMembers(globalVar(VarField.TypeData, CoreVar), members) } } @@ -2159,7 +2166,7 @@ private[emitter] object CoreJSLib { for { MethodDef(static, name, args, restParam, body) <- members } yield { - val target = if (static) classRef else classRef.prototype + val target = if (static) classRef else prototypeFor(classRef) genPropSelect(target, name) := Function(arrow = false, args, restParam, body) } } 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 506dec4d4a..c7de499363 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 @@ -64,6 +64,11 @@ final class Emitter[E >: Null <: js.Tree]( val classEmitter: ClassEmitter = new ClassEmitter(sjsGen) + val everyFileStart: List[E] = { + // This postTransform does not count in the statistics + postTransformer.transformStats(sjsGen.declarePrototypeVar, 0) + } + val coreJSLibCache: CoreJSLibCache = new CoreJSLibCache val moduleCaches: mutable.Map[ModuleID, ModuleCache] = mutable.Map.empty @@ -293,6 +298,11 @@ final class Emitter[E >: Null <: js.Tree]( * it is crucial that we verify it. */ val defTrees: List[E] = ( + /* The declaration of the `$p` variable that temporarily holds + * prototypes. + */ + state.everyFileStart.iterator ++ + /* The definitions of the CoreJSLib that come before the definition * of `j.l.Object`. They depend on nothing else. */ 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 b52be22348..5b7b846b8c 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 @@ -145,6 +145,39 @@ private[emitter] final class SJSGen( val newArrayOfThisClass = "newArrayOfThisClass" } + /* This is a `val` because it is used at the top of every file, outside of + * any cache. Fortunately it does not depend on any dynamic content. + */ + val declarePrototypeVar: List[Tree] = { + implicit val pos = Position.NoPosition + if (minify) VarDef(fileLevelVarIdent(VarField.p), None) :: Nil + else Nil + } + + def prototypeFor(classRef: Tree)(implicit pos: Position): Tree = { + import TreeDSL._ + if (minify) fileLevelVar(VarField.p) + else classRef.prototype + } + + def genAssignPrototype(classRef: Tree, value: Tree, localDecl: Boolean = false)(implicit pos: Position): Tree = { + import TreeDSL._ + val assign = classRef.prototype := value + if (!minify) + assign + else if (localDecl) + VarDef(fileLevelVarIdent(VarField.p), Some(assign)) + else + fileLevelVar(VarField.p) := assign + } + + /** Under `minify`, set `$p` to `classRef.prototype`. */ + def setPrototypeVar(classRef: Tree)(implicit pos: Position): List[Tree] = { + import TreeDSL._ + if (minify) (fileLevelVar(VarField.p) := classRef.prototype) :: Nil + else Nil + } + def genZeroOf(tpe: Type)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala index 2be691d96e..10ac75518a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala @@ -40,7 +40,10 @@ private[emitter] object VarField { /** Scala class initializers (). */ final val sct = mk("$sct") - /** Private (instance) methods. */ + /** Private (instance) methods. + * + * Also used for the `prototype` of the current class when minifying. + */ final val p = mk("$p") /** Public static methods. */ 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 fbc5b93948..92c572391a 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 = 150205, - expectedFullLinkSizeWithoutClosure = 92762, + expectedFullLinkSizeWithoutClosure = 90108, expectedFullLinkSizeWithClosure = 21325, classDefs, moduleInitializers = MainTestModuleInitializers diff --git a/project/Build.scala b/project/Build.scala index f6d3d596ab..2be6f5a261 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2005,10 +2005,10 @@ object Build { )) } else { Some(ExpectedSizes( - fastLink = 495000 to 496000, - fullLink = 337000 to 338000, - fastLinkGz = 69000 to 70000, - fullLinkGz = 50000 to 51000, + fastLink = 454000 to 455000, + fullLink = 306000 to 307000, + fastLinkGz = 65000 to 66000, + fullLinkGz = 47000 to 48000, )) } @@ -2022,10 +2022,10 @@ object Build { )) } else { Some(ExpectedSizes( - fastLink = 348000 to 349000, - fullLink = 308000 to 309000, - fastLinkGz = 54000 to 55000, - fullLinkGz = 49000 to 50000, + fastLink = 325000 to 326000, + fullLink = 285000 to 286000, + fastLinkGz = 51000 to 52000, + fullLinkGz = 47000 to 48000, )) } From 229573b5427b4b2ddfb22551154478c09a720a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 21 Feb 2024 13:12:45 +0100 Subject: [PATCH 54/65] Be smarter about how we use params to `initClass`. * Merge `isInterface` and `isJSType` as a single parameter `kind` that is an integer. * Use the first element of `ancestors` instead of independently passing the `internalName` (this is safe because ES 2015 guarantees the order of `getOwnPropertyNames`). * Really remove the `parentData` parameter when reachability analysis says it is not accessed. --- .../linker/backend/emitter/ClassEmitter.scala | 27 +++++------ .../linker/backend/emitter/CoreJSLib.scala | 46 ++++++++----------- .../linker/backend/emitter/VarField.scala | 2 - .../scalajs/linker/standard/LinkedClass.scala | 8 ++++ .../org/scalajs/linker/LibrarySizeTest.scala | 6 +-- project/Build.scala | 26 +++++------ 6 files changed, 56 insertions(+), 59 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 8b50efda5e..8af483b5ce 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 @@ -836,21 +836,26 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val isJSType = kind.isJSType - val isJSTypeParam = - if (isJSType) js.BooleanLiteral(true) - else js.Undefined() + val kindParam = { + if (isJSType) js.IntLiteral(2) + else if (kind == ClassKind.Interface) js.IntLiteral(1) + else js.IntLiteral(0) + } - val parentData = if (globalKnowledge.isParentDataAccessed) { - superClass.fold[js.Tree] { + val parentDataOpt = if (globalKnowledge.isParentDataAccessed) { + val parentData = superClass.fold[js.Tree] { if (isObjectClass) js.Null() else js.Undefined() } { parent => globalVar(VarField.d, parent.name) } + parentData :: Nil } else { - js.Undefined() + Nil } + assert(ancestors.headOption.contains(className), + s"The ancestors of ${className.nameString} do not start with itself: $ancestors") val ancestorsRecord = js.ObjectConstr( ancestors.withFilter(_ != ObjectClass).map(ancestor => (genAncestorIdent(ancestor), js.IntLiteral(1))) ) @@ -902,15 +907,11 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { isInstanceFunWithGlobals.flatMap { isInstanceFun => val allParams = List( - js.ObjectConstr(List(genAncestorIdent(className) -> js.IntLiteral(0))), - js.BooleanLiteral(kind == ClassKind.Interface), + kindParam, js.StringLiteral(RuntimeClassNameMapperImpl.map( semantics.runtimeClassNameMapper, className.nameString)), - ancestorsRecord, - isJSTypeParam, - parentData, - isInstanceFun - ) + ancestorsRecord + ) ::: parentDataOpt ::: isInstanceFun :: Nil val prunedParams = allParams.reverse.dropWhile(_.isInstanceOf[js.Undefined]).reverse 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 43d58795ad..75106195d2 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 @@ -130,7 +130,6 @@ private[emitter] object CoreJSLib { defineLinkingInfo() ::: defineJSBuiltinsSnapshotsAndPolyfills() ::: declareCachedL0() ::: - definePropertyName() ::: defineCharClass() ::: defineRuntimeFunctions() ::: defineObjectGetClassFunctions() ::: @@ -526,23 +525,6 @@ private[emitter] object CoreJSLib { )) } - private def definePropertyName(): List[Tree] = { - /* Encodes a property name for runtime manipulation. - * - * Usage: - * env.propertyName({someProp:0}) - * Returns: - * "someProp" - * Useful when the property is renamed by a global optimizer (like - * Closure) but we must still get hold of a string of that name for - * runtime reflection. - */ - defineFunction1(VarField.propertyName) { obj => - val prop = varRef("prop") - ForIn(genEmptyImmutableLet(prop.ident), obj, Return(prop)) - } - } - private def defineCharClass(): List[Tree] = { val ctor = { val c = varRef("c") @@ -1652,23 +1634,31 @@ private[emitter] object CoreJSLib { } val initClass = { - val internalNameObj = varRef("internalNameObj") - val isInterface = varRef("isInterface") + // This is an int, where 1 means isInterface; 2 means isJSType; 0 otherwise + val kind = varRef("kind") + + val hasParentData = globalKnowledge.isParentDataAccessed + val fullName = varRef("fullName") val ancestors = varRef("ancestors") - val isJSType = varRef("isJSType") val parentData = varRef("parentData") val isInstance = varRef("isInstance") val internalName = varRef("internalName") val that = varRef("that") val depth = varRef("depth") val obj = varRef("obj") - MethodDef(static = false, Ident(cpn.initClass), - paramList(internalNameObj, isInterface, fullName, ancestors, - isJSType, parentData, isInstance), None, { + val params = + if (hasParentData) paramList(kind, fullName, ancestors, parentData, isInstance) + else paramList(kind, fullName, ancestors, isInstance) + MethodDef(static = false, Ident(cpn.initClass), params, None, { Block( - const(internalName, genCallHelper(VarField.propertyName, internalNameObj)), - if (globalKnowledge.isParentDataAccessed) + /* Extract the internalName, which is the first property of ancestors. + * We use `getOwnPropertyNames()`, which since ES 2015 guarantees + * to return non-integer string keys in creation order. + */ + const(internalName, + BracketSelect(Apply(genIdentBracketSelect(ObjectRef, "getOwnPropertyNames"), List(ancestors)), 0)), + if (hasParentData) privateFieldSet(cpn.parentData, parentData) else Skip(), @@ -1679,9 +1669,9 @@ private[emitter] object CoreJSLib { Return(!(!(BracketSelect(that DOT cpn.ancestors, internalName)))) }) }), - privateFieldSet(cpn.isJSType, !(!isJSType)), + privateFieldSet(cpn.isJSType, kind === 2), publicFieldSet(cpn.name, fullName), - publicFieldSet(cpn.isInterface, isInterface), + publicFieldSet(cpn.isInterface, kind === 1), publicFieldSet(cpn.isInstance, isInstance || { genArrowFunction(paramList(obj), { Return(!(!(obj && (obj DOT classData) && diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala index 10ac75518a..0e695a24c7 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala @@ -175,8 +175,6 @@ private[emitter] object VarField { final val valueDescription = mk("$valueDescription") - final val propertyName = mk("$propertyName") - // ID hash subsystem final val systemIdentityHashCode = mk("$systemIdentityHashCode") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedClass.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedClass.scala index 3f796633ae..58b71fb725 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedClass.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedClass.scala @@ -29,6 +29,11 @@ import org.scalajs.ir.Names.{ClassName, FieldName} * P+1. The converse is not true. This guarantees that versions can be used * reliably to determine at phase P+1 whether a linked class coming from phase * P must be reprocessed. + * + * @param ancestors + * List of all the ancestor classes and interfaces of this class. It always + * contains this class name and `java.lang.Object`. This class name is + * always the first element of the list. */ final class LinkedClass( // Stuff from Tree @@ -61,6 +66,9 @@ final class LinkedClass( val version: Version) { + require(ancestors.headOption.contains(name.name), + s"ancestors for ${name.name.nameString} must start with itself: $ancestors") + def className: ClassName = name.name val hasStaticInitializer: Boolean = { 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 92c572391a..b813d24f2a 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -70,9 +70,9 @@ class LibrarySizeTest { ) testLinkedSizes( - expectedFastLinkSize = 150205, - expectedFullLinkSizeWithoutClosure = 90108, - expectedFullLinkSizeWithClosure = 21325, + expectedFastLinkSize = 148754, + expectedFullLinkSizeWithoutClosure = 89358, + expectedFullLinkSizeWithClosure = 22075, classDefs, moduleInitializers = MainTestModuleInitializers ) diff --git a/project/Build.scala b/project/Build.scala index 2be6f5a261..d7db8498ca 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1998,34 +1998,34 @@ object Build { case `default212Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 641000 to 642000, - fullLink = 101000 to 102000, - fastLinkGz = 77000 to 78000, + fastLink = 634000 to 635000, + fullLink = 102000 to 103000, + fastLinkGz = 76000 to 77000, fullLinkGz = 26000 to 27000, )) } else { Some(ExpectedSizes( - fastLink = 454000 to 455000, - fullLink = 306000 to 307000, + fastLink = 450000 to 451000, + fullLink = 303000 to 304000, fastLinkGz = 65000 to 66000, - fullLinkGz = 47000 to 48000, + fullLinkGz = 46000 to 47000, )) } case `default213Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 463000 to 464000, - fullLink = 99000 to 100000, - fastLinkGz = 60000 to 61000, - fullLinkGz = 26000 to 27000, + fastLink = 458000 to 459000, + fullLink = 100000 to 101000, + fastLinkGz = 59000 to 60000, + fullLinkGz = 27000 to 28000, )) } else { Some(ExpectedSizes( - fastLink = 325000 to 326000, - fullLink = 285000 to 286000, + fastLink = 322000 to 323000, + fullLink = 282000 to 283000, fastLinkGz = 51000 to 52000, - fullLinkGz = 47000 to 48000, + fullLinkGz = 46000 to 47000, )) } From 7b2708673abf5efa1ec966ccdac4ca3c54449a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 21 Feb 2024 17:51:39 +0100 Subject: [PATCH 55/65] Store `$c.prototype.$classData` as part of `$TypeData().initClass()`. This removes the largest source of uncompressible and non-removable occurrences of `$classData` identifiers. It does increase the size of the GCC output in the `LibrarySizeTest`, but it does not translate to the `reversi` checksizes, so it is probably a small codebase artifact. --- .../linker/backend/emitter/ClassEmitter.scala | 23 +++++++------- .../linker/backend/emitter/CoreJSLib.scala | 18 +++++++---- .../linker/backend/emitter/Emitter.scala | 31 +++++++++++++------ .../org/scalajs/linker/LibrarySizeTest.scala | 6 ++-- project/Build.scala | 28 ++++++++--------- 5 files changed, 63 insertions(+), 43 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 8af483b5ce..e2f2419835 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 @@ -826,7 +826,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { def genTypeData(className: ClassName, kind: ClassKind, superClass: Option[ClassIdent], ancestors: List[ClassName], - jsNativeLoadSpec: Option[JSNativeLoadSpec])( + jsNativeLoadSpec: Option[JSNativeLoadSpec], hasInstances: Boolean)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { import TreeDSL._ @@ -836,9 +836,18 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val isJSType = kind.isJSType - val kindParam = { + /* The `kindOrCtor` param is either: + * - an int: 1 means isInterface; 2 means isJSType; 0 otherwise + * - a Scala class constructor: means 0 + assign `kindOrCtor.prototype.$classData = ;` + * + * We must only assign the `$classData` if the class is a regular + * (non-hijacked) Scala class, and if it has instances. Otherwise there is + * no Scala class constructor for the class at all. + */ + val kindOrCtorParam = { if (isJSType) js.IntLiteral(2) else if (kind == ClassKind.Interface) js.IntLiteral(1) + else if (kind.isClass && hasInstances) globalVar(VarField.c, className) else js.IntLiteral(0) } @@ -907,7 +916,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { isInstanceFunWithGlobals.flatMap { isInstanceFun => val allParams = List( - kindParam, + kindOrCtorParam, js.StringLiteral(RuntimeClassNameMapperImpl.map( semantics.runtimeClassNameMapper, className.nameString)), ancestorsRecord @@ -923,14 +932,6 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } } - def genSetTypeData(className: ClassName)( - implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge, pos: Position): js.Tree = { - import TreeDSL._ - - globalVar(VarField.c, className).prototype DOT cpn.classData := globalVar(VarField.d, className) - } - def genModuleAccessor(className: ClassName, isJSClass: Boolean)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { 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 75106195d2..b476811b0e 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 @@ -1634,8 +1634,11 @@ private[emitter] object CoreJSLib { } val initClass = { - // This is an int, where 1 means isInterface; 2 means isJSType; 0 otherwise - val kind = varRef("kind") + /* This is either: + * - an int: 1 means isInterface; 2 means isJSType; 0 otherwise + * - a Scala class constructor: means 0 + assign `kindOrCtor.prototype.$classData = this;` + */ + val kindOrCtor = varRef("kindOrCtor") val hasParentData = globalKnowledge.isParentDataAccessed @@ -1648,8 +1651,8 @@ private[emitter] object CoreJSLib { val depth = varRef("depth") val obj = varRef("obj") val params = - if (hasParentData) paramList(kind, fullName, ancestors, parentData, isInstance) - else paramList(kind, fullName, ancestors, isInstance) + if (hasParentData) paramList(kindOrCtor, fullName, ancestors, parentData, isInstance) + else paramList(kindOrCtor, fullName, ancestors, isInstance) MethodDef(static = false, Ident(cpn.initClass), params, None, { Block( /* Extract the internalName, which is the first property of ancestors. @@ -1669,15 +1672,18 @@ private[emitter] object CoreJSLib { Return(!(!(BracketSelect(that DOT cpn.ancestors, internalName)))) }) }), - privateFieldSet(cpn.isJSType, kind === 2), + privateFieldSet(cpn.isJSType, kindOrCtor === 2), publicFieldSet(cpn.name, fullName), - publicFieldSet(cpn.isInterface, kind === 1), + publicFieldSet(cpn.isInterface, kindOrCtor === 1), publicFieldSet(cpn.isInstance, isInstance || { genArrowFunction(paramList(obj), { Return(!(!(obj && (obj DOT classData) && BracketSelect(obj DOT classData DOT cpn.ancestors, internalName)))) }) }), + If(typeof(kindOrCtor) !== str("number"), { + kindOrCtor.prototype DOT cpn.classData := This() + }), Return(This()) ) }) 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 c7de499363..cdf17260e8 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 @@ -716,21 +716,16 @@ final class Emitter[E >: Null <: js.Tree]( if (linkedClass.hasRuntimeTypeInfo) { main ++= extractWithGlobals(classTreeCache.typeData.getOrElseUpdate( + linkedClass.hasInstances, classEmitter.genTypeData( className, // invalidated by overall class cache (part of ancestors) kind, // invalidated by class version linkedClass.superClass, // invalidated by class version linkedClass.ancestors, // invalidated by overall class cache (identity) - linkedClass.jsNativeLoadSpec // invalidated by class version + linkedClass.jsNativeLoadSpec, // invalidated by class version + linkedClass.hasInstances // invalidated directly (it is the input to `getOrElseUpdate`) )(moduleContext, classCache, linkedClass.pos).map(postTransform(_, 0)))) } - - if (linkedClass.hasInstances && kind.isClass && linkedClass.hasRuntimeTypeInfo) { - main ++= classTreeCache.setTypeData.getOrElseUpdate({ - val tree = classEmitter.genSetTypeData(className)(moduleContext, classCache, linkedClass.pos) - postTransform(tree, 0) - }) - } } if (linkedClass.kind.hasModuleAccessor && linkedClass.hasInstances) { @@ -1198,7 +1193,7 @@ object Emitter { val privateJSFields = new OneTimeCache[WithGlobals[E]] val storeJSSuperClass = new OneTimeCache[WithGlobals[E]] val instanceTests = new OneTimeCache[WithGlobals[E]] - val typeData = new OneTimeCache[WithGlobals[E]] + val typeData = new InputEqualityCache[Boolean, WithGlobals[E]] val setTypeData = new OneTimeCache[E] val moduleAccessor = new OneTimeCache[WithGlobals[E]] val staticInitialization = new OneTimeCache[E] @@ -1223,6 +1218,24 @@ object Emitter { } } + /** A cache that depends on an `input: I`, testing with `==`. + * + * @tparam I + * the type of input, for which `==` must meaningful + */ + private final class InputEqualityCache[I, A >: Null] { + private[this] var lastInput: Option[I] = None + private[this] var value: A = null + + def getOrElseUpdate(input: I, v: => A): A = { + if (!lastInput.contains(input)) { + value = v + lastInput = Some(input) + } + value + } + } + private case class ClassID( ancestors: List[ClassName], moduleContext: ModuleContext) 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 b813d24f2a..cfa7b749fc 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -70,9 +70,9 @@ class LibrarySizeTest { ) testLinkedSizes( - expectedFastLinkSize = 148754, - expectedFullLinkSizeWithoutClosure = 89358, - expectedFullLinkSizeWithClosure = 22075, + expectedFastLinkSize = 147707, + expectedFullLinkSizeWithoutClosure = 88733, + expectedFullLinkSizeWithClosure = 21802, classDefs, moduleInitializers = MainTestModuleInitializers ) diff --git a/project/Build.scala b/project/Build.scala index d7db8498ca..bf039b251f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1998,16 +1998,16 @@ object Build { case `default212Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 634000 to 635000, - fullLink = 102000 to 103000, - fastLinkGz = 76000 to 77000, - fullLinkGz = 26000 to 27000, + fastLink = 626000 to 627000, + fullLink = 98000 to 99000, + fastLinkGz = 75000 to 79000, + fullLinkGz = 25000 to 26000, )) } else { Some(ExpectedSizes( - fastLink = 450000 to 451000, - fullLink = 303000 to 304000, - fastLinkGz = 65000 to 66000, + fastLink = 442000 to 443000, + fullLink = 297000 to 298000, + fastLinkGz = 64000 to 65000, fullLinkGz = 46000 to 47000, )) } @@ -2015,16 +2015,16 @@ object Build { case `default213Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 458000 to 459000, - fullLink = 100000 to 101000, - fastLinkGz = 59000 to 60000, - fullLinkGz = 27000 to 28000, + fastLink = 452000 to 453000, + fullLink = 96000 to 97000, + fastLinkGz = 58000 to 59000, + fullLinkGz = 26000 to 27000, )) } else { Some(ExpectedSizes( - fastLink = 322000 to 323000, - fullLink = 282000 to 283000, - fastLinkGz = 51000 to 52000, + fastLink = 316000 to 317000, + fullLink = 276000 to 277000, + fastLinkGz = 50000 to 51000, fullLinkGz = 46000 to 47000, )) } From 58bec3f5b099856d961c1544cc6bbf4249224729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 15 Mar 2024 19:59:06 +0100 Subject: [PATCH 56/65] Optimize `String_+` for `char` at the emitter level. Previously, our implementation of `Character.toString(c)` used JS interop itself to produce `String.fromCharCode(c)`. This was the only hijacked class to do so, as the other ones use `"" + c` instead. The reason was that the function emitter used to box chars in string concatenation to get the behavior of `$Char.toString()`, and we wanted to avoid the boxing. We now make `FunctionEmitter` smarter about primitive `char`s in `String_+`: it does not box anymore, and instead calls a dedicated helper that calls `String.fromCharCode(c)`. We replace the user-space implementation of `Character.toString(c)` with `"" + c`, which aligns it with the other hijacked classes. This is better because the optimization is more widely applicable: it applies to all string concatenations, including those generated by string interpolators, instead of only explicit `toString()` calls. Moreover, it automatically allows to constant-fold `c.toString()` when `c` is a constant character. --- .../src/main/scala/java/lang/Character.scala | 2 +- .../linker/backend/emitter/CoreJSLib.scala | 3 +++ .../backend/emitter/FunctionEmitter.scala | 19 ++++++++++++------- .../linker/backend/emitter/VarField.scala | 2 ++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/javalib/src/main/scala/java/lang/Character.scala b/javalib/src/main/scala/java/lang/Character.scala index b260948a6d..e5f132fd49 100644 --- a/javalib/src/main/scala/java/lang/Character.scala +++ b/javalib/src/main/scala/java/lang/Character.scala @@ -118,7 +118,7 @@ object Character { @inline def hashCode(value: Char): Int = value.toInt @inline def toString(c: Char): String = - js.Dynamic.global.String.fromCharCode(c.toInt).asInstanceOf[String] + "" + c def toString(codePoint: Int): String = { if (isBmpCodePoint(codePoint)) { 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 6dc814cd87..3dbcbe8afb 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 @@ -947,6 +947,9 @@ private[emitter] object CoreJSLib { defineFunction1(VarField.doubleToInt) { x => Return(If(x > 2147483647, 2147483647, If(x < -2147483648, -2147483648, x | 0))) } ::: + defineFunction1(VarField.charToString) { x => + Return(Apply(genIdentBracketSelect(StringRef, "fromCharCode"), x :: Nil)) + } ::: condDefs(semantics.stringIndexOutOfBounds != CheckedBehavior.Unchecked)( defineFunction2(VarField.charAt) { (s, i) => val r = varRef("r") 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 a518948b97..58979155fc 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 @@ -2391,8 +2391,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case BinaryOp(op, lhs, rhs) => import BinaryOp._ - val newLhs = transformExprNoChar(lhs) - val newRhs = transformExprNoChar(rhs) + val newLhs = transformExpr(lhs, preserveChar = (op == String_+)) + val newRhs = transformExpr(rhs, preserveChar = (op == String_+)) (op: @switch) match { case === | !== => @@ -2445,11 +2445,16 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.BinaryOp(JSBinaryOp.!==, newLhs, newRhs) case String_+ => - if (lhs.tpe == StringType || rhs.tpe == StringType) { - js.BinaryOp(JSBinaryOp.+, newLhs, newRhs) - } else { - js.BinaryOp(JSBinaryOp.+, js.BinaryOp(JSBinaryOp.+, - js.StringLiteral(""), newLhs), newRhs) + def charToString(t: js.Tree): js.Tree = + genCallHelper(VarField.charToString, t) + + (lhs.tpe, rhs.tpe) match { + case (CharType, CharType) => charToString(newLhs) + charToString(newRhs) + case (CharType, _) => charToString(newLhs) + newRhs + case (_, CharType) => newLhs + charToString(newRhs) + case (StringType, _) => newLhs + newRhs + case (_, StringType) => newLhs + newRhs + case _ => (js.StringLiteral("") + newLhs) + newRhs } case Int_+ => or0(js.BinaryOp(JSBinaryOp.+, newLhs, newRhs)) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala index 2be691d96e..b89239a63a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala @@ -154,6 +154,8 @@ private[emitter] object VarField { /** Box char. */ final val bC = mk("$bC") + final val charToString = mk("$cToS") + final val charAt = mk("$charAt") // Object helpers From 0dd63ff17f9ad57e8c29c525e58efc8d8ca3a942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 23 Feb 2024 17:54:42 +0100 Subject: [PATCH 57/65] Under Minify, inline `VarDef`s used only once when we can. During the optimizer, when emitting a `VarDef(x, ..., rhs)`, we try to inline it if it has been used exactly once. In order to do that, we look at the `body` in which it will be available, and replace its only occurrence if it occurs in the first evaluation context following only pure subexpressions. See the long comment in the code for more details. --- .../frontend/optimizer/OptimizerCore.scala | 343 +++++++++++++++++- .../linker/standard/CommonPhaseConfig.scala | 5 +- .../org/scalajs/linker/LibrarySizeTest.scala | 4 +- project/BinaryIncompatibilities.scala | 2 + project/Build.scala | 22 +- 5 files changed, 343 insertions(+), 33 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index d9c57e3b9a..7c484195b5 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -417,7 +417,7 @@ private[optimizer] abstract class OptimizerCore( val (newName, newOriginalName) = freshLocalName(name, originalName, mutable = false) val localDef = LocalDef(RefinedType(AnyType), mutable = false, - ReplaceWithVarRef(newName, newSimpleState(Used), None)) + ReplaceWithVarRef(newName, newSimpleState(UsedAtLeastOnce), None)) val newBody = { val bodyScope = scope.withEnv(scope.env.withLocalDef(name, localDef)) transformStat(body)(bodyScope) @@ -430,7 +430,7 @@ private[optimizer] abstract class OptimizerCore( val (newName, newOriginalName) = freshLocalName(name, originalName, mutable = false) val localDef = LocalDef(RefinedType(AnyType), true, - ReplaceWithVarRef(newName, newSimpleState(Used), None)) + ReplaceWithVarRef(newName, newSimpleState(UsedAtLeastOnce), None)) val newHandler = { val handlerScope = scope.withEnv(scope.env.withLocalDef(name, localDef)) transform(handler, isStat)(handlerScope) @@ -1380,7 +1380,7 @@ private[optimizer] abstract class OptimizerCore( case PreTransLocalDef(localDef @ LocalDef(tpe, _, replacement)) => replacement match { case ReplaceWithRecordVarRef(name, recordType, used, cancelFun) => - used.value = Used + used.value = used.value.inc PreTransRecordTree( VarRef(LocalIdent(name))(recordType), tpe, cancelFun) @@ -1599,18 +1599,21 @@ private[optimizer] abstract class OptimizerCore( if (used.value.isUsed) { val ident = LocalIdent(name) - val varDef = resolveLocalDef(value) match { + resolveLocalDef(value) match { case PreTransRecordTree(valueTree, valueTpe, cancelFun) => val recordType = valueTree.tpe.asInstanceOf[RecordType] if (!isImmutableType(recordType)) cancelFun() - VarDef(ident, originalName, recordType, mutable, valueTree) + Block(VarDef(ident, originalName, recordType, mutable, valueTree), innerBody) case PreTransTree(valueTree, valueTpe) => - VarDef(ident, originalName, tpe.base, mutable, valueTree) + val optimized = + if (used.value.count == 1 && config.minify) tryInsertAtFirstEvalContext(name, valueTree, innerBody) + else None + optimized.getOrElse { + Block(VarDef(ident, originalName, tpe.base, mutable, valueTree), innerBody) + } } - - Block(varDef, innerBody) } else { val valueSideEffects = finishTransformStat(value) Block(valueSideEffects, innerBody) @@ -1713,6 +1716,282 @@ private[optimizer] abstract class OptimizerCore( case _ => false } + /** Tries to insert `valTree` in place of the (unique) occurrence of `valName` in `body`. + * + * This function assumes that `valName` is used only once, and only inside + * `body`. It does not assume that `valTree` or `body` are side-effect-free. + * + * The replacement is done only if we can show that it will not affect + * evaluation order. In practice, this means that we only replace if we find + * the occurrence of `valName` in the first evaluation context of `body`. + * In other words, we verify that all the expressions that will evaluate + * before `valName` in `body` are pure. + * + * We consider a `VarRef(y)` pure if `valTree` does not contain any + * assignment to `y`. + * + * For example, we can replace `x` in the following bodies: + * + * {{{ + * x + * x + e + * x.foo(...) + * x.f + * e + x // if `e` is pure + * e0.foo(...e1, x, ...) // if `e0` is pure and non-null, and the `...e1`s are pure + * if (x) { ... } else { ... } + * }}} + * + * Why is this interesting? Mostly because of inlining. + * + * Inlining tends to create many bindings for the receivers and arguments of + * methods. We must do that to preserve evaluation order, and not to evaluate + * them multiple times. However, very often, the receiver and arguments are + * used exactly once in the inlined body, and in-order. Using this strategy, + * we can take the right-hand-sides of the synthetic bindings and inline them + * directly inside the body. + * + * This in turn allows more trees to remain JS-level expressions, which means + * that `FunctionEmitter` has to `unnest` less often, further reducing the + * amount of temporary variables. + * + * --- + * + * Note that we can never cross any potential undefined behavior, even when + * the corresponding semantics are `Unchecked`. That is because the + * `valTree` could throw itself, preventing the normal behavior of the code + * to reach the undefined behavior in the first place. Consider for example: + * + * {{{ + * val x: Foo = ... // maybe null + * val y: Int = if (x == null) throw new Exception() else 1 + * x.foo(y) + * }}} + * + * We cannot inline `y` in this example, because that would change + * observable behavior if `x` is `null`. + * + * It is OK to cross the potential UB if we can prove that it will not + * actually trigger, for example if we know that `x` is not null. + * + * --- + * + * We only call this function when the `minify` option is on. This is for two + * reasons: + * + * - it can be detrimental to debuggability, as even user-written `val`s can + * disappear, and their right-hand-side be evaluated out-of-order compared + * to the source code; + * - it is non-linear, as we can perform several traversals of the same body, + * if it follows a sequence of `VarDef`s that can each be successfully + * inserted. + */ + private def tryInsertAtFirstEvalContext(valName: LocalName, valTree: Tree, body: Tree): Option[Tree] = { + import EvalContextInsertion._ + + object valTreeInfo extends Traversers.Traverser { + val mutatedLocalVars = mutable.Set.empty[LocalName] + + traverse(valTree) + + override def traverse(tree: Tree): Unit = { + super.traverse(tree) + tree match { + case Assign(VarRef(ident), _) => mutatedLocalVars += ident.name + case _ => () + } + } + } + + def recs(bodies: List[Tree]): EvalContextInsertion[List[Tree]] = bodies match { + case Nil => + NotFoundPureSoFar + case firstBody :: restBodies => + rec(firstBody) match { + case Success(newFirstBody) => Success(newFirstBody :: restBodies) + case NotFoundPureSoFar => recs(restBodies).mapOrKeepGoing(firstBody :: _) + case Failed => Failed + } + } + + def rec(body: Tree): EvalContextInsertion[Tree] = { + implicit val pos = body.pos + + body match { + case VarRef(ident) => + if (ident.name == valName) + Success(valTree) + else if (valTreeInfo.mutatedLocalVars.contains(ident.name)) + Failed + else + NotFoundPureSoFar + + case Skip() => + NotFoundPureSoFar + + case Block(stats) => + recs(stats).mapOrKeepGoing(Block(_)) + + case Labeled(label, tpe, innerBody) => + rec(innerBody).mapOrKeepGoing(Labeled(label, tpe, _)) + + case Return(expr, label) => + rec(expr).mapOrFailed(Return(_, label)) + + case If(cond, thenp, elsep) => + rec(cond).mapOrFailed(If(_, thenp, elsep)(body.tpe)) + + case Throw(expr) => + rec(expr).mapOrFailed(Throw(_)) + + case Match(selector, cases, default) => + rec(selector).mapOrFailed(Match(_, cases, default)(body.tpe)) + + case New(className, ctor, args) => + recs(args).mapOrKeepGoingIf(New(className, ctor, _))( + keepGoingIf = hasElidableConstructors(className)) + + case LoadModule(className) => + if (hasElidableConstructors(className)) NotFoundPureSoFar + else Failed + + case Select(qual, field) => + rec(qual).mapOrFailed(Select(_, field)(body.tpe)) + + case Apply(flags, receiver, method, args) => + rec(receiver) match { + case Success(newReceiver) => + Success(Apply(flags, newReceiver, method, args)(body.tpe)) + case NotFoundPureSoFar if isNotNull(receiver) => + recs(args).mapOrFailed(Apply(flags, receiver, method, _)(body.tpe)) + case _ => + Failed + } + + case ApplyStatically(flags, receiver, className, method, args) => + rec(receiver) match { + case Success(newReceiver) => + Success(ApplyStatically(flags, newReceiver, className, method, args)(body.tpe)) + case NotFoundPureSoFar if isNotNull(receiver) => + recs(args).mapOrFailed(ApplyStatically(flags, receiver, className, method, _)(body.tpe)) + case _ => + Failed + } + + case ApplyStatic(flags, className, method, args) => + recs(args).mapOrFailed(ApplyStatic(flags, className, method, _)(body.tpe)) + + case UnaryOp(op, arg) => + rec(arg).mapOrKeepGoing(UnaryOp(op, _)) + + case BinaryOp(op, lhs, rhs) => + import BinaryOp._ + + rec(lhs) match { + case Success(newLhs) => Success(BinaryOp(op, newLhs, rhs)) + case Failed => Failed + + case NotFoundPureSoFar => + rec(rhs).mapOrKeepGoingIf(BinaryOp(op, lhs, _)) { + (op: @switch) match { + case Int_/ | Int_% | Long_/ | Long_% | String_+ | String_charAt => + false + case _ => + true + } + } + } + + case NewArray(typeRef, lengths) => + recs(lengths).mapOrKeepGoing(NewArray(typeRef, _)) + + case ArrayValue(typeRef, elems) => + recs(elems).mapOrKeepGoing(ArrayValue(typeRef, _)) + + case ArrayLength(array) => + rec(array).mapOrKeepGoingIf(ArrayLength(_))(keepGoingIf = isNotNull(array)) + + case ArraySelect(array, index) => + rec(array) match { + case Success(newArray) => + Success(ArraySelect(newArray, index)(body.tpe)) + case NotFoundPureSoFar if isNotNull(array) => + rec(index).mapOrFailed(ArraySelect(array, _)(body.tpe)) + case _ => + Failed + } + + case RecordValue(tpe, elems) => + recs(elems).mapOrKeepGoing(RecordValue(tpe, _)) + + case RecordSelect(record, field) => + rec(record).mapOrKeepGoingIf(RecordSelect(_, field)(body.tpe)) { + // We can keep going if the selected field is immutable + val RecordType(fields) = record.tpe: @unchecked + !fields.find(_.name == field.name).get.mutable + } + + case IsInstanceOf(expr, testType) => + rec(expr).mapOrKeepGoing(IsInstanceOf(_, testType)) + + case AsInstanceOf(expr, tpe) => + rec(expr).mapOrFailed(AsInstanceOf(_, tpe)) + + case GetClass(expr) => + rec(expr).mapOrKeepGoingIf(GetClass(_))(keepGoingIf = isNotNull(expr)) + + case Clone(expr) => + rec(expr).mapOrFailed(Clone(_)) + + case JSUnaryOp(op, arg) => + rec(arg).mapOrFailed(JSUnaryOp(op, _)) + + case JSBinaryOp(op, lhs, rhs) => + rec(lhs) match { + case Success(newLhs) => + Success(JSBinaryOp(op, newLhs, rhs)) + case NotFoundPureSoFar => + rec(rhs).mapOrKeepGoingIf(JSBinaryOp(op, lhs, _))( + keepGoingIf = op == JSBinaryOp.=== || op == JSBinaryOp.!==) + case Failed => + Failed + } + + case JSArrayConstr(items) => + if (items.exists(_.isInstanceOf[JSSpread])) + Failed // in theory we could do something better here, but the complexity is not worth it + else + recs(items.asInstanceOf[List[Tree]]).mapOrKeepGoing(JSArrayConstr(_)) + + case _: Literal => + NotFoundPureSoFar + + case This() => + NotFoundPureSoFar + + case Closure(arrow, captureParams, params, restParam, body, captureValues) => + recs(captureValues).mapOrKeepGoing(Closure(arrow, captureParams, params, restParam, body, _)) + + case _ => + Failed + } + } + + rec(body) match { + case Success(result) => Some(result) + case Failed => None + + case NotFoundPureSoFar => + /* The val was never actually used. This can happen even when the + * variable was `used` exactly once, because `used` tracks the number + * of times we have generated a `VarRef` for it. In some cases, the + * generated `VarRef` is later discarded through `keepOnlySideEffects` + * somewhere else. + */ + Some(Block(keepOnlySideEffects(valTree), body)(body.pos)) + } + } + private def pretransformApply(tree: Apply, isStat: Boolean, usePreTransform: Boolean)( cont: PreTransCont)( @@ -2066,7 +2345,7 @@ private[optimizer] abstract class OptimizerCore( if (target != expectedTarget) cancelFun() - used.value = Used + used.value = used.value.inc val module = VarRef(LocalIdent(moduleVarName))(AnyType) path.foldLeft[Tree](module) { (inner, pathElem) => JSSelect(inner, StringLiteral(pathElem)) @@ -2097,7 +2376,7 @@ private[optimizer] abstract class OptimizerCore( captureParams, params, body, captureLocalDefs, alreadyUsed, cancelFun))) if !alreadyUsed.value.isUsed && argsNoSpread.size <= params.size => - alreadyUsed.value = Used + alreadyUsed.value = alreadyUsed.value.inc val missingArgCount = params.size - argsNoSpread.size val expandedArgs = if (missingArgCount == 0) argsNoSpread @@ -4991,7 +5270,7 @@ private[optimizer] abstract class OptimizerCore( case PreTransTree(VarRef(LocalIdent(refName)), _) if !localIsMutable(refName) => buildInner(LocalDef(computeRefinedType(), false, - ReplaceWithVarRef(refName, newSimpleState(Used), None)), cont) + ReplaceWithVarRef(refName, newSimpleState(UsedAtLeastOnce), None)), cont) case _ => withDedicatedVar(computeRefinedType()) @@ -5343,7 +5622,7 @@ private[optimizer] object OptimizerCore { def newReplacement(implicit pos: Position): Tree = this.replacement match { case ReplaceWithVarRef(name, used, _) => - used.value = Used + used.value = used.value.inc VarRef(LocalIdent(name))(tpe.base) /* Allocate an instance of RuntimeLong on the fly. @@ -5352,7 +5631,7 @@ private[optimizer] object OptimizerCore { */ case ReplaceWithRecordVarRef(name, recordType, used, _) if tpe.base == ClassType(LongImpl.RuntimeLongClass) => - used.value = Used + used.value = used.value.inc createNewLong(VarRef(LocalIdent(name))(recordType)) case ReplaceWithRecordVarRef(_, _, _, cancelFun) => @@ -6451,14 +6730,40 @@ private[optimizer] object OptimizerCore { else OriginalName(base) } - private sealed abstract class IsUsed { - def isUsed: Boolean + private final case class IsUsed(count: Int) { + def isUsed: Boolean = count > 0 + + lazy val inc: IsUsed = IsUsed(count + 1) } - private case object Used extends IsUsed { - override def isUsed: Boolean = true + + private val Unused: IsUsed = IsUsed(count = 0) + private val UsedAtLeastOnce: IsUsed = Unused.inc + + private sealed abstract class EvalContextInsertion[+A] { + import EvalContextInsertion._ + + def mapOrKeepGoing[B](f: A => B): EvalContextInsertion[B] = this match { + case Success(a) => Success(f(a)) + case NotFoundPureSoFar => NotFoundPureSoFar + case Failed => Failed + } + + def mapOrKeepGoingIf[B](f: A => B)(keepGoingIf: => Boolean): EvalContextInsertion[B] = this match { + case Success(a) => Success(f(a)) + case NotFoundPureSoFar => if (keepGoingIf) NotFoundPureSoFar else Failed + case Failed => Failed + } + + def mapOrFailed[B](f: A => B): EvalContextInsertion[B] = this match { + case Success(a) => Success(f(a)) + case _ => Failed + } } - private case object Unused extends IsUsed { - override def isUsed: Boolean = false + + private object EvalContextInsertion { + final case class Success[+A](result: A) extends EvalContextInsertion[A] + case object Failed extends EvalContextInsertion[Nothing] + case object NotFoundPureSoFar extends EvalContextInsertion[Nothing] } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/CommonPhaseConfig.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/CommonPhaseConfig.scala index 700a5b1ff7..66b0c0f9ef 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/CommonPhaseConfig.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/CommonPhaseConfig.scala @@ -18,6 +18,8 @@ import org.scalajs.linker.interface._ final class CommonPhaseConfig private ( /** Core specification. */ val coreSpec: CoreSpec, + /** Apply Scala.js-specific minification of the produced .js files. */ + val minify: Boolean, /** Whether things that can be parallelized should be parallelized. * On the JavaScript platform, this setting is typically ignored. */ @@ -37,6 +39,7 @@ final class CommonPhaseConfig private ( private def this() = { this( coreSpec = CoreSpec.Defaults, + minify = false, parallel = true, batchMode = false) } @@ -47,6 +50,6 @@ private[linker] object CommonPhaseConfig { private[linker] def fromStandardConfig(config: StandardConfig): CommonPhaseConfig = { val coreSpec = CoreSpec(config.semantics, config.moduleKind, config.esFeatures) - new CommonPhaseConfig(coreSpec, config.parallel, config.batchMode) + new CommonPhaseConfig(coreSpec, config.minify, config.parallel, config.batchMode) } } 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 cfa7b749fc..c55930f74f 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -71,8 +71,8 @@ class LibrarySizeTest { testLinkedSizes( expectedFastLinkSize = 147707, - expectedFullLinkSizeWithoutClosure = 88733, - expectedFullLinkSizeWithClosure = 21802, + expectedFullLinkSizeWithoutClosure = 86729, + expectedFullLinkSizeWithClosure = 21768, classDefs, moduleInitializers = MainTestModuleInitializers ) diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 6d2c642453..49cbe2d2a0 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -24,6 +24,8 @@ object BinaryIncompatibilities { ) val Linker = Seq( + // private, not an issue + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.CommonPhaseConfig.this"), ) val LinkerInterface = Seq( diff --git a/project/Build.scala b/project/Build.scala index bf039b251f..43f4be4847 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1999,16 +1999,16 @@ object Build { if (!useMinifySizes) { Some(ExpectedSizes( fastLink = 626000 to 627000, - fullLink = 98000 to 99000, + fullLink = 97000 to 98000, fastLinkGz = 75000 to 79000, fullLinkGz = 25000 to 26000, )) } else { Some(ExpectedSizes( - fastLink = 442000 to 443000, - fullLink = 297000 to 298000, - fastLinkGz = 64000 to 65000, - fullLinkGz = 46000 to 47000, + fastLink = 433000 to 434000, + fullLink = 288000 to 289000, + fastLinkGz = 62000 to 63000, + fullLinkGz = 44000 to 45000, )) } @@ -2016,16 +2016,16 @@ object Build { if (!useMinifySizes) { Some(ExpectedSizes( fastLink = 452000 to 453000, - fullLink = 96000 to 97000, + fullLink = 94000 to 95000, fastLinkGz = 58000 to 59000, - fullLinkGz = 26000 to 27000, + fullLinkGz = 25000 to 26000, )) } else { Some(ExpectedSizes( - fastLink = 316000 to 317000, - fullLink = 276000 to 277000, - fastLinkGz = 50000 to 51000, - fullLinkGz = 46000 to 47000, + fastLink = 309000 to 310000, + fullLink = 265000 to 266000, + fastLinkGz = 49000 to 50000, + fullLinkGz = 43000 to 44000, )) } From 8b5af310db7e7cc7b918a42b14f3f25fa91bde6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 16 Mar 2024 10:41:40 +0100 Subject: [PATCH 58/65] Reduce the memory pressure of `BackwardsCompatTest`. Previously, we were running the tests for *all* previous versions in parallel, because of the behavior of `Future.traverse`. While it can reduce the wall clock time of running that test, it comes at a huge memory consumption cost, increasing with every new release. It seems we have recently hit the limit of what is reasonable, especially on JS. Even JS retains things in parallel in memory because the IO handling concurrently overlaps. We now ensure a completely sequential behavior for this test. For each previous version, in sequence, we 1. create a new cache for the IR files of the version, 2. load the IR files, 3. execute the test, 4. free the cache. This commit hopefully fixes #4961. --- .../scalajs/linker/BackwardsCompatTest.scala | 23 ++++----- .../scalajs/linker/testutils/TestIRRepo.scala | 51 ++++++++++++++++--- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/BackwardsCompatTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/BackwardsCompatTest.scala index ca12a09277..54a1a0f105 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/BackwardsCompatTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/BackwardsCompatTest.scala @@ -80,18 +80,17 @@ class BackwardsCompatTest { val classDefFiles = classDefs.map(MemClassDefIRFile(_)) val logger = new ScalaConsoleLogger(Level.Error) - Future.traverse(TestIRRepo.previousLibs.toSeq) { case (version, libFuture) => - libFuture.flatMap { lib => - val config = StandardConfig().withCheckIR(true) - val linker = StandardImpl.linker(config) - val out = MemOutputDirectory() - - linker.link(lib ++ classDefFiles, moduleInitializers, out, logger) - }.recover { - case e: Throwable => - throw new AssertionError( - s"linking stdlib $version failed: ${e.getMessage()}", e) - } + TestIRRepo.sequentiallyForEachPreviousLib { (version, lib) => + val config = StandardConfig().withCheckIR(true) + val linker = StandardImpl.linker(config) + val out = MemOutputDirectory() + + linker.link(lib ++ classDefFiles, moduleInitializers, out, logger) + .recover { + case e: Throwable => + throw new AssertionError( + s"linking stdlib $version failed: ${e.getMessage()}", e) + } } } } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRRepo.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRRepo.scala index 61fa025c77..f3048d0623 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRRepo.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRRepo.scala @@ -13,7 +13,6 @@ package org.scalajs.linker.testutils import scala.concurrent._ -import scala.concurrent.ExecutionContext.Implicits.global import org.scalajs.linker.StandardImpl import org.scalajs.linker.interface.IRFile @@ -21,14 +20,54 @@ import org.scalajs.linker.interface.IRFile object TestIRRepo { private val globalIRCache = StandardImpl.irFileCache() - val minilib: Future[Seq[IRFile]] = load(StdlibHolder.minilib) - val javalib: Future[Seq[IRFile]] = load(StdlibHolder.javalib) + val minilib: Future[Seq[IRFile]] = loadGlobal(StdlibHolder.minilib) + val javalib: Future[Seq[IRFile]] = loadGlobal(StdlibHolder.javalib) val empty: Future[Seq[IRFile]] = Future.successful(Nil) - val previousLibs: Map[String, Future[Seq[IRFile]]] = - StdlibHolder.previousLibs.map(x => x._1 -> load(x._2)) - private def load(stdlibPath: String) = { + private def loadGlobal(stdlibPath: String): Future[Seq[IRFile]] = { + import scala.concurrent.ExecutionContext.Implicits.global + Platform.loadJar(stdlibPath) .flatMap(globalIRCache.newCache.cached _) } + + /** For each previous lib, calls `f(version, irFiles)`, and combines the result. + * + * This method applies `f` *sequentially*. It waits until the returned + * `Future` completes before moving on to the next iteration. + */ + def sequentiallyForEachPreviousLib[A](f: (String, Seq[IRFile]) => Future[A])( + implicit ec: ExecutionContext): Future[List[A]] = { + + // sort for determinism + val sortedPreviousLibs = StdlibHolder.previousLibs.toList.sortBy(_._1) + + sequentialFutureTraverse(sortedPreviousLibs) { case (version, path) => + Platform.loadJar(path).flatMap { files => + val cache = globalIRCache.newCache + cache + .cached(files) + .flatMap(f(version, _)) + .andThen { case _ => cache.free() } + } + } + } + + /** Like `Future.traverse`, but waits until each `Future` has completed + * before starting the next one. + */ + private def sequentialFutureTraverse[A, B](items: List[A])(f: A => Future[B])( + implicit ec: ExecutionContext): Future[List[B]] = { + items match { + case Nil => + Future.successful(Nil) + case head :: tail => + for { + headResult <- f(head) + tailResult <- sequentialFutureTraverse(tail)(f) + } yield { + headResult :: tailResult + } + } + } } From a00497878ed250360a27622fea29ebfbb35c3690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 16 Mar 2024 10:47:41 +0100 Subject: [PATCH 59/65] CI: Decompose the ir/linkerInterface/linker JS tests in separate processes. This should also help with the memory pressure of `linkerJS/test`. --- Jenkinsfile | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 1c9dc60c29..108cd81b6b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -402,10 +402,18 @@ def Tasks = [ npm install && sbtnoretry ++$scala linker$v/test && sbtnoretry linkerPrivateLibrary/test && - sbtnoretry ++$scala irJS$v/test linkerJS$v/test linkerInterfaceJS$v/test && + sbtnoretry ++$scala irJS$v/test && + sbtnoretry ++$scala linkerInterfaceJS$v/test && + sbtnoretry ++$scala linkerJS$v/test && sbtnoretry 'set scalaJSStage in Global := FullOptStage' \ 'set scalaJSStage in testSuite.v$v := FastOptStage' \ - ++$scala irJS$v/test linkerJS$v/test linkerInterfaceJS$v/test && + ++$scala irJS$v/test && + sbtnoretry 'set scalaJSStage in Global := FullOptStage' \ + 'set scalaJSStage in testSuite.v$v := FastOptStage' \ + ++$scala linkerInterfaceJS$v/test && + sbtnoretry 'set scalaJSStage in Global := FullOptStage' \ + 'set scalaJSStage in testSuite.v$v := FastOptStage' \ + ++$scala linkerJS$v/test && sbtnoretry ++$scala testSuite$v/bootstrap:test && sbtnoretry 'set scalaJSStage in Global := FullOptStage' \ 'set scalaJSStage in testSuite.v$v := FastOptStage' \ From af924f4d5ef381e3d26bf5044aa056e25ba215c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 15 Mar 2024 22:33:33 +0100 Subject: [PATCH 60/65] Add LinkedClass.hasDirectInstances. It exposes whether the given class is directly instantiated. This is required for the WebAssembly backend, which needs to build vtables for concrete classes only. --- .../main/scala/org/scalajs/linker/frontend/BaseLinker.scala | 1 + .../main/scala/org/scalajs/linker/standard/LinkedClass.scala | 1 + project/BinaryIncompatibilities.scala | 3 +++ 3 files changed, 5 insertions(+) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala index 692eb8a7f1..f120dff28a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala @@ -166,6 +166,7 @@ private[frontend] object BaseLinker { classDef.pos, ancestors.toList, hasInstances = classInfo.isAnySubclassInstantiated, + hasDirectInstances = classInfo.isInstantiated, hasInstanceTests = classInfo.areInstanceTestsUsed, hasRuntimeTypeInfo = classInfo.isDataAccessed, fieldsRead = classInfo.fieldsRead.toSet, diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedClass.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedClass.scala index 58b71fb725..afa257b289 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedClass.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedClass.scala @@ -55,6 +55,7 @@ final class LinkedClass( // Actual Linking info val ancestors: List[ClassName], val hasInstances: Boolean, + val hasDirectInstances: Boolean, val hasInstanceTests: Boolean, val hasRuntimeTypeInfo: Boolean, val fieldsRead: Set[FieldName], diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 49cbe2d2a0..e37b343839 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -24,6 +24,9 @@ object BinaryIncompatibilities { ) val Linker = Seq( + // !!! Breaking, OK in minor release + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.LinkedClass.this"), + // private, not an issue ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.CommonPhaseConfig.this"), ) From f05baed4b572756dfcf3069eada2b9eeafc7b6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 15 Mar 2024 22:48:47 +0100 Subject: [PATCH 61/65] Only set `$c_C.prototype.$classData` if `C` has *direct* instances. It is not useful for classes that only have strict sub instances, i.e., classes that are effectively abstract. --- .../org/scalajs/linker/backend/emitter/ClassEmitter.scala | 4 ++-- .../scala/org/scalajs/linker/backend/emitter/Emitter.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 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 e2f2419835..2d0dd515ea 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 @@ -826,7 +826,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { def genTypeData(className: ClassName, kind: ClassKind, superClass: Option[ClassIdent], ancestors: List[ClassName], - jsNativeLoadSpec: Option[JSNativeLoadSpec], hasInstances: Boolean)( + jsNativeLoadSpec: Option[JSNativeLoadSpec], hasDirectInstances: Boolean)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { import TreeDSL._ @@ -847,7 +847,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val kindOrCtorParam = { if (isJSType) js.IntLiteral(2) else if (kind == ClassKind.Interface) js.IntLiteral(1) - else if (kind.isClass && hasInstances) globalVar(VarField.c, className) + else if (kind.isClass && hasDirectInstances) globalVar(VarField.c, className) else js.IntLiteral(0) } 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 cdf17260e8..9d49e5855b 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 @@ -716,14 +716,14 @@ final class Emitter[E >: Null <: js.Tree]( if (linkedClass.hasRuntimeTypeInfo) { main ++= extractWithGlobals(classTreeCache.typeData.getOrElseUpdate( - linkedClass.hasInstances, + linkedClass.hasDirectInstances, classEmitter.genTypeData( className, // invalidated by overall class cache (part of ancestors) kind, // invalidated by class version linkedClass.superClass, // invalidated by class version linkedClass.ancestors, // invalidated by overall class cache (identity) linkedClass.jsNativeLoadSpec, // invalidated by class version - linkedClass.hasInstances // invalidated directly (it is the input to `getOrElseUpdate`) + linkedClass.hasDirectInstances // invalidated directly (it is the input to `getOrElseUpdate`) )(moduleContext, classCache, linkedClass.pos).map(postTransform(_, 0)))) } } From e4861b0ede528c9dcf11263bc7004b5cf851af5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 17 Mar 2024 10:24:44 +0100 Subject: [PATCH 62/65] Upgrade to Scala 2.12.19. --- Jenkinsfile | 5 +- .../{2.12.18 => 2.12.19}/BlacklistedTests.txt | 0 .../{2.12.18 => 2.12.19}/neg/choices.check | 0 .../neg/partestInvalidFlag.check | 0 .../{2.12.18 => 2.12.19}/neg/t11952b.check | 0 .../neg/t6446-additional.check | 0 .../{2.12.18 => 2.12.19}/neg/t6446-list.check | 0 .../neg/t6446-missing.check | 0 .../neg/t6446-show-phases.check | 0 .../neg/t7494-no-options.check | 0 .../run/Course-2002-01.check | 0 .../run/Course-2002-02.check | 0 .../run/Course-2002-04.check | 0 .../run/Course-2002-08.check | 0 .../run/Course-2002-09.check | 0 .../run/Course-2002-10.check | 0 .../{2.12.18 => 2.12.19}/run/Meter.check | 0 .../run/MeterCaseClass.check | 0 .../run/anyval-box-types.check | 0 .../scalajs/{2.12.18 => 2.12.19}/run/bugs.sem | 0 .../run/caseClassHash.check | 0 .../{2.12.18 => 2.12.19}/run/classof.check | 0 .../{2.12.18 => 2.12.19}/run/deeps.check | 0 .../run/dynamic-anyval.check | 0 .../{2.12.18 => 2.12.19}/run/exceptions-2.sem | 0 .../run/exceptions-nest.check | 0 .../run/exceptions-nest.sem | 0 .../run/impconvtimes.check | 0 .../{2.12.18 => 2.12.19}/run/imports.check | 0 .../run/inlineHandlers.sem | 0 .../run/interpolation.check | 0 .../run/interpolationMultiline1.check | 0 .../run/macro-bundle-static.check | 0 .../run/macro-bundle-toplevel.check | 0 .../run/macro-bundle-whitebox-decl.check | 0 .../{2.12.18 => 2.12.19}/run/misc.check | 0 .../run/optimizer-array-load.sem | 0 .../{2.12.18 => 2.12.19}/run/pf-catch.sem | 0 .../{2.12.18 => 2.12.19}/run/promotion.check | 0 .../{2.12.18 => 2.12.19}/run/runtime.check | 0 .../{2.12.18 => 2.12.19}/run/spec-self.check | 0 .../{2.12.18 => 2.12.19}/run/structural.check | 0 .../{2.12.18 => 2.12.19}/run/t0421-new.check | 0 .../{2.12.18 => 2.12.19}/run/t0421-old.check | 0 .../{2.12.18 => 2.12.19}/run/t1503.sem | 0 .../{2.12.18 => 2.12.19}/run/t3702.check | 0 .../{2.12.18 => 2.12.19}/run/t4148.sem | 0 .../{2.12.18 => 2.12.19}/run/t4617.check | 0 .../{2.12.18 => 2.12.19}/run/t5356.check | 0 .../{2.12.18 => 2.12.19}/run/t5552.check | 0 .../{2.12.18 => 2.12.19}/run/t5568.check | 0 .../{2.12.18 => 2.12.19}/run/t5629b.check | 0 .../{2.12.18 => 2.12.19}/run/t5680.check | 0 .../{2.12.18 => 2.12.19}/run/t5866.check | 0 .../run/t6318_primitives.check | 0 .../{2.12.18 => 2.12.19}/run/t6662.check | 0 .../{2.12.18 => 2.12.19}/run/t6827.sem | 0 .../{2.12.18 => 2.12.19}/run/t7657.check | 0 .../{2.12.18 => 2.12.19}/run/t7763.sem | 0 .../{2.12.18 => 2.12.19}/run/t8570a.check | 0 .../{2.12.18 => 2.12.19}/run/t8601b.sem | 0 .../{2.12.18 => 2.12.19}/run/t8601c.sem | 0 .../{2.12.18 => 2.12.19}/run/t8601d.sem | 0 .../{2.12.18 => 2.12.19}/run/t8764.check | 0 .../{2.12.18 => 2.12.19}/run/t9387b.check | 0 .../{2.12.18 => 2.12.19}/run/t9656.check | 0 .../run/try-catch-unify.check | 0 .../run/virtpatmat_switch.check | 0 .../run/virtpatmat_typetag.check | 0 project/Build.scala | 1 + .../change-config-and-source/build.sbt | 2 +- .../incremental/change-config/build.sbt | 2 +- .../incremental/fix-compile-error/build.sbt | 2 +- .../linker/concurrent-linker-use/build.sbt | 2 +- .../sbt-test/linker/custom-linker/build.sbt | 4 +- .../no-root-dependency-resolution/build.sbt | 2 +- .../linker/non-existent-classpath/build.sbt | 2 +- .../sbt-test/settings/cross-version/build.sbt | 2 +- .../src/sbt-test/settings/env-vars/build.sbt | 2 +- .../settings/legacy-link-empty/build.sbt | 2 +- .../settings/legacy-link-tasks/build.sbt | 2 +- .../sbt-test/settings/module-init/build.sbt | 2 +- .../sbt-test/settings/source-map/build.sbt | 2 +- .../testing/multi-framework/build.sbt | 2 +- .../resources/2.12.19/BlacklistedTests.txt | 197 ++++++++++++++++++ 85 files changed, 216 insertions(+), 17 deletions(-) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/BlacklistedTests.txt (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/neg/choices.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/neg/partestInvalidFlag.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/neg/t11952b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/neg/t6446-additional.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/neg/t6446-list.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/neg/t6446-missing.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/neg/t6446-show-phases.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/neg/t7494-no-options.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/Course-2002-01.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/Course-2002-02.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/Course-2002-04.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/Course-2002-08.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/Course-2002-09.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/Course-2002-10.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/Meter.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/MeterCaseClass.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/anyval-box-types.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/bugs.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/caseClassHash.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/classof.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/deeps.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/dynamic-anyval.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/exceptions-2.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/exceptions-nest.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/exceptions-nest.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/impconvtimes.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/imports.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/inlineHandlers.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/interpolation.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/interpolationMultiline1.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/macro-bundle-static.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/macro-bundle-toplevel.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/macro-bundle-whitebox-decl.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/misc.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/optimizer-array-load.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/pf-catch.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/promotion.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/runtime.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/spec-self.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/structural.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t0421-new.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t0421-old.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t1503.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t3702.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t4148.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t4617.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t5356.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t5552.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t5568.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t5629b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t5680.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t5866.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t6318_primitives.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t6662.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t6827.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t7657.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t7763.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t8570a.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t8601b.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t8601c.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t8601d.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t8764.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t9387b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/t9656.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/try-catch-unify.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/virtpatmat_switch.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.18 => 2.12.19}/run/virtpatmat_typetag.check (100%) create mode 100644 scala-test-suite/src/test/resources/2.12.19/BlacklistedTests.txt diff --git a/Jenkinsfile b/Jenkinsfile index 108cd81b6b..f4886117a6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -479,8 +479,8 @@ def otherJavaVersions = ["11", "16"] def allJavaVersions = otherJavaVersions.clone() allJavaVersions << mainJavaVersion -def mainScalaVersion = "2.12.18" -def mainScalaVersions = ["2.12.18", "2.13.12"] +def mainScalaVersion = "2.12.19" +def mainScalaVersions = ["2.12.19", "2.13.12"] def otherScalaVersions = [ "2.12.2", "2.12.3", @@ -497,6 +497,7 @@ def otherScalaVersions = [ "2.12.15", "2.12.16", "2.12.17", + "2.12.18", "2.13.0", "2.13.1", "2.13.2", diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/BlacklistedTests.txt b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/BlacklistedTests.txt similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/BlacklistedTests.txt rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/BlacklistedTests.txt diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/choices.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/choices.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/choices.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/choices.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/partestInvalidFlag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/partestInvalidFlag.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/partestInvalidFlag.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/partestInvalidFlag.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/t11952b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t11952b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/t11952b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t11952b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/t6446-additional.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-additional.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/t6446-additional.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-additional.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/t6446-list.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-list.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/t6446-list.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-list.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/t6446-missing.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-missing.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/t6446-missing.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-missing.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/t6446-show-phases.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-show-phases.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/t6446-show-phases.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-show-phases.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/t7494-no-options.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t7494-no-options.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/neg/t7494-no-options.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t7494-no-options.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Course-2002-01.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-01.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Course-2002-01.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-01.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Course-2002-02.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-02.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Course-2002-02.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-02.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Course-2002-04.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-04.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Course-2002-04.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-04.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Course-2002-08.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-08.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Course-2002-08.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-08.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Course-2002-09.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-09.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Course-2002-09.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-09.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Course-2002-10.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-10.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Course-2002-10.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-10.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Meter.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Meter.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/Meter.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Meter.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/MeterCaseClass.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/MeterCaseClass.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/MeterCaseClass.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/MeterCaseClass.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/anyval-box-types.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/anyval-box-types.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/anyval-box-types.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/anyval-box-types.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/bugs.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/bugs.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/bugs.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/bugs.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/caseClassHash.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/caseClassHash.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/caseClassHash.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/caseClassHash.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/classof.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/classof.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/classof.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/classof.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/deeps.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/deeps.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/deeps.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/deeps.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/dynamic-anyval.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/dynamic-anyval.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/dynamic-anyval.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/dynamic-anyval.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/exceptions-2.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/exceptions-2.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/exceptions-2.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/exceptions-2.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/exceptions-nest.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/exceptions-nest.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/exceptions-nest.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/exceptions-nest.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/exceptions-nest.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/exceptions-nest.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/exceptions-nest.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/exceptions-nest.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/impconvtimes.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/impconvtimes.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/impconvtimes.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/impconvtimes.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/imports.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/imports.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/imports.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/imports.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/inlineHandlers.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/inlineHandlers.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/inlineHandlers.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/inlineHandlers.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/interpolation.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/interpolation.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/interpolation.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/interpolation.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/interpolationMultiline1.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/interpolationMultiline1.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/interpolationMultiline1.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/interpolationMultiline1.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/macro-bundle-static.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/macro-bundle-static.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/macro-bundle-static.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/macro-bundle-static.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/macro-bundle-toplevel.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/macro-bundle-toplevel.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/macro-bundle-toplevel.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/macro-bundle-toplevel.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/macro-bundle-whitebox-decl.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/macro-bundle-whitebox-decl.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/macro-bundle-whitebox-decl.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/macro-bundle-whitebox-decl.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/misc.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/misc.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/misc.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/misc.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/optimizer-array-load.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/optimizer-array-load.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/optimizer-array-load.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/optimizer-array-load.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/pf-catch.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/pf-catch.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/pf-catch.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/pf-catch.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/promotion.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/promotion.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/promotion.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/promotion.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/runtime.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/runtime.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/runtime.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/runtime.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/spec-self.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/spec-self.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/spec-self.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/spec-self.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/structural.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/structural.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/structural.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/structural.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t0421-new.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t0421-new.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t0421-new.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t0421-new.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t0421-old.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t0421-old.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t0421-old.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t0421-old.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t1503.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t1503.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t1503.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t1503.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t3702.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t3702.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t3702.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t3702.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t4148.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t4148.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t4148.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t4148.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t4617.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t4617.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t4617.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t4617.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t5356.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5356.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t5356.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5356.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t5552.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5552.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t5552.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5552.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t5568.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5568.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t5568.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5568.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t5629b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5629b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t5629b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5629b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t5680.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5680.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t5680.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5680.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t5866.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5866.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t5866.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5866.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t6318_primitives.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t6318_primitives.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t6318_primitives.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t6318_primitives.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t6662.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t6662.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t6662.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t6662.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t6827.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t6827.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t6827.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t6827.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t7657.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t7657.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t7657.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t7657.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t7763.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t7763.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t7763.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t7763.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t8570a.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8570a.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t8570a.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8570a.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t8601b.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8601b.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t8601b.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8601b.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t8601c.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8601c.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t8601c.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8601c.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t8601d.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8601d.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t8601d.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8601d.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t8764.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8764.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t8764.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8764.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t9387b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t9387b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t9387b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t9387b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t9656.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t9656.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/t9656.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t9656.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/try-catch-unify.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/try-catch-unify.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/try-catch-unify.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/try-catch-unify.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/virtpatmat_switch.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/virtpatmat_switch.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/virtpatmat_switch.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/virtpatmat_switch.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/virtpatmat_typetag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/virtpatmat_typetag.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.18/run/virtpatmat_typetag.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/virtpatmat_typetag.check diff --git a/project/Build.scala b/project/Build.scala index 43f4be4847..d392347803 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -917,6 +917,7 @@ object Build { "2.12.16", "2.12.17", "2.12.18", + "2.12.19", ), cross213ScalaVersions := Seq( "2.13.0", diff --git a/sbt-plugin/src/sbt-test/incremental/change-config-and-source/build.sbt b/sbt-plugin/src/sbt-test/incremental/change-config-and-source/build.sbt index d12712d68d..d0f231b6de 100644 --- a/sbt-plugin/src/sbt-test/incremental/change-config-and-source/build.sbt +++ b/sbt-plugin/src/sbt-test/incremental/change-config-and-source/build.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.12.18" +scalaVersion := "2.12.19" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/incremental/change-config/build.sbt b/sbt-plugin/src/sbt-test/incremental/change-config/build.sbt index d12712d68d..d0f231b6de 100644 --- a/sbt-plugin/src/sbt-test/incremental/change-config/build.sbt +++ b/sbt-plugin/src/sbt-test/incremental/change-config/build.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.12.18" +scalaVersion := "2.12.19" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/incremental/fix-compile-error/build.sbt b/sbt-plugin/src/sbt-test/incremental/fix-compile-error/build.sbt index d12712d68d..d0f231b6de 100644 --- a/sbt-plugin/src/sbt-test/incremental/fix-compile-error/build.sbt +++ b/sbt-plugin/src/sbt-test/incremental/fix-compile-error/build.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.12.18" +scalaVersion := "2.12.19" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/linker/concurrent-linker-use/build.sbt b/sbt-plugin/src/sbt-test/linker/concurrent-linker-use/build.sbt index f3238517cd..0c84905ef6 100644 --- a/sbt-plugin/src/sbt-test/linker/concurrent-linker-use/build.sbt +++ b/sbt-plugin/src/sbt-test/linker/concurrent-linker-use/build.sbt @@ -11,7 +11,7 @@ lazy val concurrentUseOfLinkerTest = taskKey[Any]("") name := "Scala.js sbt test" version := scalaJSVersion -scalaVersion := "2.12.18" +scalaVersion := "2.12.19" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/linker/custom-linker/build.sbt b/sbt-plugin/src/sbt-test/linker/custom-linker/build.sbt index 2576235cf2..1d4b83961d 100644 --- a/sbt-plugin/src/sbt-test/linker/custom-linker/build.sbt +++ b/sbt-plugin/src/sbt-test/linker/custom-linker/build.sbt @@ -13,14 +13,14 @@ inThisBuild(Def.settings( version := scalaJSVersion, - scalaVersion := "2.12.18", + scalaVersion := "2.12.19", )) lazy val check = taskKey[Any]("") lazy val customLinker = project.in(file("custom-linker")) .settings( - scalaVersion := "2.12.18", // needs to match the minor version of Scala used by sbt + scalaVersion := "2.12.19", // needs to match the minor version of Scala used by sbt libraryDependencies += "org.scala-js" %% "scalajs-linker" % scalaJSVersion, ) diff --git a/sbt-plugin/src/sbt-test/linker/no-root-dependency-resolution/build.sbt b/sbt-plugin/src/sbt-test/linker/no-root-dependency-resolution/build.sbt index 541d53caf8..2359461fa6 100644 --- a/sbt-plugin/src/sbt-test/linker/no-root-dependency-resolution/build.sbt +++ b/sbt-plugin/src/sbt-test/linker/no-root-dependency-resolution/build.sbt @@ -1,7 +1,7 @@ name := "Scala.js sbt test" version in ThisBuild := scalaJSVersion -scalaVersion in ThisBuild := "2.12.18" +scalaVersion in ThisBuild := "2.12.19" // Disable the IvyPlugin on the root project disablePlugins(sbt.plugins.IvyPlugin) diff --git a/sbt-plugin/src/sbt-test/linker/non-existent-classpath/build.sbt b/sbt-plugin/src/sbt-test/linker/non-existent-classpath/build.sbt index bf0b1a8bc8..a3d81f1d03 100644 --- a/sbt-plugin/src/sbt-test/linker/non-existent-classpath/build.sbt +++ b/sbt-plugin/src/sbt-test/linker/non-existent-classpath/build.sbt @@ -1,5 +1,5 @@ version := scalaJSVersion -scalaVersion := "2.12.18" +scalaVersion := "2.12.19" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/settings/cross-version/build.sbt b/sbt-plugin/src/sbt-test/settings/cross-version/build.sbt index 98b9b4802a..bab1e57ecf 100644 --- a/sbt-plugin/src/sbt-test/settings/cross-version/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/cross-version/build.sbt @@ -3,7 +3,7 @@ import org.scalajs.sbtplugin.ScalaJSCrossVersion val check = taskKey[Unit]("Run checks of this test") version := scalaJSVersion -scalaVersion := "2.12.18" +scalaVersion := "2.12.19" lazy val js = project.enablePlugins(ScalaJSPlugin).settings( check := { diff --git a/sbt-plugin/src/sbt-test/settings/env-vars/build.sbt b/sbt-plugin/src/sbt-test/settings/env-vars/build.sbt index 55967e1eb6..83878dc189 100644 --- a/sbt-plugin/src/sbt-test/settings/env-vars/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/env-vars/build.sbt @@ -1,5 +1,5 @@ inThisBuild(Def.settings( - scalaVersion := "2.12.18", + scalaVersion := "2.12.19", )) lazy val sharedSettings = Def.settings( diff --git a/sbt-plugin/src/sbt-test/settings/legacy-link-empty/build.sbt b/sbt-plugin/src/sbt-test/settings/legacy-link-empty/build.sbt index e8419e778a..4044def10a 100644 --- a/sbt-plugin/src/sbt-test/settings/legacy-link-empty/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/legacy-link-empty/build.sbt @@ -1,4 +1,4 @@ version := scalaJSVersion -scalaVersion := "2.12.18" +scalaVersion := "2.12.19" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/settings/legacy-link-tasks/build.sbt b/sbt-plugin/src/sbt-test/settings/legacy-link-tasks/build.sbt index 309e32ab98..001fe4b7ca 100644 --- a/sbt-plugin/src/sbt-test/settings/legacy-link-tasks/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/legacy-link-tasks/build.sbt @@ -1,7 +1,7 @@ val checkNoClosure = taskKey[Unit]("Check that fullOptJS wasn't run with closure") version := scalaJSVersion -scalaVersion := "2.12.18" +scalaVersion := "2.12.19" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/settings/module-init/build.sbt b/sbt-plugin/src/sbt-test/settings/module-init/build.sbt index a70d51266f..b3cb9bef50 100644 --- a/sbt-plugin/src/sbt-test/settings/module-init/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/module-init/build.sbt @@ -3,7 +3,7 @@ import org.scalajs.linker.interface.ModuleInitializer val check = taskKey[Unit]("Run checks of this test") version := scalaJSVersion -scalaVersion := "2.12.18" +scalaVersion := "2.12.19" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/settings/source-map/build.sbt b/sbt-plugin/src/sbt-test/settings/source-map/build.sbt index 7bfe7a52b6..fa3901cdcc 100644 --- a/sbt-plugin/src/sbt-test/settings/source-map/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/source-map/build.sbt @@ -3,7 +3,7 @@ import org.scalajs.linker.interface.ModuleInitializer val check = taskKey[Unit]("Run checks of this test") version := scalaJSVersion -scalaVersion := "2.12.18" +scalaVersion := "2.12.19" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/testing/multi-framework/build.sbt b/sbt-plugin/src/sbt-test/testing/multi-framework/build.sbt index 4b3100395b..5a8b4240c9 100644 --- a/sbt-plugin/src/sbt-test/testing/multi-framework/build.sbt +++ b/sbt-plugin/src/sbt-test/testing/multi-framework/build.sbt @@ -1,5 +1,5 @@ inThisBuild(version := scalaJSVersion) -inThisBuild(scalaVersion := "2.12.18") +inThisBuild(scalaVersion := "2.12.19") lazy val root = project.in(file(".")). aggregate(multiTestJS, multiTestJVM) diff --git a/scala-test-suite/src/test/resources/2.12.19/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.19/BlacklistedTests.txt new file mode 100644 index 0000000000..6c78101e5b --- /dev/null +++ b/scala-test-suite/src/test/resources/2.12.19/BlacklistedTests.txt @@ -0,0 +1,197 @@ +## Do not compile +scala/lang/annotations/BytecodeTest.scala +scala/lang/annotations/RunTest.scala +scala/lang/traits/BytecodeTest.scala +scala/lang/traits/RunTest.scala +scala/lang/primitives/NaNTest.scala +scala/lang/primitives/BoxUnboxTest.scala +scala/collection/SeqTest.scala +scala/collection/Sizes.scala +scala/collection/immutable/HashMapTest.scala +scala/collection/immutable/HashSetTest.scala +scala/collection/immutable/ListMapTest.scala +scala/collection/immutable/MapHashcodeTest.scala +scala/collection/immutable/SetTest.scala +scala/collection/immutable/SeqTest.scala +scala/collection/immutable/SmallMapTest.scala +scala/collection/immutable/SortedMapTest.scala +scala/collection/immutable/SortedSetTest.scala +scala/collection/immutable/TreeMapTest.scala +scala/collection/immutable/TreeSetTest.scala +scala/reflect/ClassOfTest.scala +scala/reflect/QTest.scala +scala/reflect/io/AbstractFileTest.scala +scala/reflect/io/ZipArchiveTest.scala +scala/reflect/internal/util/AbstractFileClassLoaderTest.scala +scala/reflect/internal/util/FileUtilsTest.scala +scala/reflect/internal/util/SourceFileTest.scala +scala/reflect/internal/util/StringOpsTest.scala +scala/reflect/internal/util/WeakHashSetTest.scala +scala/reflect/internal/LongNamesTest.scala +scala/reflect/internal/MirrorsTest.scala +scala/reflect/internal/NamesTest.scala +scala/reflect/internal/PositionsTest.scala +scala/reflect/internal/PrintersTest.scala +scala/reflect/internal/ScopeTest.scala +scala/reflect/internal/TreeGenTest.scala +scala/reflect/internal/TypesTest.scala +scala/reflect/macros/AttachmentsTest.scala +scala/reflect/runtime/ReflectionUtilsShowTest.scala +scala/reflect/runtime/ThreadSafetyTest.scala +scala/runtime/BooleanBoxingTest.scala +scala/runtime/ByteBoxingTest.scala +scala/runtime/CharBoxingTest.scala +scala/runtime/DoubleBoxingTest.scala +scala/runtime/IntBoxingTest.scala +scala/runtime/FloatBoxingTest.scala +scala/runtime/LongBoxingTest.scala +scala/runtime/ShortBoxingTest.scala +scala/tools/cmd/CommandLineParserTest.scala +scala/tools/nsc/Build.scala +scala/tools/nsc/DeterminismTest.scala +scala/tools/nsc/DeterminismTester.scala +scala/tools/nsc/FileUtils.scala +scala/tools/nsc/GlobalCustomizeClassloaderTest.scala +scala/tools/nsc/PickleWriteTest.scala +scala/tools/nsc/PipelineMainTest.scala +scala/tools/nsc/async/AnnotationDrivenAsync.scala +scala/tools/nsc/async/CustomFuture.scala +scala/tools/nsc/backend/jvm/BTypesTest.scala +scala/tools/nsc/backend/jvm/BytecodeTest.scala +scala/tools/nsc/backend/jvm/ClassfileParserTest.scala +scala/tools/nsc/backend/jvm/DefaultMethodTest.scala +scala/tools/nsc/backend/jvm/DirectCompileTest.scala +scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala +scala/tools/nsc/backend/jvm/IndyLambdaDirectTest.scala +scala/tools/nsc/backend/jvm/IndyLambdaTest.scala +scala/tools/nsc/backend/jvm/IndySammyTest.scala +scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala +scala/tools/nsc/backend/jvm/LineNumberTest.scala +scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala +scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala +scala/tools/nsc/backend/jvm/PerRunInitTest.scala +scala/tools/nsc/backend/jvm/StringConcatTest.scala +scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxAndInlineTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala +scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala +scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala +scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala +scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala +scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala +scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala +scala/tools/nsc/ScriptRunnerTest.scala +scala/tools/nsc/classpath/AggregateClassPathTest.scala +scala/tools/nsc/classpath/JrtClassPathTest.scala +scala/tools/nsc/classpath/MultiReleaseJarTest.scala +scala/tools/nsc/classpath/PathResolverBaseTest.scala +scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala +scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala +scala/tools/nsc/doc/html/HtmlDocletTest.scala +scala/tools/nsc/interpreter/CompletionTest.scala +scala/tools/nsc/interpreter/ScriptedTest.scala +scala/tools/nsc/interpreter/TabulatorTest.scala +scala/tools/nsc/parser/ParserTest.scala +scala/tools/nsc/reporters/ConsoleReporterTest.scala +scala/tools/nsc/reporters/WConfTest.scala +scala/tools/nsc/settings/ScalaVersionTest.scala +scala/tools/nsc/settings/SettingsTest.scala +scala/tools/nsc/settings/TargetTest.scala +scala/tools/nsc/symtab/CannotHaveAttrsTest.scala +scala/tools/nsc/symtab/FlagsTest.scala +scala/tools/nsc/symtab/FreshNameExtractorTest.scala +scala/tools/nsc/symtab/StdNamesTest.scala +scala/tools/nsc/symtab/SymbolLoadersAssociatedFileTest.scala +scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala +scala/tools/nsc/symtab/SymbolTableTest.scala +scala/tools/nsc/symtab/classfile/PicklerTest.scala +scala/tools/nsc/transform/MixinTest.scala +scala/tools/nsc/transform/SpecializationTest.scala +scala/tools/nsc/transform/ThicketTransformerTest.scala +scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala +scala/tools/nsc/transform/patmat/SolvingTest.scala +scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala +scala/tools/nsc/typechecker/Implicits.scala +scala/tools/nsc/typechecker/NamerTest.scala +scala/tools/nsc/typechecker/ParamAliasTest.scala +scala/tools/nsc/typechecker/TreeAttachmentTest.scala +scala/tools/nsc/typechecker/TypedTreeTest.scala +scala/tools/nsc/util/StackTraceTest.scala +scala/tools/testing/AllocationTest.scala +scala/tools/testing/BytecodeTesting.scala +scala/tools/testing/JOL.scala +scala/tools/testing/RunTesting.scala +scala/tools/testing/VirtualCompilerTesting.scala +scala/util/matching/RegexTest.scala + +## Do not link +scala/MatchErrorSerializationTest.scala +scala/PartialFunctionSerializationTest.scala +scala/lang/stringinterpol/StringContextTest.scala +scala/collection/IteratorTest.scala +scala/collection/NewBuilderTest.scala +scala/collection/ParallelConsistencyTest.scala +scala/collection/SetMapRulesTest.scala +scala/collection/SeqViewTest.scala +scala/collection/SetMapConsistencyTest.scala +scala/collection/concurrent/TrieMapTest.scala +scala/collection/convert/WrapperSerializationTest.scala +scala/collection/immutable/ListTest.scala +scala/collection/immutable/RedBlackTreeSerialFormat.scala +scala/collection/immutable/StreamTest.scala +scala/collection/immutable/StringLikeTest.scala +scala/collection/immutable/VectorTest.scala +scala/collection/mutable/AnyRefMapTest.scala +scala/collection/mutable/ArrayBufferTest.scala +scala/collection/mutable/MutableListTest.scala +scala/collection/mutable/OpenHashMapTest.scala +scala/collection/mutable/PriorityQueueTest.scala +scala/collection/parallel/TaskTest.scala +scala/collection/parallel/immutable/ParRangeTest.scala +scala/concurrent/FutureTest.scala +scala/concurrent/duration/SerializationTest.scala +scala/concurrent/impl/DefaultPromiseTest.scala +scala/io/SourceTest.scala +scala/runtime/ScalaRunTimeTest.scala +scala/sys/process/PipedProcessTest.scala +scala/sys/process/ProcessTest.scala +scala/tools/testing/AssertUtilTest.scala +scala/tools/testing/AssertThrowsTest.scala +scala/util/SpecVersionTest.scala +scala/util/SystemPropertiesTest.scala + +## Tests fail + +# Reflection +scala/reflect/ClassTagTest.scala + +# Require strict-floats +scala/math/BigDecimalTest.scala + +# Difference of getClass() on primitive values +scala/collection/immutable/RangeTest.scala + +# Test fails only some times with +# 'set scalaJSOptimizerOptions in scalaTestSuite ~= (_.withDisableOptimizer(true))' +# and' 'set scalaJSUseRhino in Global := false' +scala/collection/immutable/PagedSeqTest.scala + +# Bugs +scala/collection/convert/MapWrapperTest.scala + +# Tests passed but are too slow (timeouts) +scala/collection/immutable/ListSetTest.scala +scala/util/SortingTest.scala From 24266f98fcfa0e4133a3af1d504643b021f30c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 17 Mar 2024 11:05:35 +0100 Subject: [PATCH 63/65] Avoid an internal deprecation in junit-runtime. Scala 2.13.13 starts warning about these deprecations, which we must therefore avoid. It might have missed them before because they are secondary constructors. --- junit-runtime/src/main/scala/org/junit/Assume.scala | 4 ++-- .../main/scala/org/junit/AssumptionViolatedException.scala | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/junit-runtime/src/main/scala/org/junit/Assume.scala b/junit-runtime/src/main/scala/org/junit/Assume.scala index ba9bdf8011..3d44f8be5d 100644 --- a/junit-runtime/src/main/scala/org/junit/Assume.scala +++ b/junit-runtime/src/main/scala/org/junit/Assume.scala @@ -33,13 +33,13 @@ object Assume { @noinline def assumeThat[T](actual: T, matcher: Matcher[T]): Unit = { if (!matcher.matches(actual.asInstanceOf[AnyRef])) - throw new AssumptionViolatedException(actual, matcher) + throw new AssumptionViolatedException(null, matcher, actual) } @noinline def assumeThat[T](message: String, actual: T, matcher: Matcher[T]): Unit = { if (!matcher.matches(actual.asInstanceOf[AnyRef])) - throw new AssumptionViolatedException(message, actual, matcher) + throw new AssumptionViolatedException(message, matcher, actual) } @noinline diff --git a/junit-runtime/src/main/scala/org/junit/AssumptionViolatedException.scala b/junit-runtime/src/main/scala/org/junit/AssumptionViolatedException.scala index a2adc0db01..315bcfa0e3 100644 --- a/junit-runtime/src/main/scala/org/junit/AssumptionViolatedException.scala +++ b/junit-runtime/src/main/scala/org/junit/AssumptionViolatedException.scala @@ -19,6 +19,10 @@ class AssumptionViolatedException protected (fAssumption: String, def this(message: String, expected: Any, matcher: Matcher[_]) = this(message, true, fMatcher = matcher, fValue = expected.asInstanceOf[AnyRef]) + // Non-deprecated access to the full constructor for use in `Assume.scala` + private[junit] def this(message: String, matcher: Matcher[_], actual: Any) = + this(message, true, fMatcher = matcher, fValue = actual.asInstanceOf[AnyRef]) + def this(message: String) = this(message, false, null, null) From 10634fc03818fd2d2f84db2c4f3422d971eee769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 17 Mar 2024 11:22:16 +0100 Subject: [PATCH 64/65] Upgrade to Scala 2.13.13. --- Jenkinsfile | 5 +- .../scala/scalajs/js/WrappedDictionary.scala | 4 + .../scala/scalajs/js/WrappedMap.scala | 4 + .../{2.13.12 => 2.13.13}/BlacklistedTests.txt | 5 +- .../{2.13.12 => 2.13.13}/neg/choices.check | 0 .../neg/partestInvalidFlag.check | 0 .../{2.13.12 => 2.13.13}/neg/t11952b.check | 0 .../{2.13.12 => 2.13.13}/neg/t12494.check | 0 .../neg/t6446-additional.check | 0 .../{2.13.12 => 2.13.13}/neg/t6446-list.check | 0 .../neg/t6446-missing.check | 0 .../neg/t6446-show-phases.check | 0 .../neg/t7494-no-options.check | 0 .../run/Course-2002-01.check | 0 .../run/Course-2002-02.check | 0 .../run/Course-2002-04.check | 0 .../run/Course-2002-08.check | 0 .../run/Course-2002-09.check | 0 .../run/Course-2002-10.check | 0 .../{2.13.12 => 2.13.13}/run/Meter.check | 0 .../run/MeterCaseClass.check | 0 .../run/anyval-box-types.check | 0 .../scalajs/{2.13.12 => 2.13.13}/run/bugs.sem | 0 .../run/caseClassHash.check | 0 .../{2.13.12 => 2.13.13}/run/classof.check | 0 .../{2.13.12 => 2.13.13}/run/deeps.check | 0 .../run/dynamic-anyval.check | 0 .../{2.13.12 => 2.13.13}/run/exceptions-2.sem | 0 .../run/exceptions-nest.check | 0 .../run/exceptions-nest.sem | 0 .../run/impconvtimes.check | 0 .../{2.13.12 => 2.13.13}/run/imports.check | 0 .../run/inlineHandlers.sem | 0 .../run/interpolation.check | 0 .../run/interpolationMultiline1.check | 0 .../run/macro-bundle-static.check | 0 .../run/macro-bundle-toplevel.check | 0 .../run/macro-bundle-whitebox-decl.check | 0 ...expand-varargs-implicit-over-varargs.check | 0 .../{2.13.12 => 2.13.13}/run/misc.check | 0 .../run/optimizer-array-load.sem | 0 .../{2.13.12 => 2.13.13}/run/pf-catch.sem | 0 .../{2.13.12 => 2.13.13}/run/promotion.check | 0 .../{2.13.12 => 2.13.13}/run/runtime.check | 0 .../run/sammy_vararg_cbn.check | 0 .../{2.13.12 => 2.13.13}/run/spec-self.check | 0 .../run/string-switch.check | 0 .../{2.13.12 => 2.13.13}/run/structural.check | 0 .../{2.13.12 => 2.13.13}/run/t0421-new.check | 0 .../{2.13.12 => 2.13.13}/run/t0421-old.check | 0 .../{2.13.12 => 2.13.13}/run/t12221.check | 0 .../{2.13.12 => 2.13.13}/run/t1503.sem | 0 .../{2.13.12 => 2.13.13}/run/t3702.check | 0 .../{2.13.12 => 2.13.13}/run/t4148.sem | 0 .../{2.13.12 => 2.13.13}/run/t4617.check | 0 .../{2.13.12 => 2.13.13}/run/t5356.check | 0 .../{2.13.12 => 2.13.13}/run/t5552.check | 0 .../{2.13.12 => 2.13.13}/run/t5568.check | 0 .../{2.13.12 => 2.13.13}/run/t5629b.check | 0 .../{2.13.12 => 2.13.13}/run/t5680.check | 0 .../{2.13.12 => 2.13.13}/run/t5866.check | 0 .../{2.13.12 => 2.13.13}/run/t5966.check | 0 .../{2.13.12 => 2.13.13}/run/t6265.check | 0 .../run/t6318_primitives.check | 0 .../{2.13.12 => 2.13.13}/run/t6662.check | 0 .../{2.13.12 => 2.13.13}/run/t6827.sem | 0 .../{2.13.12 => 2.13.13}/run/t7657.check | 0 .../{2.13.12 => 2.13.13}/run/t7763.sem | 0 .../{2.13.12 => 2.13.13}/run/t8570a.check | 0 .../{2.13.12 => 2.13.13}/run/t8601b.sem | 0 .../{2.13.12 => 2.13.13}/run/t8601c.sem | 0 .../{2.13.12 => 2.13.13}/run/t8601d.sem | 0 .../{2.13.12 => 2.13.13}/run/t8764.check | 0 .../{2.13.12 => 2.13.13}/run/t9387b.check | 0 .../run/try-catch-unify.check | 0 .../run/virtpatmat_switch.check | 0 .../run/virtpatmat_typetag.check | 0 project/Build.scala | 20 +- .../src/sbt-test/cross-version/2.13/build.sbt | 2 +- .../sbt-test/scala3/tasty-reader/build.sbt | 2 +- .../resources/2.13.13/BlacklistedTests.txt | 247 ++++++++++++++++++ 81 files changed, 282 insertions(+), 7 deletions(-) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/BlacklistedTests.txt (99%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/neg/choices.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/neg/partestInvalidFlag.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/neg/t11952b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/neg/t12494.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/neg/t6446-additional.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/neg/t6446-list.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/neg/t6446-missing.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/neg/t6446-show-phases.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/neg/t7494-no-options.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/Course-2002-01.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/Course-2002-02.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/Course-2002-04.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/Course-2002-08.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/Course-2002-09.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/Course-2002-10.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/Meter.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/MeterCaseClass.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/anyval-box-types.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/bugs.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/caseClassHash.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/classof.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/deeps.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/dynamic-anyval.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/exceptions-2.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/exceptions-nest.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/exceptions-nest.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/impconvtimes.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/imports.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/inlineHandlers.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/interpolation.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/interpolationMultiline1.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/macro-bundle-static.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/macro-bundle-toplevel.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/macro-bundle-whitebox-decl.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/macro-expand-varargs-implicit-over-varargs.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/misc.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/optimizer-array-load.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/pf-catch.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/promotion.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/runtime.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/sammy_vararg_cbn.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/spec-self.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/string-switch.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/structural.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t0421-new.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t0421-old.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t12221.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t1503.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t3702.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t4148.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t4617.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t5356.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t5552.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t5568.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t5629b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t5680.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t5866.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t5966.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t6265.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t6318_primitives.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t6662.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t6827.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t7657.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t7763.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t8570a.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t8601b.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t8601c.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t8601d.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t8764.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/t9387b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/try-catch-unify.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/virtpatmat_switch.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.12 => 2.13.13}/run/virtpatmat_typetag.check (100%) create mode 100644 scala-test-suite/src/test/resources/2.13.13/BlacklistedTests.txt diff --git a/Jenkinsfile b/Jenkinsfile index f4886117a6..9ca49c5e3c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -480,7 +480,7 @@ def allJavaVersions = otherJavaVersions.clone() allJavaVersions << mainJavaVersion def mainScalaVersion = "2.12.19" -def mainScalaVersions = ["2.12.19", "2.13.12"] +def mainScalaVersions = ["2.12.19", "2.13.13"] def otherScalaVersions = [ "2.12.2", "2.12.3", @@ -509,7 +509,8 @@ def otherScalaVersions = [ "2.13.8", "2.13.9", "2.13.10", - "2.13.11" + "2.13.11", + "2.13.12" ] def scala3Version = "3.2.1" diff --git a/library/src/main/scala-new-collections/scala/scalajs/js/WrappedDictionary.scala b/library/src/main/scala-new-collections/scala/scalajs/js/WrappedDictionary.scala index a7624b3e46..bd379384a9 100644 --- a/library/src/main/scala-new-collections/scala/scalajs/js/WrappedDictionary.scala +++ b/library/src/main/scala-new-collections/scala/scalajs/js/WrappedDictionary.scala @@ -99,6 +99,10 @@ final class WrappedDictionary[A](private val dict: js.Dictionary[A]) def iterator: scala.collection.Iterator[(String, A)] = new DictionaryIterator(dict) + /* Warning silenced in build for 2.13.13+: + * overriding method keys in trait MapOps is deprecated (since 2.13.13): + * This method should be an alias for keySet + */ @inline override def keys: scala.collection.Iterable[String] = js.Object.keys(dict.asInstanceOf[js.Object]) diff --git a/library/src/main/scala-new-collections/scala/scalajs/js/WrappedMap.scala b/library/src/main/scala-new-collections/scala/scalajs/js/WrappedMap.scala index 04d2f08517..83d4ace69d 100644 --- a/library/src/main/scala-new-collections/scala/scalajs/js/WrappedMap.scala +++ b/library/src/main/scala-new-collections/scala/scalajs/js/WrappedMap.scala @@ -95,6 +95,10 @@ final class WrappedMap[K, V](private val underlying: js.Map[K, V]) def iterator: scala.collection.Iterator[(K, V)] = underlying.jsIterator().toIterator.map(kv => (kv._1, kv._2)) + /* Warning silenced in build for 2.13.13+: + * overriding method keys in trait MapOps is deprecated (since 2.13.13): + * This method should be an alias for keySet + */ @inline override def keys: scala.collection.Iterable[K] = underlying.asInstanceOf[js.Map.Raw[K, V]].keys().toIterator.to(Iterable) diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/BlacklistedTests.txt b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/BlacklistedTests.txt similarity index 99% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/BlacklistedTests.txt rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/BlacklistedTests.txt index f91b84d6d8..72066a2706 100644 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/BlacklistedTests.txt +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/BlacklistedTests.txt @@ -832,6 +832,7 @@ run/t12390.scala run/repl-release.scala run/eta-dependent.scala run/t10655.scala +run/repl-suspended-warnings.scala # Using Scala Script (partest.ScriptTest) @@ -950,7 +951,6 @@ run/t10641.scala run/t10751.scala run/t10819.scala run/t11385.scala -run/t11731.scala run/t11746.scala run/t11815.scala run/splain.scala @@ -972,6 +972,7 @@ run/package-object-toolbox.scala run/package-object-with-inner-class-in-ancestor.scala run/package-object-with-inner-class-in-ancestor-simpler.scala run/package-object-with-inner-class-in-ancestor-simpler-still.scala +run/t7324.scala run/t10171 # partest.StubErrorMessageTest @@ -994,6 +995,7 @@ run/t12597.scala # partest.ASMConverters run/t9403 +run/nonfatal.scala # partest.BytecodeTest run/t7106 @@ -1119,6 +1121,7 @@ run/t12195 run/t12380 run/t12523 run/t12290 +run/t9714 # Using scala-script run/t7791-script-linenums.scala diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/choices.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/choices.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/choices.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/choices.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/partestInvalidFlag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/partestInvalidFlag.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/partestInvalidFlag.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/partestInvalidFlag.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t11952b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t11952b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t11952b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t11952b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t12494.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t12494.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t12494.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t12494.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t6446-additional.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-additional.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t6446-additional.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-additional.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t6446-list.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-list.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t6446-list.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-list.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t6446-missing.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-missing.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t6446-missing.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-missing.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t6446-show-phases.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-show-phases.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t6446-show-phases.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-show-phases.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t7494-no-options.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t7494-no-options.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/neg/t7494-no-options.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t7494-no-options.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Course-2002-01.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-01.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Course-2002-01.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-01.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Course-2002-02.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-02.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Course-2002-02.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-02.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Course-2002-04.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-04.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Course-2002-04.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-04.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Course-2002-08.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-08.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Course-2002-08.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-08.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Course-2002-09.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-09.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Course-2002-09.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-09.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Course-2002-10.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-10.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Course-2002-10.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-10.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Meter.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Meter.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/Meter.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Meter.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/MeterCaseClass.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/MeterCaseClass.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/MeterCaseClass.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/MeterCaseClass.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/anyval-box-types.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/anyval-box-types.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/anyval-box-types.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/anyval-box-types.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/bugs.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/bugs.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/bugs.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/bugs.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/caseClassHash.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/caseClassHash.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/caseClassHash.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/caseClassHash.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/classof.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/classof.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/classof.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/classof.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/deeps.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/deeps.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/deeps.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/deeps.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/dynamic-anyval.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/dynamic-anyval.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/dynamic-anyval.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/dynamic-anyval.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/exceptions-2.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/exceptions-2.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/exceptions-2.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/exceptions-2.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/exceptions-nest.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/exceptions-nest.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/exceptions-nest.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/exceptions-nest.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/exceptions-nest.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/exceptions-nest.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/exceptions-nest.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/exceptions-nest.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/impconvtimes.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/impconvtimes.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/impconvtimes.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/impconvtimes.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/imports.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/imports.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/imports.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/imports.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/inlineHandlers.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/inlineHandlers.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/inlineHandlers.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/inlineHandlers.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/interpolation.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/interpolation.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/interpolation.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/interpolation.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/interpolationMultiline1.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/interpolationMultiline1.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/interpolationMultiline1.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/interpolationMultiline1.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/macro-bundle-static.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-bundle-static.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/macro-bundle-static.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-bundle-static.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/macro-bundle-toplevel.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-bundle-toplevel.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/macro-bundle-toplevel.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-bundle-toplevel.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/macro-bundle-whitebox-decl.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-bundle-whitebox-decl.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/macro-bundle-whitebox-decl.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-bundle-whitebox-decl.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/macro-expand-varargs-implicit-over-varargs.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-expand-varargs-implicit-over-varargs.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/macro-expand-varargs-implicit-over-varargs.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-expand-varargs-implicit-over-varargs.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/misc.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/misc.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/misc.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/misc.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/optimizer-array-load.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/optimizer-array-load.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/optimizer-array-load.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/optimizer-array-load.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/pf-catch.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/pf-catch.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/pf-catch.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/pf-catch.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/promotion.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/promotion.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/promotion.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/promotion.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/runtime.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/runtime.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/runtime.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/runtime.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/sammy_vararg_cbn.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/sammy_vararg_cbn.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/sammy_vararg_cbn.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/sammy_vararg_cbn.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/spec-self.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/spec-self.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/spec-self.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/spec-self.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/string-switch.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/string-switch.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/string-switch.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/string-switch.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/structural.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/structural.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/structural.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/structural.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t0421-new.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t0421-new.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t0421-new.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t0421-new.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t0421-old.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t0421-old.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t0421-old.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t0421-old.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t12221.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t12221.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t12221.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t12221.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t1503.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t1503.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t1503.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t1503.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t3702.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t3702.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t3702.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t3702.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t4148.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t4148.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t4148.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t4148.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t4617.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t4617.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t4617.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t4617.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5356.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5356.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5356.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5356.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5552.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5552.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5552.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5552.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5568.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5568.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5568.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5568.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5629b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5629b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5629b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5629b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5680.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5680.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5680.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5680.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5866.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5866.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5866.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5866.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5966.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5966.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t5966.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5966.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t6265.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6265.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t6265.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6265.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t6318_primitives.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6318_primitives.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t6318_primitives.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6318_primitives.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t6662.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6662.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t6662.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6662.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t6827.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6827.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t6827.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6827.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t7657.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t7657.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t7657.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t7657.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t7763.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t7763.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t7763.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t7763.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t8570a.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8570a.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t8570a.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8570a.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t8601b.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8601b.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t8601b.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8601b.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t8601c.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8601c.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t8601c.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8601c.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t8601d.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8601d.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t8601d.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8601d.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t8764.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8764.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t8764.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8764.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t9387b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t9387b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/t9387b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t9387b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/try-catch-unify.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/try-catch-unify.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/try-catch-unify.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/try-catch-unify.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/virtpatmat_switch.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/virtpatmat_switch.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/virtpatmat_switch.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/virtpatmat_switch.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/virtpatmat_typetag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/virtpatmat_typetag.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.12/run/virtpatmat_typetag.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/virtpatmat_typetag.check diff --git a/project/Build.scala b/project/Build.scala index d392347803..fb1d1efc85 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -933,6 +933,7 @@ object Build { "2.13.10", "2.13.11", "2.13.12", + "2.13.13", ), default212ScalaVersion := cross212ScalaVersions.value.last, @@ -1779,6 +1780,21 @@ object Build { previousArtifactSetting, mimaBinaryIssueFilters ++= BinaryIncompatibilities.Library, + /* Silence a Scala 2.13.13+ warning that we cannot address without breaking our API. + * See `js.WrappedDictionary.keys` and `js.WrappedMap.keys`. + */ + scalacOptions ++= { + /* We only need the option in 2.13.13+, but listing all previous 2.13.x + * versions is cumberson. We only exclude 2.13.0 and 2.13.1 because + * they did not support -Wconf at all. + */ + val v = scalaVersion.value + if (v.startsWith("2.13.") && v != "2.13.0" && v != "2.13.1") + List("-Wconf:msg=overriding method keys in trait MapOps is deprecated:s") + else + Nil + }, + test in Test := { streams.value.log.warn("Skipping library/test. Run testSuite/test to test library.") }, @@ -2016,14 +2032,14 @@ object Build { case `default213Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 452000 to 453000, + fastLink = 451000 to 452000, fullLink = 94000 to 95000, fastLinkGz = 58000 to 59000, fullLinkGz = 25000 to 26000, )) } else { Some(ExpectedSizes( - fastLink = 309000 to 310000, + fastLink = 308000 to 309000, fullLink = 265000 to 266000, fastLinkGz = 49000 to 50000, fullLinkGz = 43000 to 44000, diff --git a/sbt-plugin/src/sbt-test/cross-version/2.13/build.sbt b/sbt-plugin/src/sbt-test/cross-version/2.13/build.sbt index 66419e36d6..9eaf7236fc 100644 --- a/sbt-plugin/src/sbt-test/cross-version/2.13/build.sbt +++ b/sbt-plugin/src/sbt-test/cross-version/2.13/build.sbt @@ -2,6 +2,6 @@ enablePlugins(ScalaJSPlugin) enablePlugins(ScalaJSJUnitPlugin) version := scalaJSVersion -scalaVersion := "2.13.12" +scalaVersion := "2.13.13" scalaJSUseMainModuleInitializer := true diff --git a/sbt-plugin/src/sbt-test/scala3/tasty-reader/build.sbt b/sbt-plugin/src/sbt-test/scala3/tasty-reader/build.sbt index 283968ec84..a0af60a58e 100644 --- a/sbt-plugin/src/sbt-test/scala3/tasty-reader/build.sbt +++ b/sbt-plugin/src/sbt-test/scala3/tasty-reader/build.sbt @@ -10,7 +10,7 @@ lazy val app = project.in(file("app")) .enablePlugins(ScalaJSPlugin) .dependsOn(testlib) .settings( - scalaVersion := "2.13.12", + scalaVersion := "2.13.13", scalacOptions += "-Ytasty-reader", scalaJSUseMainModuleInitializer := true ) diff --git a/scala-test-suite/src/test/resources/2.13.13/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.13/BlacklistedTests.txt new file mode 100644 index 0000000000..c813883a16 --- /dev/null +++ b/scala-test-suite/src/test/resources/2.13.13/BlacklistedTests.txt @@ -0,0 +1,247 @@ +## Do not compile +scala/ExtractorTest.scala +scala/OptionTest.scala +scala/SerializationStabilityTest.scala +scala/StringTest.scala +scala/collection/FactoriesTest.scala +scala/collection/LazyZipOpsTest.scala +scala/collection/SeqTest.scala +scala/collection/immutable/HashMapTest.scala +scala/collection/immutable/HashSetTest.scala +scala/collection/immutable/IndexedSeqTest.scala +scala/collection/immutable/IntMapTest.scala +scala/collection/immutable/LazyListTest.scala +scala/collection/immutable/ListMapTest.scala +scala/collection/immutable/LongMapTest.scala +scala/collection/immutable/MapHashcodeTest.scala +scala/collection/immutable/SeqTest.scala +scala/collection/immutable/SmallMapTest.scala +scala/collection/immutable/SortedMapTest.scala +scala/collection/immutable/SortedSetTest.scala +scala/collection/immutable/TreeMapTest.scala +scala/collection/immutable/TreeSetTest.scala +scala/lang/annotations/BytecodeTest.scala +scala/lang/annotations/RunTest.scala +scala/lang/traits/BytecodeTest.scala +scala/lang/traits/RunTest.scala +scala/lang/primitives/NaNTest.scala +scala/reflect/ClassOfTest.scala +scala/reflect/FieldAccessTest.scala +scala/reflect/QTest.scala +scala/reflect/macros/AttachmentsTest.scala +scala/reflect/io/ZipArchiveTest.scala +scala/reflect/internal/InferTest.scala +scala/reflect/internal/LongNamesTest.scala +scala/reflect/internal/MirrorsTest.scala +scala/reflect/internal/NamesTest.scala +scala/reflect/internal/PositionsTest.scala +scala/reflect/internal/PrintersTest.scala +scala/reflect/internal/ScopeTest.scala +scala/reflect/internal/SubstMapTest.scala +scala/reflect/internal/TreeGenTest.scala +scala/reflect/internal/TypesTest.scala +scala/reflect/internal/util/AbstractFileClassLoaderTest.scala +scala/reflect/internal/util/FileUtilsTest.scala +scala/reflect/internal/util/SourceFileTest.scala +scala/reflect/internal/util/StringOpsTest.scala +scala/reflect/internal/util/WeakHashSetTest.scala +scala/reflect/io/AbstractFileTest.scala +scala/reflect/runtime/ReflectionUtilsShowTest.scala +scala/reflect/runtime/ThreadSafetyTest.scala +scala/runtime/BooleanBoxingTest.scala +scala/runtime/ByteBoxingTest.scala +scala/runtime/CharBoxingTest.scala +scala/runtime/DoubleBoxingTest.scala +scala/runtime/IntBoxingTest.scala +scala/runtime/FloatBoxingTest.scala +scala/runtime/LongBoxingTest.scala +scala/runtime/ShortBoxingTest.scala +scala/tools/nsc/Build.scala +scala/tools/nsc/DeterminismTest.scala +scala/tools/nsc/DeterminismTester.scala +scala/tools/nsc/FileUtils.scala +scala/tools/nsc/GlobalCustomizeClassloaderTest.scala +scala/tools/nsc/MainRunnerTest.scala +scala/tools/nsc/PhaseAssemblyTest.scala +scala/tools/nsc/PickleWriteTest.scala +scala/tools/nsc/PipelineMainTest.scala +scala/tools/nsc/ScriptRunnerTest.scala +scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala +scala/tools/nsc/async/CustomFuture.scala +scala/tools/nsc/backend/jvm/BTypesTest.scala +scala/tools/nsc/backend/jvm/BytecodeTest.scala +scala/tools/nsc/backend/jvm/ClassfileParserTest.scala +scala/tools/nsc/backend/jvm/DefaultMethodTest.scala +scala/tools/nsc/backend/jvm/DirectCompileTest.scala +scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala +scala/tools/nsc/backend/jvm/IndyLambdaTest.scala +scala/tools/nsc/backend/jvm/IndySammyTest.scala +scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala +scala/tools/nsc/backend/jvm/LineNumberTest.scala +scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala +scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala +scala/tools/nsc/backend/jvm/PerRunInitTest.scala +scala/tools/nsc/backend/jvm/StringConcatTest.scala +scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/TypeFlowAnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxAndInlineTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala +scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala +scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala +scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala +scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala +scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala +scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala +scala/tools/nsc/classpath/AggregateClassPathTest.scala +scala/tools/nsc/classpath/JrtClassPathTest.scala +scala/tools/nsc/classpath/MultiReleaseJarTest.scala +scala/tools/nsc/classpath/PathResolverBaseTest.scala +scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala +scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala +scala/tools/nsc/doc/html/HtmlDocletTest.scala +scala/tools/nsc/doc/html/ModelFactoryTest.scala +scala/tools/nsc/doc/html/StringLiteralTest.scala +scala/tools/nsc/interpreter/CompletionTest.scala +scala/tools/nsc/interpreter/ScriptedTest.scala +scala/tools/nsc/interpreter/TabulatorTest.scala +scala/tools/nsc/parser/ParserTest.scala +scala/tools/nsc/reporters/ConsoleReporterTest.scala +scala/tools/nsc/reporters/PositionFilterTest.scala +scala/tools/nsc/reporters/WConfTest.scala +scala/tools/nsc/reporters/AbstractCodeActionTest.scala +scala/tools/nsc/reporters/CodeActionXsource3Test.scala +scala/tools/nsc/reporters/CodeActionTest.scala +scala/tools/nsc/settings/ScalaVersionTest.scala +scala/tools/nsc/settings/SettingsTest.scala +scala/tools/nsc/settings/TargetTest.scala +scala/tools/nsc/symtab/CannotHaveAttrsTest.scala +scala/tools/nsc/symtab/FlagsTest.scala +scala/tools/nsc/symtab/FreshNameExtractorTest.scala +scala/tools/nsc/symtab/StdNamesTest.scala +scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala +scala/tools/nsc/symtab/SymbolTableTest.scala +scala/tools/nsc/symtab/classfile/PicklerTest.scala +scala/tools/nsc/transform/ErasureTest.scala +scala/tools/nsc/transform/MixinTest.scala +scala/tools/nsc/transform/ReleaseFenceTest.scala +scala/tools/nsc/transform/SpecializationTest.scala +scala/tools/nsc/transform/ThicketTransformerTest.scala +scala/tools/nsc/transform/UncurryTest.scala +scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala +scala/tools/nsc/transform/patmat/SolvingTest.scala +scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala +scala/tools/nsc/typechecker/ConstantFolderTest.scala +scala/tools/nsc/typechecker/ImplicitsTest.scala +scala/tools/nsc/typechecker/InferencerTest.scala +scala/tools/nsc/typechecker/NamerTest.scala +scala/tools/nsc/typechecker/OverridingPairsTest.scala +scala/tools/nsc/typechecker/ParamAliasTest.scala +scala/tools/nsc/typechecker/TreeAttachmentTest.scala +scala/tools/nsc/typechecker/TypedTreeTest.scala +scala/tools/nsc/QuickfixTest.scala +scala/tools/nsc/util/StackTraceTest.scala +scala/tools/testkit/ReflectUtilTest.scala +scala/tools/xsbt/BridgeTesting.scala +scala/tools/xsbt/BasicBridgeTest.scala +scala/tools/xsbt/ClassNameTest.scala +scala/tools/xsbt/CodeActionTest.scala +scala/tools/xsbt/DependencyTest.scala +scala/tools/xsbt/ExtractAPITest.scala +scala/tools/xsbt/ExtractUsedNamesTest.scala +scala/tools/xsbt/InteractiveConsoleInterfaceTest.scala +scala/tools/xsbt/SameAPI.scala +scala/tools/xsbt/TestCallback.scala +scala/util/ChainingOpsTest.scala + +## Do not link +scala/CollectTest.scala +scala/MatchErrorSerializationTest.scala +scala/PartialFunctionSerializationTest.scala +scala/lang/stringinterpol/StringContextTest.scala +scala/collection/IterableTest.scala +scala/collection/IteratorTest.scala +scala/collection/NewBuilderTest.scala +scala/collection/SeqViewTest.scala +scala/collection/SetMapConsistencyTest.scala +scala/collection/SetMapRulesTest.scala +scala/collection/Sizes.scala +scala/collection/ViewTest.scala +scala/collection/concurrent/TrieMapTest.scala +scala/collection/convert/EqualsTest.scala +scala/collection/convert/JConcurrentMapWrapperTest.scala +scala/collection/convert/WrapperSerializationTest.scala +scala/collection/immutable/ChampMapSmokeTest.scala +scala/collection/immutable/ChampSetSmokeTest.scala +scala/collection/immutable/LazyListGCTest.scala +scala/collection/immutable/LazyListLazinessTest.scala +scala/collection/immutable/ListTest.scala +scala/collection/immutable/SerializationTest.scala +scala/collection/immutable/StreamTest.scala +scala/collection/immutable/StringLikeTest.scala +scala/collection/immutable/VectorTest.scala +scala/collection/mutable/AnyRefMapTest.scala +scala/collection/mutable/ArrayBufferTest.scala +scala/collection/mutable/ListBufferTest.scala +scala/collection/mutable/OpenHashMapTest.scala +scala/collection/mutable/PriorityQueueTest.scala +scala/collection/mutable/SerializationTest.scala +scala/concurrent/FutureTest.scala +scala/concurrent/duration/SerializationTest.scala +scala/concurrent/impl/DefaultPromiseTest.scala +scala/io/SourceTest.scala +scala/jdk/AccumulatorTest.scala +scala/jdk/DurationConvertersTest.scala +scala/jdk/FunctionConvertersTest.scala +scala/jdk/OptionConvertersTest.scala +scala/jdk/StepperConversionTest.scala +scala/jdk/StepperTest.scala +scala/jdk/StreamConvertersTest.scala +scala/jdk/StreamConvertersTypingTest.scala +scala/math/OrderingTest.scala +scala/runtime/ScalaRunTimeTest.scala +scala/sys/env.scala +scala/sys/process/ParserTest.scala +scala/sys/process/PipedProcessTest.scala +scala/sys/process/ProcessBuilderTest.scala +scala/sys/process/ProcessTest.scala +scala/tools/testkit/AssertUtilTest.scala +scala/util/PropertiesTest.scala +scala/util/SpecVersionTest.scala +scala/util/SystemPropertiesTest.scala + +## Tests fail + +# Reflection +scala/reflect/ClassTagTest.scala + +# Require strict-floats +scala/math/BigDecimalTest.scala + +# Tests passed but are too slow (timeouts) +scala/collection/immutable/ListSetTest.scala +scala/util/SortingTest.scala + +# Whitebox testing of ArrayBuilder.ofUnit +scala/collection/mutable/ArrayBuilderTest.scala + +# Relies on undefined behavior +scala/collection/MapTest.scala +scala/collection/StringOpsTest.scala +scala/collection/StringParsersTest.scala +scala/collection/convert/CollectionConvertersTest.scala +scala/collection/convert/MapWrapperTest.scala +scala/collection/immutable/NumericRangeTest.scala +scala/math/BigIntTest.scala From ba98bb274e910a3318fc0832fb720ab50c159e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 17 Mar 2024 17:11:15 +0100 Subject: [PATCH 65/65] Version 1.16.0. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index 91d113055f..e932635d21 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,8 +17,8 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.16.0-SNAPSHOT", - binaryEmitted = "1.16-SNAPSHOT" + current = "1.16.0", + binaryEmitted = "1.16" ) /** Helper class to allow for testing of logic. */