Skip to content

Commit 256a2f4

Browse files
committed
Enable the IR checker post optimizer with RT longs
To keep the IR checker happy, we insert casts at method boundaries that cast RuntimeLongs back to LongType. The first two commits in this PR are not strictly necessary. However, I felt they significantly help understanding of what the optimizer is doing.
1 parent 817018d commit 256a2f4

File tree

4 files changed

+45
-24
lines changed

4 files changed

+45
-24
lines changed

linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,6 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) {
3434
private val analyzer =
3535
new Analyzer(config, initial = false, checkIR = checkIR, failOnError = true, irLoader)
3636

37-
/* TODO: Remove this and replace with `checkIR` once the optimizer generates
38-
* well-typed IR with runtime longs.
39-
*/
40-
private val shouldRunIRChecker = {
41-
val optimizerUsesRuntimeLong =
42-
!config.coreSpec.esFeatures.allowBigIntsForLongs &&
43-
!config.coreSpec.targetIsWebAssembly
44-
checkIR && !optimizerUsesRuntimeLong
45-
}
46-
4737
def refine(classDefs: Seq[(ClassDef, Version)],
4838
moduleInitializers: List[ModuleInitializer],
4939
symbolRequirements: SymbolRequirement, logger: Logger)(
@@ -77,7 +67,7 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) {
7767
linkedTopLevelExports.flatten.toList, moduleInitializers, globalInfo)
7868
}
7969

80-
if (shouldRunIRChecker) {
70+
if (checkIR) {
8171
logger.time("Refiner: Check IR") {
8272
val errorCount = IRChecker.check(result, logger, postOptimizer = true)
8373
if (errorCount != 0) {

linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -774,10 +774,22 @@ private[optimizer] abstract class OptimizerCore(
774774
def addCaptureParam(newName: LocalName): LocalDef = {
775775
val newOriginalName = originalNameForFresh(paramName, originalName, newName)
776776

777+
val captureTpe = {
778+
/* Do not refine the capture type for longs:
779+
* The pretransform might be a stack allocated RuntimeLong.
780+
* We cannot (trivially) capture it in stack allocated form.
781+
* Therefore, we keep the primitive type and let finishTransformExpr
782+
* allocate a RuntimeLong.
783+
* TODO: Improve this and allocate two capture params for lo/hi?
784+
*/
785+
if (useRuntimeLong && paramDef.ptpe == LongType) RefinedType(LongType)
786+
else tcaptureValue.tpe
787+
}
788+
777789
val replacement = ReplaceWithVarRef(newName, newSimpleState(Unused))
778-
val localDef = LocalDef(tcaptureValue.tpe, mutable, replacement)
790+
val localDef = LocalDef(captureTpe, mutable, replacement)
779791
val localIdent = LocalIdent(newName)(ident.pos)
780-
val newParamDef = ParamDef(localIdent, newOriginalName, tcaptureValue.tpe.base, mutable)(paramDef.pos)
792+
val newParamDef = ParamDef(localIdent, newOriginalName, captureTpe.base, mutable)(paramDef.pos)
781793

782794
/* Note that the binding will never create a fresh name for a
783795
* ReplaceWithVarRef. So this will not put our name alignment at risk.
@@ -6446,8 +6458,8 @@ private[optimizer] object OptimizerCore {
64466458
private def createNewLong(lo: Tree, hi: Tree)(
64476459
implicit pos: Position): Tree = {
64486460

6449-
New(LongImpl.RuntimeLongClass, MethodIdent(LongImpl.initFromParts),
6450-
List(lo, hi))
6461+
makeCast(New(LongImpl.RuntimeLongClass, MethodIdent(LongImpl.initFromParts),
6462+
List(lo, hi)), LongType)
64516463
}
64526464

64536465
/** Tests whether `x + y` is valid without falling out of range. */

linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -420,18 +420,10 @@ object IRCheckerTest {
420420
moduleInitializers: List[ModuleInitializer],
421421
logger: Logger, postOptimizer: Boolean)(
422422
implicit ec: ExecutionContext): Future[Unit] = {
423-
val baseConfig = StandardConfig()
423+
val config = StandardConfig()
424424
.withCheckIR(true)
425425
.withOptimizer(false)
426426

427-
val config = {
428-
/* Disable RuntimeLongs to workaround the Refiner disabling IRChecks in this case.
429-
* TODO: Remove once we run IRChecks post optimizer all the time.
430-
*/
431-
if (postOptimizer) baseConfig.withESFeatures(_.withAllowBigIntsForLongs(true))
432-
else baseConfig
433-
}
434-
435427
val noSymbolRequirements = SymbolRequirement
436428
.factory("IRCheckerTest")
437429
.none()

linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,33 @@ class OptimizerTest {
487487
}
488488
}
489489

490+
@Test
491+
def testLongCaptures(): AsyncResult = await {
492+
val calc = m("calc", Nil, LongRef)
493+
494+
val classDefs = Seq(
495+
classDef(
496+
MainTestClassName,
497+
kind = ClassKind.Class,
498+
superClass = Some(ObjectClass),
499+
methods = List(
500+
// @noinline static def calc(): Long = 1L
501+
MethodDef(EMF.withNamespace(PublicStatic), calc, NON, Nil,
502+
LongType, Some(LongLiteral(1)))(EOH.withNoinline(true), UNV),
503+
mainMethodDef(Block(
504+
VarDef("x", NON, LongType, mutable = false,
505+
ApplyStatic(EAF, MainTestClassName, calc, Nil)(LongType)),
506+
consoleLog(Closure(true, List(paramDef("y", LongType)), Nil, None,
507+
VarRef("y")(LongType), List(VarRef("x")(LongType))))
508+
))
509+
)
510+
)
511+
)
512+
513+
// Check it doesn't fail IRChecking.
514+
linkToModuleSet(classDefs, MainTestModuleInitializers)
515+
}
516+
490517
private def commonClassDefsForFieldRemovalTests(classInline: Boolean,
491518
witnessMutable: Boolean): Seq[ClassDef] = {
492519
val methodName = m("method", Nil, I)

0 commit comments

Comments
 (0)