diff --git a/hkmc2/shared/src/main/scala/hkmc2/Uid.scala b/hkmc2/shared/src/main/scala/hkmc2/Uid.scala index 235b375c37..5af536aa14 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Uid.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Uid.scala @@ -13,6 +13,7 @@ object Uid: curUid def reset = curUid = -1 object Symbol extends Handler[semantics.Symbol] + object StratVar extends Handler[codegen.deforest.StratVarState] extension [T] (x: Uid[T]) def <=(rhs: Uid[T]) = x <= rhs diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 875247e616..188be88cf9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -412,6 +412,11 @@ enum Case: sealed trait TrivialResult extends Result +object Result: + opaque type ResultId = Int + private def ResultId(v: Int): ResultId = v + + sealed abstract class Result extends AutoLocated: protected def children: List[Located] = this match @@ -465,6 +470,13 @@ sealed abstract class Result extends AutoLocated: case DynSelect(qual, fld, arrayIdx) => qual.freeVarsLLIR ++ fld.freeVarsLLIR case Value.Rcd(args) => args.flatMap(arg => arg.idx.fold(Set.empty)(_.freeVarsLLIR) ++ arg.value.freeVarsLLIR).toSet + def uid = + import Result.* + val uidValue = ResultId(System.identityHashCode(this)) + uidValue + + + // type Local = LocalSymbol type Local = Symbol diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/LambdaRewriter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/LambdaRewriter.scala index 6005f51d4a..70e36ed524 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/LambdaRewriter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/LambdaRewriter.scala @@ -24,7 +24,10 @@ object LambdaRewriter: val lambdaRewriter = new BlockDataTransformer(SymbolSubst()): override def applyValue(v: Value): Value = v match case lam: Value.Lam => - val sym = BlockMemberSymbol("lambda", Nil, nameIsMeaningful = false) + val sym = BlockMemberSymbol( + "lambda", + syntax.Tree.TermDef(syntax.Fun, syntax.Tree.Empty(), N) :: Nil, + nameIsMeaningful = false) lambdasList ::= (sym -> super.applyLam(lam)) Value.Ref(sym) case _ => super.applyValue(v) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Analyze.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Analyze.scala new file mode 100644 index 0000000000..0c8f6f1f99 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Analyze.scala @@ -0,0 +1,681 @@ +package hkmc2 +package codegen +package deforest + +import semantics.* +import syntax.Tree +import utils.* +import mlscript.utils.*, shorthands.* +import scala.collection.mutable +import Result.ResultId + +final case class NotDeforestableException(msg: String) extends Exception(msg) + +type StratVarId = Uid[StratVarState] +type InstantiationId = Ls[ResultId] + +class StratVarState(val uid: StratVarId, val name: Str, val generatedForDef: Opt[BlockMemberSymbol]): + lazy val asProdStrat = ProdVar(this) + lazy val asConsStrat = ConsVar(this) + override def toString(): String = s"${if name.isEmpty() then "var" else name}@${uid}@$generatedForDef" +object StratVarState: + def freshVar(nme: String, generatedForDef: Opt[BlockMemberSymbol])(using vuid: Uid.StratVar.State) = + val newId = vuid.nextUid + StratVarState(newId, nme, generatedForDef) +trait StratVar(s: StratVarState): + this: ProdVar | ConsVar => + def asProdStrat = s.asProdStrat + def asConsStrat = s.asConsStrat + def uid = s.uid + + +sealed abstract class ProdStrat +case class ProdVar(s: StratVarState) extends ProdStrat with StratVar(s) +case class ProdFun(params: Ls[ConsStrat], res: ProdStrat) extends ProdStrat +case object NoProd extends ProdStrat +class Ctor( + val exprId: ResultId, + val instantiationId: Opt[InstantiationId], + val ctor: ClassLikeSymbol, + val args: Ls[TermSymbol -> ProdStrat]) extends ProdStrat + +sealed abstract class ConsStrat +case class ConsVar(s: StratVarState) extends ConsStrat with StratVar(s) +case class ConsFun(params: Ls[ProdStrat], res: ConsStrat) extends ConsStrat +case object NoCons extends ConsStrat +class FieldSel( + val exprId: ResultId, + val instantiationId: Opt[InstantiationId], + val field: Tree.Ident, + val consVar: ConsVar) extends ConsStrat: + val filter = mutable.Map.empty[ProdVar, Ls[ClassLikeSymbol]].withDefaultValue(Nil) + def updateFilter(p: ProdVar, c: Ls[ClassLikeSymbol]) = + filter += p -> (c ::: filter(p)) + +class Dtor( + val scrutExprId: ResultId, + val instantiationId: Opt[InstantiationId]) extends ConsStrat + +class ProdStratScheme(s: StratVarState, constraints: Ls[ProdStrat -> ConsStrat]): + def instantiate(referSite: ResultId)(using d: DeforestConstraintsCollector, cc: d.ConstraintsAndCacheHitCollector): ProdVar = + val instantiatingFunSym = d.preAnalyzer.getResult(referSite) match + case Value.Ref(l) => l.asBlkMember.get + case s: Select => s.symbol.flatMap(_.asBlkMember).get + case _ => die + val instantiatingRecursiveGroup = d.funSymToProdStratScheme.recursiveGroups(instantiatingFunSym) + val stratVarMap = mutable.Map.empty[StratVarState, StratVarState] + def updateInstantiationId(instId: Opt[InstantiationId]) = + S(instId.fold(referSite :: Nil)(l => referSite :: l)) + def duplicateVarState(s: StratVarState) = + if s.generatedForDef.fold(false)(instantiatingRecursiveGroup.contains) then + stratVarMap.getOrElseUpdate.curried(s): + StratVarState.freshVar(s.name, cc.forFun)(using d.stratVarUidState) + else + s + def duplicateProdStrat(s: ProdStrat): ProdStrat = s match + case ProdVar(s) => duplicateVarState(s).asProdStrat + case ProdFun(params, res) => ProdFun(params.map(duplicateConsStrat), duplicateProdStrat(res)) + case NoProd => NoProd + case c: Ctor => new Ctor( + c.exprId, + updateInstantiationId(c.instantiationId), + c.ctor, + c.args.map((a, b) => a -> duplicateProdStrat(b)) + ) + def duplicateConsStrat(c: ConsStrat): ConsStrat = c match + case ConsVar(s) => duplicateVarState(s).asConsStrat + case ConsFun(params, res) => ConsFun(params.map(duplicateProdStrat), duplicateConsStrat(res)) + case NoCons => NoCons + case fSel: FieldSel => + val res = new FieldSel( + fSel.exprId, + updateInstantiationId(fSel.instantiationId), + fSel.field, + duplicateVarState(fSel.consVar.s).asConsStrat) + fSel.filter.foreach: (p, ls) => + res.updateFilter(duplicateVarState(p.s).asProdStrat, ls) + res + case dtor: Dtor => new Dtor(dtor.scrutExprId, updateInstantiationId(dtor.instantiationId)) + val newProd = duplicateVarState(s).asProdStrat + constraints.foreach: (p, c) => + cc.constrain(duplicateProdStrat(p), duplicateConsStrat(c)) + newProd + +class DeforestPreAnalyzer(val b: Block) extends BlockTraverser: + given stratVarUidState: Uid.StratVar.State = new Uid.StratVar.State + import StratVarState.freshVar + + val noProdStratVar = freshVar("primitive", N).asProdStrat + val resultIdToResult = mutable.Map.empty[ResultId, Result] + val topLevelFunSymToFun = mutable.Map.empty[BlockMemberSymbol, FunDefn] + val matchScrutToMatchBlock = mutable.Map.empty[ResultId, Match] + val matchScrutToParentMatchScruts = mutable.Map.empty[ResultId, Ls[ResultId]] + val matchScrutInFunDef = mutable.Map.empty[ResultId, Opt[BlockMemberSymbol]] + val selsToMatchingArmsContainingIt = mutable.Map.empty[ResultId, Ls[ResultId -> Opt[ClassLikeSymbol]]] + val symToStratVar = mutable.Map.empty[Symbol, ProdVar] + val usedFunSyms = mutable.Set.empty[BlockMemberSymbol] + lazy val topLevelDefinedFunSyms = topLevelFunSymToFun.keySet + val nonTopLevelDefinedFunSyms = mutable.Set.empty[BlockMemberSymbol] + def getProdVarForSym(s: Symbol) = s match + case _: (BuiltinSymbol | TopLevelSymbol) => noProdStratVar + case _ if s.asCls.isDefined => noProdStratVar + case _ => symToStratVar(s) + def getTopLevelFunDefnForSym(s: BlockMemberSymbol) = topLevelFunSymToFun.get(s) + def getCtorSymFromCtorLikeExprId(id: ResultId): Opt[ClassLikeSymbol] = + resultIdToResult(id).getCtorSymFromCtorLikeExpr + def getMatchFromMatchScrutExprId(scrutExprId: ResultId): Opt[Match] = + matchScrutToMatchBlock.get(scrutExprId) + def getResult(id: ResultId) = resultIdToResult(id) + def getStableResultId(id: ResultId) = resultIdToStableId.getOrElseUpdate.curried(id): + val prev = stableResuldIt + stableResuldIt += 1 + prev + def getReferredFunSym(id: ResultId) = + def chk(s: BlockMemberSymbol) = + assert(s.isFunction) + s + resultIdToResult(id) match + case s: Select => chk(s.symbol.get.asBlkMember.get) + case Value.Ref(l) => chk(l.asBlkMember.get) + case _ => die + + + private val resultIdToStableId = mutable.Map.empty[ResultId, Int] + private var stableResuldIt = 0 + private var inMatchScrutsArms: Ls[ResultId -> Opt[ClassLikeSymbol]] = Nil + private def inMatchScruts = inMatchScrutsArms.unzip._1 + private var inFunDef: Opt[BlockMemberSymbol] = N + private var symsDefinedForFun: Opt[Set[Symbol]] = N + override def applyFunDefn(fun: FunDefn): Unit = + inFunDef match + case N => + topLevelFunSymToFun += fun.sym -> fun + inFunDef = S(fun.sym) + symsDefinedForFun = S(fun.body.definedVars ++ fun.params.flatMap(_.params.map(_.sym)) + fun.sym) + super.applyFunDefn(fun) + inFunDef = N + symsDefinedForFun = N + case S(value) => + // nothing special for non-top-level functions + nonTopLevelDefinedFunSyms += fun.sym + super.applyFunDefn(fun) + + override def applySymbol(s: Symbol): Unit = s match + case s: BlockMemberSymbol if s.isFunction => symToStratVar.updateWith(s): + case N => S(freshVar(s.nme, S(s)).asProdStrat) + case S(x) => S(x) + // term symbol: variable in patterns so they are always inside the current fundefn (if any) + case s: (TermSymbol | TempSymbol) => symToStratVar.updateWith(s): + case N => S(freshVar(s.nme, inFunDef).asProdStrat) + case S(x) => S(x) + case v: (BlockMemberSymbol | VarSymbol) => symToStratVar.updateWith(s): + case N => + val inFunOrNot = inFunDef.fold(false): _ => + symsDefinedForFun.get.contains(s) + S(freshVar(s.nme, if inFunOrNot then inFunDef else N).asProdStrat) + case S(x) => S(x) + case _: (TopLevelSymbol | BuiltinSymbol | ClassLikeSymbol) => () + case _ => die + + override def applyResult(r: Result): Unit = + resultIdToResult += r.uid -> r + super.applyResult(r) + + override def applyPath(p: Path): Unit = + resultIdToResult += p.uid -> p + p match + case s@Select(path, nme) => + selsToMatchingArmsContainingIt += s.uid -> inMatchScrutsArms + if s.symbol.exists(_.isFunction) then usedFunSyms += s.symbol.get.asBlkMember.get + case _ => () + super.applyPath(p) + + override def applyValue(v: Value): Unit = + resultIdToResult += v.uid -> v + v match + case Value.Ref(l) if l.isFunction => usedFunSyms += l.asBlkMember.get + case _ => () + super.applyValue(v) + + override def applyBlock(b: Block): Unit = b match + case m@Match(scrut, arms, dflt, rest) => + matchScrutToMatchBlock += scrut.uid -> m + matchScrutInFunDef += scrut.uid -> inFunDef + matchScrutToParentMatchScruts += scrut.uid -> inMatchScruts + applyPath(scrut) + + val prev = inMatchScrutsArms + arms.foreach: arm => + val cse = arm._1 match + case Case.Cls(cls, path) => S(cls) + case _ => N + inMatchScrutsArms = (scrut.uid -> cse) :: inMatchScrutsArms + applyCase(arm._1); applySubBlock(arm._2) + inMatchScrutsArms = prev + + inMatchScrutsArms = (scrut.uid -> N) :: inMatchScrutsArms + dflt.foreach(applySubBlock) + inMatchScrutsArms = prev + + applySubBlock(rest) + case _ => super.applyBlock(b) + + applyBlock(b) + + +class DeforestConstraintsCollector(val preAnalyzer: DeforestPreAnalyzer): + given stratVarUidState: Uid.StratVar.State = preAnalyzer.stratVarUidState + import StratVarState.freshVar + + val constraints = processTopLevel(preAnalyzer.b) + class ConstraintsAndCacheHitCollector(val forFun: Opt[BlockMemberSymbol]): + var constraints: Ls[ProdStrat -> ConsStrat] = Nil + var trackedFunctionSymbolsInOneRecGroup: Ls[BlockMemberSymbol] = Nil + def constrain(p: ProdStrat, c: ConsStrat) = constraints ::= p -> c + def constrain(cs: Ls[ProdStrat -> ConsStrat]) = constraints :::= cs + def hit(s: BlockMemberSymbol) = trackedFunctionSymbolsInOneRecGroup ::= s + def hit(ss: Ls[BlockMemberSymbol]) = trackedFunctionSymbolsInOneRecGroup :::= ss + + object funSymToProdStratScheme: + val store = mutable.Map.empty[BlockMemberSymbol, ProdStratScheme] + val recursiveGroups = mutable.Map.empty[BlockMemberSymbol, Ls[BlockMemberSymbol]] + def getOrUpdate(s: BlockMemberSymbol)(using processingDefs: Ls[BlockMemberSymbol], cc: ConstraintsAndCacheHitCollector): ProdVar | ProdStratScheme = + preAnalyzer.getTopLevelFunDefnForSym(s) match + // not a fun defined in the current block, just return its prodvar + case None => + preAnalyzer.getProdVarForSym(s) // TODO: consider functions being imported? + case Some(funDefn) => store.get(s) match + case Some(scheme) => scheme + case None => processingDefs.filter(_ is s) match + case sym :: Nil => + cc.hit(sym) + processingDefs.headOption.foreach: h => + cc.hit(h) + preAnalyzer.getProdVarForSym(sym) + case Nil => + // start processing this function, if the cache hit contains the currently processing defs functions + // then: 1. the referred function belongs to the same recursion group and need to share the constraints 2. return the prodvar + // else: we found a new recursive group, for each member of the group, update the store with the correct type scheme and return the type scheme + val newcc = processFunDefn(funDefn, processingDefs) + if newcc.trackedFunctionSymbolsInOneRecGroup.exists(x => processingDefs.contains(x)) then + cc.hit(newcc.trackedFunctionSymbolsInOneRecGroup) + cc.constrain(newcc.constraints) + processingDefs.headOption.foreach: h => + cc.hit(h) + preAnalyzer.getProdVarForSym(s) + else + val recursiveGroupMembers = (s :: newcc.trackedFunctionSymbolsInOneRecGroup).distinct + recursiveGroupMembers.foreach: f => + store.updateWith(f): + case N => S(ProdStratScheme(preAnalyzer.getProdVarForSym(f).s, newcc.constraints)) + case S(_) => die // this means the scc is not computed corrrectely + recursiveGroups.updateWith(f): + case N => S(recursiveGroupMembers) + case S(_) => die // this means the scc is not computed corrrectely + store(s) + case _ => die // die if something occurs twice in the processing list + + def processTopLevel(b: Block): Ls[ProdStrat -> ConsStrat] = + val cc = new ConstraintsAndCacheHitCollector(N) + val strat = processBlock(b)(using Nil, cc) + cc.constrain(strat, NoCons) + cc.constrain(preAnalyzer.noProdStratVar, NoCons) + cc.constrain(NoProd, preAnalyzer.noProdStratVar.asConsStrat) + preAnalyzer.usedFunSyms + .diff(preAnalyzer.topLevelDefinedFunSyms) + .diff(preAnalyzer.nonTopLevelDefinedFunSyms) + .foreach: usedButNotDefined => + cc.constrain(NoProd, preAnalyzer.getProdVarForSym(usedButNotDefined).asConsStrat) + cc.constraints + + def processFunDefn(defn: FunDefn, processingDefs: Ls[BlockMemberSymbol]): ConstraintsAndCacheHitCollector = + val thisFunVar = preAnalyzer.getProdVarForSym(defn.sym) + val paramSyms = defn.params.head.params.map: // TODO: handle multiple param list and the `restParam` + case Param(sym = sym, _) => preAnalyzer.getProdVarForSym(sym).asConsStrat + val cc = new ConstraintsAndCacheHitCollector(S(defn.sym)) + val bodyStrat = processBlock(defn.body)(using defn.sym :: processingDefs, cc) + val res = freshVar(s"${defn.sym.nme}_res", S(defn.sym)) + cc.constrain(bodyStrat, res.asConsStrat) + cc.constrain(ProdFun(paramSyms, res.asProdStrat), thisFunVar.asConsStrat) + cc + + def processBlock(b: Block)(using + processingDefs: Ls[BlockMemberSymbol], + cc: ConstraintsAndCacheHitCollector + ): ProdStrat = b match + case m@Match(scrut, arms, dflt, rest) => + val scrutStrat = processResult(scrut) + cc.constrain(scrutStrat, new Dtor(scrut.uid, cc.forFun.fold(S(Nil))(_ => N))) + val armsRes = + if arms.forall{ case (cse, _) => cse.isInstanceOf[Case.Cls] } then + arms.map: + case (Case.Cls(clsSym, _), body) => processBlock(body) + else + arms.map: + case (_, armBody) => processBlock(armBody) + val dfltRes = dflt.map(processBlock) + rest match + case End(msg) => + val matchRes = freshVar("", cc.forFun) + armsRes.appendedAll(dfltRes).foreach: r => + cc.constrain(r, matchRes.asConsStrat) + matchRes.asProdStrat + case _ => processBlock(rest) + + case Return(res, implct) => processResult(res) + case Assign(lhs, rhs, rest) => + cc.constrain(processResult(rhs), preAnalyzer.getProdVarForSym(lhs).asConsStrat) + processBlock(rest) + case Begin(sub, rest) => + processBlock(sub) + processBlock(rest) + case Define(defn, rest) => + defn match + case FunDefn(_, sym, params, body) => + if processingDefs.nonEmpty then + val paramSyms = params.head.params.map: // TODO: handle multiple param list and the `restParam` + case Param(sym = sym, _) => preAnalyzer.getProdVarForSym(sym).asConsStrat + val bodyStrat = processBlock(body) + val res = freshVar(s"${sym.nme}_res", cc.forFun) + cc.constrain(bodyStrat, res.asConsStrat) + cc.constrain(ProdFun(paramSyms, res.asProdStrat), preAnalyzer.getProdVarForSym(sym).asConsStrat) + else + () // skip toplevel fundefs are they are processed when needed + case v: ValDefn => throw NotDeforestableException("No support for `ValDefn` yet") + case c: ClsLikeDefn => throw NotDeforestableException("No support for `ClsLikeDefn` yet") + processBlock(rest) + case End(msg) => NoProd + // make it a type var instead of `NoProd` so that things like `throw match error` in + // default else branches do not block fusion... + case Throw(exc) => + processResult(exc) + freshVar("throw", cc.forFun).asProdStrat + + def processResult(r: Result)(using + processingDefs: Ls[BlockMemberSymbol], + cc: ConstraintsAndCacheHitCollector + ): ProdStrat = + val generatedForDef = cc.forFun + val instantiationId = cc.forFun.fold(S(Nil))(_ => N) + def handleCallLike(f: Path, args: Ls[Path], c: Result): ProdStrat = + val argsTpe = args.map(processResult) + f match + case s@Select(p, nme) => + s.symbol.map(_.asCls) match + case None => + val pStrat = processResult(p) + val tpeVar = freshVar("", generatedForDef) + cc.constrain(pStrat, new FieldSel(s.uid, instantiationId, nme, tpeVar.asConsStrat)) + val appRes = freshVar("", generatedForDef) // unknown function symbol + cc.constrain(tpeVar.asProdStrat, ConsFun(argsTpe, appRes.asConsStrat)) + appRes.asProdStrat + case Some(None) => + val funSym = s.symbol.get + val appRes = freshVar("call_" + funSym.nme + "_res", generatedForDef) + funSym.asBlkMember match + case None => + cc.constrain(preAnalyzer.getProdVarForSym(funSym), ConsFun(argsTpe, appRes.asConsStrat)) + case Some(s) => + funSymToProdStratScheme.getOrUpdate(s) match + case v: ProdVar => cc.constrain(v, ConsFun(argsTpe, appRes.asConsStrat)) + case t: ProdStratScheme => + val instantiated = t.instantiate(f.uid)(using this, cc) + cc.constrain(instantiated, ConsFun(argsTpe, appRes.asConsStrat)) + appRes.asProdStrat + case Some(Some(s)) => + val clsFields = s.tree.clsParams + new Ctor(c.uid, instantiationId, s, clsFields.zip(argsTpe)) + case Value.Ref(funSym) => + funSym.asCls match + case Some(s) => + val clsFields = s.tree.clsParams + new Ctor(c.uid, instantiationId, s, clsFields.zip(argsTpe)) + case _ => // then it is a function + val appRes = freshVar("call_" + funSym.nme + "_res", generatedForDef) + funSym.asBlkMember match + case None => + cc.constrain(preAnalyzer.getProdVarForSym(funSym), ConsFun(argsTpe, appRes.asConsStrat)) + case Some(s) => + funSymToProdStratScheme.getOrUpdate(s) match + case v: ProdVar => cc.constrain(v, ConsFun(argsTpe, appRes.asConsStrat)) + case t: ProdStratScheme => + val instantiated = t.instantiate(f.uid)(using this, cc) + cc.constrain(instantiated, ConsFun(argsTpe, appRes.asConsStrat)) + appRes.asProdStrat + case lam@Value.Lam(params, body) => + val funTpe = processResult(lam) + val appRes = freshVar("call_lam_res", generatedForDef) + cc.constrain(funTpe, ConsFun(argsTpe, appRes.asConsStrat)) + appRes.asProdStrat + case Value.This(sym) => throw NotDeforestableException("No support for `this` as a callee yet") + case Value.Lit(lit) => lastWords(s"try to call literal $lit") + case Value.Arr(elems) => lastWords(s"try to call array $elems") + r match + case c@Call(f, args) => handleCallLike(f, args.map {case Arg(false, value) => value}, c) + case i@Instantiate(cls, args) => handleCallLike(cls, args, i) + case sel@Select(p, nme) => sel.symbol match + case Some(s) if s.asObj.isDefined => + new Ctor(sel.uid, instantiationId, s.asObj.get, Nil) + case Some(s) if s.isFunction && + preAnalyzer.topLevelDefinedFunSyms.contains(s.asBlkMember.get) => + funSymToProdStratScheme.getOrUpdate(s.asBlkMember.get) match + case v: ProdVar => v + case t: ProdStratScheme => + val instantiated = t.instantiate(sel.uid)(using this, cc) + instantiated + case _ => + val pStrat = processResult(p) + pStrat match + case ProdVar(pStratVar) => + val inMatchingArm = preAnalyzer.selsToMatchingArmsContainingIt(sel.uid).flatMap: + case (scrutUid, S(inArm)) => + preAnalyzer.matchScrutToMatchBlock(scrutUid).scrut match + case Value.Ref(l) => + S(preAnalyzer.getProdVarForSym(l) -> inArm) + case _ => N + case _ => N + val tpeVar = freshVar("sel_res", generatedForDef) + val selStrat = new FieldSel(sel.uid, instantiationId, nme, tpeVar.asConsStrat) + inMatchingArm.foreach: (p, c) => + selStrat.updateFilter(p, c :: Nil) + cc.constrain(pStrat, selStrat) + tpeVar.asProdStrat + case _ => + val tpeVar = freshVar("sel_res", generatedForDef) + cc.constrain(pStrat, new FieldSel(sel.uid, instantiationId, nme, tpeVar.asConsStrat)) + tpeVar.asProdStrat + + case v@Value.Ref(l) => l.asObj match + case None => + // println(s"ref $l: ${l.asBlkMember.map(_.trees)}") + if l.asBlkMember.exists(preAnalyzer.topLevelDefinedFunSyms.contains) then + funSymToProdStratScheme.getOrUpdate(l.asBlkMember.get) match + case v: ProdVar => v + case t: ProdStratScheme => + val instantiated = t.instantiate(v.uid)(using this, cc) + instantiated + else + preAnalyzer.getProdVarForSym(l) + case Some(m) => new Ctor(v.uid, instantiationId, m, Nil) + + case Value.This(sym) => throw NotDeforestableException("No support for `this` yet") + case Value.Lit(lit) => NoProd + case Value.Lam(ParamList(_, params, N), body) => + val paramSyms = params.map: // TODO: handle multiple param list and the `restParam` + case Param(sym = sym, _) => preAnalyzer.getProdVarForSym(sym).asConsStrat + val bodyStrat = processBlock(body) + val res = freshVar(s"lam_res", generatedForDef) + cc.constrain(bodyStrat, res.asConsStrat) + ProdFun(paramSyms, res.asProdStrat) + case Value.Arr(elems) => + NoProd + + +class DeforestConstrainSolver(val collector: DeforestConstraintsCollector): + val preAnalyzer = collector.preAnalyzer + val constraints = collector.constraints + val upperBounds = mutable.Map.empty[StratVarId, Ls[ConsStrat]].withDefaultValue(Nil) + val lowerBounds = mutable.Map.empty[StratVarId, Ls[ProdStrat]].withDefaultValue(Nil) + object ctorDests: + val store = mutable.LinkedHashMap.empty[Ctor, Ls[Dtor | FieldSel] -> Bool] + def update(c: Ctor, d: Dtor | FieldSel | NoCons.type) = d match + case NoCons => store.updateWith(c): + case S(l -> _) => S(l, true) + case N => S(Nil, true) + case d: (Dtor | FieldSel) => store.updateWith(c): + case S(l -> b) => S(d :: l, b) + case N => S(d :: Nil, false) + def get(c: Ctor) = store.get(c).map(l => l._1.distinct -> l._2) + object dtorSources: + val store = mutable.LinkedHashMap.empty[Dtor | FieldSel, Ls[Ctor] -> Bool] + def update(d: Dtor | FieldSel, c: Ctor | NoProd.type) = c match + case NoProd => store.updateWith(d): + case S(l -> _) => S(l, true) + case N => S(Nil, true) + case c: Ctor => store.updateWith(d): + case S(l -> b) => S(c :: l, b) + case N => S(c :: Nil, false) + + private def handle(constraint: ProdStrat -> ConsStrat)(using cache: mutable.Set[ProdStrat -> ConsStrat]): Unit = + val prod = constraint._1 + val cons = constraint._2 + val proceed = cache.add(constraint) + assert: + (!prod.isInstanceOf[Ctor] || prod.asInstanceOf[Ctor].instantiationId.isDefined) && + (!cons.isInstanceOf[Dtor] || cons.asInstanceOf[Dtor].instantiationId.isDefined) + if proceed then constraint match + case (c: Ctor, d: Dtor) => + ctorDests.update(c, d) + dtorSources.update(d, c) + case (c: Ctor, d: FieldSel) => + ctorDests.update(c, d) + dtorSources.update(d, c) + c.args.find(a => a._1.id == d.field).map: p => + handle(p._2 -> d.consVar) + case (c: Ctor, d: ConsFun) => () + case (p: ProdVar, _) => + upperBounds += p.uid -> (cons :: upperBounds(p.uid)) + lowerBounds(p.uid).foreach: l => + (l, cons) match + case (l: ProdVar, sel: FieldSel) => + sel.updateFilter(l, sel.filter(p)) + handle(l -> cons) + case (c: Ctor, sel: FieldSel) => + if sel.filter.get(p).forall(_.contains(c.ctor)) then + handle(l -> cons) + case _ => handle(l -> cons) + case (_, c: ConsVar) => + lowerBounds += c.uid -> (prod :: lowerBounds(c.uid)) + upperBounds(c.uid).foreach: u => + (prod, u) match + case (ctor: Ctor, sel: FieldSel) => + if sel.filter.get(c.asProdStrat).forall(_.contains(ctor.ctor)) then + handle(prod -> u) + case (_: ProdVar, _) => die + case _ => handle(prod -> u) + case (ctor: Ctor, NoCons) => + ctorDests.update(ctor, NoCons) + ctor.args.foreach(a => handle(a._2, NoCons)) + case (ProdFun(l, r), _: Dtor) => () // ignore + case (ProdFun(l, r), _: FieldSel) => () // ignore + case (ProdFun(lp, rp), ConsFun(lc, rc)) => + lc.zip(lp).foreach(handle) + handle(rp, rc) + case (ProdFun(l, r), NoCons) => + l.foreach(a => handle(NoProd, a)) + handle(r, NoCons) + case (NoProd, d: Dtor) => dtorSources.update(d, NoProd) + case (NoProd, fSel: FieldSel) => dtorSources.update(fSel, NoProd) + case (NoProd, ConsFun(l, r)) => + l.foreach(a => handle(a, NoCons)) + handle(NoProd, r) + case (NoProd, NoCons) => () + + locally: + given mutable.Set[ProdStrat -> ConsStrat] = mutable.Set.empty + constraints.foreach(handle) + + val resolveClashes = + val ctorToDtor = ctorDests.store + val dtorToCtor = dtorSources.store + def removeCtor(rm: (Ctor | ResultId)): Unit = rm match + case rm: Ctor => + for + (dtors, _) <- ctorToDtor.remove(rm) + dtor <- dtors + do removeDtor(dtor) + case _ => + for rm <- ctorToDtor.keySet.filter(x => x.exprId == rm) do removeCtor(rm) + def removeDtor(rm: Dtor | FieldSel) = + for (ctors, _) <- dtorToCtor.remove(rm) + x <- ctors do removeCtor(x) + + // remove clashes + for + (rm, dtors -> noCons) <- ctorToDtor + (mats, sels) = dtors.partitionMap: + case d: Dtor => L(d) + case s: FieldSel => R(s) + if noCons || !locally: + mats.size == 0 && sels.size == 1 || + mats.size == 1 && locally: + val matScrutExprId = mats.head.scrutExprId + val matExprInstantiationId = mats.head.instantiationId.get + val matScrutSym = preAnalyzer.getResult(matScrutExprId).asInstanceOf[Value.Ref].l + sels.forall: s => + val selExprId = s.exprId + preAnalyzer.getResult(selExprId) match + case Select(Value.Ref(l), _) => + preAnalyzer.selsToMatchingArmsContainingIt(selExprId).exists(_._1 == matScrutExprId) && + (l is matScrutSym) && + s.instantiationId.get == matExprInstantiationId + case _ => false + do removeCtor(rm) + for + case (rm, _ -> true) <- dtorToCtor + do removeDtor(rm) + + // remove cycle + def getCtorInArm(ctorExprId: ResultId , dtorScrutExprId: ResultId) = + val ctorSym = preAnalyzer.getCtorSymFromCtorLikeExprId(ctorExprId).get + val dtor = preAnalyzer.getMatchFromMatchScrutExprId(dtorScrutExprId).get + val arm = + dtor.arms.find: + case (Case.Cls(c1, _) -> body) => c1 is ctorSym + .map(_._2).orElse(dtor.dflt).get + val armAndMatchRest = + preAnalyzer + .matchScrutToParentMatchScruts(dtorScrutExprId) + .foldLeft(Begin(arm, dtor.rest)): (acc, x) => + val newRest = preAnalyzer.getMatchFromMatchScrutExprId(x).get.rest + Begin(acc, newRest) + val traverser = new GetCtorsTraverser(armAndMatchRest) + traverser.ctors + def findCycle(c: Ctor, d: Dtor): Ls[ResultId] = + val cache = mutable.Set(c.exprId) + def go(ctorAndMatchesScrutExprIds: Ls[ResultId -> ResultId]): Ls[ResultId] = + val newCtorsAndNewMatches = for + (c, m) <- ctorAndMatchesScrutExprIds + c <- getCtorInArm(c, m) + (_, ds -> noCons) <- ctorToDtor.filter(x => x._1.exprId == c) + _ = assert(!noCons) + (mats, _) = ds.partitionMap: + case d: Dtor => L(d) + case s: FieldSel => R(s) + m <- mats.headOption + yield c -> m.scrutExprId + val cycled = newCtorsAndNewMatches.filter: c => + !cache.add(c._1) + if newCtorsAndNewMatches.isEmpty then + Nil + else if cycled.nonEmpty then + cycled.map(_._1) + else + go(newCtorsAndNewMatches) + c.instantiationId -> d.instantiationId match + case S(id1) -> S(id2) if id1 == id2 => + go(Ls(c.exprId -> d.scrutExprId)) + case _ => Nil + for + (ctor, dtors -> noCons) <- ctorToDtor + (mats, sels) = dtors.partitionMap: + case d: Dtor => L(d) + case s: FieldSel => R(s) + _ = assert(!noCons && mats.size <= 1) + dtor <- mats + rm <- findCycle(ctor, dtor) + do removeCtor(rm) + + ctorToDtor -> dtorToCtor + + + +class GetCtorsTraverser(b: Block) extends BlockTraverser: + var ctors = Set.empty[ResultId] + override def applyResult(r: Result): Unit = r match + case Call(f, args) => + if f.asClsSymbol.isDefined then ctors += r.uid + args.foreach: + case Arg(false, v) => applyResult(v) + case Instantiate(cls, args) => + if cls.asClsSymbol.isDefined then ctors += r.uid + args.foreach(applyResult) + case p: Path => if p.asObjSymbol.isDefined then ctors += r.uid + applyBlock(b) + +extension (p: Path) + def asClsSymbol = p match + case s: Select => s.symbol.flatMap(_.asCls) + case Value.Ref(l) => l.asCls + case _ => N + def asObjSymbol = p match + case s: Select => s.symbol.flatMap(_.asObj) + case Value.Ref(l) => l.asObj + case _ => N + +extension (r: Result) + def getCtorSymFromCtorLikeExpr = r match + case Call(f, _) => f.asClsSymbol + case Instantiate(cls, _) => cls.asClsSymbol + case p: Path => p.asObjSymbol + diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala new file mode 100644 index 0000000000..5aa398a0a0 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala @@ -0,0 +1,42 @@ +package hkmc2 +package codegen +package deforest + +import semantics.* +import syntax.Tree +import utils.* +import mlscript.utils.*, shorthands.* +import scala.collection.mutable +import Result.ResultId + + +object Deforest: + class State: + val topLevelFunSymToFunDefn = collection.mutable.Map.empty[BlockMemberSymbol, FunDefn] + + def apply(p: Program)(using State, Elaborator.State): Either[Program -> String -> String, String] = + try + val pre = new DeforestPreAnalyzer(p.main) + val col = new DeforestConstraintsCollector(pre) + val ana = new DeforestConstrainSolver(col) + val rwp = new DeforestRewritePrepare(ana) + val rw = new DeforestRewriter(rwp) + val detail = col.constraints + .map: (p, c) => + (s"$p --> $c") + .mkString("\n") + val summary = rwp.ctorIdToFinalDest + .map: (ctorid, dest) => + pre.getResult(ctorid._1).toString() + + "@" + + pre.getStableResultId(ctorid._1) + + "@" + + ctorid._2.makeSuffix(pre) + + " --> " + + dest.toString(pre) + .mkString("\n") + val deforestRes = rw() + L: + Program(p.imports, deforestRes) -> summary -> detail + catch + case NotDeforestableException(msg) => R(msg) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala new file mode 100644 index 0000000000..e6d46cb890 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala @@ -0,0 +1,671 @@ +package hkmc2 +package codegen +package deforest + + +import semantics.* +import semantics.Elaborator.State +import syntax.Tree +import utils.* +import mlscript.utils.*, shorthands.* +import scala.collection.mutable +import Result.ResultId + + +type CtorId = ResultId -> InstantiationId +type MatchId = ResultId -> InstantiationId +type SelId = ResultId -> InstantiationId + + +enum FinalDest: + // A match arm is considered a final destination. + // `selsInArm` totally depends on `matchId` and `arm`, while + // `tmpSymbolForASpecificCtorId` depends on the ctor that reaches this destination. + // This field is in the second param list because we want maps from `FinalDest`s + // to only consider `matchId` and `arm` (i.e. the match arm). + case Match(val matchId: MatchId, val arm: Opt[ClassLikeSymbol])(val selsInArm: Ls[SelId], val tmpSymbolForASpecificCtorId: Ls[TempSymbol]) + case Sel(val s: SelId) + def toString(pre: DeforestPreAnalyzer): String = this match + case Match(matchId, arm) => pre.resultIdToResult(matchId._1).toString() + "@" + matchId._2.makeSuffix(pre) + "@" + arm.fold("dflt")(_.nme) + case Sel(s) => pre.resultIdToResult(s._1).toString() + "@" + s._2.makeSuffix(pre) + + + +class DeforestRewritePrepare(val sol: DeforestConstrainSolver)(using Elaborator.State): + drwp => + val preAnalyzer = sol.preAnalyzer + + val instIdToMappingFromOldToNewSyms = mutable.Map.empty[InstantiationId, Map[BlockMemberSymbol, BlockMemberSymbol]] + // need to be ordered because we need to traverse it to generate new fun def bodies + val newSymToInstIdAndOldSym = mutable.LinkedHashMap.empty[BlockMemberSymbol, InstantiationId -> BlockMemberSymbol] + locally: + for + (ctor, _) <- sol.resolveClashes._1 + instantiationId = ctor.instantiationId.get + case instId@(_ :+ invokedReferSite) <- instantiationId.scanLeft(Nil)(_ :+ _) + do instIdToMappingFromOldToNewSyms.getOrElseUpdate.curried(instId): + val invokedFunSym = preAnalyzer.getReferredFunSym(invokedReferSite) + val recursiveGroupFunSym = sol.collector.funSymToProdStratScheme.recursiveGroups(invokedFunSym) + val newSymSuffix = instId.makeSuffix(preAnalyzer) + val mapping = recursiveGroupFunSym.map: old => + val newSymbol = BlockMemberSymbol(old.nme + s"_$newSymSuffix", Nil, old.nameIsMeaningful) + newSymToInstIdAndOldSym.updateWith(newSymbol): + case N => S(instId -> old) + case S(_) => die + old -> newSymbol + mapping.toMap + // TODO: can merge all ids that *only* have dtor to one id, but also need to consider all other + // referred functions along the way? + for + (dtorOrSel, _) <- sol.resolveClashes._2 + instantiationId = dtorOrSel match + case f: FieldSel => f.instantiationId.get + case d: Dtor => d.instantiationId.get + case instId@(_ :+ invokedReferSite) <- instantiationId.scanLeft(Nil)(_ :+ _) + do instIdToMappingFromOldToNewSyms.getOrElseUpdate.curried(instId): + val invokedFunSym = preAnalyzer.getReferredFunSym(invokedReferSite) + val recursiveGroupFunSym = sol.collector.funSymToProdStratScheme.recursiveGroups(invokedFunSym) + val newSymSuffix = instId.makeSuffix(preAnalyzer) + val mapping = recursiveGroupFunSym.map: old => + val newSymbol = BlockMemberSymbol(old.nme + s"_$newSymSuffix", Nil, old.nameIsMeaningful) + newSymToInstIdAndOldSym.updateWith(newSymbol): + case N => S(instId -> old) + case S(_) => die + old -> newSymbol + mapping.toMap + + val finalDestToCtorIds = mutable.LinkedHashMap.empty[FinalDest, Set[CtorId]] + val ctorIdToFinalDest = mutable.LinkedHashMap.empty[CtorId, FinalDest] + sol.resolveClashes._1.foreach: + case (c: Ctor, dtors -> noCons) => + assert(!noCons) + val (dtor, sel) = dtors.partitionMap: + case d: Dtor => L(d) + case s: FieldSel => R(s) + val ctorExprId = c.exprId -> c.instantiationId.get + val ctorFinalDest = + if dtor.isEmpty then + assert(sel.size == 1) + FinalDest.Sel(sel.head.exprId -> sel.head.instantiationId.get) + else + assert(dtor.size == 1) + val dtorScrutExprId = dtor.head.scrutExprId -> dtor.head.instantiationId.get + val distinctSels = sel + .map: s => + assert(s.instantiationId.get == dtorScrutExprId._2) + s.exprId -> s.instantiationId.get + .distinct // need to preserve order + assert: + distinctSels.forall: s => + preAnalyzer.selsToMatchingArmsContainingIt(s._1).exists: armInfo => + armInfo._1 == dtorScrutExprId._1 && + armInfo._2.fold(true): clsSym => + clsSym is c.ctor + val ctorCls = preAnalyzer.getCtorSymFromCtorLikeExprId(c.exprId).get + val whichArm = preAnalyzer.matchScrutToMatchBlock(dtorScrutExprId._1).arms + .flatMap: x => + x._1 match + case Case.Cls(cls, _) => if (cls is ctorCls) then S(cls) else N + case _ => die + .headOption + val tmpSymbolsForFieldsOfCtor = ctorCls.asCls + .map: cls => + cls.tree.clsParams.map: fieldName => + TempSymbol(N, fieldName.name) + .getOrElse(Nil) + FinalDest.Match(dtorScrutExprId, whichArm)(distinctSels, tmpSymbolsForFieldsOfCtor) + finalDestToCtorIds.updateWith(ctorFinalDest): + case N => S(Set(ctorExprId)) + case S(s) => S(s + ctorExprId) + ctorIdToFinalDest.updateWith(ctorExprId): + case N => S(ctorFinalDest) + case S(_) => die + + val rewritingMatchIds -> rewritingSelIds = finalDestToCtorIds.keySet.partitionMap: + case FinalDest.Match(matchId, _) => L(matchId) + case FinalDest.Sel(s) => R(s) + + // not all symbols are used because some of the match rests are just `End`s, so no need to + // create a new function for them. + val fusingMatchIdToMatchRestFunSymbols = mutable.LinkedHashMap.empty[MatchId, BlockMemberSymbol] + locally: + // the order of traversal is deterministic because rewritingMatchIds is from + // the keySet of linkedHashMap + for + case (FinalDest.Match(matchId, _), _) <- finalDestToCtorIds + numOfMatchingArms = finalDestToCtorIds.keySet.count: + case FinalDest.Match(matId, _) => matId == matchId + case _ => false + if numOfMatchingArms > 1 + do + fusingMatchIdToMatchRestFunSymbols.updateWith(matchId): + case N => + val scrutName = preAnalyzer.getResult(matchId._1).asInstanceOf[Value.Ref].l.nme + S(BlockMemberSymbol( + s"match_${scrutName}_rest_${matchId._2.makeSuffix(preAnalyzer)}", + Nil)) + case S(x) => S(x) + + + // if a key doesn't exist, it means the final dest is only used once + val finalDestToVarSymbolsToReplaceSelInArms = mutable.Map.empty[ + FinalDest, + Map[SelId, VarSymbol] -> Map[Tree.Ident, VarSymbol]] + // if a key doesn't exist, it means the matchId doesn't have any arm that is used more than once + val fusingMatchIdToVarSymbolsToReplacedInAllBranches = mutable.Map.empty[ + MatchId, + Map[SelId, VarSymbol]] + // if a key doesn't exist, it means the all the arms of this matchId are used more than once + val fusingMatchIdToTmpSymbolsToReplacedInAllBranches = mutable.Map.empty[ + MatchId, + Map[SelId, TempSymbol]] + // if a key doesn't exist, it means the final dest is only used once + val finalDestToMatchArmFunSymbols = mutable.Map.empty[FinalDest, BlockMemberSymbol] + val selIdsInAllArmsToSymbolsToReplace = mutable.Map.empty[SelId, TempSymbol | VarSymbol] + locally: + def matDestToTempSymbolMap(mat: FinalDest.Match) = + mat.arm.fold(Nil): c => + mat.selsInArm.map: selId => + val ctorFieldNames = c.asCls.get.tree.clsParams + val selName = preAnalyzer.getResult(selId._1).asInstanceOf[Select].name + val idx = ctorFieldNames.indexWhere(_.id == selName) + selId -> mat.tmpSymbolForASpecificCtorId(idx) + for case (c, matchArmDest@FinalDest.Match(matchId, cls)) <- ctorIdToFinalDest do + val numOfCtors = finalDestToCtorIds(matchArmDest).size + if numOfCtors < 1 then + die + else if numOfCtors == 1 then + val selsToTmpSyms = matDestToTempSymbolMap(matchArmDest) + fusingMatchIdToTmpSymbolsToReplacedInAllBranches.updateWith(matchId): + case N => S(selsToTmpSyms.toMap) + case S(m) => S(m ++ selsToTmpSyms) + selIdsInAllArmsToSymbolsToReplace.addAll(selsToTmpSyms) + else + finalDestToMatchArmFunSymbols.getOrElseUpdate.curried(matchArmDest): + for cls <- cls do + val selNameToNewSymbol = mutable.Map.empty[Tree.Ident, VarSymbol] + val selExprIdToNewSymbol = mutable.Map.empty[SelId, VarSymbol] + for selId <- matchArmDest.selsInArm do + val selName = preAnalyzer.getResult(selId._1).asInstanceOf[Select].name + val symName = s"_deforest_${cls.nme}_${selName.name}_${selId._2.makeSuffix(preAnalyzer)}" + val sym = selNameToNewSymbol.getOrElseUpdate.curried(selName): + VarSymbol(Tree.Ident(symName)) + selExprIdToNewSymbol += selId -> sym + finalDestToVarSymbolsToReplaceSelInArms += matchArmDest -> (selExprIdToNewSymbol.toMap -> selNameToNewSymbol.toMap) + fusingMatchIdToVarSymbolsToReplacedInAllBranches.updateWith(matchId): + case N => S(selExprIdToNewSymbol.toMap) + case S(x) => S(x ++ selExprIdToNewSymbol.toMap) + selIdsInAllArmsToSymbolsToReplace.addAll(selExprIdToNewSymbol) + val scrutName = preAnalyzer.getResult(matchId._1).asInstanceOf[Value.Ref].l.nme + val armName = cls.fold("default")(_.nme) + val funSym = BlockMemberSymbol( + s"match_${scrutName}_arm_${armName}_${matchId._2.makeSuffix(preAnalyzer)}", + Nil) + funSym + + + val alwaysNonFreeVars = + sol.preAnalyzer.b.definedVars ++ + newSymToInstIdAndOldSym.keySet ++ + fusingMatchIdToMatchRestFunSymbols.values ++ + finalDestToMatchArmFunSymbols.values + + State.globalThisSymbol + + State.runtimeSymbol + + object freeVarsOfOriginalMatchesConsideringDeforestation: + val store = mutable.Map.empty[MatchId, Ls[Symbol]] + def apply(m: MatchId) = store.getOrElseUpdate.curried(m): + new FreeVarTraverserForMatchConsideringDeforestation(m, drwp).freeVars + + + +class DeforestRewriter(val rewritePrepare: DeforestRewritePrepare)(using Elaborator.State): + val preAnalyzer = rewritePrepare.preAnalyzer + + def apply() = + val withDuplicatedDefs = rewritePrepare.newSymToInstIdAndOldSym.foldRight(Transform(Nil)(preAnalyzer.b)): + case (newSym -> (instId -> oldSym), acc) => + // 1. find original fundefs + // 2. transform fun body under the specific instantiation id + // 3. accumulate the definition + val FunDefn(_, _, param, body) = preAnalyzer.getTopLevelFunDefnForSym(oldSym).get + val oldToNewParam = mutable.Map.empty[VarSymbol, VarSymbol] + val newParam = param.map: + case ParamList(flags, params, restParam) => + def makeNewParam(p: Param) = + val Param(flags, sym, sign, modulefulness) = p + val newSym = VarSymbol(sym.id) + oldToNewParam += sym -> newSym + Param(flags, newSym, sign, modulefulness) + val newParams = params.map(makeNewParam) + val newRestParam = restParam.map(makeNewParam) + ParamList(flags, newParams, newRestParam) + val newBody = Transform(instId)(body).replaceSymbols(oldToNewParam.toMap) + val newFunDefn = FunDefn(N, newSym, newParam, newBody) + Define(newFunDefn, acc) + matchRestOfFusingMatches.getAllFunDefs: + matchArmsOfFusingMatches.getAllFunDefs: + withDuplicatedDefs + + object matchRestOfFusingMatches: + // from match scrut expr id to either a function def with a set of args that should be applied + // or a block containing the computation in the `rest` of the match + val store = mutable.Map.empty[MatchId, Either[FunDefn -> Ls[Symbol], Block]] + def getAllFunDefs = + rewritePrepare.fusingMatchIdToMatchRestFunSymbols.foldRight(identity: Block => Block): + case matchId -> _ -> k => store(matchId) match + case Left(fdef -> _) => r => Define(fdef, k(r)) + case Right(b) => + assert(b.isInstanceOf[End]) + k + + // returns the block of match rest, or a `Return` block that calls the function extracted + // from the match rest + private val outsideQueryAvailable = mutable.Set.empty[MatchId] + opaque type IsInnerCall = Bool + def getOrElseUpdate(matchId: MatchId)(using inner: IsInnerCall = false): Block = store.get(matchId) match + case S(R(blk)) => + if inner || blk.isInstanceOf[End] || outsideQueryAvailable.remove(matchId) then + blk + else + lastWords(s"match rest $blk was expected to be used only once, but now it's used more than once") + case S(L(fdef -> args)) => + Return( + Call(Value.Ref(fdef.sym), args.map(a => Arg(false, Value.Ref(a))))(true, false), + false) + case N => + val scrutExprId -> instantiationId = matchId + val parentMatchesScrutExprIds -> thePossiblyFusingOne = preAnalyzer + .matchScrutToParentMatchScruts(scrutExprId) + .span: x => + !rewritePrepare.rewritingMatchIds.contains(x -> instantiationId) + val transform = new Transform(instantiationId) + val transformedCurrentMatchRest = + transform(preAnalyzer.matchScrutToMatchBlock(scrutExprId).rest) + val withAllParentMatchesRests = + parentMatchesScrutExprIds.foldLeft[Block](transformedCurrentMatchRest): (acc, p) => + val parentMatchRest = transform(preAnalyzer.matchScrutToMatchBlock(p).rest) + val isEnd = parentMatchRest.isInstanceOf[End] + if isEnd then acc else Begin(acc, parentMatchRest) + val withTheRestOfPossiblyFusingMatch = thePossiblyFusingOne.headOption + .fold(withAllParentMatchesRests): scrutExprIdOfTheFusingOne => + Begin(withAllParentMatchesRests, getOrElseUpdate(scrutExprIdOfTheFusingOne, instantiationId)(using true)) + .flattened + + // if the rest is empty or only going to be used once, + // then no need to build a function for it + val noNeedToBuild = + withTheRestOfPossiblyFusingMatch.isInstanceOf[End] || + rewritePrepare.fusingMatchIdToMatchRestFunSymbols.get(matchId).isEmpty + if noNeedToBuild then + if inner then outsideQueryAvailable += matchId + store.updateWith(matchId): + case S(_) => die + case N => S(R(withTheRestOfPossiblyFusingMatch)) + withTheRestOfPossiblyFusingMatch + else // build a new function for the rest of this fusing match + val sym = rewritePrepare.fusingMatchIdToMatchRestFunSymbols(matchId) + val freeVars = withTheRestOfPossiblyFusingMatch + .sortedFvsForTransformedBlocks(rewritePrepare.alwaysNonFreeVars) + val newSymbols = freeVars.map(s => VarSymbol(Tree.Ident(s.nme))) + val newFunDef = FunDefn(N, sym, newSymbols.asParamList :: Nil, + withTheRestOfPossiblyFusingMatch.replaceSymbols(freeVars.zip(newSymbols).toMap)) + store.updateWith(matchId): + case S(_) => die + case N => S(L(newFunDef -> freeVars)) + Return( + Call(Value.Ref(sym), freeVars.map(a => Arg(false, Value.Ref(a))))(true, false), + false) + + object matchArmsOfFusingMatches: + val store = mutable.Map.empty[FinalDest.Match, Either[FunDefn, Block]] + def getAllFunDefs = + rewritePrepare.finalDestToCtorIds.keys.foldRight(identity: Block => Block): + case (m: FinalDest.Match, acc) => + store.get(m) match + case S(L(fdefn)) => rest => Define(fdefn, acc(rest)) + case _ => acc + case (_, acc) => acc + + // return a lambda, which either calls the extracted arm function + // or contains the computations in matching arms + def getOrElseUpdate(ctorId: CtorId): Value.Lam = + val dest = rewritePrepare.ctorIdToFinalDest(ctorId).asInstanceOf[FinalDest.Match] + val freeVarsInTheMatch = + rewritePrepare.freeVarsOfOriginalMatchesConsideringDeforestation(dest.matchId) + val armFunOrBlk = store.updateWith(dest): + case S(R(_)) => die + case S(f@L(_)) => S(f) + case N => S: + val transformedRest = matchRestOfFusingMatches.getOrElseUpdate(dest.matchId) + val originalMatchArmBody = + val matchExpr = preAnalyzer.matchScrutToMatchBlock(dest.matchId._1) + dest.arm.fold(matchExpr.dflt.get): armCls => + matchExpr.arms.find(a => a._1.asInstanceOf[Case.Cls].cls is armCls).get._2 + // this rewrittenBody here already has its selection replaced with + // the pre-computed var symbols (if the arm is used multiple times) + // or the temp symbols (if the arm is used only once) + val rewrittenBody = Begin(Transform(dest.matchId._2)(originalMatchArmBody), transformedRest).flattened + rewritePrepare.finalDestToMatchArmFunSymbols.get(dest) match + case N => R(rewrittenBody) + case Some(funSym) => + val freeVarSymForFunDef = freeVarsInTheMatch.map: x => + VarSymbol(Tree.Ident(x.nme)) + val funBody = rewrittenBody.replaceSymbols(freeVarsInTheMatch.zip(freeVarSymForFunDef).toMap).mapTail: + case Return(res, implct) => Return(res, false) + case t => t + val varSymbolsThatReplacedSelections = + // the order is the same as class ctor param + preAnalyzer.getCtorSymFromCtorLikeExprId(ctorId._1).get.asCls.fold(Nil): c => + c.tree.clsParams.map: p => + rewritePrepare + .finalDestToVarSymbolsToReplaceSelInArms(dest)._2 + .getOrElse(Tree.Ident(p.name), VarSymbol(Tree.Ident(s"_unused_${p.name}"))) + L(FunDefn( + N, + funSym, + (freeVarSymForFunDef ::: varSymbolsThatReplacedSelections).asParamList :: Nil, + funBody)) + val symsForArmFreeVarsInLam = freeVarsInTheMatch.map: x => + VarSymbol(Tree.Ident(x.nme)) + armFunOrBlk.get match + case L(fdefn) => Value.Lam( + symsForArmFreeVarsInLam.asParamList, + Return( + Call( + Value.Ref(fdefn.sym), + symsForArmFreeVarsInLam.asArgsList ::: dest.tmpSymbolForASpecificCtorId.asArgsList)(true, false), + false)) + case R(b) => Value.Lam( + symsForArmFreeVarsInLam.asParamList, + b.replaceSymbols(freeVarsInTheMatch.zip(symsForArmFreeVarsInLam).toMap).mapTail: + case Return(res, implct) => Return(res, false) + case t => t) + + private val uselessSymbolSubst = new SymbolSubst + // rewrite ctor and dtors + // replace selections in match arms + // replace refer site symbols to their new symbols + class Transform(instId: InstantiationId) extends BlockTransformer(uselessSymbolSubst): + extension (resId: ResultId) + def withInstId = resId -> instId + override def applyBlock(b: Block): Block = b match + case mat@Match(scrut, arms, dflt, rest) => + if rewritePrepare.rewritingMatchIds.contains(scrut.uid.withInstId) then + // since all fusing matches will be considered to be in the tail position, + // if any of the parent `rest`s has explicit return, the rewritten match will have explicit return + val oneOfParentMatchRestHasExplicitRet = + preAnalyzer.matchScrutToParentMatchScruts(scrut.uid).foldRight(false): (pid, acc) => + acc || preAnalyzer.matchScrutToMatchBlock(pid).rest.hasExplicitRet + val needExplicitRet = rest.hasExplicitRet || arms.exists(_._2.hasExplicitRet) || oneOfParentMatchRestHasExplicitRet + val freeVars = rewritePrepare.freeVarsOfOriginalMatchesConsideringDeforestation(scrut.uid.withInstId) + Return(Call(scrut, freeVars.asArgsList)(false, false), !needExplicitRet) + else + val allArmWillBeNonEnd = + dflt.fold(false)(_.willBeNonEndTailBlock(instId, rewritePrepare)) && + arms.forall: + case (_, body) => body.willBeNonEndTailBlock(instId, rewritePrepare) + if allArmWillBeNonEnd then + super.applyBlock(Match(scrut, arms, dflt, End(""))) + else + super.applyBlock(b) + case _ => super.applyBlock(b) + + override def applyResult(r: Result): Result = r match + case _: Call => + // calls to fusing contructors are handled in `applyResult2` + // here we only handle calls to non-fusing constructors and functions + assert(!rewritePrepare.ctorIdToFinalDest.isDefinedAt(r.uid.withInstId)) + super.applyResult(r) + case _ => super.applyResult(r) + + override def applyResult2(r: Result)(k: Result => Block): Block = + def handleCallLike(f: Path, ctorResId: ResultId)(args: Ls[Path]) = + val c = preAnalyzer.getCtorSymFromCtorLikeExprId(ctorResId).get.asCls.get + rewritePrepare.ctorIdToFinalDest(ctorResId.withInstId) match + case matchDest@FinalDest.Match(matchId, whichArm) => + // use pre-determined symbols + val tempSymbolsForFields = matchDest.tmpSymbolForASpecificCtorId + tempSymbolsForFields + .zip(args) + .foldRight(k(matchArmsOfFusingMatches.getOrElseUpdate(ctorResId.withInstId))): + case (tmpSym, arg) -> rest => + applyResult2(arg): r => + Assign(tmpSym, r, rest) + case FinalDest.Sel(s) => + val selFieldName = preAnalyzer.getResult(s._1).asInstanceOf[Select].name + val idx = c.tree.clsParams.indexWhere(_.id == selFieldName) + k(args(idx)) + r match + case call@Call(f, args) if rewritePrepare.ctorIdToFinalDest.isDefinedAt(call.uid.withInstId) => + handleCallLike(f, call.uid): + args.map: + case Arg(false, value) => value + case ins@Instantiate(cls, args) if rewritePrepare.ctorIdToFinalDest.isDefinedAt(ins.uid.withInstId) => + handleCallLike(cls, ins.uid)(args) + case _ => super.applyResult2(r)(k) + + override def applyPath(p: Path): Path = p match + // a selection which is a consumer on its own + case Select(qual, _) if rewritePrepare.rewritingSelIds(p.uid.withInstId) => applyPath(qual) + // a selection inside a fusing match that needs to be replaced by pre-computed symbols + case s: Select if rewritePrepare.selIdsInAllArmsToSymbolsToReplace.get(s.uid.withInstId).isDefined => + Value.Ref(rewritePrepare.selIdsInAllArmsToSymbolsToReplace(s.uid.withInstId)) + case s@Select(p, nme) => s.symbol.flatMap(_.asObj) match + // a fusing object constructor + case Some(obj) if rewritePrepare.ctorIdToFinalDest.isDefinedAt(s.uid.withInstId) => + matchArmsOfFusingMatches.getOrElseUpdate(s.uid.withInstId) + case _ => s.symbol.flatMap(_.asBlkMember) match + case Some(blk) if blk.isFunction && preAnalyzer.topLevelDefinedFunSyms.contains(blk) => + val inTheSameRecursiveGroup = instId.lastOption.fold(false): currentReferSite => + val currentSym = preAnalyzer.getReferredFunSym(currentReferSite) + rewritePrepare.sol.collector.funSymToProdStratScheme.recursiveGroups(currentSym).contains(blk) + val newInstId = if inTheSameRecursiveGroup then instId else instId :+ s.uid + rewritePrepare.instIdToMappingFromOldToNewSyms.get(newInstId).fold(super.applyPath(s)): m => + Value.Ref(m(blk)) + case _ => super.applyPath(s) + case v: Value => applyValue(v) + case _ => super.applyPath(p) + + override def applyValue(v: Value): Value = v match + case r@Value.Ref(l) => l.asObj match + case Some(obj) if rewritePrepare.ctorIdToFinalDest.isDefinedAt(r.uid.withInstId) => + matchArmsOfFusingMatches.getOrElseUpdate(r.uid.withInstId) + case None => l.asBlkMember match + case Some(blk) if blk.isFunction && preAnalyzer.topLevelDefinedFunSyms.contains(blk) => + val inTheSameRecursiveGroup = instId.lastOption.fold(false): currentReferSite => + val currentSym = preAnalyzer.getReferredFunSym(currentReferSite) + rewritePrepare.sol.collector.funSymToProdStratScheme.recursiveGroups(currentSym).contains(blk) + val newInstId = if inTheSameRecursiveGroup then instId else instId :+ r.uid + rewritePrepare.instIdToMappingFromOldToNewSyms.get(newInstId).fold(super.applyValue(v)): m => + Value.Ref(m(blk)) + case _ => super.applyValue(v) + case _ => super.applyValue(v) + case _ => super.applyValue(v) + + override def applyFunDefn(fun: FunDefn): FunDefn = + if instId.isEmpty then + fun // skip top level functions + else + super.applyFunDefn(fun) + def apply(b: Block) = applyBlock(b) + + + +// Compute free vars for a block, without considering deforestation. +// Used on blocks after the deforestation transformation. +// This means that for matches we don't need to consider the extra +// free vars that may be introduced by deforestation: +// 1. the free vars from the `rest` of the their parent matches +// 2. the free vars caused by the substitution of selections of scrutinees of their parent matches +class FreeVarTraverser(val blk: Block, alwaysDefined: Set[Symbol]) extends BlockTraverser: + protected val ctx = mutable.Set.from(alwaysDefined) + protected val result = mutable.Set.empty[Symbol] + + override def applyBlock(b: Block): Unit = b match + case Match(scrut, arms, dflt, rest) => + applyPath(scrut) + (arms.map(_._2) ++ dflt).foreach: a => + // dflt may just be `throw error``, and `rest` may use vars assigned in non default arms. + // So use `flattened` to remove dead code (after `throw error`) and spurious free vars. + val realArm = Begin(a, rest) + applyBlock(realArm) + + case Assign(lhs, rhs, rest) => + applyResult(rhs) + ctx += lhs + applyBlock(rest) + ctx -= lhs + case Begin(sub, rest) => applyBlock(b.flattened) + case Define(defn, rest) => defn match + case FunDefn(owner, sym, params, body) => + val paramSymbols = params.flatMap: + case ParamList(_, params, restParam) => (params ++ restParam).map: + case Param(sym = sym, _) => sym + ctx += sym + ctx ++= paramSymbols + applyBlock(body) + ctx --= paramSymbols + applyBlock(rest) + ctx -= sym + case ValDefn(owner, k, sym, rhs) => + ctx += sym + applyPath(rhs) + applyBlock(rest) + ctx -= sym + case c: ClsLikeDefn => die // not supported + + case _ => super.applyBlock(b) + + override def applyValue(v: Value): Unit = v match + case Value.Ref(l) => l match + // builtin symbols and toplevel symbols are always in scope + case _: (BuiltinSymbol | TopLevelSymbol) => () + // NOTE: assume all class definitions are in the toplevel + case b: BlockMemberSymbol if b.asClsLike.isDefined => () + case _ => if !ctx.contains(l) then result += l + case _ => super.applyValue(v) + + override def applyLam(l: Value.Lam): Unit = + val paramSymbols = l.params.params.map(p => p.sym) + ctx ++= paramSymbols + applyBlock(l.body) + ctx --= paramSymbols + + lazy val freeVars = + applyBlock(blk) + result.toList.sortBy(_.uid) + +// Used on match blocks before deforestation transformation +// Compute free vars considering new vars introduced by deforestation +class FreeVarTraverserForMatchConsideringDeforestation( + matchId: MatchId, + drwp: DeforestRewritePrepare +) extends FreeVarTraverser(drwp.preAnalyzer.matchScrutToMatchBlock(matchId._1), drwp.alwaysNonFreeVars): + val instantiationId = matchId._2 + val preAnalyzer = drwp.preAnalyzer + + val selsReplacementByCurrentMatch = + drwp.fusingMatchIdToVarSymbolsToReplacedInAllBranches.getOrElse[Map[SelId, VarSymbol | TempSymbol]](matchId, Map.empty) ++ + drwp.fusingMatchIdToTmpSymbolsToReplacedInAllBranches.getOrElse(matchId, Map.empty) + val selsReplacementNotForThisMatch = + drwp.selIdsInAllArmsToSymbolsToReplace.toMap -- + selsReplacementByCurrentMatch.keySet + val currentMatchScrutSymbol = blk.asInstanceOf[Match].scrut.asInstanceOf[Value.Ref].l + + + override def applyBlock(b: Block): Unit = b match + // a nested match + case m@Match(scrut, _, _, _) => + result ++= drwp.freeVarsOfOriginalMatchesConsideringDeforestation(scrut.uid -> instantiationId) + + // sub-matches' scruts (which are not included in freeVarsOfNonTransformedMatches) + // are also free vars + val Value.Ref(l) = scrut: @unchecked + if !ctx(l) then result += l + + // free vars in nested-matches reported by freeVarsOfNonTransformedMatches may also contain + // spurious ones: those that are going to be substitued by the current match, + // and those that are in the ctx + result --= selsReplacementByCurrentMatch.values + result --= ctx + case _ => super.applyBlock(b) + + override def applyPath(p: Path): Unit = p match + case p@Select(qual, name) => selsReplacementNotForThisMatch.get(p.uid -> instantiationId) match + case None => qual match + // if it is the scrut of current match and the computation containing + // this selection is moved, then the selection will be replaced and there will be no free vars + case Value.Ref(l) if l is currentMatchScrutSymbol => () + case _ => super.applyPath(p) + case Some(s) => result += s + case _ => super.applyPath(p) + + override lazy val freeVars: List[Symbol] = + val Match(_, arms, dflt, rest) = blk: @unchecked + val parentMatchRest = drwp.preAnalyzer + .matchScrutToParentMatchScruts(matchId._1) + .foldRight[Block](End("")): (p, acc) => + Begin(preAnalyzer.matchScrutToMatchBlock(p).rest, acc) + (arms.map(_._2) ++ dflt).foreach: a => + // dflt may just be `throw error``, and `rest` may use vars assigned in non default arms. + // So use `flattened` to remove dead code (after `throw error`) and spurious free vars. + // Also take care of the `rest`s of its parent match blocks. + val realArm = Begin(a, Begin(rest, parentMatchRest)).flattened + applyBlock(realArm) + result.toList.sortBy(_.uid) + +class ReplaceLocalSymTransformer(freeVarsAndTheirNewSyms: Map[Symbol, Symbol]) extends BlockTransformer(new SymbolSubst()): + override def applyValue(v: Value): Value = v match + case Value.Ref(l) => Value.Ref(freeVarsAndTheirNewSyms.getOrElse(l, l)) + case _ => super.applyValue(v) + +class HasExplicitRetTraverser(b: Block) extends BlockTraverserShallow: + var result = false + override def applyBlock(b: Block): Unit = b match + case Return(_, imp) => result = !imp + case _ => super.applyBlock(b) + + applyBlock(b) + +class WillBeNonEndTailBlockTraverser(b: Block, instId: InstantiationId, drwp: DeforestRewritePrepare) extends BlockTraverserShallow: + var result = false + override def applyBlock(b: Block): Unit = b match + case Match(scrut, arms, dflt, rest) => + result = + drwp.rewritingMatchIds.contains(scrut.uid -> instId) || + rest.willBeNonEndTailBlock(instId, drwp) || + locally: + dflt.fold(true)(_.willBeNonEndTailBlock(instId, drwp)) && + arms.forall: + case (_, b) => b.willBeNonEndTailBlock(instId, drwp) + case _: End => () + case _: BlockTail => result = true + case _ => super.applyBlock(b) + + applyBlock(b) + +extension (b: Block) + def replaceSymbols(freeVarsAndTheirNewSyms: Map[Symbol, Symbol]) = + new ReplaceLocalSymTransformer(freeVarsAndTheirNewSyms).applyBlock(b) + def sortedFvsForTransformedBlocks(alwaysDefined: Set[Symbol]) = + new FreeVarTraverser(b, alwaysDefined).freeVars + def hasExplicitRet = + new HasExplicitRetTraverser(b).result + def willBeNonEndTailBlock(instId: InstantiationId, drwp: DeforestRewritePrepare) = + new WillBeNonEndTailBlockTraverser(b, instId, drwp).result + +extension (ps: Ls[VarSymbol]) + def asParamList = ParamList( + ParamListFlags.empty, + ps.map(s => Param(FldFlags.empty, s, N, Modulefulness.none)), + N) + +extension (ss: Ls[Symbol]) + def asArgsList = ss.map: s => + Arg(false, Value.Ref(s)) + +extension (instId: InstantiationId) + def makeSuffix(preAnalyzer: DeforestPreAnalyzer) = + "inst_" + instId.map(preAnalyzer.getStableResultId).mkString("_") + "_tsni" + diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 654ebdb78a..4c1cdadd33 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -29,6 +29,7 @@ abstract class Symbol(using State) extends Located: def refsNumber: Int = directRefs.size def isModule: Bool = asMod.nonEmpty + def isFunction: Bool = asBlkMember.exists(b => b.isFunctionSymbol) def asCls: Opt[ClassSymbol] = this match case cls: ClassSymbol => S(cls) @@ -157,6 +158,7 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[Tree], val nameIsMeaningful: case t: Tree.TermDef if t.rhs.isDefined => t def isParameterizedMethod: Bool = trmTree.exists(_.sParameterizedMethod) + def isFunctionSymbol: Bool = trmTree.exists(_.k is syntax.Fun) lazy val hasLiftedClass: Bool = objTree.isDefined || trmTree.isDefined || clsTree.exists(_.paramLists.nonEmpty) diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index 4d5e9aa828..077652d3e5 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -359,6 +359,20 @@ id(Foo)(1) // ——— ——— ——— +if x > 0 + then A + then B +//│ ╔══[ERROR] Unexpected infix use of keyword 'then' here +//│ ║ l.362: if x > 0 +//│ ║ ^^^^^ +//│ ║ l.363: then A +//│ ╙── ^^^^^^^^ +//│ ╔══[ERROR] Name not found: B +//│ ║ l.364: then B +//│ ╙── ^ + +// ——— ——— ——— + data class Foo(...args) Foo(1, 2, 3).args @@ -367,22 +381,22 @@ Foo(1, 2, 3).args :todo if Foo(1, 2, 3) is Foo(...args) then args //│ ╔══[ERROR] Unrecognized pattern (spread) -//│ ║ l.368: if Foo(1, 2, 3) is Foo(...args) then args +//│ ║ l.382: if Foo(1, 2, 3) is Foo(...args) then args //│ ╙── ^^^^ //│ ╔══[ERROR] the constructor does not take any arguments but found 1 -//│ ║ l.368: if Foo(1, 2, 3) is Foo(...args) then args +//│ ║ l.382: if Foo(1, 2, 3) is Foo(...args) then args //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] Error: match error if Foo(1, 2, 3) is Foo(a, b, c) then [a, b, c] //│ ╔══[ERROR] the constructor does not take any arguments but found 3 -//│ ║ l.377: if Foo(1, 2, 3) is Foo(a, b, c) then [a, b, c] +//│ ║ l.391: if Foo(1, 2, 3) is Foo(a, b, c) then [a, b, c] //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error if Foo(1, 2, 3) is Foo(arg) then arg //│ ╔══[ERROR] the constructor does not take any arguments but found 1 -//│ ║ l.383: if Foo(1, 2, 3) is Foo(arg) then arg +//│ ║ l.397: if Foo(1, 2, 3) is Foo(arg) then arg //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/deforest/append.mls b/hkmc2/shared/src/test/mlscript/deforest/append.mls new file mode 100644 index 0000000000..f34b761d7e --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/append.mls @@ -0,0 +1,103 @@ +:js +:deforest + +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +object Nil +data class (::) Cons(h, t) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun append1(xs1, ys) = if xs1 is + h :: t then h :: append1(t, ys) + Nil then ys +fun append2(xs2, ys) = if xs2 is + h :: t then h :: append2(t, ys) + Nil then ys +fun appendThree(xs, ys, zs) = + append1(append2(xs, ys), zs) +appendThree of + id(1 :: 2 :: Nil) + id(3 :: 4 :: Nil) + id(5 :: 6 :: Nil) +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +// maybe the fusion target for the previous program +fun appendReified(ys, zs) = if ys is + h :: t then h :: appendReified(t, zs) + Nil then zs +fun append1(ys, zs) = ys(zs) +fun append2(xs, ys) = + if xs is + h :: t then + zs => h :: append1(append2(t, ys), zs) // normal fusion + Nil then zs => appendReified(ys, zs) // reified +fun test(xs, ys, zs) = append1(append2(xs, ys), zs) +test of + id(1 :: 2 :: Nil) + id(3 :: 4 :: Nil) + id(5 :: 6 :: Nil) +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun idList(l) = if l is + h :: t then h :: idList(t) + Nil then Nil +fun append(xs, ys) = if xs is + h :: t then h :: append(t, ys) + Nil then idList(ys) +fun appendThree(xs, ys, zs) = + append(append(xs, ys), zs) +appendThree of + id(1 :: 2 :: Nil) + id(3 :: 4 :: Nil) + id(5 :: 6 :: Nil) +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:Nil)@4@inst_0_1_2_tsni --> Ref(xs)@inst_0_3_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(h)), Arg(false,Ref($tmp))))@5@inst_0_1_2_tsni --> Ref(xs)@inst_0_3_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Ref(h)), Arg(false,Ref($tmp))))@6@inst_0_1_tsni --> Ref(xs)@inst_0_3_tsni@Cons +//│ -------------- executing ------------- +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun append(xs, ys) = if xs is + h :: t then h :: append(t, ys) + Nil then idList(ys) +fun concat(lss) = if lss is + hh :: tt then append(hh, concat(tt)) + Nil then Nil +concat of id of + (1 :: 2 :: Nil) :: + (3 :: 4 :: Nil) :: + (5 :: 6 :: Nil) :: Nil +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/def-dup/new-dup-simple.mls b/hkmc2/shared/src/test/mlscript/deforest/def-dup/new-dup-simple.mls new file mode 100644 index 0000000000..dd85db68ff --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/def-dup/new-dup-simple.mls @@ -0,0 +1,248 @@ +:js + +data class A(x) +data class B(x) +data class (::) Cons(h, t) +object Nil + + + +:deforest +fun to(n, acc) = if n == 0 then acc else to(n - 1, n :: acc) +fun f1(ls1, acc) = if ls1 is + Nil then acc + h :: t then f1(t, acc + h) +fun f2(ls2, acc) = if ls2 is + Nil then acc + hh :: tt then f2(tt, acc + hh + 1) +f1(to(4, Nil), 0) + f2(to(5, Nil), 0) + f1(to(6, Nil), 0) +//│ = 51 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:Nil)@6@inst__tsni --> Ref(ls1)@inst_3_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref(acc))))@7@inst_0_tsni --> Ref(ls1)@inst_3_tsni@Cons +//│ Ref(member:Nil)@8@inst__tsni --> Ref(ls2)@inst_4_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref(acc))))@7@inst_1_tsni --> Ref(ls2)@inst_4_tsni@Cons +//│ Ref(member:Nil)@9@inst__tsni --> Ref(ls1)@inst_5_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref(acc))))@7@inst_2_tsni --> Ref(ls1)@inst_5_tsni@Cons +//│ -------------- executing ------------- +//│ = 51 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +:deforest +fun id(x) = x +fun f1(a1) = if a1 is A then 1 +fun f2(a2) = if a2 is A then 2 +f1(id(A(1))) + f2(id(A(2))) +//│ = 3 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Lit(IntLit(2)))))@2@inst__tsni --> Ref(a2)@inst_0_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Lit(IntLit(1)))))@3@inst__tsni --> Ref(a1)@inst_1_tsni@A +//│ -------------- executing ------------- +//│ = 3 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +:deforest +fun p(x) = A(x) +fun wrap1(x) = p(x) +fun wrap(x) = wrap1(x) +fun f1(a1) = if a1 is A then 1 +fun f2(a2) = if a2 is A then 2 +f1(wrap(1)) + f2(wrap(2)) +//│ = 3 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@6@inst_0_1_2_tsni --> Ref(a2)@inst_4_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@6@inst_3_1_2_tsni --> Ref(a1)@inst_5_tsni@A +//│ -------------- executing ------------- +//│ = 3 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +:deforest +fun f(x) = if x == 0 then Nil else x :: g(x - 1) +fun g(y) = if y == 0 then Nil else y :: f(y - 1) +fun cons(a) = if a is + Nil then 0 + h :: t then h + cons(t) +fun cons1(a1) = if a1 is + Nil then 1 + h :: t then h + cons1(t) +cons(f(0)) + cons1(f(1)) +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:Nil)@4@inst_0_tsni --> Ref(a1)@inst_2_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(x)), Arg(false,Ref($tmp))))@5@inst_0_tsni --> Ref(a1)@inst_2_tsni@Cons +//│ Ref(member:Nil)@6@inst_0_tsni --> Ref(a1)@inst_2_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(y)), Arg(false,Ref($tmp))))@7@inst_0_tsni --> Ref(a1)@inst_2_tsni@Cons +//│ Ref(member:Nil)@4@inst_1_tsni --> Ref(a)@inst_3_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(x)), Arg(false,Ref($tmp))))@5@inst_1_tsni --> Ref(a)@inst_3_tsni@Cons +//│ Ref(member:Nil)@6@inst_1_tsni --> Ref(a)@inst_3_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(y)), Arg(false,Ref($tmp))))@7@inst_1_tsni --> Ref(a)@inst_3_tsni@Cons +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +:deforest +fun map(ls, f) = if ls is + Nil then Nil + h :: t then f(h) :: map(t, f) +map(map(1 :: 2 :: Nil, x => x + 1), x => x * 2) +//│ = Cons(4, Cons(6, Nil)) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:Nil)@2@inst_0_tsni --> Ref(ls)@inst_1_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@3@inst_0_tsni --> Ref(ls)@inst_1_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(1))), Arg(false,Ref($tmp))))@4@inst__tsni --> Ref(ls)@inst_0_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(2))), Arg(false,Ref(member:Nil))))@5@inst__tsni --> Ref(ls)@inst_0_tsni@Cons +//│ Ref(member:Nil)@6@inst__tsni --> Ref(ls)@inst_0_tsni@Nil +//│ -------------- executing ------------- +//│ = Cons(4, Cons(6, Nil)) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// for selection +:deforest +fun map(ls, f) = if ls is + Nil then Nil + h :: t then f(h) :: map(t, f) +map(1 :: 2 :: Nil, x => x + 1) +//│ = Cons(2, Cons(3, Nil)) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(1))), Arg(false,Ref($tmp))))@1@inst__tsni --> Ref(ls)@inst_0_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(2))), Arg(false,Ref(member:Nil))))@2@inst__tsni --> Ref(ls)@inst_0_tsni@Cons +//│ Ref(member:Nil)@3@inst__tsni --> Ref(ls)@inst_0_tsni@Nil +//│ -------------- executing ------------- +//│ = Cons(2, Cons(3, Nil)) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// for cyclic strategies removal +:deforest +fun f(x) = if x is + A(_) then f(A(2)) + else 0 +f(Nil) +//│ = 0 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = 0 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// for selection filter +:deforest +fun c1(x) = if x is + h :: t then + if t is + Nil then 0 +c1(1 :: Nil) +//│ = 0 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(1))), Arg(false,Ref(member:Nil))))@1@inst__tsni --> Ref(x)@inst_0_tsni@Cons +//│ Ref(member:Nil)@2@inst__tsni --> Ref(t)@inst_0_tsni@Nil +//│ -------------- executing ------------- +//│ = 0 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// for selection filter +:deforest +fun f(x) = if x is + A then g1(x.x) + B then g2(x.x) +fun g1(y1) = if y1 is + A then 0 +fun g2(y2) = if y2 is + A then 1 +fun apply(f) = + let inner = A(0) + f(A(inner)) +apply(f) +//│ = 0 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Ref(inner))))@3@inst_0_tsni --> Ref(x)@inst_1_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Lit(IntLit(0)))))@4@inst_0_tsni --> Ref(y1)@inst_1_2_tsni@A +//│ -------------- executing ------------- +//│ = 0 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +:deforest +fun inner(y, z) = if y is + B then z +fun dtor(x) = if x is + A then inner(B(4), x.x) +dtor(A(B(0))) +//│ = B(0) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:B),List(Arg(false,Lit(IntLit(4)))))@2@inst_0_tsni --> Ref(y)@inst_0_1_tsni@B +//│ Call(Ref(member:A),List(Arg(false,Ref($tmp))))@3@inst__tsni --> Ref(x)@inst_0_tsni@A +//│ -------------- executing ------------- +//│ = B(0) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +:deforest +fun localFuse(x) = if A(x) is + A(y) then B(y) +if localFuse(3) is + B(a) then a +localFuse(4) +//│ = B(4) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@2@inst_0_tsni --> Ref($scrut)@inst_0_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@2@inst_1_tsni --> Ref($scrut)@inst_1_tsni@A +//│ Call(Ref(member:B),List(Arg(false,Ref(y))))@3@inst_1_tsni --> Ref($scrut)@inst__tsni@B +//│ -------------- executing ------------- +//│ = B(4) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +// cyclic strategy, shouldn't fuse +:deforest +fun f(x) = + let res = if x is + A(x) then x + if res > 0 then f(A(res - 1)) else 0 +f(A(4)) +//│ = 0 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = 0 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +// shouldn't have any fusion due to strategy clash +:deforest +fun g(x) = if x is + A(a) then a +fun f(x) = x.x +let o = A(1) +g(o) + f(o) + o.x +//│ = 3 +//│ o = A(1) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = 3 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/def-dup/new-simple.mls b/hkmc2/shared/src/test/mlscript/deforest/def-dup/new-simple.mls new file mode 100644 index 0000000000..9745482085 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/def-dup/new-simple.mls @@ -0,0 +1,98 @@ +:js + +data class A(x) +data class B(x) +data class (::) Cons(h, t) +object Nil + + + +:deforest +fun f(x) = if x is + A(x) then x +f(A(1)) +//│ = 1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Lit(IntLit(1)))))@1@inst__tsni --> Ref(x)@inst_0_tsni@A +//│ -------------- executing ------------- +//│ = 1 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +:deforest +fun f(x) = if x is + A(a) then x.x + a +f(A(1)) +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Lit(IntLit(1)))))@1@inst__tsni --> Ref(x)@inst_0_tsni@A +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +:deforest +fun p(x) = A(x) +fun wrap1(x) = p(x) +fun wrap(x) = wrap1(x) +fun f1(a1) = if a1 is A then 1 +fun f2(a2) = if a2 is A then 2 +f1(wrap(1)) + f2(wrap(2)) +//│ = 3 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@6@inst_0_1_2_tsni --> Ref(a2)@inst_4_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@6@inst_3_1_2_tsni --> Ref(a1)@inst_5_tsni@A +//│ -------------- executing ------------- +//│ = 3 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +:deforest +fun p1(x) = p2(x) +fun p2(x) = if x == 0 then A(x) else p1(x - 1) +fun c(a) = if a is A(x) then x +c(p1(3)) +//│ = 0 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@2@inst_0_tsni --> Ref(a)@inst_1_tsni@A +//│ -------------- executing ------------- +//│ = 0 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +:deforest +fun f(x) = if x is + A(x) then 2 +f(A(2)) + f(A(3)) +//│ = 4 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Lit(IntLit(3)))))@2@inst__tsni --> Ref(x)@inst_0_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Lit(IntLit(2)))))@3@inst__tsni --> Ref(x)@inst_1_tsni@A +//│ -------------- executing ------------- +//│ = 4 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// let binding outside function body +:deforest +let x = Nil +fun f() = + if x is + Nil then 0 +f() +//│ = 0 +//│ x = Nil +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:Nil)@1@inst__tsni --> Ref(x)@inst_0_tsni@Nil +//│ -------------- executing ------------- +//│ = 0 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/def-dup/simple.mls b/hkmc2/shared/src/test/mlscript/deforest/def-dup/simple.mls new file mode 100644 index 0000000000..44f02b9d4f --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/def-dup/simple.mls @@ -0,0 +1,312 @@ +:js +:deforest + +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +data class A(x) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +data class (::) Cons(h, t) +object Nil +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// `A(1)` is in the definition of `test`, but +// no duplication is helpful +fun test() = + let p = A(1) + let a = if p is + A(x) then x + let b = if p is + A(x) then x + a + b +test() + test() +//│ = 4 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = 4 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun p(d) = A(3) +fun c1(x) = if x is A then 1 +fun c2(x) = if x is A then 2 +fun test() = + c1(p(1)) + c2(p(2)) +test() +//│ = 3 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Lit(IntLit(3)))))@5@inst_0_1_tsni --> Ref(x)@inst_0_3_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Lit(IntLit(3)))))@5@inst_0_2_tsni --> Ref(x)@inst_0_4_tsni@A +//│ -------------- executing ------------- +//│ = 3 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun p(x) = A(x) +fun f1(a1) = if a1 is A(aa) then aa +fun f2(a2) = if a2 is A(aa) then aa +fun f3(a3) = if a3 is A(aa) then aa +fun f4(a4) = if a4 is A(aa) then aa +fun f5(a5) = if a5 is A(aa) then aa +f1(p(1)) + f2(p(2)) + f3(p(3)) + f4(p(4)) + f5(p(5)) +//│ = 15 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@10@inst_0_tsni --> Ref(a5)@inst_5_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@10@inst_1_tsni --> Ref(a4)@inst_6_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@10@inst_2_tsni --> Ref(a3)@inst_7_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@10@inst_3_tsni --> Ref(a2)@inst_8_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@10@inst_4_tsni --> Ref(a1)@inst_9_tsni@A +//│ -------------- executing ------------- +//│ = 15 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +// calls to `to` should be able to be duplicated +fun to(n) = + if n > 0 then + let m = n - 1 + n :: to(m) + else + Nil +fun f1(ls1) = if ls1 is + h :: t then h + f1(t) + Nil then 2 +fun f2(ls2) = if ls2 is + h :: t then h + f2(t) + Nil then 3 +fun f3(ls3) = if ls3 is + h :: t then h + f3(t) + Nil then 4 +fun f4(ls4) = if ls4 is + h :: t then h + f4(t) + Nil then 5 +f1(to(4)) + f2(to(5)) + f3(to(6)) + f4(to(7)) +//│ = 88 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref($tmp))))@8@inst_0_tsni --> Ref(ls4)@inst_4_tsni@Cons +//│ Ref(member:Nil)@9@inst_0_tsni --> Ref(ls4)@inst_4_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref($tmp))))@8@inst_1_tsni --> Ref(ls3)@inst_5_tsni@Cons +//│ Ref(member:Nil)@9@inst_1_tsni --> Ref(ls3)@inst_5_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref($tmp))))@8@inst_2_tsni --> Ref(ls2)@inst_6_tsni@Cons +//│ Ref(member:Nil)@9@inst_2_tsni --> Ref(ls2)@inst_6_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref($tmp))))@8@inst_3_tsni --> Ref(ls1)@inst_7_tsni@Cons +//│ Ref(member:Nil)@9@inst_3_tsni --> Ref(ls1)@inst_7_tsni@Nil +//│ -------------- executing ------------- +//│ = 88 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun to(n) = + if n > 0 then + let m = n - 1 + n :: to(m) + else + Nil +fun f1(ls1) = if ls1 is + h :: t then h + f1(t) + Nil then 2 +f1(to(4)) +//│ = 12 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref($tmp))))@2@inst_0_tsni --> Ref(ls1)@inst_1_tsni@Cons +//│ Ref(member:Nil)@3@inst_0_tsni --> Ref(ls1)@inst_1_tsni@Nil +//│ -------------- executing ------------- +//│ = 12 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun map(ls, f) = if ls is + Nil then Nil + h :: t then f(h) :: map(t, f) +fun succ(x) = x + 1 +fun double(x) = x * 2 +fun test() = + map(map(1 :: 2 :: Nil, succ), double) +test() +//│ = Cons(4, Cons(6, Nil)) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(1))), Arg(false,Ref($tmp))))@3@inst_0_tsni --> Ref(ls)@inst_0_1_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(2))), Arg(false,Ref(member:Nil))))@4@inst_0_tsni --> Ref(ls)@inst_0_1_tsni@Cons +//│ Ref(member:Nil)@5@inst_0_tsni --> Ref(ls)@inst_0_1_tsni@Nil +//│ Ref(member:Nil)@6@inst_0_1_tsni --> Ref(ls)@inst_0_2_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@7@inst_0_1_tsni --> Ref(ls)@inst_0_2_tsni@Cons +//│ -------------- executing ------------- +//│ = Cons(4, Cons(6, Nil)) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun p(x) = A(x) +fun g1(b) = if b then p(1) else p(2) +fun wrap(b) = g1(b) +fun g2(b) = if b then p(3) else p(4) +fun f1(p) = if p is A(a) then a + 1 +fun f2(p) = if p is A(b) then b + 2 +f1(wrap(true)) + f2(g2(false)) +//│ = 8 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@9@inst_0_1_tsni --> Ref(p)@inst_7_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@9@inst_0_2_tsni --> Ref(p)@inst_7_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@9@inst_3_4_5_tsni --> Ref(p)@inst_8_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@9@inst_3_4_6_tsni --> Ref(p)@inst_8_tsni@A +//│ -------------- executing ------------- +//│ = 8 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +fun p(x) = A(x) +let one = p(1) +let two = p(2) +fun f1(p) = if p is A(a) then a + 1 +fun f2(p) = if p is A(b) then b + 2 +fun f3(p) = if p is A(c) then c + 3 +f1(one) + f2(one) + f3(two) +//│ = 10 +//│ one = A(1) +//│ two = A(2) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@2@inst_0_tsni --> Ref(p)@inst_1_tsni@A +//│ -------------- executing ------------- +//│ = 10 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun id(x) = x +fun f1(a) = if a is A(i) then i +fun f2(a) = if a is A(i) then i + 1 +f1(id(A(1))) + f2(id(A(2))) +//│ = 4 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Lit(IntLit(2)))))@2@inst__tsni --> Ref(a)@inst_0_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Lit(IntLit(1)))))@3@inst__tsni --> Ref(a)@inst_1_tsni@A +//│ -------------- executing ------------- +//│ = 4 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + + + + +fun to(n) = if n > 0 then n :: to(n - 1) else Nil +fun f1(ls) = if ls is + h :: t then h + Nil then 2 +fun f2(ls) = if ls is + h :: t then h + 1 + Nil then 3 +fun wrap(n) = to(n) +f1(wrap(4)) + f2(wrap(5)) +//│ = 10 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref($tmp))))@5@inst_0_1_tsni --> Ref(ls)@inst_3_tsni@Cons +//│ Ref(member:Nil)@6@inst_0_1_tsni --> Ref(ls)@inst_3_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref($tmp))))@5@inst_2_1_tsni --> Ref(ls)@inst_4_tsni@Cons +//│ Ref(member:Nil)@6@inst_2_1_tsni --> Ref(ls)@inst_4_tsni@Nil +//│ -------------- executing ------------- +//│ = 10 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + + + +fun to(n, acc) = if n == 0 then acc else to(n - 1, n :: acc) +fun f1(ls, acc) = if ls is + Nil then acc + h :: t then f1(t, acc + h) +fun f2(ls, acc) = if ls is + Nil then acc + h :: t then f2(t, acc + h + 1) +f1(to(4, Nil), 0) + f2(to(5, Nil), 0) + f1(to(6, Nil), 0) +//│ = 51 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:Nil)@6@inst__tsni --> Ref(ls)@inst_3_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref(acc))))@7@inst_0_tsni --> Ref(ls)@inst_3_tsni@Cons +//│ Ref(member:Nil)@8@inst__tsni --> Ref(ls)@inst_4_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref(acc))))@7@inst_1_tsni --> Ref(ls)@inst_4_tsni@Cons +//│ Ref(member:Nil)@9@inst__tsni --> Ref(ls)@inst_5_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(n)), Arg(false,Ref(acc))))@7@inst_2_tsni --> Ref(ls)@inst_5_tsni@Cons +//│ -------------- executing ------------- +//│ = 51 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun map(ls, f) = if ls is + Nil then Nil + h :: t then f(h) :: map(t, f) +fun succ(x) = x + 1 +fun double(x) = x * 2 +fun triple(x) = x * 3 +fun test() = + map(map(map(1 :: 2 :: Nil, succ), double), triple) +test() +// //│ = Cons(12, Cons(18, Nil)) +// //│ duplication chances: +// //│ Call(Ref(member:map),List(Arg(false,Ref($tmp)), Arg(false,Ref(member:succ)))) <-- dup --> member:map_duplicated@1750 +// //│ Call(Ref(member:map),List(Arg(false,Ref($tmp)), Arg(false,Ref(member:double)))) <-- dup --> member:map_duplicated@1750 +// //│ ========= +// //│ No fusion opportunity +//│ = Cons(12, Cons(18, Nil)) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(1))), Arg(false,Ref($tmp))))@4@inst_0_tsni --> Ref(ls)@inst_0_1_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(2))), Arg(false,Ref(member:Nil))))@5@inst_0_tsni --> Ref(ls)@inst_0_1_tsni@Cons +//│ Ref(member:Nil)@6@inst_0_tsni --> Ref(ls)@inst_0_1_tsni@Nil +//│ Ref(member:Nil)@7@inst_0_1_tsni --> Ref(ls)@inst_0_2_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@8@inst_0_1_tsni --> Ref(ls)@inst_0_2_tsni@Cons +//│ Ref(member:Nil)@7@inst_0_2_tsni --> Ref(ls)@inst_0_3_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@8@inst_0_2_tsni --> Ref(ls)@inst_0_3_tsni@Cons +//│ -------------- executing ------------- +//│ = Cons(12, Cons(18, Nil)) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +fun p(x) = A(x) +fun f1(a1) = if a1 is A(a) then a + 1 +fun f2(a2) = if a2 is A(a) then a + 2 +fun wrap(a) = a +fun test() = + f1(wrap(p(2))) + f2(p(1)) +test() +//│ = 6 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@5@inst_0_1_tsni --> Ref(a1)@inst_0_3_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@5@inst_0_2_tsni --> Ref(a2)@inst_0_4_tsni@A +//│ -------------- executing ------------- +//│ = 6 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/def-dup/todo.mls b/hkmc2/shared/src/test/mlscript/deforest/def-dup/todo.mls new file mode 100644 index 0000000000..9618fdcfb6 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/def-dup/todo.mls @@ -0,0 +1,103 @@ +:js +:deforest + +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +data class A(x) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +data class (::) Cons(h, t) +object Nil +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun to(n) = if n > 0 then n :: to(n - 1) else Nil +fun f1(ls) = if ls is + h :: t then h + Nil then 2 +fun f2(ls) = if ls is + h :: t then h + 1 + Nil then 3 +fun badWrap(n) = + let x = to(n) + f1(x) + f2(x) + x +f1(badWrap(4)) + f2(badWrap(5)) +//│ = 10 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = 10 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +fun p(x) = A(x) +let one = p(1) +let two = p(2) +fun f1(p) = if p is A(a) then a + 1 +fun f2(p) = if p is A(b) then b + 2 +f1(one) + f2(one) + f2(two) +//│ = 9 +//│ one = A(1) +//│ two = A(2) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Ref(x))))@2@inst_0_tsni --> Ref(p)@inst_1_tsni@A +//│ -------------- executing ------------- +//│ = 9 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun p(a) = a +fun f1(a1) = if a1 is A(x1) then x1 +fun f2(a2) = if a2 is A(x2) then x2 + 1 +fun test() = + let x = A(1) + let y = A(2) + let res1 = p(if true then x else y) // both x and y flows into this callsiteres + let res2 = p(x) // x flows into this callsiteres + let res3 = p(y) // y flows into this callsiteres + // f1(res1) + f2(res1) + f1(res2) + f2(res3) + f1(res1) + f1(res2) + f2(res3) +test() +//│ = 5 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = 5 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun f1(a1) = if a1 is A(x1) then x1 +fun f2(a2) = if a2 is A(x2) then x2 + 1 +fun p(x, clashedCtor) = + if true then A(x) + else clashedCtor +let clashed = A(3) +f1(p(1, clashed)) + f2(p(2, clashed)) +//│ = 4 +//│ clashed = A(3) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = 4 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + diff --git a/hkmc2/shared/src/test/mlscript/deforest/determinism.mls b/hkmc2/shared/src/test/mlscript/deforest/determinism.mls new file mode 100644 index 0000000000..07c1c5de36 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/determinism.mls @@ -0,0 +1,199 @@ +:js + +data class A(a) +data class B(a, b, c, d, e) + +:deforest +:sjs +if B(1,2,3,4,5) is + B(a,b,c,d,e) then 0 +//│ JS (unsanitized): +//│ let scrut, param0, param1, param2, param3, param4, a, b, c, d, e; +//│ scrut = B1(1, 2, 3, 4, 5); +//│ if (scrut instanceof B1.class) { +//│ param0 = scrut.a; +//│ param1 = scrut.b; +//│ param2 = scrut.c; +//│ param3 = scrut.d; +//│ param4 = scrut.e; +//│ a = param0; +//│ b = param1; +//│ c = param2; +//│ d = param3; +//│ e = param4; +//│ 0 +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ >>>>>>>>>>>>>>>>>>>>>>>>> Deforestation JS >>>>>>>>>>>>>>>>>>>>>>>>>> +//│ let scrut, a, b, c, d, e; +//│ a = 1; +//│ b = 2; +//│ c = 3; +//│ d = 4; +//│ e = 5; +//│ scrut = () => { +//│ let param0, param1, param2, param3, param4, a1, b1, c1, d1, e1; +//│ param0 = a; +//│ param1 = b; +//│ param2 = c; +//│ param3 = d; +//│ param4 = e; +//│ a1 = param0; +//│ b1 = param1; +//│ c1 = param2; +//│ d1 = param3; +//│ e1 = param4; +//│ return 0 +//│ }; +//│ runtime.safeCall(scrut()) +//│ <<<<<<<<<<<<<<<<<<<<<<<<< Deforestation JS <<<<<<<<<<<<<<<<<<<<<<<<<< +//│ = 0 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:B),List(Arg(false,Lit(IntLit(1))), Arg(false,Lit(IntLit(2))), Arg(false,Lit(IntLit(3))), Arg(false,Lit(IntLit(4))), Arg(false,Lit(IntLit(5)))))@0@inst__tsni --> Ref($scrut)@inst__tsni@B +//│ -------------- executing ------------- +//│ = 0 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +:sjs +:deforest +fun c2(x) = if x is + A(A(A(A(A(A(a)))))) then a +c2 of A(A(A(A(A(A(1)))))) +//│ JS (unsanitized): +//│ let c2, tmp, tmp1, tmp2, tmp3, tmp4, tmp5; +//│ c2 = function c2(x) { +//│ let param01, param02, param03, param04, param05, param06, a2; +//│ if (x instanceof A1.class) { +//│ param01 = x.a; +//│ if (param01 instanceof A1.class) { +//│ param02 = param01.a; +//│ if (param02 instanceof A1.class) { +//│ param03 = param02.a; +//│ if (param03 instanceof A1.class) { +//│ param04 = param03.a; +//│ if (param04 instanceof A1.class) { +//│ param05 = param04.a; +//│ if (param05 instanceof A1.class) { +//│ param06 = param05.a; +//│ a2 = param06; +//│ return a2 +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ tmp = A1(1); +//│ tmp1 = A1(tmp); +//│ tmp2 = A1(tmp1); +//│ tmp3 = A1(tmp2); +//│ tmp4 = A1(tmp3); +//│ tmp5 = A1(tmp4); +//│ c2(tmp5) +//│ >>>>>>>>>>>>>>>>>>>>>>>>> Deforestation JS >>>>>>>>>>>>>>>>>>>>>>>>>> +//│ let c2, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, c2_inst_0_tsni, a2, a3, a4, a5, a6, a7; +//│ c2_inst_0_tsni = function c2_inst_0_tsni(x) { +//│ return runtime.safeCall(x()) +//│ }; +//│ c2 = function c2(x) { +//│ let param01, param02, param03, param04, param05, param06, a8; +//│ if (x instanceof A1.class) { +//│ param01 = x.a; +//│ if (param01 instanceof A1.class) { +//│ param02 = param01.a; +//│ if (param02 instanceof A1.class) { +//│ param03 = param02.a; +//│ if (param03 instanceof A1.class) { +//│ param04 = param03.a; +//│ if (param04 instanceof A1.class) { +//│ param05 = param04.a; +//│ if (param05 instanceof A1.class) { +//│ param06 = param05.a; +//│ a8 = param06; +//│ return a8 +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ a7 = 1; +//│ tmp = () => { +//│ let param01, a8; +//│ param01 = a7; +//│ a8 = param01; +//│ return a8 +//│ }; +//│ a6 = tmp; +//│ tmp1 = () => { +//│ let param01; +//│ param01 = a6; +//│ return runtime.safeCall(param01()) +//│ }; +//│ a5 = tmp1; +//│ tmp2 = () => { +//│ let param01; +//│ param01 = a5; +//│ return runtime.safeCall(param01()) +//│ }; +//│ a4 = tmp2; +//│ tmp3 = () => { +//│ let param01; +//│ param01 = a4; +//│ return runtime.safeCall(param01()) +//│ }; +//│ a3 = tmp3; +//│ tmp4 = () => { +//│ let param01; +//│ param01 = a3; +//│ return runtime.safeCall(param01()) +//│ }; +//│ a2 = tmp4; +//│ tmp5 = () => { +//│ let param01; +//│ param01 = a2; +//│ return runtime.safeCall(param01()) +//│ }; +//│ c2_inst_0_tsni(tmp5) +//│ <<<<<<<<<<<<<<<<<<<<<<<<< Deforestation JS <<<<<<<<<<<<<<<<<<<<<<<<<< +//│ = 1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:A),List(Arg(false,Ref($tmp))))@1@inst__tsni --> Ref(x)@inst_0_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref($tmp))))@2@inst__tsni --> Ref($param0)@inst_0_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref($tmp))))@3@inst__tsni --> Ref($param0)@inst_0_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref($tmp))))@4@inst__tsni --> Ref($param0)@inst_0_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Ref($tmp))))@5@inst__tsni --> Ref($param0)@inst_0_tsni@A +//│ Call(Ref(member:A),List(Arg(false,Lit(IntLit(1)))))@6@inst__tsni --> Ref($param0)@inst_0_tsni@A +//│ -------------- executing ------------- +//│ = 1 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/imperative.mls b/hkmc2/shared/src/test/mlscript/deforest/imperative.mls new file mode 100644 index 0000000000..70de64f95a --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/imperative.mls @@ -0,0 +1,67 @@ +:js +:deforest + +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +object A +object B +// object C +// class AA(aa) +// class BB(bb) +// object None +// class Some(value) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +// * Not fused: two `x is A` consumers +let x = if true then A else B +fun foo(x) = + if x is A do print(123) + if x is + A then 1 + B then 2 +//│ x = A +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +// * We could make it work. But it's a special case that's probably not very important +let x = if true then A else B +fun foo(x) = + if x is A do print(123) + if x is B do print(456) +//│ x = A +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +fun foo(k, x) = + if x === 0 do k(A) + k of + if x > 0 + then A + else B +fun bar(v) = if v is + A then 1 + B then 2 +foo(bar, 123) +//│ = 1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:B)@2@inst_0_tsni --> Ref(v)@inst_1_tsni@B +//│ Ref(member:A)@3@inst_0_tsni --> Ref(v)@inst_1_tsni@A +//│ Ref(member:A)@4@inst_0_tsni --> Ref(v)@inst_1_tsni@A +//│ -------------- executing ------------- +//│ = 1 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + diff --git a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls new file mode 100644 index 0000000000..95893feec4 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls @@ -0,0 +1,132 @@ +:js +:deforest + +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< +object Nil +data class (::) Cons(h, t) +data class Pair(a, b) +object A +object B +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun zip(xs_zip, ys_zip) = if + xs_zip is x :: xt and ys_zip is y :: yt then Pair(x, y) :: zip(xt, yt) + else Nil +fun enumFromTo(a, b) = if a < (b + 1) then a :: enumFromTo(a + 1, b) else Nil +fun test() = + fun lscomp1(ls) = if ls is + Pair(x, y1) :: t then + fun lscomp2(ls2) = if ls2 is + Pair(y2, z) :: t2 and + y1 == y2 then Pair(x, z) :: lscomp2(t2) + else lscomp2(t2) + else lscomp1(t) + lscomp2(zip(enumFromTo(x, x + 2), enumFromTo(y1, y1 + 1))) + else Nil + lscomp1(zip(enumFromTo(1, 3), enumFromTo(2, 4))) +test() +//│ = Cons(Pair(1, 3), Cons(Pair(2, 4), Cons(Pair(3, 5), Nil))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref(a)), Arg(false,Ref($tmp))))@7@inst_0_1_tsni --> Ref(xs_zip)@inst_0_3_tsni@Cons +//│ Ref(member:Nil)@8@inst_0_1_tsni --> Ref(xs_zip)@inst_0_3_tsni@dflt +//│ Call(Ref(member:Cons),List(Arg(false,Ref(a)), Arg(false,Ref($tmp))))@7@inst_0_2_tsni --> Ref(ys_zip)@inst_0_3_tsni@Cons +//│ Ref(member:Nil)@8@inst_0_2_tsni --> Ref(ys_zip)@inst_0_3_tsni@dflt +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@9@inst_0_3_tsni --> Ref(ls2)@inst_0_tsni@Cons +//│ Ref(member:Nil)@10@inst_0_3_tsni --> Ref(ls2)@inst_0_tsni@dflt +//│ Ref(member:Nil)@11@inst_0_3_tsni --> Ref(ls2)@inst_0_tsni@dflt +//│ Call(Ref(member:Pair),List(Arg(false,Ref(x)), Arg(false,Ref(y))))@12@inst_0_3_tsni --> Ref($param0)@inst_0_tsni@Pair +//│ Call(Ref(member:Cons),List(Arg(false,Ref(a)), Arg(false,Ref($tmp))))@7@inst_0_4_tsni --> Ref(xs_zip)@inst_0_6_tsni@Cons +//│ Ref(member:Nil)@8@inst_0_4_tsni --> Ref(xs_zip)@inst_0_6_tsni@dflt +//│ Call(Ref(member:Cons),List(Arg(false,Ref(a)), Arg(false,Ref($tmp))))@7@inst_0_5_tsni --> Ref(ys_zip)@inst_0_6_tsni@Cons +//│ Ref(member:Nil)@8@inst_0_5_tsni --> Ref(ys_zip)@inst_0_6_tsni@dflt +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@9@inst_0_6_tsni --> Ref(ls)@inst_0_tsni@Cons +//│ Ref(member:Nil)@10@inst_0_6_tsni --> Ref(ls)@inst_0_tsni@dflt +//│ Ref(member:Nil)@11@inst_0_6_tsni --> Ref(ls)@inst_0_tsni@dflt +//│ Call(Ref(member:Pair),List(Arg(false,Ref(x)), Arg(false,Ref(y))))@12@inst_0_6_tsni --> Ref($param0)@inst_0_tsni@Pair +//│ -------------- executing ------------- +//│ = Cons(Pair(1, 3), Cons(Pair(2, 4), Cons(Pair(3, 5), Nil))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun append(xs, ys) = if xs is + h :: t then h :: append(t, ys) + Nil then ys +fun concatMap(f, ls) = if ls is + h :: t then append(f(h), concatMap(f, t)) + Nil then Nil +fun test() = + fun f1(a1) = if a1 is + Pair(a, b) then + fun f2(a2) = if a2 is Pair(c, d) then Pair(a, c) :: Nil else Nil + concatMap(f2, Pair(1, 3) :: Pair(2, 3) :: Pair(a, b) :: Nil) + else Nil + concatMap(f1, Pair(5, 10) :: Nil) +test() +//│ = Cons(Pair(5, 1), Cons(Pair(5, 2), Cons(Pair(5, 5), Nil))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@3@inst_0_tsni --> Ref(ls)@inst_0_1_tsni@Cons +//│ Call(Ref(member:Pair),List(Arg(false,Lit(IntLit(1))), Arg(false,Lit(IntLit(3)))))@4@inst_0_tsni --> Ref(a2)@inst_0_tsni@Pair +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@5@inst_0_tsni --> Ref(ls)@inst_0_1_tsni@Cons +//│ Call(Ref(member:Pair),List(Arg(false,Lit(IntLit(2))), Arg(false,Lit(IntLit(3)))))@6@inst_0_tsni --> Ref(a2)@inst_0_tsni@Pair +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref(member:Nil))))@7@inst_0_tsni --> Ref(ls)@inst_0_1_tsni@Cons +//│ Call(Ref(member:Pair),List(Arg(false,Ref(a)), Arg(false,Ref(b))))@8@inst_0_tsni --> Ref(a2)@inst_0_tsni@Pair +//│ Ref(member:Nil)@9@inst_0_tsni --> Ref(ls)@inst_0_1_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref(member:Nil))))@10@inst_0_tsni --> Ref(ls)@inst_0_2_tsni@Cons +//│ Call(Ref(member:Pair),List(Arg(false,Lit(IntLit(5))), Arg(false,Lit(IntLit(10)))))@11@inst_0_tsni --> Ref(a1)@inst_0_tsni@Pair +//│ Ref(member:Nil)@12@inst_0_tsni --> Ref(ls)@inst_0_2_tsni@Nil +//│ -------------- executing ------------- +//│ = Cons(Pair(5, 1), Cons(Pair(5, 2), Cons(Pair(5, 5), Nil))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun lscomp1(ls1, ls2) = if ls1 is + h1 :: t1 then lscomp2(ls2, h1, t1) + Nil then Nil +fun lscomp2(ls2, h1, t1) = if ls2 is + h2 :: t2 then Pair(h1, h2) :: lscomp2(t2, h1, t1) + Nil then lscomp1(t1, ls2) +lscomp1(1 :: 2 :: Nil, 3 :: 4 :: Nil) +//│ = Cons(Pair(1, 3), Cons(Pair(1, 4), Nil)) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(3))), Arg(false,Ref($tmp))))@1@inst__tsni --> Ref(ls2)@inst_0_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(4))), Arg(false,Ref(member:Nil))))@2@inst__tsni --> Ref(ls2)@inst_0_tsni@Cons +//│ Ref(member:Nil)@3@inst__tsni --> Ref(ls2)@inst_0_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(1))), Arg(false,Ref($tmp))))@4@inst__tsni --> Ref(ls1)@inst_0_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(2))), Arg(false,Ref(member:Nil))))@5@inst__tsni --> Ref(ls1)@inst_0_tsni@Cons +//│ Ref(member:Nil)@6@inst__tsni --> Ref(ls1)@inst_0_tsni@Nil +//│ -------------- executing ------------- +//│ = Cons(Pair(1, 3), Cons(Pair(1, 4), Nil)) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun test(k, ls) = + fun lscomp(ls) = if ls is + h :: t then (h + 1) :: lscomp(t) + Nil then Nil + if k is + A then lscomp(ls) + B then Nil +test(A, 1 :: 2 :: 3 :: Nil) +//│ = Cons(2, Cons(3, Cons(4, Nil))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst__tsni --> Ref(k)@inst_0_tsni@A +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(1))), Arg(false,Ref($tmp))))@2@inst__tsni --> Ref(ls)@inst_0_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(2))), Arg(false,Ref($tmp))))@3@inst__tsni --> Ref(ls)@inst_0_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(3))), Arg(false,Ref(member:Nil))))@4@inst__tsni --> Ref(ls)@inst_0_tsni@Cons +//│ Ref(member:Nil)@5@inst__tsni --> Ref(ls)@inst_0_tsni@Nil +//│ -------------- executing ------------- +//│ = Cons(2, Cons(3, Cons(4, Nil))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls b/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls new file mode 100644 index 0000000000..f2968507f3 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls @@ -0,0 +1,442 @@ +:js +:deforest + +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< +object A +object B +object C +data class AA(x) +data class BB(x) +data class CC(x) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +object Nil +data class Cons(h, t) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +object None +data class Some(x) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun f(x, y) = + let t = if x is + AA(a) then + let m = if a is + AA(x) then x + m + 9 + BB(b) then b + t + y +fun g(x) = if x is + AA then 0 +let a = AA(AA(3)) +f(a, 2) + g(a) + f(BB(3), 2) + f(AA(AA(4)), 5) +//│ = 37 +//│ a = AA(AA(3)) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@2@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(3)))))@3@inst__tsni --> Ref(x)@inst_1_tsni@BB +//│ -------------- executing ------------- +//│ = 37 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun f(x, y) = + let n = if x is + AA(a) then + if a is + AA(m) then m + n + 2 + y +f(AA(AA(3)), 9) + f(AA(AA(4)), 10) +//│ = 30 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@2@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(4)))))@3@inst__tsni --> Ref(a)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@4@inst__tsni --> Ref(x)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(3)))))@5@inst__tsni --> Ref(a)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = 30 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun f(x) = + let n = if x is + AA(a) then + if a is + AA(m) then m + n + 2 +f(AA(AA(3))) +//│ = 5 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@1@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(3)))))@2@inst__tsni --> Ref(a)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 5 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun f(x) = + let t = if x is + AA(AA(a)) then a + t + 3 +f(AA(AA(A))) + f(AA(AA(A))) +//│ = "A3A3" +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@2@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref(member:A))))@3@inst__tsni --> Ref($param0)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@4@inst__tsni --> Ref(x)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref(member:A))))@5@inst__tsni --> Ref($param0)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = "A3A3" +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun c2(x) = if x is + AA(AA(a)) then a +c2(AA(AA(0))) +//│ = 0 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@1@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(0)))))@2@inst__tsni --> Ref($param0)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 0 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun f(a) = if a is + AA(BB(B)) then 3 +f(AA(BB(B))) +//│ = 3 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@1@inst__tsni --> Ref(a)@inst_0_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Ref(member:B))))@2@inst__tsni --> Ref($param0)@inst_0_tsni@BB +//│ Ref(member:B)@3@inst__tsni --> Ref($param0)@inst_0_tsni@B +//│ -------------- executing ------------- +//│ = 3 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun test(x, y, z, i) = if x is + AA(a) then + let m = if y is + AA(a1) then a1 + i + let n = if z is + BB(a2) then a2 - i + m + n +test(AA(1), AA(2), BB(3), 4) + test(AA(1), AA(2), BB(3), 4) +//│ = 10 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(3)))))@2@inst__tsni --> Ref(z)@inst_0_tsni@BB +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@3@inst__tsni --> Ref(y)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(1)))))@4@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(3)))))@5@inst__tsni --> Ref(z)@inst_1_tsni@BB +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@6@inst__tsni --> Ref(y)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(1)))))@7@inst__tsni --> Ref(x)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = 10 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// only the match in the middle is fused +fun f1(x) = if x is + AA(x1) then + if x1 is + BB(x2) then + if x2 is + CC(x3) then x3 +fun f2(x) = if x is AA then "f2" +fun f3(x) = if x is CC then "f3" + x.x +fun aa(x) = AA(x) +let cc = CC("cc") +f1(aa(BB(cc))) + f2(aa(BB(CC(0)))) + f3(cc) +//│ = "ccf2f3cc" +//│ cc = CC("cc") +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref(x))))@4@inst_0_tsni --> Ref(x)@inst_2_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref(x))))@4@inst_1_tsni --> Ref(x)@inst_3_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Ref(cc))))@5@inst__tsni --> Ref(x1)@inst_3_tsni@BB +//│ -------------- executing ------------- +//│ = "ccf2f3cc" +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun c(x, y) = if x is + AA then + let t = if y is + A then 2 + t + x.x +let y = A +c(AA(2), y) +//│ = 4 +//│ y = A +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@1@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Ref(member:A)@2@inst__tsni --> Ref(y)@inst_0_tsni@A +//│ -------------- executing ------------- +//│ = 4 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun c(x, y) = if x is + AA then + let t = if y is + A then 2 + x.x + t + x.x +fun c2(y) = if y is A then 3 +let y = A +c(AA(2), y) + c2(y) +//│ = 9 +//│ y = A +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@1@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 9 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// need to include computations of `rest` from more than one levels of parent matches +fun test(x) = + let t = if x is + AA(AA(AA(a))) then a + AA then "3" + t + "5" +fun f(a) = if a is + AA(AA) then "0" +let p = AA(AA(AA("10"))) +test(p) + f(p) + test(AA(A)) +//│ = "105035" +//│ p = AA(AA(AA("10"))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref(member:A))))@2@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Ref(member:A)@3@inst__tsni --> Ref($param0)@inst_0_tsni@dflt +//│ Call(Ref(member:AA),List(Arg(false,Lit(StrLit(10)))))@4@inst__tsni --> Ref($param0)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = "105035" +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +fun f(x, y) = + let tmp = BB(y + 1) + if tmp is + BB then + let m = if x is + AA then + if AA(2) is + AA(yf) then yf + if AA(3) is + AA then m + tmp.x +let aa = AA(3) +f(aa, 3) + f(aa, 4) +//│ = 13 +//│ aa = AA(3) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:BB),List(Arg(false,Ref($tmp))))@2@inst_0_tsni --> Ref(tmp)@inst_0_tsni@BB +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@3@inst_0_tsni --> Ref($scrut)@inst_0_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Ref($tmp))))@2@inst_1_tsni --> Ref(tmp)@inst_1_tsni@BB +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@3@inst_1_tsni --> Ref($scrut)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = 13 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun test(n, p, q) = + n + + if AA(0) is + AA then + let k = if p is + AA(x) then x + if q is + AA(y) then k + y +test(3, AA(2), AA(3)) + test(3, AA(2), AA(3)) +//│ = 16 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(0)))))@2@inst_0_tsni --> Ref($scrut)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(3)))))@3@inst__tsni --> Ref(q)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@4@inst__tsni --> Ref(p)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(0)))))@2@inst_1_tsni --> Ref($scrut)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(3)))))@5@inst__tsni --> Ref(q)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@6@inst__tsni --> Ref(p)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = 16 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun test(n, p, q, a) = + let o + if a is + A then + let k + if p is + AA(x) then + k = x + if q is + AA(y) then + o = k + y + o + n +let a = A +fun c(a) = if a is A then 0 +test(1, AA(2), AA(3), a) + test(2, AA(2), AA(3), a) + c(a) +//│ = 13 +//│ a = A +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(3)))))@2@inst__tsni --> Ref(q)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@3@inst__tsni --> Ref(p)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(3)))))@4@inst__tsni --> Ref(q)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@5@inst__tsni --> Ref(p)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = 13 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun test(x, y, z, i) = + let k = if x is + AA(a) then + let m = if y is + AA(a1) then a1 + i + let n = if z is + BB(a2) then a2 - i + m + n + k + i +test(AA(1), AA(2), BB(3), 4) + test(AA(1), AA(2), BB(3), 4) +//│ = 18 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(3)))))@2@inst__tsni --> Ref(z)@inst_0_tsni@BB +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@3@inst__tsni --> Ref(y)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(1)))))@4@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(3)))))@5@inst__tsni --> Ref(z)@inst_1_tsni@BB +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@6@inst__tsni --> Ref(y)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(1)))))@7@inst__tsni --> Ref(x)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = 18 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun test(x, y, z, i) = + let k = if x is + AA(a) then + let m = if y is + AA(a1) then a1 + i + BB(b2) then b2 - i + let n = if z is + BB(a2) then a2 - i + m + n + k + i +test(AA(1), AA(2), BB(3), 4) + test(AA(1), BB(2), BB(3), 4) +//│ = 10 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(3)))))@2@inst__tsni --> Ref(z)@inst_0_tsni@BB +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(2)))))@3@inst__tsni --> Ref(y)@inst_0_tsni@BB +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(1)))))@4@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(3)))))@5@inst__tsni --> Ref(z)@inst_1_tsni@BB +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@6@inst__tsni --> Ref(y)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(1)))))@7@inst__tsni --> Ref(x)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = 10 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun test(x) = + let n = if x is + AA(BB(b)) then b + AA(CC(c)) then c + else 0 + n + 3 +fun c(x) = if x is AA then 0 +fun p(x) = AA(x) +test(p(BB(3))) + test(p(CC(3))) + c(p(A)) +//│ = 12 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref(x))))@6@inst_0_tsni --> Ref(x)@inst_3_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref(x))))@6@inst_1_tsni --> Ref(x)@inst_4_tsni@AA +//│ Call(Ref(member:CC),List(Arg(false,Lit(IntLit(3)))))@7@inst__tsni --> Ref($param0)@inst_4_tsni@CC +//│ Call(Ref(member:AA),List(Arg(false,Ref(x))))@6@inst_2_tsni --> Ref(x)@inst_5_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(3)))))@8@inst__tsni --> Ref($param0)@inst_5_tsni@BB +//│ -------------- executing ------------- +//│ = 12 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + + +fun f(x, y, z, k) = + let tmp = if z then BB(y + 1) else BB(y - 1) + tmp.x + + if tmp is + BB then + let m = if x is + AA then + if AA(2) is + AA(yf) then yf + if AA(3) is + AA then m + k +f(AA(3), 3, true, 10) + f(AA(5), 4, false, 20) +//│ = 41 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@2@inst_0_tsni --> Ref($scrut)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(3)))))@3@inst_0_tsni --> Ref($scrut)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(5)))))@4@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@2@inst_1_tsni --> Ref($scrut)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(3)))))@3@inst_1_tsni --> Ref($scrut)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(3)))))@5@inst__tsni --> Ref(x)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = 41 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun test(x) = + let t = if x is + AA(AA(AA(a))) then a + t + 5 +fun f(a) = if a is + AA(AA) then 0 +let p = AA(AA(AA(10))) +test(p) + f(p) +//│ = 15 +//│ p = AA(AA(AA(10))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(10)))))@1@inst__tsni --> Ref($param0)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 15 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/new-todo.mls b/hkmc2/shared/src/test/mlscript/deforest/new-todo.mls new file mode 100644 index 0000000000..3293fcdb5e --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/new-todo.mls @@ -0,0 +1,126 @@ +:js + + +data class AA(x) +data class BB(x) +object A +object B + +// TODO: should fuse `A` with the pattern match in `f`. +// This is because the same symbol is used for the `x`s +// introduced in the patterns of both branches, causing +// `A` also flows into consumers in `g1` and `g2` +:deforest +fun f(x) = if x is A then 1 else 2 +fun g1(x) = if x is A then 2 else 3 +fun g2(x) = if x is A then 3 else 4 +fun test() = + let prod = if true then AA(A) else BB(B) + if prod is + AA(x) then f(x) + BB(x) then g1(x) + g2(x) +test() +//│ = 1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref(member:A))))@1@inst_0_tsni --> Ref(prod)@inst_0_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Ref(member:B))))@2@inst_0_tsni --> Ref(prod)@inst_0_tsni@BB +//│ -------------- executing ------------- +//│ = 1 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// TODO: fusion opportunity is lost +// when instantiation ids of dtors are also tracked: +// two call sites to `inner` leads to two `inner`s +// to be called, causing a clashing consumer. +:deforest +fun inner(x) = if x is A then 0 else 1 +fun outter1(x) = inner(x) +fun outter2(x) = inner(x) +fun test() = + let x = A + outter1(x) + outter2(x) +test() +//│ = 0 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = 0 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// TODO: duplicated instantiations of the consumer `f` +:sjs +:deforest +fun f(x) = if x is A then 1 +f(A) + f(A) +//│ JS (unsanitized): +//│ let f1, tmp, tmp1; +//│ f1 = function f(x) { +//│ if (x instanceof A1.class) { +//│ return 1 +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ tmp = f1(A1); +//│ tmp1 = f1(A1); +//│ tmp + tmp1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>> Deforestation JS >>>>>>>>>>>>>>>>>>>>>>>>>> +//│ let f1, tmp, tmp1, f_inst_0_tsni, f_inst_1_tsni; +//│ f_inst_0_tsni = function f_inst_0_tsni(x) { +//│ return runtime.safeCall(x()) +//│ }; +//│ f_inst_1_tsni = function f_inst_1_tsni(x) { +//│ return runtime.safeCall(x()) +//│ }; +//│ f1 = function f(x) { +//│ if (x instanceof A1.class) { +//│ return 1 +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ tmp = f_inst_1_tsni(() => { +//│ return 1 +//│ }); +//│ tmp1 = f_inst_0_tsni(() => { +//│ return 1 +//│ }); +//│ tmp + tmp1 +//│ <<<<<<<<<<<<<<<<<<<<<<<<< Deforestation JS <<<<<<<<<<<<<<<<<<<<<<<<<< +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@2@inst__tsni --> Ref(x)@inst_0_tsni@A +//│ Ref(member:A)@3@inst__tsni --> Ref(x)@inst_1_tsni@A +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// TODO: after lowering the definition of `f` is located before the +// defining site of `x`, so that `f` is not in the `rest` of the fusing +// pattern matching and `x` in `f` becomes free. +:re +:ge +:deforest +fun test() = + let x = if A is + A then 1 + fun f() = x + f() +test() +//│ = 1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst_0_tsni --> Ref($scrut)@inst_0_tsni@A +//│ ╔══[COMPILATION ERROR] No definition found in scope for 'x' +//│ ║ l.111: let x = if A is +//│ ╙── ^ +//│ -------------- executing ------------- +//│ ═══[RUNTIME ERROR] ReferenceError: x is not defined +//│ ═══[RUNTIME ERROR] The result from deforestated program ("undefined") is different from the one computed by the original prorgam ("1") +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/recursive.mls b/hkmc2/shared/src/test/mlscript/deforest/recursive.mls new file mode 100644 index 0000000000..9bc4c9eb6c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/recursive.mls @@ -0,0 +1,337 @@ +:js +:deforest + +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +object Nil +data class (::) Cons(h, t) +object None +data class Some(v) +object A +object B +data class T(n, l, r) +object L +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +fun mk(n) = if n < 0 then Nil else n :: mk(n - 1) +fun map(f, ls_map) = if ls_map is + h :: t then f(h) :: map(f, t) + Nil then Nil +fun map1(f, ls_map1) = if ls_map1 is + h :: t then f(h) :: map1(f, t) + Nil then Nil +fun incr(x) = x + 1 +fun double(x) = x * 2 +fun test(ls) = map1(incr, map(double, ls)) +test(id(mk(5))) +//│ = Cons(11, Cons(9, Cons(7, Cons(5, Cons(3, Cons(1, Nil)))))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@3@inst_0_1_tsni --> Ref(ls_map1)@inst_0_2_tsni@Cons +//│ Ref(member:Nil)@4@inst_0_1_tsni --> Ref(ls_map1)@inst_0_2_tsni@Nil +//│ -------------- executing ------------- +//│ = Cons(11, Cons(9, Cons(7, Cons(5, Cons(3, Cons(1, Nil)))))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun enumFromTo(a, b) = if a < b then a :: enumFromTo(a + 1, b) else Nil +fun map(ls) = if ls is + Nil then Nil + h :: t then (h + 4) :: map(t) +map(enumFromTo(1, 4)) +//│ = Cons(5, Cons(6, Cons(7, Nil))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref(a)), Arg(false,Ref($tmp))))@2@inst_0_tsni --> Ref(ls)@inst_1_tsni@Cons +//│ Ref(member:Nil)@3@inst_0_tsni --> Ref(ls)@inst_1_tsni@Nil +//│ -------------- executing ------------- +//│ = Cons(5, Cons(6, Cons(7, Nil))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +fun enumFromTo(a, b) = if a < b then a :: enumFromTo(a + 1, b) else Nil +fun sum(ls) = if ls is + Nil then 0 + h :: t then h + sum(t) +sum(enumFromTo(1,10)) +//│ = 45 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref(a)), Arg(false,Ref($tmp))))@2@inst_0_tsni --> Ref(ls)@inst_1_tsni@Cons +//│ Ref(member:Nil)@3@inst_0_tsni --> Ref(ls)@inst_1_tsni@Nil +//│ -------------- executing ------------- +//│ = 45 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +fun enumFromTo(a, b) = if a < b then a :: enumFromTo(a + 1, b) else Nil +fun map(f, ls) = + if ls is + Nil then Nil + h :: t then f(h) :: map(f, t) +map(x => x + 4, enumFromTo(1, 4)) +//│ = Cons(5, Cons(6, Cons(7, Nil))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref(a)), Arg(false,Ref($tmp))))@2@inst_0_tsni --> Ref(ls)@inst_1_tsni@Cons +//│ Ref(member:Nil)@3@inst_0_tsni --> Ref(ls)@inst_1_tsni@Nil +//│ -------------- executing ------------- +//│ = Cons(5, Cons(6, Cons(7, Nil))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +fun enumFromTo(a, b) = if a < b then a :: enumFromTo(a + 1, b) else Nil +fun map(f, ls) = + (if ls is + Nil then f => Nil + h :: t then f => f(h) :: map(f, t) + )(f) +map(x => x + 4, enumFromTo(1, 4)) +//│ = Cons(5, Cons(6, Cons(7, Nil))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref(a)), Arg(false,Ref($tmp))))@2@inst_0_tsni --> Ref(ls)@inst_1_tsni@Cons +//│ Ref(member:Nil)@3@inst_0_tsni --> Ref(ls)@inst_1_tsni@Nil +//│ -------------- executing ------------- +//│ = Cons(5, Cons(6, Cons(7, Nil))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun enumFromTo(a, b) = if a < b then a :: enumFromTo(a + 1, b) else Nil +fun sum(ls, a) = if ls is + Nil then a + h :: t then sum(t, h + a) +sum(enumFromTo(1, 10), 0) +//│ = 45 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref(a)), Arg(false,Ref($tmp))))@2@inst_0_tsni --> Ref(ls)@inst_1_tsni@Cons +//│ Ref(member:Nil)@3@inst_0_tsni --> Ref(ls)@inst_1_tsni@Nil +//│ -------------- executing ------------- +//│ = 45 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun mk(n) = if n < 0 then Nil else n :: mk(n - 1) +fun incr(x) = x + 1 +fun map(f, xs_map) = if xs_map is + Nil then Nil + x :: xs then f(x) :: map(f, xs) +fun rev(xs_rev, acc) = if xs_rev is + x :: xs then rev(xs, x :: acc) + Nil then acc +fun test(xs) = map(incr, rev(xs, Nil)) +test(id(mk(5))) +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:Nil)@3@inst_0_tsni --> Ref(xs_map)@inst_0_2_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(x)), Arg(false,Ref(acc))))@4@inst_0_1_tsni --> Ref(xs_map)@inst_0_2_tsni@Cons +//│ -------------- executing ------------- +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun mk(n) = if n < 0 then Nil else n :: mk(n - 1) +fun incr(x) = x + 1 +fun map(f, xs_map) = if xs_map is + Nil then Nil + x :: xs then f(x) :: map(f, xs) +fun rev(xs_rev, acc) = if xs_rev is + x :: xs then rev(xs, x :: acc) + Nil then acc +fun test(xs) = rev(map(incr, xs), Nil) +test(id(mk(3))) +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Nil)))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:Nil)@3@inst_0_1_tsni --> Ref(xs_rev)@inst_0_2_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@4@inst_0_1_tsni --> Ref(xs_rev)@inst_0_2_tsni@Cons +//│ -------------- executing ------------- +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Nil)))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +data class Pair(a, b) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun pair_up(xs) = + if xs is + x :: xss then + if xss is + y :: xs then Pair(x, y) :: pair_up(xs) + else Nil + else Nil +fun mk(n) = if n > 0 then (n - 1) :: n :: (n + 1) :: mk(n - 1) else Nil +fun test(x) = pair_up(mk(x)) +test(4) +//│ = Cons(Pair(3, 4), Cons(Pair(5, 2), Cons(Pair(3, 4), Cons(Pair(1, 2), Cons(Pair(3, 0), Cons(Pair(1, 2), Nil)))))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = Cons(Pair(3, 4), Cons(Pair(5, 2), Cons(Pair(3, 4), Cons(Pair(1, 2), Cons(Pair(3, 0), Cons(Pair(1, 2), Nil)))))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun mk(n) = if n > 0 then n :: mk(n - 1) else Nil +fun mk2d(n) = if n > 0 then mk(n) :: mk2d(n - 1) else Nil +fun append(xs, ys) = if xs is + Nil then ys + x :: xs then x :: append(xs, ys) +fun sum(ls_sum) = if ls_sum is + Nil then 0 + x :: xs then x + sum(xs) +fun flatten(ls_flatten) = if ls_flatten is + Nil then Nil + x :: xs then append(x, flatten(xs)) +fun test(ls) = sum(flatten(ls)) +test(id(mk2d(4))) +//│ = 20 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref(x)), Arg(false,Ref($tmp))))@4@inst_0_1_2_tsni --> Ref(ls_sum)@inst_0_3_tsni@Cons +//│ Ref(member:Nil)@5@inst_0_1_tsni --> Ref(ls_sum)@inst_0_3_tsni@Nil +//│ -------------- executing ------------- +//│ = 20 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun count(c, xs) = if xs is + h :: t then count(c + 1, t) + Nil then c +fun rev(a, ys) = if ys is + h :: t then rev(h :: a, t) + Nil then a +count(0, rev(Nil, 1 :: 2 :: Nil)) +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:Nil)@2@inst__tsni --> Ref(xs)@inst_1_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(h)), Arg(false,Ref(a))))@3@inst_0_tsni --> Ref(xs)@inst_1_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(1))), Arg(false,Ref($tmp))))@4@inst__tsni --> Ref(ys)@inst_0_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(2))), Arg(false,Ref(member:Nil))))@5@inst__tsni --> Ref(ys)@inst_0_tsni@Cons +//│ Ref(member:Nil)@6@inst__tsni --> Ref(ys)@inst_0_tsni@Nil +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun last(ls) = if ls is + h :: t and t is + Nil then Some(h) + _ :: _ then last(t) + Nil then None +let p = 1 :: 2 :: Nil +last(p) +//│ = Some(2) +//│ p = Cons(1, Cons(2, Nil)) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = Some(2) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun map(f, xs_map) = if xs_map is + Nil then Nil + x :: xs then f(x) :: map(f, xs) +map(x => if x is A then 1 else 0, A :: B :: Nil) +//│ = Cons(1, Cons(0, Nil)) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref(member:A)), Arg(false,Ref($tmp))))@2@inst__tsni --> Ref(xs_map)@inst_0_tsni@Cons +//│ Ref(member:A)@3@inst__tsni --> Ref(x)@inst_1_tsni@A +//│ Call(Ref(member:Cons),List(Arg(false,Ref(member:B)), Arg(false,Ref(member:Nil))))@4@inst__tsni --> Ref(xs_map)@inst_0_tsni@Cons +//│ Ref(member:Nil)@5@inst__tsni --> Ref(xs_map)@inst_0_tsni@Nil +//│ Ref(member:B)@6@inst__tsni --> Ref(x)@inst_1_tsni@dflt +//│ -------------- executing ------------- +//│ = Cons(1, Cons(0, Nil)) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun c(x) = if x is + T(n, l, r) then T of + if n is + A then 0 + B then 1 + c(l) + c(r) + L then L +c(T(A, T(B, L, L), T(A, L, L))) +//│ = T(0, T(1, L, L), T(0, L, L)) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:T),List(Arg(false,Ref(member:A)), Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@1@inst__tsni --> Ref(x)@inst_0_tsni@T +//│ Ref(member:A)@2@inst__tsni --> Ref(n)@inst_0_tsni@A +//│ Call(Ref(member:T),List(Arg(false,Ref(member:A)), Arg(false,Ref(member:L)), Arg(false,Ref(member:L))))@3@inst__tsni --> Ref(x)@inst_0_tsni@T +//│ Ref(member:L)@4@inst__tsni --> Ref(x)@inst_0_tsni@L +//│ Ref(member:L)@5@inst__tsni --> Ref(x)@inst_0_tsni@L +//│ Ref(member:A)@6@inst__tsni --> Ref(n)@inst_0_tsni@A +//│ Call(Ref(member:T),List(Arg(false,Ref(member:B)), Arg(false,Ref(member:L)), Arg(false,Ref(member:L))))@7@inst__tsni --> Ref(x)@inst_0_tsni@T +//│ Ref(member:L)@8@inst__tsni --> Ref(x)@inst_0_tsni@L +//│ Ref(member:L)@9@inst__tsni --> Ref(x)@inst_0_tsni@L +//│ Ref(member:B)@10@inst__tsni --> Ref(n)@inst_0_tsni@B +//│ -------------- executing ------------- +//│ = T(0, T(1, L, L), T(0, L, L)) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun max(ms, m) = if ms is + h :: t and + h > m then max(t, h) + else max(t, m) + Nil then m +max(3 :: 2 :: 4 :: 1 :: 0 :: Nil, 0) +//│ = 4 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(3))), Arg(false,Ref($tmp))))@1@inst__tsni --> Ref(ms)@inst_0_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(2))), Arg(false,Ref($tmp))))@2@inst__tsni --> Ref(ms)@inst_0_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(4))), Arg(false,Ref($tmp))))@3@inst__tsni --> Ref(ms)@inst_0_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(1))), Arg(false,Ref($tmp))))@4@inst__tsni --> Ref(ms)@inst_0_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Lit(IntLit(0))), Arg(false,Ref(member:Nil))))@5@inst__tsni --> Ref(ms)@inst_0_tsni@Cons +//│ Ref(member:Nil)@6@inst__tsni --> Ref(ms)@inst_0_tsni@Nil +//│ -------------- executing ------------- +//│ = 4 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun filter(ls, f) = if ls is + h :: t and + f(h) then h :: filter(t, f) + else filter(t, f) + Nil then Nil +fun last(ls_last) = + fun go(a, ls_go) = if ls_go is + Nil then a + h :: t then go(h, t) + if ls_last is + h :: t then Some(go(h, t)) + Nil then None +fun lastFilter(ls, f) = last(filter(ls, f)) +lastFilter(id(1 :: 2 :: 3 :: 4 :: 5 :: Nil), x => (x % 2 == 0)) +//│ = Some(4) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = Some(4) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + diff --git a/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls b/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls new file mode 100644 index 0000000000..7001c5c8b6 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls @@ -0,0 +1,161 @@ +:js +:deforest + +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< +object A +object B +object C +data class AA(x) +data class BB(x) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +object Nil +data class Cons(h, t) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +object None +data class Some(x) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +fun c(x) = if x is + AA then + if A is + A then x.x +c(AA(2)) +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst_0_tsni --> Ref($scrut)@inst_0_tsni@A +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@2@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun c(x, y) = if x is + AA then + if y is + A then x.x +c(AA(2), A) +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst__tsni --> Ref(y)@inst_0_tsni@A +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@2@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun c(x, y) = if x is + AA then + if y is + A then x.x +fun p() = AA(2) +c(p(), A) +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@2@inst__tsni --> Ref(y)@inst_1_tsni@A +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@3@inst_0_tsni --> Ref(x)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +fun c(x, y) = if x is + AA then + let a = if y is + A then 1 + a + x.x +fun p() = AA(2) +c(p(), A) +//│ = 3 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@2@inst__tsni --> Ref(y)@inst_1_tsni@A +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@3@inst_0_tsni --> Ref(x)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = 3 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +fun f(x) = if x is + AA(AA(a)) then g(a) +fun g(x) = if x is + AA(b) then f(b) + A then 42 +f(AA(AA(AA(AA(AA(A)))))) +//│ = 42 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@1@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@2@inst__tsni --> Ref($param0)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@3@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@4@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref(member:A))))@5@inst__tsni --> Ref($param0)@inst_0_tsni@AA +//│ Ref(member:A)@6@inst__tsni --> Ref(x)@inst_0_tsni@A +//│ -------------- executing ------------- +//│ = 42 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +fun f(x) = if x is + AA(AA(a)) then a +f(AA(AA(A))) +//│ = A +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@1@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref(member:A))))@2@inst__tsni --> Ref($param0)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = A +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +fun f(x) = if x is + AA(AA(a)) then a +fun p() = AA(AA(A)) +f(p()) +//│ = A +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@2@inst_0_tsni --> Ref(x)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref(member:A))))@3@inst_0_tsni --> Ref($param0)@inst_1_tsni@AA +//│ -------------- executing ------------- +//│ = A +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun c(x, y) = + let t = if x is + AA then + if A is + A then x.x + t + y +c(AA(2), 10) +//│ = 12 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst_0_tsni --> Ref($scrut)@inst_0_tsni@A +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@2@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 12 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/simple.mls b/hkmc2/shared/src/test/mlscript/deforest/simple.mls new file mode 100644 index 0000000000..97bd315312 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/simple.mls @@ -0,0 +1,661 @@ +:js +:deforest + +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +object A +object B +object C +data class AA(aa) +data class BB(bb) +data class AAA(x, y) +data class BBB(x, y) +data class CCC(c) +object None +data class Some(value) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +fun test() = + let x = if true then A else B + if x is + A then 1 + B then 2 +test() +//│ = 1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst_0_tsni --> Ref(x)@inst_0_tsni@A +//│ Ref(member:B)@2@inst_0_tsni --> Ref(x)@inst_0_tsni@B +//│ -------------- executing ------------- +//│ = 1 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +fun test() = + let x = if true then AA(A) else BB(B) + if x is + AA(x) then x + BB(x) then x +test() +//│ = A +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref(member:A))))@1@inst_0_tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Ref(member:B))))@2@inst_0_tsni --> Ref(x)@inst_0_tsni@BB +//│ -------------- executing ------------- +//│ = A +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun f(a) = if a is + A then 1 + B then 2 +fun test() = + let x = if true then AA(A) else BB(B) + if x is + AA(x) then f(x) + BB(x) then f(x) +test() +//│ = 1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref(member:A))))@1@inst_0_tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Ref(member:B))))@2@inst_0_tsni --> Ref(x)@inst_0_tsni@BB +//│ -------------- executing ------------- +//│ = 1 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// `x.x` is successfully fused +fun f1(a) = if a is + A then 1 + B then 2 + C then 3 +fun f2(a) = if a is + A then 4 + B then 5 + C then 6 +fun test() = + let x = if true then AA(A) else BB(B) + if x is + AA then f1(x.aa) + BB then f2(x.bb) +test() +//│ = 1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref(member:A))))@3@inst_0_tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Ref(member:B))))@4@inst_0_tsni --> Ref(x)@inst_0_tsni@BB +//│ Ref(member:A)@5@inst_0_tsni --> Ref(a)@inst_0_1_tsni@A +//│ Ref(member:B)@6@inst_0_tsni --> Ref(a)@inst_0_2_tsni@B +//│ -------------- executing ------------- +//│ = 1 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun test() = + fun g(x) = if true then AA(11) else BB(22) + fun c(x) = if x is + AA(x) then x + BB(x) then x + c(g(true)) +test() +//│ = 11 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(11)))))@1@inst_0_tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(22)))))@2@inst_0_tsni --> Ref(x)@inst_0_tsni@BB +//│ -------------- executing ------------- +//│ = 11 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +// multiple match, no fusion +fun test() = + let x = B + if x is + A then 1 + B then 3 + if x is + B then 2 +test() +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun test() = + let x = A + let y = B + if x is + A then 1 + if y is + B then 2 +test() +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst_0_tsni --> Ref(x)@inst_0_tsni@A +//│ Ref(member:B)@2@inst_0_tsni --> Ref(y)@inst_0_tsni@B +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun c(a) = + let x = if a is + A then 1 + B then 2 + print(x) + x +c(A) + c(B) +//│ > 1 +//│ > 2 +//│ = 3 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:B)@2@inst__tsni --> Ref(a)@inst_0_tsni@B +//│ Ref(member:A)@3@inst__tsni --> Ref(a)@inst_1_tsni@A +//│ -------------- executing ------------- +//│ > 1 +//│ > 2 +//│ = 3 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +// simple free var example + + + + + +:expect 5 +fun c(x) = + let t = x.aa + let n = if x is + AA then 2 + n + t +c(AA(3)) +//│ = 5 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = 5 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun f(a, b) = if a is + A then if b is + B then 3 +f(A, B) +//│ = 3 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst__tsni --> Ref(a)@inst_0_tsni@A +//│ Ref(member:B)@2@inst__tsni --> Ref(b)@inst_0_tsni@B +//│ -------------- executing ------------- +//│ = 3 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun f(x) = if x is + Some then if x.value > 1 then f(Some(x.value - 1)) else 0 +f(Some(2)) +//│ = 0 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ = 0 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +let x = A +let y = B +if x is + A then 1 +if y is + B then 2 +//│ = 2 +//│ x = A +//│ y = B +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:B)@0@inst__tsni --> Ref(y)@inst__tsni@B +//│ Ref(member:A)@1@inst__tsni --> Ref(x)@inst__tsni@A +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun test() = + let x = A + let y = B + if x is + A then 1 + if y is + B then 2 +test() +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst_0_tsni --> Ref(x)@inst_0_tsni@A +//│ Ref(member:B)@2@inst_0_tsni --> Ref(y)@inst_0_tsni@B +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun f(x, y) = + let a = if x is + AAA(n, m) then y + n - m + BBB(n, m) then m + 1 - n + a + 3 +f(AAA(1, 3), 1) + f(BBB(2, 3), 2) + f(AAA(3, 2), 4) + f(BBB(4, 6), 0) +//│ = 21 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:BBB),List(Arg(false,Lit(IntLit(4))), Arg(false,Lit(IntLit(6)))))@4@inst__tsni --> Ref(x)@inst_0_tsni@BBB +//│ Call(Ref(member:AAA),List(Arg(false,Lit(IntLit(3))), Arg(false,Lit(IntLit(2)))))@5@inst__tsni --> Ref(x)@inst_1_tsni@AAA +//│ Call(Ref(member:BBB),List(Arg(false,Lit(IntLit(2))), Arg(false,Lit(IntLit(3)))))@6@inst__tsni --> Ref(x)@inst_2_tsni@BBB +//│ Call(Ref(member:AAA),List(Arg(false,Lit(IntLit(1))), Arg(false,Lit(IntLit(3)))))@7@inst__tsni --> Ref(x)@inst_3_tsni@AAA +//│ -------------- executing ------------- +//│ = 21 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun c1(x) = if x is + AA then + if A is + A then x.aa +fun c2(x) = if x is + AA then x.aa +fun p(a) = c1(a) + c2(a) +p(AA(1)) +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@2@inst_0_1_tsni --> Ref($scrut)@inst_0_1_tsni@A +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun c(x, y) = if x is + AA(a) then + if y is + A then a +c(AA(2), A) +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst__tsni --> Ref(y)@inst_0_tsni@A +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@2@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun f(x, y) = + let a = if x is + AAA(n, m) then y + n - m + CCC(n) then n + 1 + a + 3 +f(AAA(1, 3), 1) + f(CCC(2), 2) + f(AAA(3, 2), 4) + f(CCC(4), 0) +//│ = 24 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:CCC),List(Arg(false,Lit(IntLit(4)))))@4@inst__tsni --> Ref(x)@inst_0_tsni@CCC +//│ Call(Ref(member:AAA),List(Arg(false,Lit(IntLit(3))), Arg(false,Lit(IntLit(2)))))@5@inst__tsni --> Ref(x)@inst_1_tsni@AAA +//│ Call(Ref(member:CCC),List(Arg(false,Lit(IntLit(2)))))@6@inst__tsni --> Ref(x)@inst_2_tsni@CCC +//│ Call(Ref(member:AAA),List(Arg(false,Lit(IntLit(1))), Arg(false,Lit(IntLit(3)))))@7@inst__tsni --> Ref(x)@inst_3_tsni@AAA +//│ -------------- executing ------------- +//│ = 24 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +fun f(x, y) = + let a = if x is + AAA then y + x.y + CCC(n) then n + 1 + a + 3 +f(AAA(1, 3), 1) + f(CCC(2), 2) + f(AAA(3, 2), 4) +//│ = 22 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AAA),List(Arg(false,Lit(IntLit(3))), Arg(false,Lit(IntLit(2)))))@3@inst__tsni --> Ref(x)@inst_0_tsni@AAA +//│ Call(Ref(member:CCC),List(Arg(false,Lit(IntLit(2)))))@4@inst__tsni --> Ref(x)@inst_1_tsni@CCC +//│ Call(Ref(member:AAA),List(Arg(false,Lit(IntLit(1))), Arg(false,Lit(IntLit(3)))))@5@inst__tsni --> Ref(x)@inst_2_tsni@AAA +//│ -------------- executing ------------- +//│ = 22 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun c(x, m) = if x is + AA(n) then n + 1 + else m +c(BB(3), 0) +//│ = 0 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(3)))))@1@inst__tsni --> Ref(x)@inst_0_tsni@dflt +//│ -------------- executing ------------- +//│ = 0 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +fun f(x) = if x is + AA(b) then g(b) +fun g(b) = if b is + AA(a) then a + 1 +f(AA(AA(0))) +//│ = 1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@2@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(0)))))@3@inst__tsni --> Ref(b)@inst_0_1_tsni@AA +//│ -------------- executing ------------- +//│ = 1 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun f(x) = x.aa.aa +f(AA(AA(3))) +//│ = 3 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@1@inst__tsni --> Select(Ref(x),Ident(aa))@inst_0_tsni +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(3)))))@2@inst__tsni --> Select(Select(Ref(x),Ident(aa)),Ident(aa))@inst_0_tsni +//│ -------------- executing ------------- +//│ = 3 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +fun inner(x, y) = + let t = if x is + AA(a) then a + y + BB(b) then b - y + t * y +fun outer1(x, y, z) = + let t = if x is + AA(a) then inner(a, y) + BB(b) then inner(b, y) + t + z +fun outer2(x, y, z) = + let t = if x is + AA(a) then inner(a, z) + BB(b) then inner(b, y) + t + y +let p = AA(AA(3)) +outer1(p, 1, 2) + outer2(p, 3, 4) + inner(AA(5), 6) +//│ = 103 +//│ p = AA(AA(3)) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(5)))))@1@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 103 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun mapHead(f, x) = if x is + AAA(h, t) then f(h) +mapHead(x => x, AAA(1, AAA(2, None))) +//│ = 1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AAA),List(Arg(false,Lit(IntLit(1))), Arg(false,Ref($tmp))))@1@inst__tsni --> Ref(x)@inst_0_tsni@AAA +//│ -------------- executing ------------- +//│ = 1 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun test() = + let k + if A is + A then + k = 3 + k + 2 +test() +//│ = 5 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst_0_tsni --> Ref($scrut)@inst_0_tsni@A +//│ -------------- executing ------------- +//│ = 5 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +fun test(a) = if a is + AA then 1 + else 2 +test(B) + test(C) +//│ = 4 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:C)@2@inst__tsni --> Ref(a)@inst_0_tsni@dflt +//│ Ref(member:B)@3@inst__tsni --> Ref(a)@inst_1_tsni@dflt +//│ -------------- executing ------------- +//│ = 4 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +// technically ill-typed, so `AA(3)` (which flows to `z`) is not fused +fun test(x, y, z) = if x is + AA(a) then + let m = if y is + AA(a1) then a1 + z + let n = if z is + AA(a2) then a2 - z + m + n +test(AA(1), AA(2), AA(3)) +//│ = "2AA(3)NaN" +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@1@inst__tsni --> Ref(y)@inst_0_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(1)))))@2@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = "2AA(3)NaN" +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun test(x) = + let t = id of + if x is + AA(a) then a + BB(a) then a + t(123).c + 5 * 4 - 3 + 2 - 1 +let p = if true then AA(CCC) else BB(CCC) +test(p) +//│ = 141 +//│ p = AA([function CCC]) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:BB),List(Arg(false,Ref(member:CCC))))@1@inst__tsni --> Ref(x)@inst_0_tsni@BB +//│ Call(Ref(member:AA),List(Arg(false,Ref(member:CCC))))@2@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 141 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun test(x, y) = + let t = + if y is CCC then + if x is + AA(a) then a + BB(a) then a + t(123).c + 5 * 4 - 3 + 2 - 1 +let p = if true then AA(CCC) else BB(CCC) +test(p, id(CCC(123))) +//│ = 141 +//│ p = AA([function CCC]) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:BB),List(Arg(false,Ref(member:CCC))))@1@inst__tsni --> Ref(x)@inst_0_tsni@BB +//│ Call(Ref(member:AA),List(Arg(false,Ref(member:CCC))))@2@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 141 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +:sjs +fun f(x) = if x is + A then 1 +f(id(A)) + f(A) +//│ JS (unsanitized): +//│ let f10, tmp113, tmp114, tmp115; +//│ f10 = function f(x10) { +//│ if (x10 instanceof A1.class) { +//│ return 1 +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ tmp113 = Predef.id(A1); +//│ tmp114 = f10(tmp113); +//│ tmp115 = f10(A1); +//│ tmp114 + tmp115 +//│ >>>>>>>>>>>>>>>>>>>>>>>>> Deforestation JS >>>>>>>>>>>>>>>>>>>>>>>>>> +//│ let f10, tmp113, tmp114, tmp115, f_inst_0_tsni6; +//│ f_inst_0_tsni6 = function f_inst_0_tsni(x10) { +//│ return runtime.safeCall(x10()) +//│ }; +//│ f10 = function f(x10) { +//│ if (x10 instanceof A1.class) { +//│ return 1 +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ tmp113 = Predef.id(A1); +//│ tmp114 = f10(tmp113); +//│ tmp115 = f_inst_0_tsni6(() => { +//│ return 1 +//│ }); +//│ tmp114 + tmp115 +//│ <<<<<<<<<<<<<<<<<<<<<<<<< Deforestation JS <<<<<<<<<<<<<<<<<<<<<<<<<< +//│ = 2 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst__tsni --> Ref(x)@inst_0_tsni@A +//│ -------------- executing ------------- +//│ = 2 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun f(x) = if x is + A then 0 + B then 1 +fun g(x) = if x is + AA then A + BB then B +f(g(AA(1))) +//│ = 0 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@2@inst_0_tsni --> Ref(x)@inst_1_tsni@A +//│ Ref(member:B)@3@inst_0_tsni --> Ref(x)@inst_1_tsni@B +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(1)))))@4@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 0 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun f(a, b) = if a is + A and b is B then 1 + else 0 +f(A, B) +//│ = 1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@1@inst__tsni --> Ref(a)@inst_0_tsni@A +//│ Ref(member:B)@2@inst__tsni --> Ref(b)@inst_0_tsni@B +//│ -------------- executing ------------- +//│ = 1 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// cannot fuse intermediate values created by +// calling data constructors passed around like functions, +// so `c1` is not fused. +fun app(f) = f(1) +fun identity(x) = x +fun c1(x) = if x is + AA(i) then i +fun c2(x) = if x is + AA(i) then i + A then 0 +c1(app(AA)) + c1(AA(2)) + c2(AA(3)) + c2(A) +//│ = 6 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@3@inst__tsni --> Ref(x)@inst_0_tsni@A +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(3)))))@4@inst__tsni --> Ref(x)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(2)))))@5@inst__tsni --> Ref(x)@inst_2_tsni@AA +//│ -------------- executing ------------- +//│ = 6 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +:re +fun p(x) = if x + then new AA(2) + else BB(3) +fun c(x) = if x is + AA(a) then a + BB(b) then b + else throw (if A is A then Error("e1") else Error("e2")) +print(c(p(true)) + c(p(false))) +c(B) +//│ > 5 +//│ ═══[RUNTIME ERROR] Error: e1 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:A)@5@inst_0_tsni --> Ref($scrut)@inst_0_tsni@A +//│ Ref(member:B)@6@inst__tsni --> Ref(x)@inst_0_tsni@dflt +//│ Ref(member:A)@5@inst_1_tsni --> Ref($scrut)@inst_1_tsni@A +//│ Instantiate(Select(Ref(member:AA),Ident(class)),List(Lit(IntLit(2))))@7@inst_2_tsni --> Ref(x)@inst_1_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(3)))))@8@inst_2_tsni --> Ref(x)@inst_1_tsni@BB +//│ Ref(member:A)@5@inst_3_tsni --> Ref($scrut)@inst_3_tsni@A +//│ Instantiate(Select(Ref(member:AA),Ident(class)),List(Lit(IntLit(2))))@7@inst_4_tsni --> Ref(x)@inst_3_tsni@AA +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(3)))))@8@inst_4_tsni --> Ref(x)@inst_3_tsni@BB +//│ -------------- executing ------------- +//│ > 5 +//│ ═══[RUNTIME ERROR] Error: e1 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +fun f(a, b) = if a is + AA(x) then x + b.bb +f(AA(1), BB(2)) +//│ = 3 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:BB),List(Arg(false,Lit(IntLit(2)))))@1@inst__tsni --> Select(Ref(b),Ident(bb))@inst_0_tsni +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(1)))))@2@inst__tsni --> Ref(a)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 3 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/todos.mls b/hkmc2/shared/src/test/mlscript/deforest/todos.mls new file mode 100644 index 0000000000..ca7ee9aa2f --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/todos.mls @@ -0,0 +1,313 @@ +:js +:deforest + +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< +object A +object B +object C +data class AA(x) +data class BB(x) +data class CC(x) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +object Nil +data class Cons(h, t) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +object None +data class Some(x) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// since `x.x` is considered as being consumed +// at two places,the inner match is not fused +:sjs +fun f(x) = if x is + AA then if x.x is + AA then x.x.x +f(AA(AA(3))) +//│ JS (unsanitized): +//│ let f, tmp, tmp1; +//│ f = function f(x) { +//│ let scrut; +//│ if (x instanceof AA1.class) { +//│ scrut = x.x; +//│ if (scrut instanceof AA1.class) { +//│ return x.x.x +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ tmp = AA1(3); +//│ tmp1 = AA1(tmp); +//│ f(tmp1) +//│ >>>>>>>>>>>>>>>>>>>>>>>>> Deforestation JS >>>>>>>>>>>>>>>>>>>>>>>>>> +//│ let f, tmp, tmp1, f_inst_0_tsni, x; +//│ f_inst_0_tsni = function f_inst_0_tsni(x1) { +//│ return runtime.safeCall(x1()) +//│ }; +//│ f = function f(x1) { +//│ let scrut; +//│ if (x1 instanceof AA1.class) { +//│ scrut = x1.x; +//│ if (scrut instanceof AA1.class) { +//│ return x1.x.x +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ tmp = AA1(3); +//│ x = tmp; +//│ tmp1 = () => { +//│ let scrut; +//│ scrut = x; +//│ if (scrut instanceof AA1.class) { +//│ return x.x +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ }; +//│ f_inst_0_tsni(tmp1) +//│ <<<<<<<<<<<<<<<<<<<<<<<<< Deforestation JS <<<<<<<<<<<<<<<<<<<<<<<<<< +//│ = 3 +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@1@inst__tsni --> Ref(x)@inst_0_tsni@AA +//│ -------------- executing ------------- +//│ = 3 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// TODO: the computation of `t + 5 * 4 - 3 + 2 - 1` is still duplicated... +:sjs +fun test(x) = + let t = if x is + AA(AA(AA(a))) then a + else 4 + t + 5 * 4 - 3 + 2 - 1 +fun f(a) = if a is + AA(AA) then 0 +let p = AA(AA(AA(10))) +test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) +//│ JS (unsanitized): +//│ let test, f1, p, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; +//│ test = function test(x1) { +//│ let t, param0, param01, param02, a, tmp16, tmp17, tmp18, tmp19, tmp20; +//│ if (x1 instanceof AA1.class) { +//│ param0 = x1.x; +//│ if (param0 instanceof AA1.class) { +//│ param01 = param0.x; +//│ if (param01 instanceof AA1.class) { +//│ param02 = param01.x; +//│ a = param02; +//│ tmp16 = a; +//│ } else { +//│ tmp16 = 4; +//│ } +//│ } else { +//│ tmp16 = 4; +//│ } +//│ } else { +//│ tmp16 = 4; +//│ } +//│ t = tmp16; +//│ tmp17 = 5 * 4; +//│ tmp18 = t + tmp17; +//│ tmp19 = tmp18 - 3; +//│ tmp20 = tmp19 + 2; +//│ return tmp20 - 1 +//│ }; +//│ f1 = function f(a) { +//│ let param0; +//│ if (a instanceof AA1.class) { +//│ param0 = a.x; +//│ if (param0 instanceof AA1.class) { +//│ return 0 +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ tmp4 = AA1(10); +//│ tmp5 = AA1(tmp4); +//│ tmp6 = AA1(tmp5); +//│ p = tmp6; +//│ tmp7 = test(p); +//│ tmp8 = f1(p); +//│ tmp9 = tmp7 + tmp8; +//│ tmp10 = AA1(10); +//│ tmp11 = AA1(tmp10); +//│ tmp12 = AA1(tmp11); +//│ tmp13 = test(tmp12); +//│ tmp14 = tmp9 + tmp13; +//│ tmp15 = test(B1); +//│ tmp14 + tmp15 +//│ >>>>>>>>>>>>>>>>>>>>>>>>> Deforestation JS >>>>>>>>>>>>>>>>>>>>>>>>>> +//│ let test, f1, p, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, test_inst_0_tsni, test_inst_1_tsni, test_inst_2_tsni, x1, x2, x3, x4; +//│ test_inst_0_tsni = function test_inst_0_tsni(x5) { +//│ return runtime.safeCall(x5()) +//│ }; +//│ test_inst_1_tsni = function test_inst_1_tsni(x5) { +//│ return runtime.safeCall(x5()) +//│ }; +//│ test_inst_2_tsni = function test_inst_2_tsni(x5) { +//│ let t, param0, param01, tmp16, tmp17, tmp18, tmp19, tmp20; +//│ if (x5 instanceof AA1.class) { +//│ param0 = x5.x; +//│ if (param0 instanceof AA1.class) { +//│ param01 = param0.x; +//│ return runtime.safeCall(param01()) +//│ } else { +//│ tmp16 = 4; +//│ } +//│ } else { +//│ tmp16 = 4; +//│ } +//│ t = tmp16; +//│ tmp17 = 5 * 4; +//│ tmp18 = t + tmp17; +//│ tmp19 = tmp18 - 3; +//│ tmp20 = tmp19 + 2; +//│ return tmp20 - 1 +//│ }; +//│ test = function test(x5) { +//│ let t, param0, param01, param02, a, tmp16, tmp17, tmp18, tmp19, tmp20; +//│ if (x5 instanceof AA1.class) { +//│ param0 = x5.x; +//│ if (param0 instanceof AA1.class) { +//│ param01 = param0.x; +//│ if (param01 instanceof AA1.class) { +//│ param02 = param01.x; +//│ a = param02; +//│ tmp16 = a; +//│ } else { +//│ tmp16 = 4; +//│ } +//│ } else { +//│ tmp16 = 4; +//│ } +//│ } else { +//│ tmp16 = 4; +//│ } +//│ t = tmp16; +//│ tmp17 = 5 * 4; +//│ tmp18 = t + tmp17; +//│ tmp19 = tmp18 - 3; +//│ tmp20 = tmp19 + 2; +//│ return tmp20 - 1 +//│ }; +//│ f1 = function f(a) { +//│ let param0; +//│ if (a instanceof AA1.class) { +//│ param0 = a.x; +//│ if (param0 instanceof AA1.class) { +//│ return 0 +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ x4 = 10; +//│ tmp4 = () => { +//│ let t, param0, a, tmp16, tmp17, tmp18, tmp19, tmp20; +//│ param0 = x4; +//│ a = param0; +//│ tmp16 = a; +//│ t = tmp16; +//│ tmp17 = 5 * 4; +//│ tmp18 = t + tmp17; +//│ tmp19 = tmp18 - 3; +//│ tmp20 = tmp19 + 2; +//│ return tmp20 - 1 +//│ }; +//│ tmp5 = AA1(tmp4); +//│ tmp6 = AA1(tmp5); +//│ p = tmp6; +//│ tmp7 = test_inst_2_tsni(p); +//│ tmp8 = f1(p); +//│ tmp9 = tmp7 + tmp8; +//│ x3 = 10; +//│ tmp10 = () => { +//│ let t, param0, a, tmp16, tmp17, tmp18, tmp19, tmp20; +//│ param0 = x3; +//│ a = param0; +//│ tmp16 = a; +//│ t = tmp16; +//│ tmp17 = 5 * 4; +//│ tmp18 = t + tmp17; +//│ tmp19 = tmp18 - 3; +//│ tmp20 = tmp19 + 2; +//│ return tmp20 - 1 +//│ }; +//│ x2 = tmp10; +//│ tmp11 = () => { +//│ let param0; +//│ param0 = x2; +//│ return runtime.safeCall(param0()) +//│ }; +//│ x1 = tmp11; +//│ tmp12 = () => { +//│ let param0; +//│ param0 = x1; +//│ return runtime.safeCall(param0()) +//│ }; +//│ tmp13 = test_inst_1_tsni(tmp12); +//│ tmp14 = tmp9 + tmp13; +//│ tmp15 = test_inst_0_tsni(() => { +//│ let t, tmp16, tmp17, tmp18, tmp19, tmp20; +//│ tmp16 = 4; +//│ t = tmp16; +//│ tmp17 = 5 * 4; +//│ tmp18 = t + tmp17; +//│ tmp19 = tmp18 - 3; +//│ tmp20 = tmp19 + 2; +//│ return tmp20 - 1 +//│ }); +//│ tmp14 + tmp15 +//│ <<<<<<<<<<<<<<<<<<<<<<<<< Deforestation JS <<<<<<<<<<<<<<<<<<<<<<<<<< +//│ = 78 +//│ p = AA(AA(AA(10))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:B)@3@inst__tsni --> Ref(x)@inst_0_tsni@dflt +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@4@inst__tsni --> Ref(x)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Ref($tmp))))@5@inst__tsni --> Ref($param0)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(10)))))@6@inst__tsni --> Ref($param0)@inst_1_tsni@AA +//│ Call(Ref(member:AA),List(Arg(false,Lit(IntLit(10)))))@7@inst__tsni --> Ref($param0)@inst_2_tsni@AA +//│ -------------- executing ------------- +//│ = 78 +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +// TODO: currently nothing is done at all when seeing +// `Define` blocks defining things other than functions +data class Global(x) +fun test() = + data class Local(x) + if Global(1) is + Global(x) then Local(x + 1) +test() +//│ = Local(2) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls b/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls new file mode 100644 index 0000000000..a1b7518da5 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls @@ -0,0 +1,92 @@ +:js +:deforest + +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ +//│ -------------- executing ------------- +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + +object Nil +data class (::) Cons(h, t) +data class Pair(a, b) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ Not deforestable: No support for `ClsLikeDefn` yet +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + +fun zip(xs_zip, ys_zip) = if + xs_zip is x :: xt and ys_zip is y :: yt then Pair(x, y) :: zip(xt, yt) + else Nil +fun unzip(ls_unzip) = if ls_unzip is + Pair(a, b) :: t and unzip(t) is Pair(atail, btail) then Pair(a :: atail, b :: btail) + Nil then Pair(Nil, Nil) +fun enumFromTo(a, b) = if a < b then a :: enumFromTo(a + 1, b) else Nil +fun testUnzipZip(n) = unzip(zip(id(enumFromTo(1, n)), id(enumFromTo(2, n + 3)))) +testUnzipZip(3) +//│ = Pair(Cons(1, Cons(2, Nil)), Cons(2, Cons(3, Nil))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@3@inst_0_1_tsni --> Ref(ls_unzip)@inst_0_2_tsni@Cons +//│ Call(Ref(member:Pair),List(Arg(false,Ref(x)), Arg(false,Ref(y))))@4@inst_0_1_tsni --> Ref($param0)@inst_0_2_tsni@Pair +//│ Ref(member:Nil)@5@inst_0_1_tsni --> Ref(ls_unzip)@inst_0_2_tsni@Nil +//│ Ref(member:Nil)@6@inst_0_1_tsni --> Ref(ls_unzip)@inst_0_2_tsni@Nil +//│ -------------- executing ------------- +//│ = Pair(Cons(1, Cons(2, Nil)), Cons(2, Cons(3, Nil))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun zip(xs_zip, ys_zip) = if + xs_zip is x :: xt and ys_zip is y :: yt then Pair(x, y) :: zip(xt, yt) + else Nil +fun unzip(ls_unzip) = if ls_unzip is + Pair(a, b) :: t and unzip(t) is Pair(atail, btail) then Pair(a :: atail, b :: btail) + Nil then Pair(Nil, Nil) +fun makeZippedList(n) = if n > 0 then Pair(n, n + 1) :: makeZippedList(n - 1) else Nil +fun testZipUnzip(n) = if unzip(id(makeZippedList(n))) is + Pair(xs, ys) then zip(xs, ys) +testZipUnzip(3) +//│ = Cons(Pair(3, 4), Cons(Pair(2, 3), Cons(Pair(1, 2), Nil))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:Nil)@3@inst_0_1_tsni --> Ref(xs_zip)@inst_0_2_tsni@dflt +//│ Call(Ref(member:Cons),List(Arg(false,Ref(a)), Arg(false,Ref(atail))))@4@inst_0_1_tsni --> Ref(xs_zip)@inst_0_2_tsni@Cons +//│ Ref(member:Nil)@5@inst_0_1_tsni --> Ref(ys_zip)@inst_0_2_tsni@dflt +//│ Call(Ref(member:Cons),List(Arg(false,Ref(b)), Arg(false,Ref(btail))))@6@inst_0_1_tsni --> Ref(ys_zip)@inst_0_2_tsni@Cons +//│ -------------- executing ------------- +//│ = Cons(Pair(3, 4), Cons(Pair(2, 3), Cons(Pair(1, 2), Nil))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< + + + +fun zip(xs_zip, ys_zip) = if + xs_zip is x :: xt and ys_zip is y :: yt then Pair(x, y) :: zip(xt, yt) + else Nil +fun unzip(ls_unzip) = if ls_unzip is + Pair(a, b) :: t and unzip(t) is Pair(atail, btail) then Pair(a :: atail, b :: btail) + Nil then Pair(Nil, Nil) +fun map(f, ls_map) = if ls_map is + h :: t then f(h) :: map(f, t) + Nil then Nil +fun makeZippedList(n) = if n > 0 then Cons(Pair(n, n + 1), makeZippedList(n - 1)) else Nil +fun testZipMapBothUnzip(n) = if unzip(id(makeZippedList(n))) is + Pair(xs, ys) then zip( + map(x => x + 1, xs), + map(x => x * x, ys) + ) +testZipMapBothUnzip(4) +//│ = Cons(Pair(5, 25), Cons(Pair(4, 16), Cons(Pair(3, 9), Cons(Pair(2, 4), Nil)))) +//│ >>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>> +//│ ---------- deforest summary ---------- +//│ Ref(member:Nil)@5@inst_0_1_tsni --> Ref(ls_map)@inst_0_2_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(a)), Arg(false,Ref(atail))))@6@inst_0_1_tsni --> Ref(ls_map)@inst_0_2_tsni@Cons +//│ Ref(member:Nil)@7@inst_0_1_tsni --> Ref(ls_map)@inst_0_3_tsni@Nil +//│ Call(Ref(member:Cons),List(Arg(false,Ref(b)), Arg(false,Ref(btail))))@8@inst_0_1_tsni --> Ref(ls_map)@inst_0_3_tsni@Cons +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@9@inst_0_2_tsni --> Ref(xs_zip)@inst_0_4_tsni@Cons +//│ Ref(member:Nil)@10@inst_0_2_tsni --> Ref(xs_zip)@inst_0_4_tsni@dflt +//│ Call(Ref(member:Cons),List(Arg(false,Ref($tmp)), Arg(false,Ref($tmp))))@9@inst_0_3_tsni --> Ref(ys_zip)@inst_0_4_tsni@Cons +//│ Ref(member:Nil)@10@inst_0_3_tsni --> Ref(ys_zip)@inst_0_4_tsni@dflt +//│ -------------- executing ------------- +//│ = Cons(Pair(5, 25), Cons(Pair(4, 16), Cons(Pair(3, 9), Cons(Pair(2, 4), Nil)))) +//│ <<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<< diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala index e519427559..c1dc1160d5 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -24,6 +24,8 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: val showJS = NullaryCommand("sjs") val showRepl = NullaryCommand("showRepl") val traceJS = NullaryCommand("traceJS") + val deforestFlag = NullaryCommand("deforest") + val deforestInfo = NullaryCommand("deforestInfo") val expect = Command("expect"): ln => ln.trim @@ -42,6 +44,10 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: override def doTrace = showRepl.isSet override def emitDbg(str: String): Unit = output(str) + val deforestTL = new TraceLogger: + override def doTrace: Bool = deforestInfo.isSet + override def emitDbg(str: String): Unit = output(str) + lazy val host = hostCreated = true given TL = replTL @@ -56,6 +62,10 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: h private var hostCreated = false + + + given deforestState: deforest.Deforest.State = new deforest.Deforest.State + override def run(): Unit = try super.run() finally if hostCreated then host.terminate() @@ -64,6 +74,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: val outerRaise: Raise = summon val reportedMessages = mutable.Set.empty[Str] + var correctResult: Opt[Str] = None if showJS.isSet then given Raise = @@ -83,6 +94,18 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: val jsStr = je.stripBreaks.mkString(100) output(s"JS (unsanitized):") output(jsStr) + + if deforestFlag.isSet then + import codegen.deforest.* + output(">>>>>>>>>>>>>>>>>>>>>>>>> Deforestation JS >>>>>>>>>>>>>>>>>>>>>>>>>>") + Deforest(le) match + case R(msg) => output(s"Not deforestable: $msg") + case L(deforestRes -> _ -> _) => + val jsStr = baseScp.nest.givenIn: + jsb.program(deforestRes, N, wd).stripBreaks.mkString(100) + output(jsStr) + output("<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation JS <<<<<<<<<<<<<<<<<<<<<<<<<<") + if js.isSet then given Elaborator.Ctx = curCtx given Raise = @@ -97,43 +120,40 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: val jsb = ltl.givenIn: new JSBuilder with JSBuilderArgNumSanityChecks - val resSym = new TempSymbol(S(blk), "block$res") - val lowered0 = low.program(blk) - val le = lowered0.copy(main = lowered0.main.mapTail: - case e: End => - Assign(resSym, Value.Lit(syntax.Tree.UnitLit(false)), e) - case Return(res, implct) => - assert(implct) - Assign(resSym, res, Return(Value.Lit(syntax.Tree.UnitLit(false)), true)) - case tl: (Throw | Break | Continue) => tl - ) - if showLoweredTree.isSet then - output(s"Lowered:") - output(le.showAsTree) - // * We used to do this to avoid needlessly generating new variable names in separate blocks: - // val nestedScp = baseScp.nest - val nestedScp = baseScp - // val nestedScp = codegen.js.Scope(S(baseScp), curCtx.outer, collection.mutable.Map.empty) // * not needed + def getResSymAndResNme(n: Str) = + val resSym = new TempSymbol(S(blk), n) + resSym -> baseScp.allocateName(resSym) - val resNme = nestedScp.allocateName(resSym) + def assignResultSymForBlock(lowered: Program, resSym: TempSymbol) = + lowered.copy(main = lowered.main.mapTail: + case e: End => + Assign(resSym, Value.Lit(syntax.Tree.UnitLit(false)), e) + case Return(res, implct) => + assert(implct) + Assign(resSym, res, Return(Value.Lit(syntax.Tree.UnitLit(false)), true)) + case tl: (Throw | Break | Continue) => tl + ) - if ppLoweredTree.isSet then - output(s"Pretty Lowered:") - output(Printer.mkDocument(le)(using summon[Raise], nestedScp).toString) + def mkJS(le: Program) = + val (pre, js) = baseScp.givenIn: + jsb.worksheet(le) + val preStr = pre.stripBreaks.mkString(100) + val jsStr = js.stripBreaks.mkString(100) + if showSanitizedJS.isSet then + output(s"JS:") + output(jsStr) + preStr -> jsStr - val (pre, js) = nestedScp.givenIn: - jsb.worksheet(le) - val preStr = pre.stripBreaks.mkString(100) - val jsStr = js.stripBreaks.mkString(100) - if showSanitizedJS.isSet then - output(s"JS:") - output(jsStr) - def mkQuery(preStr: Str, jsStr: Str)(k: Str => Unit) = + def mkQuery(preStr: Str, jsStr: Str)(handleResult: Iterable[Str] => Unit) = val queryStr = jsStr.replaceAll("\n", " ") val (reply, stderr) = host.query(preStr, queryStr, !expectRuntimeOrCodeGenErrors && fixme.isUnset && todo.isUnset) reply match - case ReplHost.Result(content) => k(content) + case ReplHost.Result(content) => + val res :+ end = content.splitSane('\n') : @unchecked + // TODO: seems that not all programs end with "undefined" now + // assert(end == "undefined") + handleResult(res) case ReplHost.Empty => case ReplHost.Unexecuted(message) => ??? case ReplHost.Error(isSyntaxError, message, otherOutputs) => @@ -151,20 +171,60 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: source = Diagnostic.Source.Runtime)) if stderr.nonEmpty then output(s"// Standard Error:\n${stderr}") - if traceJS.isSet then - host.execute( - s"$runtimeNme.TraceLogger.enabled = true; " + - s"$runtimeNme.TraceLogger.resetIndent(0)") + def executeJS(preStr: Str, jsStr: Str, resNme: Str) = + if traceJS.isSet then + host.execute( + s"$runtimeNme.TraceLogger.enabled = true; " + + s"$runtimeNme.TraceLogger.resetIndent(0)") + + // * Sometimes the JS block won't execute due to a syntax or runtime error so we always set this first + host.execute(s"$resNme = undefined") + + mkQuery(preStr, jsStr): stdout => + stdout.foreach: line => + output(s"> ${line}") + if traceJS.isSet then + host.execute(s"$runtimeNme.TraceLogger.enabled = false") - // * Sometimes the JS block won't execute due to a syntax or runtime error so we always set this first - host.execute(s"$resNme = undefined") + def handleDefinedValues(nme: Str, sym: Symbol, expect: Opt[Str])(handleResult: Str => Unit) = + val le = + import codegen.* + Return( + Call( + Value.Ref(Elaborator.State.runtimeSymbol).selSN("printRaw"), + Arg(false, Value.Ref(sym)) :: Nil)(true, false), + implct = true) + val je = baseScp.givenIn: + jsb.block(le, endSemi = false) + val jsStr = je.stripBreaks.mkString(100) + mkQuery("", jsStr): out => + val result = out.mkString + expect match + case S(expected) if result =/= expected => raise: + ErrorReport(msg"Expected: '${expected}', got: '${result}'" -> N :: Nil, + source = Diagnostic.Source.Runtime) + case _ => () + val anon = nme.isEmpty + handleResult(result) + result match + case "undefined" if anon => + case "()" if anon => + case _ => + output(s"${if anon then "" else s"$nme "}= ${result.indentNewLines("| ")}") - mkQuery(preStr, jsStr): stdout => - stdout.splitSane('\n').init // should always ends with "undefined" (TODO: check) - .foreach: line => - output(s"> ${line}") - if traceJS.isSet then - host.execute(s"$runtimeNme.TraceLogger.enabled = false") + val lowered0 = low.program(blk) + val resSym -> resNme = getResSymAndResNme("block$res") + val le = assignResultSymForBlock(lowered0, resSym) + if showLoweredTree.isSet then + output(s"Lowered:") + output(le.showAsTree) + + if ppLoweredTree.isSet then + output(s"Pretty Lowered:") + output(Printer.mkDocument(le)(using summon[Raise], baseScp).toString) + + val (preStr, jsStr) = mkJS(le) + executeJS(preStr, jsStr, resNme) if silent.isUnset then import Elaborator.Ctx.* @@ -178,29 +238,42 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: case _ => N case _ => N val valuesToPrint = ("", resSym, expect.get) +: definedValues.toSeq.sortBy(_._1) - valuesToPrint.foreach: (nme, sym, expect) => - val le = - import codegen.* - Return( - Call( - Value.Ref(Elaborator.State.runtimeSymbol).selSN("printRaw"), - Arg(false, Value.Ref(sym)) :: Nil)(true, false), - implct = true) - val je = nestedScp.givenIn: - jsb.block(le, endSemi = false) - val jsStr = je.stripBreaks.mkString(100) - mkQuery("", jsStr): out => - val result = out.splitSane('\n').init.mkString // should always ends with "undefined" (TODO: check) - expect match - case S(expected) if result =/= expected => raise: - ErrorReport(msg"Expected: '${expected}', got: '${result}'" -> N :: Nil, - source = Diagnostic.Source.Runtime) - case _ => () - val anon = nme.isEmpty - result match - case "undefined" if anon => - case "()" if anon => - case _ => - output(s"${if anon then "" else s"$nme "}= ${result.indentNewLines("| ")}") + valuesToPrint.foreach: (nme, sym, expected) => + handleDefinedValues(nme, sym, expected)(if sym === resSym then r => correctResult = S(r) else _ => ()) + if deforestFlag.isSet then + import codegen.deforest.* + output(">>>>>>>>>>>>>>>>>>>>>>>>>>> Deforestation >>>>>>>>>>>>>>>>>>>>>>>>>>>") + val deforestLow = ltl.givenIn: + codegen.Lowering() + val le = deforestLow.program(blk) + Deforest(le) match + case R(msg) => output(s"Not deforestable: $msg") + case L(deforestRes -> summary -> detail) => + if deforestInfo.isSet then + output(detail) + output("---------- deforest summary ----------") + output(summary) + val resSym -> resNme = getResSymAndResNme("block$res_deforest") + val deforestRes2 = assignResultSymForBlock(deforestRes, resSym) + if showLoweredTree.isSet then + output(s"Lowered:") + output(deforestRes2.showAsTree) + if ppLoweredTree.isSet then + output(s"Pretty Lowered:") + output(Printer.mkDocument(deforestRes2)(using summon[Raise], baseScp).toString) + val (preStr, jsStr) = mkJS(deforestRes2) + if showSanitizedJS.isSet then + output("------ deforested sanitized js -------") + output(jsStr) + output("-------------- executing -------------") + executeJS(preStr, jsStr, resNme) + if silent.isUnset then + handleDefinedValues("", resSym, expect.get): result => + if correctResult.fold(false)(_ != result) then raise: + ErrorReport( + msg"The result from deforestated program (\"${result}\") is different from the one computed by the original prorgam (\"${correctResult.get}\")" -> N :: Nil, + source = Diagnostic.Source.Runtime) + + output("<<<<<<<<<<<<<<<<<<<<<<<<<<< Deforestation <<<<<<<<<<<<<<<<<<<<<<<<<<<")