From f27a5222184d7e1322446f293928c424c3df247f Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 8 Jun 2025 11:04:43 +0200 Subject: [PATCH 1/4] Optimizer: Introduce PreTransCast Going foward, we want to cast pre transformed records (for longs). So we need PreTransCast to avoid eagerly transforming the record. As a nice side effect, we see better receiver variable allocation: ```diff --- baseline.js 2025-06-07 09:56:18.846667192 +0200 +++ pre-trans-cast.js 2025-06-08 14:20:11.704783221 +0200 @@ -17540,8 +17540,7 @@ var logException = ((!$n($thiz.Lorg_scalajs_junit_Reporter__f_settings).Lorg_scalajs_junit_RunSettings__f_notLogExceptionClass) && ($n($thiz.Lorg_scalajs_junit_Reporter__f_settings).Lorg_scalajs_junit_RunSettings__f_logAssert || (!(t instanceof $c_jl_AssertionError)))); if (logException) { var this$1 = $n(t); - var this$2 = $objectGetClass(this$1); - var fmtName = ($p_Lorg_scalajs_junit_Reporter__formatClass__T__T__T($thiz, this$2.data.name, "\u001b[31m") + ": "); + var fmtName = ($p_Lorg_scalajs_junit_Reporter__formatClass__T__T__T($thiz, $objectClassName(this$1), "\u001b[31m") + ": "); } else { var fmtName = ""; } ``` --- .../frontend/optimizer/OptimizerCore.scala | 71 ++++++++++--------- 1 file changed, 39 insertions(+), 32 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 bf1ff6e9a2..ee533ff74e 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 @@ -1346,7 +1346,7 @@ private[optimizer] abstract class OptimizerCore( PreTransTree(finishTransformBindings(bindingsAndStats, tree), tpe) } - case _:PreTransUnaryOp | _:PreTransBinaryOp => + case _:PreTransUnaryOp | _:PreTransBinaryOp | _:PreTransCast => PreTransTree(finishTransformExpr(preTrans), preTrans.tpe) case PreTransLocalDef(localDef) => @@ -1385,7 +1385,7 @@ private[optimizer] abstract class OptimizerCore( case PreTransBlock(_, result) => resolveRecordStructure(result) - case _:PreTransUnaryOp | _:PreTransBinaryOp => + case _:PreTransUnaryOp | _:PreTransBinaryOp | _:PreTransCast => None case PreTransLocalDef(localDef @ LocalDef(tpe, _, replacement)) => @@ -1441,6 +1441,8 @@ private[optimizer] abstract class OptimizerCore( UnaryOp(op, finishTransformExpr(lhs)) case PreTransBinaryOp(op, lhs, rhs) => BinaryOp(op, finishTransformExpr(lhs), finishTransformExpr(rhs)) + case PreTransCast(expr, refinedType) => + makeCast(finishTransformExpr(expr), refinedType.base) case PreTransLocalDef(localDef) => localDef.newReplacement @@ -1531,6 +1533,8 @@ private[optimizer] abstract class OptimizerCore( finishNoSideEffects } + case PreTransCast(expr, _) => + finishTransformStat(expr) case PreTransLocalDef(_) => Skip()(stat.pos) case PreTransRecordTree(tree, _, _) => @@ -3056,14 +3060,9 @@ private[optimizer] abstract class OptimizerCore( case ClassGetName => optTReceiver.get match { - case PreTransMaybeBlock(bindingsAndStats, - PreTransTree(MaybeCast(UnaryOp(UnaryOp.GetClass, expr)), _)) => - contTree(finishTransformBindings( - bindingsAndStats, Transient(ObjectClassName(expr)))) - // Same thing, but the argument stayed as a PreTransUnaryOp case PreTransMaybeBlock(bindingsAndStats, - PreTransUnaryOp(UnaryOp.GetClass, texpr)) => + MaybeCast(PreTransUnaryOp(UnaryOp.GetClass, texpr))) => contTree(finishTransformBindings( bindingsAndStats, Transient(ObjectClassName(finishTransformExpr(texpr))))) @@ -5288,38 +5287,36 @@ private[optimizer] abstract class OptimizerCore( private def foldCast(arg: PreTransform, tpe: Type)( implicit pos: Position): PreTransform = { - def default(arg: PreTransform, newTpe: RefinedType): PreTransform = - PreTransTree(makeCast(finishTransformExpr(arg), newTpe.base), newTpe) - - def castLocalDef(arg: PreTransform, newTpe: RefinedType): PreTransform = arg match { - case PreTransMaybeBlock(bindingsAndStats, PreTransLocalDef(localDef)) => - val refinedLocalDef = localDef.tryWithRefinedType(newTpe) - if (refinedLocalDef ne localDef) - PreTransBlock(bindingsAndStats, PreTransLocalDef(refinedLocalDef)) - else - default(arg, newTpe) + def isCastFreeAtRunTime = tpe != CharType - case _ => - default(arg, newTpe) - } - - if (isSubtype(arg.tpe.base, tpe)) { - arg - } else { + lazy val castTpe = { val tpe1 = if (arg.tpe.isNullable) tpe else tpe.toNonNullable + RefinedType(tpe1, isExact = false, arg.tpe.allocationSite) + } - val castTpe = RefinedType(tpe1, isExact = false, arg.tpe.allocationSite) + def default = PreTransCast(arg, castTpe) - val isCastFreeAtRunTime = tpe != CharType + arg match { + case PreTransCast(arg, _) => + // Replace existing cast. + foldCast(arg, tpe) - if (isCastFreeAtRunTime) { + case arg if isSubtype(arg.tpe.base, tpe) => + // Cast is redundant. + arg + + case PreTransMaybeBlock(bindingsAndStats, PreTransLocalDef(localDef)) if isCastFreeAtRunTime => // Try to push the cast down to usages of LocalDefs, in order to preserve aliases - castLocalDef(arg, castTpe) - } else { - default(arg, castTpe) - } + val refinedLocalDef = localDef.tryWithRefinedType(castTpe) + if (refinedLocalDef ne localDef) + PreTransBlock(bindingsAndStats, PreTransLocalDef(refinedLocalDef)) + else + default + + case _ => + default } } @@ -6342,6 +6339,8 @@ private[optimizer] object OptimizerCore { lhs.contains(localDef) case PreTransBinaryOp(_, lhs, rhs) => lhs.contains(localDef) || rhs.contains(localDef) + case PreTransCast(expr, _) => + expr.contains(localDef) case PreTransLocalDef(thisLocalDef) => thisLocalDef.contains(localDef) case _: PreTransGenTree => @@ -6482,6 +6481,9 @@ private[optimizer] object OptimizerCore { val tpe: RefinedType = RefinedType(BinaryOp.resultTypeOf(op)) } + private final case class PreTransCast(expr: PreTransform, tpe: RefinedType)( + implicit val pos: Position) extends PreTransResult + /** A virtual reference to a `LocalDef`. */ private final case class PreTransLocalDef(localDef: LocalDef)( implicit val pos: Position) extends PreTransResult { @@ -7068,6 +7070,11 @@ private[optimizer] object OptimizerCore { case Transient(Cast(inner, _)) => Some(inner) case _ => Some(tree) } + + def unapply(tree: PreTransform): Some[PreTransform] = tree match { + case PreTransCast(inner, _) => Some(inner) + case _ => Some(tree) + } } private val TraitInitSimpleMethodName = SimpleMethodName("$init$") From 80ced03e5a445550d47e9c6c3b7e301f4be56357 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 15 Jun 2025 10:53:54 +0200 Subject: [PATCH 2/4] Optimizer: Resolve casts / local defs later Since we have PreTransCast now, we do not need to push down casts to preserve aliases. No diff. Related: 6fd932263b5f1df0ba25c063ff545068a86cd7e9 --- .../frontend/optimizer/OptimizerCore.scala | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 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 ee533ff74e..8e558eaaf9 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 @@ -5287,8 +5287,6 @@ private[optimizer] abstract class OptimizerCore( private def foldCast(arg: PreTransform, tpe: Type)( implicit pos: Position): PreTransform = { - def isCastFreeAtRunTime = tpe != CharType - lazy val castTpe = { val tpe1 = if (arg.tpe.isNullable) tpe @@ -5296,8 +5294,6 @@ private[optimizer] abstract class OptimizerCore( RefinedType(tpe1, isExact = false, arg.tpe.allocationSite) } - def default = PreTransCast(arg, castTpe) - arg match { case PreTransCast(arg, _) => // Replace existing cast. @@ -5307,16 +5303,8 @@ private[optimizer] abstract class OptimizerCore( // Cast is redundant. arg - case PreTransMaybeBlock(bindingsAndStats, PreTransLocalDef(localDef)) if isCastFreeAtRunTime => - // Try to push the cast down to usages of LocalDefs, in order to preserve aliases - val refinedLocalDef = localDef.tryWithRefinedType(castTpe) - if (refinedLocalDef ne localDef) - PreTransBlock(bindingsAndStats, PreTransLocalDef(refinedLocalDef)) - else - default - case _ => - default + PreTransCast(arg, castTpe) } } @@ -5719,6 +5707,14 @@ private[optimizer] abstract class OptimizerCore( */ buildInner(localDef, cont) + case PreTransCast(PreTransLocalDef( + localDef @ LocalDef(_, mutable, replacement)), refinedType) + if !mutable && refinedType.base != CharType => + // Casts to Char are not free, so we do not want to duplicate them. + val newLocalDef = LocalDef(refinedType, mutable, + ReplaceWithOtherLocalDef(localDef)) + buildInner(newLocalDef, cont) + case PreTransTree(literal: Literal, _) => buildInner(LocalDef(value.tpe, false, ReplaceWithConstant(literal)), cont) @@ -6165,22 +6161,6 @@ private[optimizer] object OptimizerCore { false }) } - - def tryWithRefinedType(refinedType: RefinedType): LocalDef = { - /* Only adjust if the replacement if ReplaceWithVarRef, because other - * types have nothing to gain (e.g., ReplaceWithConstant) or we want to - * keep them unwrapped because they are examined in optimizations - * (notably all the types with virtualized objects). - */ - replacement match { - case _:ReplaceWithVarRef => - LocalDef(refinedType, mutable, ReplaceWithOtherLocalDef(this)) - case replacement: ReplaceWithOtherLocalDef => - LocalDef(refinedType, mutable, replacement) - case _ => - this - } - } } private sealed abstract class LocalDefReplacement From ca3ac051d0bb4c47398b19a8c4e80a26a75792e1 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 15 Jun 2025 11:22:31 +0200 Subject: [PATCH 3/4] Optimizer: Allow resolving records accross casts This will become relevant, as we introduce casts for RuntimeLong. No diff (TODO: re-check) --- .../frontend/optimizer/OptimizerCore.scala | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 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 8e558eaaf9..2d1089082e 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 @@ -1337,6 +1337,18 @@ private[optimizer] abstract class OptimizerCore( private def resolveLocalDef(preTrans: PreTransform): PreTransGenTree = { implicit val pos = preTrans.pos preTrans match { + case PreTransCast(inner, refinedType) => + resolveLocalDef(inner) match { + case tree: PreTransRecordTree => + /* The call site will have to inspect the structure of the record + * tree. Therefore, dropping the cast here is fine. + */ + tree + + case PreTransTree(innerTree, _) => + PreTransTree(makeCast(innerTree, refinedType.base), refinedType) + } + case PreTransBlock(bindingsAndStats, result) => resolveLocalDef(result) match { case PreTransRecordTree(tree, structure, cancelFun) => @@ -1346,7 +1358,7 @@ private[optimizer] abstract class OptimizerCore( PreTransTree(finishTransformBindings(bindingsAndStats, tree), tpe) } - case _:PreTransUnaryOp | _:PreTransBinaryOp | _:PreTransCast => + case _:PreTransUnaryOp | _:PreTransBinaryOp => PreTransTree(finishTransformExpr(preTrans), preTrans.tpe) case PreTransLocalDef(localDef) => @@ -1382,10 +1394,13 @@ private[optimizer] abstract class OptimizerCore( private def resolveRecordStructure( preTrans: PreTransform): Option[(InlineableClassStructure, CancelFun)] = { preTrans match { + case PreTransCast(preTrans, _) => + resolveRecordStructure(preTrans) + case PreTransBlock(_, result) => resolveRecordStructure(result) - case _:PreTransUnaryOp | _:PreTransBinaryOp | _:PreTransCast => + case _:PreTransUnaryOp | _:PreTransBinaryOp => None case PreTransLocalDef(localDef @ LocalDef(tpe, _, replacement)) => From bd4a860d223b10a7471741e7f80a77611b5d1bb1 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 24 Nov 2024 10:22:38 +0100 Subject: [PATCH 4/4] Optimizer: Use casts for RuntimeLong inlining This allows us to enable the IRChecker. --- .../org/scalajs/linker/frontend/Refiner.scala | 12 +-- .../frontend/optimizer/OptimizerCore.scala | 99 ++++++++++++------- .../org/scalajs/linker/IRCheckerTest.scala | 10 +- .../org/scalajs/linker/OptimizerTest.scala | 27 +++++ 4 files changed, 93 insertions(+), 55 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala index 4f778351ba..64f66f46c1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala @@ -38,16 +38,6 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) { new Analyzer(config, initial = false, checkIRFor, failOnError = true, irLoader) } - /* TODO: Remove this and replace with `checkIR` once the optimizer generates - * well-typed IR with runtime longs. - */ - private val shouldRunIRChecker = { - val optimizerUsesRuntimeLong = - !config.coreSpec.esFeatures.allowBigIntsForLongs && - !config.coreSpec.targetIsWebAssembly - checkIR && !optimizerUsesRuntimeLong - } - def refine(classDefs: Seq[(ClassDef, Version)], moduleInitializers: List[ModuleInitializer], symbolRequirements: SymbolRequirement, logger: Logger)( @@ -81,7 +71,7 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) { linkedTopLevelExports.flatten.toList, moduleInitializers, globalInfo) } - if (shouldRunIRChecker) { + if (checkIR) { logger.time("Refiner: Check IR") { val errorCount = IRChecker.check(linkTimeProperties, result, logger, CheckingPhase.Optimizer) 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 2d1089082e..8f2fa55c13 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 @@ -263,29 +263,8 @@ private[optimizer] abstract class OptimizerCore( private val isSubclassFun = isSubclass _ - private def isSubtype(lhs: Type, rhs: Type): Boolean = { - assert(lhs != VoidType) - assert(rhs != VoidType) - - Types.isSubtype(lhs, rhs)(isSubclassFun) || { - (lhs, rhs) match { - case (LongType, ClassType(LongImpl.RuntimeLongClass, _)) => - true - case (ClassType(LongImpl.RuntimeLongClass, false), LongType) => - true - case (ClassType(BoxedLongClass, lhsNullable), - ClassType(LongImpl.RuntimeLongClass, rhsNullable)) => - rhsNullable || !lhsNullable - - case (ClassType(LongImpl.RuntimeLongClass, lhsNullable), - ClassType(BoxedLongClass, rhsNullable)) => - rhsNullable || !lhsNullable - - case _ => - false - } - } - } + private def isSubtype(lhs: Type, rhs: Type): Boolean = + Types.isSubtype(lhs, rhs)(isSubclassFun) /** Transforms a statement. * @@ -577,8 +556,16 @@ private[optimizer] abstract class OptimizerCore( case IsInstanceOf(expr, testType) => trampoline { pretransformExpr(expr) { texpr => + val texprType = texpr.tpe.base.toNonNullable + + // Note: Disregards nullability because we can optimize null-check only. + val staticSubtype = { + isSubtype(texprType, testType) || + (useRuntimeLong && isRTLong(testType) && isRTLong(texprType)) + } + val result = { - if (isSubtype(texpr.tpe.base.toNonNullable, testType)) { + if (staticSubtype) { if (texpr.tpe.isNullable) BinaryOp(BinaryOp.!==, finishTransformExpr(texpr), Null()) else @@ -762,10 +749,23 @@ private[optimizer] abstract class OptimizerCore( def addCaptureParam(newName: LocalName): LocalDef = { val newOriginalName = originalNameForFresh(paramName, originalName, newName) + val captureTpe = { + /* Do not refine the capture type for longs: + * The pretransform might be a stack allocated RuntimeLong. + * We cannot (trivially) capture it in stack allocated form. + * Therefore, we keep the primitive type and let finishTransformExpr + * allocate a RuntimeLong. + * + * TODO: Improve this and allocate two capture params for lo/hi? + */ + if (useRuntimeLong && paramDef.ptpe == LongType) RefinedType(LongType) + else tcaptureValue.tpe + } + val replacement = ReplaceWithVarRef(newName, newSimpleState(Unused)) - val localDef = LocalDef(tcaptureValue.tpe, mutable, replacement) + val localDef = LocalDef(captureTpe, mutable, replacement) val localIdent = LocalIdent(newName)(ident.pos) - val newParamDef = ParamDef(localIdent, newOriginalName, tcaptureValue.tpe.base, mutable)(paramDef.pos) + val newParamDef = ParamDef(localIdent, newOriginalName, captureTpe.base, mutable)(paramDef.pos) /* Note that the binding will never create a fresh name for a * ReplaceWithVarRef. So this will not put our name alignment at risk. @@ -1297,12 +1297,22 @@ private[optimizer] abstract class OptimizerCore( } if (lhsStructure.className == LongImpl.RuntimeLongClass && trhs.tpe.base == LongType) { - /* The lhs is a stack-allocated RuntimeLong, but the rhs is a - * primitive Long. We expand the primitive Long into a new - * stack-allocated RuntimeLong so that we do not need to cancel. - */ - expandLongValue(trhs) { expandedRhs => - buildInner(expandedRhs) + // The lhs is a stack-allocated RuntimeLong, the rhs is *typed* as primitive long. + + trhs match { + case PreTransCast(trhs: PreTransRecordTree, _) => + /* The rhs is also a stack allocated Long but was cast back to + * a primitive Long (due to method inlining). Remove the cast. + */ + buildInner(trhs) + + case _ => + /* The rhs is a primitive Long. We expand the primitive Long into + * a new stack-allocated RuntimeLong so that we do not need to cancel. + */ + expandLongValue(trhs) { expandedRhs => + buildInner(expandedRhs) + } } } else { buildInner(trhs) @@ -5291,7 +5301,16 @@ private[optimizer] abstract class OptimizerCore( def mayRequireUnboxing: Boolean = arg.tpe.isNullable && tpe.isInstanceOf[PrimType] - if (semantics.asInstanceOfs == CheckedBehavior.Unchecked && !mayRequireUnboxing) + /* In methods on RuntimeLong, we often asInstanceOf Long to RuntimeLong and + * vice versa. We know that these are the same at runtime, so we lower to casts. + */ + val castForRTLong: Boolean = useRuntimeLong && { + val vtpe = arg.tpe.base + (!vtpe.isNullable || tpe.isNullable) && + isRTLong(arg.tpe.base) && isRTLong(tpe) + } + + if (semantics.asInstanceOfs == CheckedBehavior.Unchecked && !mayRequireUnboxing || castForRTLong) foldCast(arg, tpe) else if (isSubtype(arg.tpe.base, tpe)) arg @@ -5825,6 +5844,16 @@ private[optimizer] abstract class OptimizerCore( else upperBound } + /** Whether the given type is a RuntimeLong long at runtime. + * + * Assumes useRuntimeLong. + */ + private def isRTLong(tpe: Type) = tpe match { + case LongType => true + case ClassType(LongImpl.RuntimeLongClass | BoxedLongClass, _) => true + case _ => false + } + /** Trampolines a pretransform */ private def trampoline(tailrec: => TailRec[Tree]): Tree = { // scalastyle:off return @@ -6687,8 +6716,8 @@ private[optimizer] object OptimizerCore { private def createNewLong(lo: Tree, hi: Tree)( implicit pos: Position): Tree = { - New(LongImpl.RuntimeLongClass, MethodIdent(LongImpl.initFromParts), - List(lo, hi)) + makeCast(New(LongImpl.RuntimeLongClass, MethodIdent(LongImpl.initFromParts), + List(lo, hi)), LongType) } /** Tests whether `x + y` is valid without falling out of range. */ diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index 1c6ae731b1..afde621644 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -490,18 +490,10 @@ object IRCheckerTest { moduleInitializers: List[ModuleInitializer], logger: Logger, postOptimizer: Boolean)( implicit ec: ExecutionContext): Future[Unit] = { - val baseConfig = StandardConfig() + val config = StandardConfig() .withCheckIR(true) .withOptimizer(false) - val config = { - /* Disable RuntimeLongs to workaround the Refiner disabling IRChecks in this case. - * TODO: Remove once we run IRChecks post optimizer all the time. - */ - if (postOptimizer) baseConfig.withESFeatures(_.withAllowBigIntsForLongs(true)) - else baseConfig - } - val noSymbolRequirements = SymbolRequirement .factory("IRCheckerTest") .none() 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 61fb3992c2..7c073fc9de 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala @@ -489,6 +489,33 @@ class OptimizerTest { } } + @Test + def testLongCaptures(): AsyncResult = await { + val calc = m("calc", Nil, LongRef) + + val classDefs = Seq( + classDef( + MainTestClassName, + kind = ClassKind.Class, + superClass = Some(ObjectClass), + methods = List( + // @noinline static def calc(): Long = 1L + MethodDef(EMF.withNamespace(PublicStatic), calc, NON, Nil, + LongType, Some(LongLiteral(1)))(EOH.withNoinline(true), UNV), + mainMethodDef(Block( + VarDef("x", NON, LongType, mutable = false, + ApplyStatic(EAF, MainTestClassName, calc, Nil)(LongType)), + consoleLog(Closure(ClosureFlags.arrow, List(paramDef("y", LongType)), + Nil, None, AnyType, VarRef("y")(LongType), List(VarRef("x")(LongType)))) + )) + ) + ) + ) + + // Check it doesn't fail IRChecking. + linkToModuleSet(classDefs, MainTestModuleInitializers) + } + private def commonClassDefsForFieldRemovalTests(classInline: Boolean, witnessMutable: Boolean): Seq[ClassDef] = { val methodName = m("method", Nil, I)