Skip to content

Commit 0aa68ed

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 4e5155d commit 0aa68ed

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
@@ -38,16 +38,6 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) {
3838
new Analyzer(config, initial = false, checkIRFor, failOnError = true, irLoader)
3939
}
4040

41-
/* TODO: Remove this and replace with `checkIR` once the optimizer generates
42-
* well-typed IR with runtime longs.
43-
*/
44-
private val shouldRunIRChecker = {
45-
val optimizerUsesRuntimeLong =
46-
!config.coreSpec.esFeatures.allowBigIntsForLongs &&
47-
!config.coreSpec.targetIsWebAssembly
48-
checkIR && !optimizerUsesRuntimeLong
49-
}
50-
5141
def refine(classDefs: Seq[(ClassDef, Version)],
5242
moduleInitializers: List[ModuleInitializer],
5343
symbolRequirements: SymbolRequirement, logger: Logger)(
@@ -81,7 +71,7 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) {
8171
linkedTopLevelExports.flatten.toList, moduleInitializers, globalInfo)
8272
}
8373

84-
if (shouldRunIRChecker) {
74+
if (checkIR) {
8575
logger.time("Refiner: Check IR") {
8676
val errorCount = IRChecker.check(linkTimeProperties, result, logger,
8777
CheckingPhase.Optimizer)

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
@@ -755,10 +755,22 @@ private[optimizer] abstract class OptimizerCore(
755755
def addCaptureParam(newName: LocalName): LocalDef = {
756756
val newOriginalName = originalNameForFresh(paramName, originalName, newName)
757757

758+
val captureTpe = {
759+
/* Do not refine the capture type for longs:
760+
* The pretransform might be a stack allocated RuntimeLong.
761+
* We cannot (trivially) capture it in stack allocated form.
762+
* Therefore, we keep the primitive type and let finishTransformExpr
763+
* allocate a RuntimeLong.
764+
* TODO: Improve this and allocate two capture params for lo/hi?
765+
*/
766+
if (useRuntimeLong && paramDef.ptpe == LongType) RefinedType(LongType)
767+
else tcaptureValue.tpe
768+
}
769+
758770
val replacement = ReplaceWithVarRef(newName, newSimpleState(Unused))
759-
val localDef = LocalDef(tcaptureValue.tpe, mutable, replacement)
771+
val localDef = LocalDef(captureTpe, mutable, replacement)
760772
val localIdent = LocalIdent(newName)(ident.pos)
761-
val newParamDef = ParamDef(localIdent, newOriginalName, tcaptureValue.tpe.base, mutable)(paramDef.pos)
773+
val newParamDef = ParamDef(localIdent, newOriginalName, captureTpe.base, mutable)(paramDef.pos)
762774

763775
/* Note that the binding will never create a fresh name for a
764776
* ReplaceWithVarRef. So this will not put our name alignment at risk.
@@ -6485,8 +6497,8 @@ private[optimizer] object OptimizerCore {
64856497
private def createNewLong(lo: Tree, hi: Tree)(
64866498
implicit pos: Position): Tree = {
64876499

6488-
New(LongImpl.RuntimeLongClass, MethodIdent(LongImpl.initFromParts),
6489-
List(lo, hi))
6500+
makeCast(New(LongImpl.RuntimeLongClass, MethodIdent(LongImpl.initFromParts),
6501+
List(lo, hi)), LongType)
64906502
}
64916503

64926504
/** 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
@@ -490,18 +490,10 @@ object IRCheckerTest {
490490
moduleInitializers: List[ModuleInitializer],
491491
logger: Logger, postOptimizer: Boolean)(
492492
implicit ec: ExecutionContext): Future[Unit] = {
493-
val baseConfig = StandardConfig()
493+
val config = StandardConfig()
494494
.withCheckIR(true)
495495
.withOptimizer(false)
496496

497-
val config = {
498-
/* Disable RuntimeLongs to workaround the Refiner disabling IRChecks in this case.
499-
* TODO: Remove once we run IRChecks post optimizer all the time.
500-
*/
501-
if (postOptimizer) baseConfig.withESFeatures(_.withAllowBigIntsForLongs(true))
502-
else baseConfig
503-
}
504-
505497
val noSymbolRequirements = SymbolRequirement
506498
.factory("IRCheckerTest")
507499
.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
@@ -489,6 +489,33 @@ class OptimizerTest {
489489
}
490490
}
491491

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

0 commit comments

Comments
 (0)