diff --git a/hkmc2/shared/src/main/scala/hkmc2/Config.scala b/hkmc2/shared/src/main/scala/hkmc2/Config.scala index 98bbe2f4b0..35f73c1294 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Config.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Config.scala @@ -27,17 +27,13 @@ case class Config( stageCode: Bool, target: CompilationTarget, rewriteWhileLoops: Bool, - tailRecOpt: Bool, - deforest: Opt[Deforest], etaExpansion: Opt[EtaExpansion], - inlining: Opt[Inliner], - deadBranchRemoval: Bool, qqEnabled: Bool, funcToCls: Bool, commentGeneratedCode: Bool, noFreeze: Bool, noModuleCheck: Bool, - deadParamElim: Opt[DeadParamElim], + optimizer: Optimizer, ): def stackSafety: Opt[StackSafety] = effectHandlers.flatMap(_.stackSafety) @@ -55,6 +51,18 @@ case class Config( def shouldRewriteWhile: Bool = rewriteWhileLoops + def tailRecOpt: Bool = optimizer.tailRecOpt + + def deforest: Opt[Deforest] = optimizer.deforest + + def inlining: Opt[Inliner] = optimizer.inlining + + def deadBranchRemoval: Bool = optimizer.deadBranchRemoval + + def deadParamElim: Opt[DeadParamElim] = optimizer.deadParamElim + + def mapOptimizer(f: Optimizer => Optimizer): Config = copy(optimizer = f(optimizer)) + end Config @@ -71,17 +79,13 @@ object Config: target = CompilationTarget.JS, rewriteWhileLoops = false, stageCode = false, - tailRecOpt = true, - deforest = N, etaExpansion = S(EtaExpansion.default), - inlining = S(Inliner(default.inlineThreshold)), - deadBranchRemoval = default.deadBranchRemoval, qqEnabled = false, funcToCls = false, commentGeneratedCode = false, noFreeze = false, noModuleCheck = false, - deadParamElim = S(DeadParamElim.default) + optimizer = Optimizer.FastOpt, ) object default: val patMatConsequentSharingThreshold = S(15) @@ -229,6 +233,37 @@ object Config: .foldLeft(identity[Config]): (acc, modify) => cfg => modify(acc(cfg)) configModify(config) + + case class Optimizer( + deforest: Opt[Deforest], + tailRecOpt: Bool, + inlining: Opt[Inliner], + deadBranchRemoval: Bool, + deadCodeElim: Bool, + dataFlowAnalysis: Bool, + deadParamElim: Opt[DeadParamElim], + ) + + object Optimizer: + val NoOpt = Optimizer( + N, + false, + N, + false, + false, + false, + N + ) + + val FastOpt = Optimizer( + deforest = N, + tailRecOpt = true, + inlining = S(Inliner(default.inlineThreshold)), + deadBranchRemoval = default.deadBranchRemoval, + deadCodeElim = true, + dataFlowAnalysis = true, + deadParamElim = S(DeadParamElim.default), + ) end Config @@ -575,8 +610,10 @@ object ConfigParser: /** Parse a single field override like `tailRecOpt: false`. */ private def parseField(name: Str, value: Tree)(using Raise): Config => Config = name match case "language" => parseLanguageOverride(value) + case "noOpt" => + parsedField(value)(parseBool)(v => _.mapOptimizer(_ => Optimizer.NoOpt)) case "tailRecOpt" => - parsedField(value)(parseBool)(v => _.copy(tailRecOpt = v)) + parsedField(value)(parseBool)(v => _.mapOptimizer(_.copy(tailRecOpt = v))) case "noFreeze" => parsedField(value)(parseBool)(v => _.copy(noFreeze = v)) case "noModuleCheck" => @@ -600,7 +637,7 @@ object ConfigParser: case "deforest" => optionalFieldWithCurrent(value)(_.deforest)( (tree, current) => parseDeforest(tree, current) - )(v => _.copy(deforest = v)) + )(v => _.mapOptimizer(_.copy(deforest = v))) case "etaExpansion" => optionalFieldWithCurrent(value)(_.etaExpansion)( (tree, current) => parseEtaExpansion(tree, current) @@ -608,15 +645,15 @@ object ConfigParser: case "deadParamElim" => optionalFieldWithCurrent(value)(_.deadParamElim)( (tree, current) => parseDeadParamElim(tree, current) - )(v => _.copy(deadParamElim = v)) + )(v => _.mapOptimizer(_.copy(deadParamElim = v))) case "sanityChecks" => optionalField(value)(_ => S(Config.SanityChecks(light = true, checkUnreachable = true)))(v => _.copy(sanityChecks = v)) case "patMatConsequentSharingThreshold" => parsedField(value)(parseInt)(v => _.copy(patMatConsequentSharingThreshold = S(v))) case "inlining" => - optionalField(value)(parseInt)(v => _.copy(inlining = v.map(Inliner(_)))) + optionalField(value)(parseInt)(v => _.mapOptimizer(_.copy(inlining = v.map(Inliner(_))))) case "deadBranchRemoval" => - parsedField(value)(parseBool)(v => _.copy(deadBranchRemoval = v)) + parsedField(value)(parseBool)(v => _.mapOptimizer(_.copy(deadBranchRemoval = v))) case _ => raise(ErrorReport( msg"Unknown config field '${name}'" -> value.toLoc :: Nil, diff --git a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala index 22da5a8376..04848d6c71 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala @@ -6,6 +6,7 @@ import hkmc2.utils.*, shorthands.* import hkmc2.io import utils.* +import hkmc2.codegen.CompilationPipeline import hkmc2.semantics.* import hkmc2.syntax.Keyword.`override` import semantics.Elaborator.{Ctx, State} @@ -117,16 +118,13 @@ class MLsCompiler new codegen.Lowering() val jsb = ltl.givenIn: codegen.js.JSBuilder() - val lowered = low.program(blk, symbolsToPreserve = Set.empty) - var optimized = lowered + val lowered = ltl.givenIn: + low.program(blk, symbolsToPreserve = Set.empty) val nme = file.baseName val exportedSymbol = parsed.definedSymbols.find(_._1 === nme).map(_._2) - optimized = - val printer = (p: codegen.Program) => p.showAsTree // TODO: proper printing like in diff-tests - optimized = codegen.WorkerWrapper(exportedSymbol.toSet, dtl, printer)(optimized) - codegen.BlockSimplifier(exportedSymbol.toSet, dtl, printer)(optimized) - ltl.givenIn: - optimized = codegen.DeadParamElim(optimized) + val optimized = ltl.givenIn: + val printer = (p: codegen.Program) => p.showAsTree + CompilationPipeline().run(lowered, printer, exportedSymbol.toSet, dtl) val baseScp: utils.Scope = utils.Scope.empty(utils.Scope.Cfg.default) // * This line serves for `import.meta.url`, which retrieves directory and file names of mjs files. diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala index 23f20269d4..92ca97effd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala @@ -47,25 +47,27 @@ class BlockSimplifier log(s"⬤ Simplif. iter. $iteration") - // * Running DCE once sometimes produces more DCE opportunities; - // * it is important to apply all of them so that later passes, such as COC, - // * are not impeded by things like unused labels from inlining. - var dceIteration = 0 - while - val dce = new DeadCodeElim() - res = dce.apply(res) - changed ||= dce.changed - if dce.changed then - log("▶ DCE:\n" + printRes) - dceIteration += 1 - dceIteration < MaxDCEIterationsPerIter - else false - do () + if summon[Config].optimizer.deadCodeElim then + // * Running DCE once sometimes produces more DCE opportunities; + // * it is important to apply all of them so that later passes, such as COC, + // * are not impeded by things like unused labels from inlining. + var dceIteration = 0 + while + val dce = new DeadCodeElim() + res = dce.apply(res) + changed ||= dce.changed + if dce.changed then + log("▶ DCE:\n" + printRes) + dceIteration += 1 + dceIteration < MaxDCEIterationsPerIter + else false + do () - val vp = new DataFlowAnalysis(LocalVars.analyze(res.main)) - res = vp.apply(res) - changed ||= vp.changed - if vp.changed then log("▶ VP:\n" + printRes) + if summon[Config].optimizer.dataFlowAnalysis then + val vp = new DataFlowAnalysis(LocalVars.analyze(res.main)) + res = vp.apply(res) + changed ||= vp.changed + if vp.changed then log("▶ VP:\n" + printRes) summon[Config].inlining.foreach: cfg => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BufferableTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BufferableTransform.scala index 8f73827353..9462078022 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BufferableTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BufferableTransform.scala @@ -15,7 +15,7 @@ import hkmc2.Message.MessageContext import hkmc2.syntax.Tree.DummyTypeDef class BufferableTransform()(using Ctx, State, Raise): - def transform(blk: Block): Block = + def transform(prog: Program): Program = val transformer = new BlockTransformer(SymbolSubst.Id): override def applyDefn(defn: Defn)(k: Defn => Block): Block = defn match case cls: ClsLikeDefn if cls.k is syntax.Cls => @@ -113,4 +113,4 @@ class BufferableTransform()(using Ctx, State, Raise): cls.bufferable, )(cls.configOverride, cls.annotations) case _ => super.applyDefn(defn)(k) - transformer.applyBlock(blk) + transformer.applyProgram(prog) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassParamFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassParamFlattener.scala index 6851ac4a7f..caa313e9aa 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassParamFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassParamFlattener.scala @@ -34,6 +34,7 @@ class ClassParamFlattener(using State) extends BlockTransformer(SymbolSubst.Id): /** Normalize class params so that `paramsOpt = N` and `auxParams` has exactly one element. */ private def flattenClsParams(cls: ClsLikeDefn): ClsLikeDefn = + if cls.paramsOpt.isEmpty && cls.auxParams.sizeIs == 1 then return cls val paramss = cls.paramsOpt.toList ::: cls.auxParams val flatAux = paramss match case Nil => PlainParamList(Nil) @@ -105,6 +106,3 @@ end ClassParamFlattener object ClassParamFlattener: def apply(program: Program)(using State): Program = new ClassParamFlattener().applyProgram(program) - - def apply(block: Block)(using State): Block = - new ClassParamFlattener().applyBlock(block) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala new file mode 100644 index 0000000000..8694ed0906 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala @@ -0,0 +1,64 @@ +package hkmc2 +package codegen + +import hkmc2.utils.*, shorthands.* +import utils.* + +import hkmc2.Config +import hkmc2.semantics.Elaborator.{Ctx, State} +import hkmc2.semantics.SymbolPrinter +import hkmc2.utils.TL + +class CompilationPipeline(using Config, Raise, State, Ctx, SymbolPrinter): + + def preOptimizeHook(prog: Program) = () + + def passHook(passName: Str, before: Program, after: Program) = () + + private inline def blockPass(inline pass: Block => Block)(prog: Program): Program = + val blk = pass(prog.main) + if blk is prog.main then prog else Program(prog.imports, blk) + + def run(prog: Program, printer: Program => Str, symbolsToPreserve: Set[BoundSymbol], otl: TL)(using TL): Program = + var result = prog + inline def runPass(passName: Str)(inline transform: Program => Program) = + val before = result + result = transform(before) + passHook(passName, before, result) + + runPass("LambdaRewriter")(LambdaRewriter.desugar) + runPass("Deforest"): prog => + val outterTl = tl + config.deforest match + case None => prog + case Some(dCfg) => + flowAnalysis.FlowAnalysis.mkTraceLogger(dCfg.config, "deforest > ", outterTl).givenIn: + deforest.Deforest(prog) + runPass("EtaExpansion")(EtaExpansion.apply) + runPass("Lifter"): prog => + if config.liftDefns.isDefined then + blockPass(Lifter(_).transform)(prog) + else prog + runPass("HandlerLowering"): prog => + config.effectHandlers.fold(prog): opt => + HandlerLowering(new HandlerPaths, opt).translateProgram(prog) + runPass("Flattening")(blockPass(_.flattened)) + runPass("BufferableTransform")(BufferableTransform().transform) + runPass("MergeMatchArmTransformer")(MergeMatchArmTransformer.applyProgram) + runPass("FirstClassFunctionTransformer"): prog => + if config.funcToCls then + blockPass(FirstClassFunctionTransformer().transform(_))(prog) + else prog + runPass("Lifter"): prog => + if config.funcToCls then + blockPass(Lifter(_).transform)(prog) + else prog + runPass("ClassParamFlattener")(ClassParamFlattener.apply) + runPass("ReflectionInstrumenter")(ReflectionInstrumenter(using summon).apply) + runPass("TailRecOpt")(TailRecOpt().transform) + preOptimizeHook(result) + runPass("WorkerWrapper")(WorkerWrapper(symbolsToPreserve, otl, printer)) + runPass("BlockSimplifier")(BlockSimplifier(symbolsToPreserve, otl, printer).apply) + runPass("DeadParamElim")(otl.givenIn(DeadParamElim.apply)) + + result diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala index 183d9be2d5..95a7246285 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala @@ -89,7 +89,10 @@ class FirstClassFunctionTransformer override def applyResult(r: Result)(k: Result => Block): Block = r match case c @ Call(fun, argss) => applyListOf(argss, (args, k2) => applyArgs(args)(k2)): argss2 => - def call(f: Path) = Call(f, argss2.ne_!)(c.metadata) + def call(f: Path) = + if (f is fun) && (argss is argss2) + then c + else Call(f, argss2.ne_!)(c.metadata) fun match case ref @ Value.SimpleRef(sym) => sym match case _: VarSymbol | _: TempSymbol => k(call(ref.selSN("call"))) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 95f44cf6ec..0536f02b4e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -777,12 +777,17 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, topLevelPostTransform.applyBlock(b) - def translateTopLevel(b: Block): Block = + def translateProgram(prog: Program): Program = val ctx = HandlerCtx.TopLevel - val transformed = translateBlock(b, ctx, Set.empty) - blockBuilder - .staticif( - !opt.doNotInstrumentTopLevelModCtor, - _.assign(NoSymbol, Call(paths.resetEffects, Nil ne_:: Nil)(CallMetadata.defaultMlsFun)) + val transformed = blockBuilder + .staticif( + !opt.doNotInstrumentTopLevelModCtor, + _.assign(NoSymbol, Call(paths.resetEffects, Nil ne_:: Nil)(CallMetadata.defaultMlsFun)) + ) + .rest(translateBlock(prog.main, ctx, Set.empty)) + if transformed is prog.main then prog + else + Program( + prog.imports, + transformed ) - .rest(transformed) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/LambdaRewriter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/LambdaRewriter.scala index fd86eb9105..492bd0559f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/LambdaRewriter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/LambdaRewriter.scala @@ -10,7 +10,7 @@ import hkmc2.syntax.Tree object LambdaRewriter: - def desugar(b: Block)(using State) = + def desugar(b: Program)(using State) = val transformer = new BlockTransformer(SymbolSubst.Id): @@ -38,6 +38,6 @@ object LambdaRewriter: Scoped(Set.single(newSym), blk) case _ => super.applyBlock(b) - transformer.applyBlock(b) + transformer.applyProgram(b) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index 06de6b4ac0..1a582cf06a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -906,9 +906,14 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): val liftedChildSyms = node.allChildNodes.collect: case s @ ScopeNode(obj = l: ScopedObject.Liftable[?]) if s.isLifted => l.defn.sym - val (syms, rewritten) = (obj.block.syms.toSet -- liftedChildSyms, rewriter.rewrite(obj.block.body)) + val syms = if obj.block.syms.forall(!liftedChildSyms.contains(_)) + then obj.block.syms else obj.block.syms.toSet -- liftedChildSyms + val rewritten = rewriter.rewrite(obj.block.body) val withCapture = addExtraSyms(rewritten) - LifterResult(Scoped(syms, withCapture), rewriter.extraDefns.toList) + if (syms is obj.block.syms) && (withCapture is obj.block.body) then + LifterResult(obj.block, Nil) + else + LifterResult(Scoped(syms, withCapture), rewriter.extraDefns.toList) class RewrittenLoop(override val obj: ScopedObject.Loop)(using ctx: LifterCtxNew) extends RewrittenScope[Block](obj) with GenericRewrittenScope[Block]: override def rewriteImpl: LifterResult[Block] = @@ -958,15 +963,21 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): val rewrittenCtor = rewriterCtor.rewrite(obj.cls.ctor) val rewrittenPrector = rewriterPreCtor.rewrite(obj.cls.preCtor) val ctorWithCap = initCaptureField(rewrittenCtor) + val rewrittenPrivateFields = appendCaptureField(liftedObjsOrdered.map(liftedObjsSyms) ::: obj.cls.privateFields) val LifterResult(newMtds, extras) = rewriteMethods(node, obj.cls.methods) - val newCls = obj.cls.copy( - ctor = ctorWithCap, - preCtor = rewrittenPrector, - privateFields = appendCaptureField(liftedObjsOrdered.map(liftedObjsSyms) ::: obj.cls.privateFields), - methods = newMtds, - )(obj.cls.configOverride, obj.cls.annotations) - LifterResult(newCls, rewriterCtor.extraDefns.toList ::: rewriterPreCtor.extraDefns.toList ::: extras) + if (obj.cls.ctor is ctorWithCap) && (obj.cls.preCtor is rewrittenPrector) && + (obj.cls.privateFields is rewrittenPrivateFields) && (obj.cls.methods is newMtds) + then LifterResult(obj.cls, Nil) + else + val newCls = obj.cls.copy( + ctor = ctorWithCap, + preCtor = rewrittenPrector, + privateFields = rewrittenPrivateFields, + methods = newMtds, + )(obj.cls.configOverride, obj.cls.annotations) + LifterResult(newCls, rewriterCtor.extraDefns.toList ::: rewriterPreCtor.extraDefns.toList ::: extras) + class RewrittenCompanion(override val obj: ScopedObject.Companion)(using ctx: LifterCtxNew) extends RewrittenScope[ClsLikeBody](obj) @@ -979,13 +990,18 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): val rewriterCtor = new BlockRewriter(N) val rewrittenCtor = rewriterCtor.rewrite(obj.clsBody.ctor) val ctorWithCap = initCaptureField(rewrittenCtor) + val rewrittenPrivateFields = appendCaptureField(liftedObjsOrdered.map(liftedObjsSyms) ::: obj.clsBody.privateFields) val LifterResult(newMtds, extras) = rewriteMethods(node, obj.clsBody.methods) - val newComp = obj.clsBody.copy( - ctor = ctorWithCap, - privateFields = appendCaptureField(liftedObjsOrdered.map(liftedObjsSyms) ::: obj.clsBody.privateFields), - methods = newMtds - ) - LifterResult(newComp, rewriterCtor.extraDefns.toList ::: extras) + if (obj.clsBody.ctor is ctorWithCap) && (obj.clsBody.privateFields is rewrittenPrivateFields) && + (obj.clsBody.methods is newMtds) + then LifterResult(obj.clsBody, Nil) + else + val newComp = obj.clsBody.copy( + ctor = ctorWithCap, + privateFields = rewrittenPrivateFields, + methods = newMtds + ) + LifterResult(newComp, rewriterCtor.extraDefns.toList ::: extras) class LiftedFunc(override val obj: ScopedObject.Func)(using ctx: LifterCtxNew) extends LiftedScope[FunDefn](obj) with GenericRewrittenScope[FunDefn]: private val passedSymsMap_ : Map[ValueSymbol, VarSymbol] = passedSymsOrdered.map: s => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index c3a2652577..c495bd97de 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -93,7 +93,6 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case t => t val lowerHandlers: Bool = config.effectHandlers.isDefined - val lift: Bool = config.liftDefns.isDefined private lazy val wasmBinaryIntrinsicMap: Map[Str, Str] = Map( "+" -> "plus_impl", @@ -1364,46 +1363,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): inScopedBlockExcept(symbolsToPreserve)(using LoweringCtx.empty): block(funs ::: rest, R(main.res))(ImplctRet) - val desug = LambdaRewriter.desugar(blk) - - val deforested = - val outterTl = tl - config.deforest match - case None => desug - case Some(dCfg) => - flowAnalysis.FlowAnalysis.mkTraceLogger(dCfg.config, "deforest > ", outterTl).givenIn: - deforest.Deforest(Program(imps.map(imp => imp.sym -> imp.str), desug)).main - - val etaExpanded = - EtaExpansion(Program(imps.map(imp => imp.sym -> imp.str), deforested)).main - - val lifted = - if lift then Lifter(etaExpanded).transform - else etaExpanded - - val withHandlers = config.effectHandlers.fold(lifted): opt => - HandlerLowering(new HandlerPaths, opt).translateTopLevel(lifted) - - val bufferable = BufferableTransform().transform(withHandlers.flattened) - - // * TODO[Anto]: Can we remove MergeMatchArmTransformer? Seems no longer necessary - val merged = MergeMatchArmTransformer.applyBlock(bufferable) - - val funcToCls = - if config.funcToCls then Lifter(FirstClassFunctionTransformer().transform(merged)).transform - else merged - - val flatClassParams = ClassParamFlattener(funcToCls) - - val staged = ReflectionInstrumenter(using summon).apply(flatClassParams) - - val res = - if config.tailRecOpt then TailRecOpt().transform(staged) - else staged - Program( imps.map(imp => imp.sym -> imp.str), - res + blk ) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ReflectionInstrumenter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ReflectionInstrumenter.scala index 3758177930..f1e8c8e3eb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ReflectionInstrumenter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ReflectionInstrumenter.scala @@ -430,6 +430,6 @@ class ReflectionInstrumenter(using State, Raise, Ctx) extends BlockTransformer(n case _ => super.applyDefn(defn) transformer.applyBlock(b) - def apply(b: Block) = - mkDefnMap(b) - applyBlock(b) + def apply(prog: Program) = + mkDefnMap(prog.main) + applyProgram(prog) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/TailRecOpt.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/TailRecOpt.scala index 239a50ade1..433c886b02 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/TailRecOpt.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/TailRecOpt.scala @@ -563,7 +563,7 @@ class TailRecOpt(using State, TL, Raise): case None => (newFns, fns_ ::: fns) // preserve the order of function defns val fMap = fsOpt.map(f => (f.dSym, f)).toMap - val fsRet = fs.map(f => fMap(f.dSym)) + val fsRet = fs.mapConserve(f => fMap(f.dSym)) (newFsOpt, fsRet) def reportClassesTailrec(c: ClsLikeDefn) = @@ -586,18 +586,27 @@ class TailRecOpt(using State, TL, Raise): if c.k is syntax.Cls then reportClassesTailrec(c) - val companion = c.companion.map: comp => + val companion = c.companion.mapConserve: comp => val cMtds = optFunctionsFlat(comp.methods, S(comp.isym)) - comp.copy(methods = cMtds) - c.copy(companion = companion)(c.configOverride, c.annotations) + if cMtds is comp.methods + then comp + else comp.copy(methods = cMtds) + if (c.companion is companion) + then c + else c.copy(companion = companion)(c.configOverride, c.annotations) else val mtds = optFunctionsFlat(c.methods, S(c.isym)) - val companion = c.companion.map: comp => + val companion = c.companion.mapConserve: comp => val cMtds = optFunctionsFlat(comp.methods, S(comp.isym)) - comp.copy(methods = cMtds) - c.copy(methods = mtds, companion = companion)(c.configOverride, c.annotations) + if cMtds is comp.methods + then comp + else comp.copy(methods = cMtds) + if (c.methods is mtds) && (c.companion is companion) + then c + else c.copy(methods = mtds, companion = companion)(c.configOverride, c.annotations) - def transform(b: Block) = + def transform(prog: Program)(using Config): Program = + if !config.tailRecOpt then return prog /* To avoid `x` being overridden in the following when the lifter is not run: * * let lam @@ -608,7 +617,7 @@ class TailRecOpt(using State, TL, Raise): * we need to do some analysis on what nested functions use what variables. We * re-use the analysis from the lifter to do this. */ - + val b = prog.main given (ScopeData, AccessMap) = // IgnoredScoes can be an empty set, since that information is only relevant for lifting given IgnoredScopes = IgnoredScopes(S(Set.empty)) @@ -665,4 +674,6 @@ class TailRecOpt(using State, TL, Raise): case _ => super.applyDefn(defn) .applyBlock(result) - result + if result is b + then prog + else Program(prog.imports, result) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala index 3ada72ad66..70aba1a3bf 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -121,48 +121,49 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: new codegen.Lowering() with codegen.LoweringTraceLog(traceJS.isSet) - var lowered = low.program(blk, symbolsToPreserve = symbolsToPreserve) + val lowered = ltl.givenIn: + low.program(blk, symbolsToPreserve = symbolsToPreserve) - var optimized = lowered + val optimized = ltl.givenIn: + val customPipeline = new CompilationPipeline: + override def passHook(passName: Str, before: Program, after: Program) = + // TODO: Preserve object identity in Lifter + if (passName =/= "Lifter") && (before isnt after) && (before === after) then + output(s"/!\\ Warning: object identity between equal objects was not preserved by ${passName}") + def rec(lhs: Product, rhs: Product): Bool = + (lhs is rhs) || { + if + lhs.productIterator.zip(rhs.productIterator).forall: + case (s1: Product, s2: Product) => rec(s1, s2) + case (s1, s2) => (s1 is s2) || { + output(s"/!\\ Offending is part of: ${lhs.showAsTree}") + false + } + then + output(s"/!\\ Offending: ${lhs.showAsTree}") + false + else false + } + rec(before.main, after.main) + override def preOptimizeHook(prog: Program) = + if showLoweredTree.isSet then + outputSeparator("Lowered IR Tree") + output(prog.showAsTree) + if showIR.isSet || showIRLines.isSet then + given ShowCfg = ShowCfg( + showExpansionMappings = false, + showFlowSymbols = true, + debug = debug.isSet, + ) + val irStr = Printer().worksheet(prog)(using irPrintingScp).mkString(output.ColWidth) + val sloc = irStr.count(_ == '\n') + 1 + if showIRLines.isSet then output(s"Lines of IR: ${sloc}") + if showIR.isSet then + outputSeparator("Lowered IR") + output(irStr) + super.preOptimizeHook(prog) + customPipeline.run(lowered, print, symbolsToPreserve, dtl) - if showLoweredTree.isSet then - outputSeparator("Lowered IR Tree") - output(optimized.showAsTree) - - if showIR.isSet || showIRLines.isSet then - given ShowCfg = ShowCfg( - showExpansionMappings = false, - showFlowSymbols = true, - debug = debug.isSet, - ) - val irStr = Printer().worksheet(optimized)(using irPrintingScp).mkString(output.ColWidth) - val sloc = irStr.count(_ == '\n') + 1 - if showIRLines.isSet then output(s"Lines of IR: ${sloc}") - if showIR.isSet then - outputSeparator("Lowered IR") - output(irStr) - - if noOptimizations.isUnset then - optimized = WorkerWrapper(symbolsToPreserve, dtl, print)(optimized) - - optimized = BlockSimplifier(symbolsToPreserve, dtl, print)(optimized) - ltl.givenIn: - optimized = DeadParamElim(optimized) - - // TODO: Test that transformers retain object identity when there are no changes - if (optimized isnt lowered) && (optimized === lowered) then - output("/!\\ Warning: object identity between equal objects was not preserved by BlockSimplifier or DeadParamElim") - def rec(lhs: Block, rhs: Block): Bool = - (lhs is rhs) || { - if - lhs.subBlocks.iterator.zip(rhs.subBlocks.iterator).forall: - case (s1: Block, s2: Block) => rec(s1, s2) - then - output(s"/!\\ Offending subblock: ${lhs.showAsTree}") - false - else false - } - rec(optimized.main, lowered.main) if checkIR.isSet then BlockChecker().applyProgram(optimized) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 5824acb7b4..94d57698fe 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -155,22 +155,7 @@ abstract class MLsDiffMaker extends DiffMaker: stageCode = stageCode.isSet, target = if wasm.isSet then CompilationTarget.Wasm else CompilationTarget.JS, rewriteWhileLoops = rewriteWhile.isSet, - tailRecOpt = !noTailRecOpt.isSet, - deforest = Opt.when(deforest.isSet): - val flags = parseFlags(deforest.get) - reportUnknownFlags(":deforest", flags, DeforestKnownFlags) - reportExclusiveFlagConflict(":deforest", flags, "trackNonAffine", "noTrackNonAffine") - reportExclusiveFlagConflict(":deforest", flags, "trackAccumulator", "noTrackAccumulator") - reportExclusiveFlagConflict(":deforest", flags, "logNonAffine", "noLogNonAffine") - reportExclusiveFlagConflict(":deforest", flags, "logAccumulator", "noLogAccumulator") - Deforest(FlowAnalysisConfig( - debug = true, - mono = flags.contains("mono"), - trackNonAffine = resolveFlag(flags, "trackNonAffine", "noTrackNonAffine", default = true), - trackAccumulator = resolveFlag(flags, "trackAccumulator", "noTrackAccumulator", default = flags.contains("logAccumulator")), - logNonAffine = resolveFlag(flags, "logNonAffine", "noLogNonAffine", default = false), - logAccumulator = resolveFlag(flags, "logAccumulator", "noLogAccumulator", default = false), - )), + etaExpansion = val etaExpansionFlags = if etaExpansion.isUnset then Set.empty[Str] @@ -181,32 +166,52 @@ abstract class MLsDiffMaker extends DiffMaker: reportExclusiveFlagConflict(":etaExpansion", etaExpansionFlags, "on", "off") if etaExpansionFlags.contains("off") then N else S(EtaExpansion.withDebug(etaExpansionFlags.contains("debug"))), - inlining = Opt.when(!noInlineOpt.isSet)(Config.Inliner(inlineThreshold = - inlineThreshold.get.getOrElse(Config.default.inlineThreshold))), - deadBranchRemoval = Config.default.deadBranchRemoval, qqEnabled = importQQ.isSet, funcToCls = funcToCls.isSet, commentGeneratedCode = debug.isSet, noFreeze = noFreeze.isSet, noModuleCheck = noModuleCheck.isSet, - deadParamElim = - if deadParamElim.isUnset then S(DeadParamElim.default) - else - val flags = parseFlags(deadParamElim.get) - reportUnknownFlags(":deadParamElim", flags, DeadParamElimKnownFlags) - reportExclusiveFlagConflict(":deadParamElim", flags, "mono", "poly") - if flags.contains("off") && (flags - "off").nonEmpty then - output(s"$errMarker ':deadParamElim off' conflicts with other flags") - if flags.contains("off") then N + optimizer = Optimizer( + deforest = Opt.when(deforest.isSet): + val flags = parseFlags(deforest.get) + reportUnknownFlags(":deforest", flags, DeforestKnownFlags) + reportExclusiveFlagConflict(":deforest", flags, "trackNonAffine", "noTrackNonAffine") + reportExclusiveFlagConflict(":deforest", flags, "trackAccumulator", "noTrackAccumulator") + reportExclusiveFlagConflict(":deforest", flags, "logNonAffine", "noLogNonAffine") + reportExclusiveFlagConflict(":deforest", flags, "logAccumulator", "noLogAccumulator") + Deforest(FlowAnalysisConfig( + debug = true, + mono = flags.contains("mono"), + trackNonAffine = resolveFlag(flags, "trackNonAffine", "noTrackNonAffine", default = true), + trackAccumulator = resolveFlag(flags, "trackAccumulator", "noTrackAccumulator", default = flags.contains("logAccumulator")), + logNonAffine = resolveFlag(flags, "logNonAffine", "noLogNonAffine", default = false), + logAccumulator = resolveFlag(flags, "logAccumulator", "noLogAccumulator", default = false), + )), + tailRecOpt = !noTailRecOpt.isSet, + inlining = Opt.when(!noInlineOpt.isSet)(Config.Inliner(inlineThreshold = + inlineThreshold.get.getOrElse(Config.default.inlineThreshold))), + deadBranchRemoval = Config.default.deadBranchRemoval, + deadCodeElim = noOptimizations.isUnset, + deadParamElim = + if deadParamElim.isUnset then S(DeadParamElim.default) else - S(DeadParamElim(FlowAnalysisConfig( - debug = flags.contains("debug"), - mono = !flags.contains("poly"), - trackNonAffine = false, - trackAccumulator = false, - logNonAffine = false, - logAccumulator = false, - ))), + val flags = parseFlags(deadParamElim.get) + reportUnknownFlags(":deadParamElim", flags, DeadParamElimKnownFlags) + reportExclusiveFlagConflict(":deadParamElim", flags, "mono", "poly") + if flags.contains("off") && (flags - "off").nonEmpty then + output(s"$errMarker ':deadParamElim off' conflicts with other flags") + if flags.contains("off") then N + else + S(DeadParamElim(FlowAnalysisConfig( + debug = flags.contains("debug"), + mono = !flags.contains("poly"), + trackNonAffine = false, + trackAccumulator = false, + logNonAffine = false, + logAccumulator = false, + ))), + dataFlowAnalysis = noOptimizations.isUnset, + ) ) @@ -302,7 +307,7 @@ abstract class MLsDiffMaker extends DiffMaker: () if file != preludeFile then val cfg = mkConfig - given Config = cfg.copy( + given Config = cfg.copy(optimizer = cfg.optimizer.copy( deforest = cfg.deforest.map: d => d.copy(config = d.config.copy( debug = false, @@ -315,7 +320,7 @@ abstract class MLsDiffMaker extends DiffMaker: logAccumulator = false, logNonAffine = false )) - ) + )) processTrees( PrefixApp(Keywrd(`import`), StrLit(predefFile.toString)) :: Open(Ident("Predef"))