diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/EtaExpansion.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/EtaExpansion.scala index acf7ec55c0..480bcad701 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/EtaExpansion.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/EtaExpansion.scala @@ -97,7 +97,7 @@ class EtaExpansionSolver(val constraintSolver: FlowConstraintSolver, tl: TraceLo EtaTargets(a.paramCount, a.requiredParamCount, a.hasRestParam, a.prodFuns ++ b.prodFuns) go(S(mergedRes)) else Nil - case UnknownProd | UnsafeEta | NoProd => Nil + case UnknownProd | UnsafeEta => Nil case _: Ctor => Nil end go @@ -107,7 +107,7 @@ class EtaExpansionSolver(val constraintSolver: FlowConstraintSolver, tl: TraceLo case _ => false then go(N) else Nil - case UnknownProd | UnsafeEta | NoProd => Nil + case UnknownProd | UnsafeEta => Nil case _: Ctor => Nil end funResShape diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/PolyInstantiationRewrite.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/PolyInstantiationRewrite.scala index 84b2007a01..1251b19b20 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/PolyInstantiationRewrite.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/PolyInstantiationRewrite.scala @@ -71,8 +71,8 @@ abstract class PolyInstantiationRewrite(val constraintSolver: FlowConstraintSolv val res = LinkedHashMap.empty[InstantiationId, Map[TermSymbol, (BlockMemberSymbol, TermSymbol)]] def funSymsToBeCopiedAt(path: InstantiationId): Iterator[TermSymbol] = collector.synthesizedInstIdToFunSym.get(path) match - case S(entry) => collector.funToSccGroups(entry).iterator.filterNot(_ is entry) - case N => collector.funToSccGroups(path.last.getReferredFun.get).iterator + case S(entry) => collector.funToScc(entry).iterator.filterNot(_ is entry) + case N => collector.funToScc(path.last.getReferredFun.get).iterator for instId <- solvers.iterator.flatMap(_.polyInstIds) path <- instId.inits diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/EffectAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/EffectAnalysis.scala index 4328c2d1d4..531372167b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/EffectAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/EffectAnalysis.scala @@ -13,11 +13,9 @@ enum EffectSummary: object EffectAnalysis: - def summarize(effectVar: StratVar | NoProd): EffectSummary = - effectVar match - case NoProd => EffectSummary.Pure - case effectVar: StratVar => - if effectVar.lowerBounds.forall(_.isInstanceOf[StratVar]) + def summarize(effectVar: EffectVar): EffectSummary = + effectVar.fold(EffectSummary.Pure): v => + if v.lowerBounds.isEmpty then EffectSummary.Pure else EffectSummary.UnsafeEta diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala index e0d82ec681..a808e60281 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala @@ -80,23 +80,26 @@ object FlowAnalysis: nonAffineTracking: Bool, accumulatorTracking: Bool, logEffects: Bool, - )(using TraceLogger, Elaborator.State, Raise, SymbolPrinter) = + )(using Config, TraceLogger, Elaborator.State, Raise, SymbolPrinter) = given State = new State val pre = new FlowPreAnalyzer(pgrm) - val constrCol = new FlowConstraintsCollector( + val collector = new FlowConstraintsCollector( pre, mono, nonAffineTracking, accumulatorTracking, ) - val solver = new FlowConstraintSolver(constrCol) + val solver = new FlowConstraintSolver(collector)(collector.globalScope) + solver.solve() + solver.log() + if config.checkInvariants then solver.assertConcrete() val tl = summon[TraceLogger] if logEffects then tl.scoped(TraceScope.Effects): if tl.isTracing then EffectAnalysis.logResults(solver) solver // * FIXME: not all symbols have irDefn; add and look up an irHeader instead - private[flowAnalysis] def isPureCall(call: Call, whichArgList: Int): Bool = call.fun match + def hasPureAnnotation(call: Call, whichArgList: Int): Bool = call.fun match case Value.SimpleRef(b: BuiltinSymbol) => whichArgList == 0 && b.isPure case p => p.targetSymbol.exists: case s: TermSymbol => s.irFunDefn match @@ -202,11 +205,33 @@ object FunRef: case _ => N -sealed trait ProdStrat -sealed trait ConsStrat +sealed trait Strat -type NoProd = NoProd.type -case object NoProd extends ProdStrat +sealed trait ProdStrat extends Strat +sealed trait ConsStrat extends Strat + +type Constraint = ProdStrat -> ConsStrat + +/** [[BottomEffect]] represents that an effect variable is known to be the + * bottom type (no effect). This avoids allocating [[StratVar]]. */ +case object BottomEffect + +/** [[EffectVar]] represents an effect variable for some entities. + * + * An [[EffectVar]]'s LB can only be [[UnsafeEta]]. + */ +type EffectVar = StratVar | BottomEffect.type + +extension (effect: EffectVar) + inline def fold[A](ifBot: => A)(f: StratVar => A): A = effect match + case BottomEffect => ifBot + case variable: StratVar => f(variable) + + inline def map(f: StratVar => StratVar): EffectVar = + effect.fold[EffectVar](BottomEffect)(f) + + inline def foreach(f: StratVar => Unit): Unit = + effect.fold(())(f) class StratVar(val name: Str, val sourceSymbol: Opt[Symbol], val generatedForFun: Opt[TermSymbol])(using eState: Elaborator.State) extends Symbol(using eState) with ProdStrat with ConsStrat: @@ -214,8 +239,8 @@ class StratVar(val name: Str, val sourceSymbol: Opt[Symbol], val generatedForFun def subst(using SymbolSubst): StratVar = this def toLoc: Opt[Loc] = sourceSymbol.flatMap(_.toLoc) // Bounds belong to this analysis-local variable and are populated by FlowConstraintSolver. - val upperBounds = LinkedHashSet.empty[ConsStrat] - val lowerBounds = LinkedHashSet.empty[ProdStrat] + val upperBounds: mutable.Set[ConsStrat] = LinkedHashSet.empty + val lowerBounds: mutable.Set[ProdStrat] = LinkedHashSet.empty lazy val asIntoParam = new StratVar.IntoParamImpl(this) lazy val asPossibleAccumulator = new StratVar.PossibleAccumulatorImpl(this) @@ -254,8 +279,8 @@ sealed trait StratWithOrigin[A <: OriginId]: sealed trait MarkerProdStrat extends ProdStrat sealed trait MarkerConsStrat extends ConsStrat -sealed trait ConcreteCtorConsumer extends StratWithOrigin[ResultId]: - val srcs = MutSet.empty[Ctor | MarkerProdStrat] +sealed trait ConcreteCtorConsumer extends ConsStrat with StratWithOrigin[ResultId]: + val srcs: mutable.Set[Ctor | MarkerProdStrat] = MutSet.empty class ProdFun( val exprId: FunId, @@ -267,10 +292,10 @@ class ProdFun( val restParam: Opt[ConsStrat], val res: ProdStrat, val capturedVarUpperbound: StratVar, - val effect: StratVar | NoProd, + val effect: EffectVar, ) extends ProdStrat with StratWithOrigin[FunId]: def paramInfo: (Int, Int, Bool) = (params.size, requiredParamCount, restParam.isDefined) - val dests = MutSet.empty[ConsFun | MarkerConsStrat] + val dests: mutable.Set[ConsFun | MarkerConsStrat] = MutSet.empty override def toString(): String = s"(${params.map(_.toString()).mkString(", ")}) -> ${res.toString()}" @@ -286,7 +311,7 @@ class Ctor( val ctor: CtorCls, val args: Ls[SelField -> ProdStrat] ) extends ProdStrat with StratWithOrigin[ResultId]: - val dests = MutSet.empty[ConcreteCtorConsumer | MarkerConsStrat] + val dests: mutable.Set[ConcreteCtorConsumer | MarkerConsStrat] = MutSet.empty override def toString(): String = s"$ctor(${args.map(_.toString()).mkString(", ")})" @@ -297,9 +322,9 @@ class ConsFun( )( val params: Ls[ProdStrat], val res: ConsStrat, - val effect: StratVar | NoProd, + val effect: EffectVar, ) extends ConsStrat with StratWithOrigin[CallId]: - val srcs = MutSet.empty[ProdFun | MarkerProdStrat] + val srcs: mutable.Set[ProdFun | MarkerProdStrat] = MutSet.empty override def toString(): String = s"(${params.map(_.toString()).mkString(", ")}) -> ${res.toString()}" @@ -330,7 +355,33 @@ case class ConcreteId[A <: OriginId](exprId: A, instId: InstantiationId): case r: ResultId => s"${r.getResult}" -class ProdStratScheme(val s: StratVar, val constraints: Ls[ProdStrat -> ConsStrat]) +/** A solved strategy graph rooted at a strategy variable, reusable through + * polymorphic instantiation (see `Instantiator`). + * + * In practice, this is for instantiating a function producer ([[ProdFun]]), + * corresponding to function's polymorphic splitting. But [[StratScheme]] + * itself is general enough to represent a scheme for arbitrary strategies. + * + * Whenever in the scheme there is an (reference to an) external strategy + * variable that is not part of the scheme (e.g., a captured variable), the + * collector creates a proxy of that external strategy variable and uses it in + * place of the external strategy variable in [[strategies]] and the mapping is + * in [[environment]]. During instantiation, the collector replaces the proxy + * with the strategy variable for the current scope in appropriate bounds of + * the instantiated variables. + * + * @param s The "root" strategy variable that instantiation instantiates and + * returns. + * @param strategies The (solved) strategies, containing strategy variables, + * producers, or consumers, that need to be duplicated on instantiation. + * @param environment A list of environment entries (external strategy variable + * -> internal strategy variable). + */ +class StratScheme( + val s: StratVar, + val strategies: Ls[Strat], + val environment: Ls[StratVar -> StratVar], +) class FlowPreAnalyzer(val pgrm: Program)(using val tl: TraceLogger, @@ -368,9 +419,9 @@ class FlowPreAnalyzer(val pgrm: Program)(using val labelSymToCtxOfLabel = MutMap.empty[Symbol, Ls[InCtx]] val selToCtxOfSel = MutMap.empty[ResultId, Ls[InCtx]] val modSymToBms = MutMap.empty[Symbol, BlockMemberSymbol] - val generatedVars = MutMap.empty[Symbol, StratVar] // TODO: tighten `Symbol` type! - val generatedEffects = MutMap.empty[Symbol, StratVar] // TODO: tighten `Symbol` type! // TODO: populate for getters! - val capturedVars = MutMap.empty[FunId, LinkedHashSet[Symbol]] + val generatedVars = LinkedHashMap.empty[Symbol, StratVar] // TODO: tighten `Symbol` type! + val generatedEffects = LinkedHashMap.empty[Symbol, EffectVar] // TODO: tighten `Symbol` type! // TODO: populate for getters! + val capturedVars = LinkedHashMap.empty[FunId, LinkedHashSet[Symbol]] val affinityCounts = MutMap.empty[Symbol, Int].withDefaultValue(0) def getEnclosingMatchesForSel(selExprId: ResultId) = selToCtxOfSel.get(selExprId).fold(Iterator.empty): @@ -723,54 +774,291 @@ class FlowConstraintsCollector( given tl: TraceLogger = preAnalyzer.tl import StratVar.freshVar - private class ConstraintsCollector private ( - val forFunGroup: Opt[TermSymbol], + + private case class Ctx( + result: ConsStrat, + fun: Opt[FunCtx], + suppressEffect: Bool, // e.g., if @pure + ) + + private case class FunCtx( + effect: EffectVar, + paramLists: Ls[LoweredParamList], + ): + def isOwnParam(sym: Symbol): Bool = + paramLists.exists(_.paramSyms.contains(sym)) + + private object Ctx: + val default = Ctx(result = UnknownCons, fun = N, suppressEffect = false) + + + /** [[ConstraintScope]] represents the current scope of constraints being + * collected. There are four types of scopes (`forScc`, `forFun`): the global + * scope `(N, N)`, the scope of an SCC in poly mode `(S, N)`, the scope of a + * function in an SCC in poly mode `(S, S)`, and the scope of a function in + * mono mode `(N, S)`. It contains the following information: + * + * @param forScc The symbol of the representative function of the SCC of the + * function being analyzed. + * @param forFun The symbol of the owning root function; (nested functions + * retain this). + * @param constraints The constraints collected in this scope. + * @param strategies The (solved) strategies from instantiation. + * [[constraints]] (endpoints) with [[strategies]] together contribute to + * [[StratScheme.strategies]]. + * @param environment The mapping from the environment strategy variables to + * the local strategy variables, correspdonding to + * [[StratScheme.environment]]. + * @param variables The mapping from the original strategy variables from the + * pre-analyzer to the strategy variables local to this SCC. This is used + * to ensure that each original strategy variable is not shared between + * different SCCs. + */ + class ConstraintScope private ( + val forScc: Opt[TermSymbol], val forFun: Opt[TermSymbol], - buffer: mutable.ListBuffer[ProdStrat -> ConsStrat], + val constraints: mutable.Buffer[Constraint], + val strategies: mutable.Buffer[Strat], + val environment: mutable.Map[StratVar, StratVar], + val variables: mutable.Map[StratVar, StratVar], ): - def constraints: Ls[ProdStrat -> ConsStrat] = buffer.toList - val instId: Opt[InstantiationId] = forFunGroup.fold(S(Nil))(_ => N) - def constrain(p: ProdStrat, c: ConsStrat) = buffer.prepend(p -> c) - def constrain(cs: Iterable[ProdStrat -> ConsStrat]) = buffer.prependAll(cs) - def forActualRootFun(fun: TermSymbol): ConstraintsCollector = - assert(preAnalyzer.res.rootFunDefns.contains(fun)) - new ConstraintsCollector(forFunGroup, S(fun), buffer) - private object ConstraintsCollector: - def apply(forFunGroup: Opt[TermSymbol], forFun: Opt[TermSymbol]): ConstraintsCollector = - new ConstraintsCollector(forFunGroup, forFun, mutable.ListBuffer.empty) + val instId: Opt[InstantiationId] = forScc.fold(S(Nil))(_ => N) + + def constrain(p: ProdStrat, c: ConsStrat): Unit = + constraints.append(p -> c) + + def constrainOpaque(v: StratVar): Unit = + constrain(UnknownProd, v) + constrain(v, UnknownCons) + + /** Query the strategy variable for this scope. + * + * - If in mono mode or the global scope in poly mode, use `v`. + * - If in other scopes in poly mode, + * - If `v` belongs to the current SCC, create a local copy of `v` for + * this scope and cache it in [[variables]]. + * - If `v` does not belong to the current SCC, create a local proxy of + * `v` for this scope and cache it in [[environment]]. + */ + def variable(v: StratVar): StratVar = forScc match + case S(forScc) => + if v.generatedForFun.flatMap(funToSccRep).contains(forScc) then + variables.getOrElseUpdate(v, locally: + val vv = new StratVar(v.name, v.sourceSymbol, v.generatedForFun) + fState.stratVars += vv + vv + ) + else + environment.getOrElseUpdate(v, locally: + freshVar(s"env_${v.name}", N, S(forScc)) + ) + case N => v + + def forActualRootFun(forFun: TermSymbol): ConstraintScope = + assert(preAnalyzer.res.rootFunDefns.contains(forFun)) + new ConstraintScope(forScc, S(forFun), constraints, strategies, environment, variables) - private val globalCollector = ConstraintsCollector(N, N) - def allConstraints = globalCollector.constraints - val funToSccGroups = MutMap.empty[TermSymbol, Ls[TermSymbol]] - def funToSccRep(tSym: TermSymbol): Option[TermSymbol] = funToSccGroups.get(tSym).map(_.head) + object ConstraintScope: + def apply(forScc: Opt[TermSymbol], forFun: Opt[TermSymbol]): ConstraintScope = + new ConstraintScope( + forScc, forFun, + constraints = mutable.Buffer.empty, + strategies = mutable.Buffer.empty, + environment = LinkedHashMap.empty, + variables = LinkedHashMap.empty + ) + + val globalScope = ConstraintScope(N, N) + + val funToScc = MutMap.empty[TermSymbol, Ls[TermSymbol]] + def funToSccRep(tSym: TermSymbol): Option[TermSymbol] = funToScc.get(tSym).map(_.head) // for fusing strictly internal parts of functions val synthesizedInstIdToFunSym = LinkedHashMap.empty[InstantiationId, TermSymbol] val allRealCtors = mutable.Buffer.empty[Ctor] - private def registerCtor(c: Ctor)(using cc: ConstraintsCollector): Ctor = - if cc.forFunGroup.isEmpty then allRealCtors += c + private def registerCtor(c: Ctor)(using cc: ConstraintScope): Ctor = + if cc.forScc.isEmpty then allRealCtors += c c - private val generatedVars: collection.Map[Symbol, StratVar] = // TODO: tighten `Symbol` type! - preAnalyzer.res.generatedVars.withDefaultValue(preAnalyzer.res.primitiveStratVar) - // Unknown getter effects must not enter primitiveStratVar: it also describes runtime values. + private def generatedVars(sym: Symbol)(using cc: ConstraintScope): StratVar = // TODO: tighten `Symbol` type! + cc.variable(preAnalyzer.res.generatedVars.getOrElse(sym, preAnalyzer.res.primitiveStratVar)) + + // Unknown getter effects must not flow into primitiveStratVar: its bounds also + // feed runtime value slots, where effect markers have no representation. private val unknownEffect = freshVar("unknown_effect") - private val generatedEffects: collection.Map[Symbol, StratVar] = // TODO: tighten `Symbol` type! // TODO: use at access sites of getters! - preAnalyzer.res.generatedEffects.withDefaultValue(unknownEffect) + private def generatedEffects(sym: Symbol)(using cc: ConstraintScope): EffectVar = // TODO: tighten `Symbol` type! // TODO: use at access sites of getters! + preAnalyzer.res.generatedEffects.getOrElse(sym, unknownEffect).map(cc.variable) // * This is for effect analysis. The nonlocal variables that are potentially shared. // * If a function accesses a nonlocal, it is conservatively considered unsafe for eta-expansion. private val nonlocals = preAnalyzer.res.capturedVars.valuesIterator.flatten .filterNot(_ == eState.runtimeSymbol).toSet - locally { - val funsToProdStratScheme = MutMap.empty[TermSymbol, ProdStratScheme] + /** [[Instantiator]] instantiates a strategy graph for a function producer + * ([[ProdFun]]), represented by a polymorphic splitting scheme + * ([[StratScheme]]). + * + * @param referSite The reference site that is instantiating the scheme. + * @param referringTo The symbol of the function being instantiated. + */ + private class Instantiator( + referSite: ResultId, + referringTo: TermSymbol, + )(using cc: ConstraintScope): + + // The representative of the SCC of the function being instantiated. + private val groupRep: TermSymbol = funToSccRep(referringTo).get + + // Instantiation copies the strategy graph preserving shared nodes. The + // following maps ensure that each original strategy is copied once only. + private val copiesOfStratVar = MutMap.empty[StratVar, StratVar] + private val copiesOfProdFun = MutMap.empty[ProdFun, ProdFun] + private val copiesOfConsFun = MutMap.empty[ConsFun, ConsFun] + private val copiesOfCtor = MutMap.empty[Ctor, Ctor] + private val copiesOfDtor = MutMap.empty[Dtor, Dtor] + private val copiesOfFieldSel = MutMap.empty[FieldSel, FieldSel] + + // A strategy graph may contain cycles so instantiation can't be done in one + // pass. The following stack of callbacks stores the work to do in the + // second pass, completing the bounds of copied strategies after the first. + private type Callback = () => Unit + private val callbacks = mutable.Stack.empty[Callback] + + private def newInstantiation(instId: Opt[InstantiationId]): InstantiationId = + referSite :: instId.getOrElse(Nil) + + private def duplicateProdStrat[T <: ProdStrat](s: T): T = (s match + case v: StratVar => duplicateStratVar(v) + case p: ProdFun => duplicateProdFun(p) + case c: Ctor => duplicateCtor(c) + case s: MarkerProdStrat => s + ).asInstanceOf[T] + + private def duplicateConsStrat[T <: ConsStrat](c: T): T = (c match + case v: StratVar => duplicateStratVar(v) + case c: ConsFun => duplicateConsFun(c) + case d: Dtor => duplicateDtor(d) + case s: FieldSel => duplicateFieldSel(s) + case c: MarkerConsStrat => c + case pAcc: PossibleAccumulator => duplicateStratVar(pAcc.s).asPossibleAccumulator + case iPrm: IntoParam => duplicateStratVar(iPrm.s).asIntoParam + ).asInstanceOf[T] + + private def duplicateStratVar(s: StratVar): StratVar = + assert(s.generatedForFun.flatMap(funToSccRep).contains(groupRep)) + copiesOfStratVar.getOrElseUpdate(s, locally: + val copy = freshVar(s.name, s.sourceSymbol, cc.forFun) + cc.strategies += copy + callbacks.push: () => + for lower <- s.lowerBounds do copy.lowerBounds += duplicateProdStrat(lower) + for upper <- s.upperBounds do copy.upperBounds += duplicateConsStrat(upper) + copy + ) + + private def duplicateProdFun(p: ProdFun): ProdFun = copiesOfProdFun.getOrElseUpdate(p, locally: + val copy = new ProdFun(p.exprId, S(newInstantiation(p.instantiationId)))( + p.params.map(duplicateConsStrat), + p.requiredParamCount, + p.restParam.map(duplicateConsStrat), + duplicateProdStrat(p.res), + duplicateStratVar(p.capturedVarUpperbound), + p.effect.map(duplicateStratVar) + ) + cc.strategies += copy + callbacks.push: () => + for dest <- p.dests do copy.dests += duplicateConsStrat(dest) + copy + ) + + private def duplicateConsFun(c: ConsFun): ConsFun = copiesOfConsFun.getOrElseUpdate(c, locally: + val copy = new ConsFun(c.exprId, S(newInstantiation(c.instantiationId)))( + c.params.map(duplicateProdStrat), + duplicateConsStrat(c.res), + c.effect.map(duplicateStratVar) + ) + cc.strategies += copy + callbacks.push: () => + for src <- c.srcs do copy.srcs += duplicateProdStrat(src) + copy + ) + + private def duplicateCtor(c: Ctor): Ctor = copiesOfCtor.getOrElseUpdate(c, locally: + val copy = registerCtor(new Ctor(c.exprId, S(newInstantiation(c.instantiationId)))( + c.ctor, + c.args.map((a, b) => a -> duplicateProdStrat(b)) + )) + cc.strategies += copy + callbacks.push: () => + for dest <- c.dests do copy.dests += duplicateConsStrat(dest) + copy + ) + + private def duplicateDtor(c: Dtor): Dtor = copiesOfDtor.getOrElseUpdate(c, locally: + val copy = new Dtor(c.exprId, S(newInstantiation(c.instantiationId))) + cc.strategies += copy + callbacks.push: () => + for src <- c.srcs do copy.srcs += duplicateProdStrat(src) + copy + ) + + private def duplicateFieldSel(c: FieldSel): FieldSel = copiesOfFieldSel.getOrElseUpdate(c, locally: + val copy = new FieldSel(c.exprId, S(newInstantiation(c.instantiationId)))( + c.field, + c.selectsFrom, + duplicateStratVar(c.consVar) + ) + cc.strategies += copy + callbacks.push: () => + for src <- c.srcs do copy.srcs += duplicateProdStrat(src) + copy + ) + + def instantiate(scheme: StratScheme): StratVar = + // Establish mapping from the scheme's proxy strategy variables to the + // environment strategy variables. See `StratScheme.environment`. + for (external, proxy) <- scheme.environment do + copiesOfStratVar(proxy) = cc.variable(external) + + val s = duplicateStratVar(scheme.s) + scheme.strategies.foreach: + case p: ProdStrat => duplicateProdStrat(p) + case c: ConsStrat => duplicateConsStrat(c) + + // The bounds of environment, i.e., the boundary between the scheme's + // internal and external strategy variables, need to be established again. + for (external, proxy) <- scheme.environment do + val internal = copiesOfStratVar(proxy) + proxy.lowerBounds.foreach(p => cc.constrain(duplicateProdStrat(p), internal)) + proxy.upperBounds.foreach(c => cc.constrain(internal, duplicateConsStrat(c))) + + while callbacks.nonEmpty do + val callback = callbacks.pop() + callback() + + s + + end Instantiator + + private def instantiate( + scheme: StratScheme, + referSite: ResultId, + referringTo: TermSymbol, + )(using cc: ConstraintScope): StratVar = + new Instantiator(referSite, referringTo).instantiate(scheme) + + /** Maps a function (symbol) to its corresponding polymorphic splitting scheme. */ + private val funToScheme = MutMap.empty[TermSymbol, StratScheme] + /** Analyze and prepare reusable function schemes ([[StratScheme]]) and store + * them into [[funToScheme]]. + */ + // TODO: analyze in mono mode too! + private def analyze(): Unit = if !mono then - // Computing the ProdStratScheme for each scc group, this way the sccs of the call graph is + // Computing the StratScheme for each scc group, this way the sccs of the call graph is // never materialized - object ProdStratSchemeAnalysisInScc extends SccAnalysis[TermSymbol]: + object SccStratSchemeAnalysis extends SccAnalysis[TermSymbol]: protected def successors(f: TermSymbol): Ls[TermSymbol] = var callees = Ls.empty[TermSymbol] object CollectAllReferredFun extends BlockTraverser: @@ -781,33 +1069,50 @@ class FlowConstraintsCollector( case _ => () CollectAllReferredFun.applyBlock(preAnalyzer.res.rootFunDefns(f).body) callees - protected def isHandled(f: TermSymbol) = funsToProdStratScheme.contains(f) + protected def isHandled(f: TermSymbol) = funToScheme.contains(f) protected def handleScc(groupedFuns: Ls[TermSymbol], sccId: Int): Unit = - for f <- groupedFuns do funToSccGroups(f) = groupedFuns + for f <- groupedFuns do funToScc(f) = groupedFuns val groupRep = groupedFuns.head - ConstraintsCollector(Some(groupRep), N).givenIn: cc ?=> + ConstraintScope(Some(groupRep), N).givenIn: cc ?=> + given Ctx = Ctx.default for funSym <- groupedFuns do - given ConstraintsCollector = cc.forActualRootFun(funSym) - processFunctionBody(preAnalyzer.res.funSymToFunDefn(funSym)) + given ConstraintScope = cc.forActualRootFun(funSym) + processFunctionDefn(preAnalyzer.res.funSymToFunDefn(funSym)) if nonAffineTracking then for sym <- preAnalyzer.res.nonAffineSyms stratVar <- preAnalyzer.res.generatedVars.get(sym) if stratVar.generatedForFun.flatMap(funToSccRep).contains(groupRep) - do cc.constrain(stratVar, NonAffine) + do cc.constrain(cc.variable(stratVar), NonAffine) + + val solver = new FlowConstraintSolver(FlowConstraintsCollector.this)(cc) + solver.solve() + + val strategies = + ( + cc.constraints.iterator.flatMap((p, c) => Iterator(p, c)) ++ + cc.strategies.iterator + ).distinct.toList + val environment = cc.environment.toList for funSym <- groupedFuns do - funsToProdStratScheme(funSym) = ProdStratScheme(generatedVars(funSym), cc.constraints) - end ProdStratSchemeAnalysisInScc + funToScheme(funSym) = StratScheme(generatedVars(funSym), strategies, environment) + + end SccStratSchemeAnalysis - ProdStratSchemeAnalysisInScc.queryAll(preAnalyzer.res.rootFunDefns.keys) + SccStratSchemeAnalysis.queryAll(preAnalyzer.res.rootFunDefns.keys) end if - + end analyze + + /** Collect the program's constraints by processing the program recursively, + * and then assemble the program's global constraints. + */ + private def collect(): Unit = // collect constraints from the top-level block - globalCollector.givenIn: cc ?=> + globalScope.givenIn: cc ?=> cc.constrain(preAnalyzer.res.primitiveStratVar, UnknownCons) cc.constrain(UnknownProd, preAnalyzer.res.primitiveStratVar) cc.constrain(UnsafeEta, unknownEffect) - processBlock(preAnalyzer.pgrm.main)(using cc, UnknownCons, N) + processBlock(preAnalyzer.pgrm.main)(using cc, Ctx.default) // this places non-affine constraints correctly: // - in mono mode there are no per-scc collectors, @@ -835,145 +1140,90 @@ class FlowConstraintsCollector( cc.constrain(generatedVars(fun.dSym), UnknownCons) else for (funSym, fun) <- preAnalyzer.res.rootFunDefns do - val pScheme = funsToProdStratScheme(funSym) + val scheme = funToScheme(funSym) val synthesizedRefUid = preAnalyzer.res.funSymToFunDefn(funSym).sym.asMemberRef(funSym).uid - val selfProd = pScheme.instantiate(synthesizedRefUid, funSym) + val selfProd = instantiate(scheme, synthesizedRefUid, funSym) cc.constrain(selfProd, UnknownCons) val selfInstId = synthesizedRefUid :: Nil synthesizedInstIdToFunSym(selfInstId) = funSym - // ========================= - - extension (pScheme: ProdStratScheme) def instantiate( - referSite: ResultId, - referringTo: TermSymbol - )(using cc: ConstraintsCollector): StratVar = - val groupRep: TermSymbol = funToSccRep(referringTo).get - val stratVarMap = MutMap.empty[StratVar, StratVar] - def updateInstantiationId(instId: Opt[InstantiationId]) = - S(instId.fold(referSite :: Nil)(referSite :: _)) - def duplicateVarStateOpt(s: StratVar | NoProd): StratVar | NoProd = - s match - case v: StratVar => duplicateVarState(v) - case NoProd => NoProd - def duplicateVarState(s: StratVar) = - if s.generatedForFun.fold(false): - forFun => funToSccRep(forFun).fold(false)(_ is groupRep) - then stratVarMap.getOrElseUpdate(s, freshVar(s.name, s.sourceSymbol, cc.forFun)) - else s - def duplicateProdStrat(s: ProdStrat): ProdStrat = s match - case v: StratVar => duplicateVarState(v) - case p: ProdFun => - new ProdFun(p.exprId, updateInstantiationId(p.instantiationId))( - p.params.map(duplicateConsStrat), - p.requiredParamCount, - p.restParam.map(duplicateConsStrat), - duplicateProdStrat(p.res), - duplicateVarState(p.capturedVarUpperbound), - duplicateVarStateOpt(p.effect)) - case UnknownProd => UnknownProd - case UnsafeEta => UnsafeEta - case NoProd => NoProd - case c: Ctor => registerCtor( - 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 v: StratVar => duplicateVarState(v) - case c: ConsFun => - new ConsFun(c.exprId, updateInstantiationId(c.instantiationId))( - c.params.map(duplicateProdStrat), - duplicateConsStrat(c.res), - duplicateVarStateOpt(c.effect)) - case UnknownCons => UnknownCons - case NonAffine => NonAffine - case Accumulator => Accumulator - case pAcc: PossibleAccumulator => duplicateVarState(pAcc.s).asPossibleAccumulator - case iPrm: IntoParam => duplicateVarState(iPrm.s).asIntoParam - case fSel: FieldSel => - new FieldSel(fSel.exprId, updateInstantiationId(fSel.instantiationId))( - fSel.field, - fSel.selectsFrom, - duplicateVarState(fSel.consVar)) - case dtor: Dtor => new Dtor(dtor.exprId, updateInstantiationId(dtor.instantiationId)) - val newProd = duplicateVarState(pScheme.s) - pScheme.constraints.foreach: (p, c) => - cc.constrain(duplicateProdStrat(p), duplicateConsStrat(c)) - newProd + end collect - extension (v: StratVar) - def constrainOpaque(using cc: ConstraintsCollector): Unit = - cc.constrain(UnknownProd, v) - cc.constrain(v, UnknownCons) + private def processFunctionBody( + resName: String, + params: NELs[LoweredParamList], + body: Block, + funLamId: FunId, + annotations: Ls[Annot], + )(using cc: ConstraintScope, ctx: Ctx): ProdStrat = + def paramListFunId(whichParamList: Int): FunId = + funLamId match + case (sym: TermSymbol, -1) => (sym, whichParamList) + case lambdaExprId: ResultId => + assert(whichParamList == 0) + lambdaExprId + case other => lastWords(s"unexpected funLamId shape: $other") + val res = freshVar(resName, cc.forFun) + val sourceSymbol = funLamId match + case (sym: TermSymbol, _) => S(sym) + case _: ResultId => N + val effect = + freshVar(s"effect_$funLamId", sourceSymbol, cc.forFun) + params.foreach: ps => + ps.restParam.foreach: p => + cc.constrainOpaque(generatedVars(p.sym)) + // * The argument of an optional parameter may be omitted, in which case the parameter is + // * `null`, a value that is not tracked by the analysis. + ps.params.drop(ps.paramCountLB).foreach: p => + cc.constrain(UnknownProd, generatedVars(p.sym)) + val funValueStrat = params.zipWithIndex.foldRight[ProdStrat](res): + case ((ps, whichParamList), acc) => + val plFunId = paramListFunId(whichParamList) + // A declared curried function runs its body only at the final list. + // Applying earlier parameter lists only captures arguments; each needs its own, initially pure summary. + val effectParamList = + if whichParamList == params.size - 1 then effect + else freshVar(s"effect_$plFunId", cc.forFun) + val capUB = freshVar(s"cap_ub_$plFunId", cc.forFun) + for + v <- preAnalyzer.res.capturedVars(plFunId) + capturedSymStrat <- preAnalyzer.res.generatedVars.get(v) + do + cc.constrain(cc.variable(capturedSymStrat), capUB) + new ProdFun(plFunId, cc.instId)( + ps.params.map(p => generatedVars(p.sym)), + ps.paramCountLB, + ps.restParam.map(p => generatedVars(p.sym)), + acc, + capUB, + effectParamList) - def mkFunProdStrat( - resName: String, - params: NELs[LoweredParamList], - body: Block, - funLamId: FunId, - annotations: Ls[Annot] - )(using cc: ConstraintsCollector): ProdStrat = - def paramListFunId(whichParamList: Int): FunId = - funLamId match - case (sym: TermSymbol, -1) => (sym, whichParamList) - case lambdaExprId: ResultId => - assert(whichParamList == 0) - lambdaExprId - case other => lastWords(s"unexpected funLamId shape: $other") - val res = freshVar(resName, cc.forFun) - val sourceSymbol = funLamId match - case (sym: TermSymbol, _) => S(sym) - case _: ResultId => N - val effect = - freshVar(s"effect_$funLamId", sourceSymbol, cc.forFun) - params.foreach: ps => - ps.restParam.foreach: p => - generatedVars(p.sym).constrainOpaque - // * The argument of an optional parameter may be omitted, in which case the parameter is - // * `null`, a value that is not tracked by the analysis. - ps.params.drop(ps.paramCountLB).foreach: p => - cc.constrain(UnknownProd, generatedVars(p.sym)) - val funValueStrat = params.zipWithIndex.foldRight[ProdStrat](res): - case ((ps, whichParamList), acc) => - val plFunId = paramListFunId(whichParamList) - // A declared curried function runs its body only at the final list. - // Applying earlier parameter lists only captures arguments; each needs its own, initially pure summary. - val effectParamList = - if whichParamList == params.size - 1 then effect - else freshVar(s"effect_$plFunId", cc.forFun) - val capUB = freshVar(s"cap_ub_$plFunId", cc.forFun) - for - v <- preAnalyzer.res.capturedVars(plFunId) - capturedSymStrat <- generatedVars.get(v) - do - cc.constrain(capturedSymStrat, capUB) - new ProdFun(plFunId, cc.instId)( - ps.params.map(p => generatedVars(p.sym)), - ps.paramCountLB, - ps.restParam.map(p => generatedVars(p.sym)), - acc, - capUB, - effectParamList) - // An explicit @pure annotation overrides the latent effect. Keep analyzing body - // calls, but do not let their effects flow into this trusted override. - processBlock(body)(using cc, res, - if annotations.exists(_.isInstanceOf[Annot.Pure]) - then N else S(effect)) - funValueStrat - - def processFunctionDefn(fun: FunDefn)(using cc: ConstraintsCollector): Unit = - val isRoot = preAnalyzer.res.rootFunDefns.contains(fun.dSym) - if mono || !isRoot then - given ConstraintsCollector = if isRoot then cc.forActualRootFun(fun.dSym) else cc - processFunctionBody(fun) - - def processFunctionBody(fun: FunDefn)(using cc: ConstraintsCollector): Unit = + given ctx: Ctx = Ctx( + result = res, + fun = S(FunCtx(effect, params)), + suppressEffect = annotations.exists(_.isInstanceOf[Annot.Pure]) + ) + processBlock(body) + funValueStrat + + private def processFunctionDefn(fun: FunDefn)(using cc: ConstraintScope, ctx: Ctx): Unit = + val isRoot = preAnalyzer.res.rootFunDefns.contains(fun.dSym) + val belongsToCurrentScope = !isRoot || cc.forFun.contains(fun.dSym) + if mono || belongsToCurrentScope then + given ConstraintScope = if belongsToCurrentScope + then cc // If belongs to the current scope: just reuse the scope + else cc.forActualRootFun(fun.dSym) // If mono mode: create scope fun.params match case Nil => - processBlock(fun.body)(using cc, generatedVars(fun.dSym), S(generatedEffects(fun.dSym))) + given ctx: Ctx = Ctx( + result = generatedVars(fun.dSym), + fun = S(FunCtx(generatedEffects(fun.dSym), Nil)), + suppressEffect = fun.annotations.exists(_.isInstanceOf[Annot.Pure]) + ) + processBlock(fun.body)(using cc, ctx) case nel: NELs[?] => - val funProdStrat = mkFunProdStrat( + val funProdStrat = processFunctionBody( s"${fun.dSym.nme}_res", nel, fun.body, @@ -981,264 +1231,276 @@ class FlowConstraintsCollector( fun.annotations) cc.constrain(funProdStrat, generatedVars(fun.dSym)) - def processClsLikeDefn(cls: ClsLikeDefn)(using cc: ConstraintsCollector, currentEffect: Opt[StratVar]): Unit = - cls.methodKeys.valuesIterator.foreach(constrainOpaqueResult) - cls.privateFields.foreach(sym => generatedVars(sym).constrainOpaque) - cls.publicFields.foreach: (_, tsym) => - generatedVars(tsym).constrainOpaque - cls.methods.foreach: fun => - generatedVars(fun.dSym).constrainOpaque - fun.params.foreach(_.allParams.foreach(p => generatedVars(p.sym).constrainOpaque)) - processBlock(fun.body)(using cc, UnknownCons, N /* methods are always considered impure; we don't track their effects */) - processBlock(cls.preCtor)(using cc, UnknownCons, N /* FIXME: ctor effects should NOT be discarded */) - processBlock(cls.ctor)(using cc, UnknownCons, N /* FIXME: ctor effects should NOT be discarded */) - cls.companion.foreach: mod => - mod.methodKeys.valuesIterator.foreach(constrainOpaqueResult) - mod.privateFields.foreach(sym => generatedVars(sym).constrainOpaque) - mod.publicFields.foreach: (_, tsym) => - generatedVars(tsym).constrainOpaque - mod.methods.foreach: fun => - processFunctionDefn(fun) - processBlock(mod.ctor)(using cc, UnknownCons, N /* FIXME: ctor effects should NOT be discarded */) - - def constrainOpaqueResult(r: Result)(using cc: ConstraintsCollector, currentEffect: Opt[StratVar]): Unit = - cc.constrain(processResult(r), UnknownCons) - - def markUnsafeEta()(using cc: ConstraintsCollector, currentEffect: Opt[StratVar]): Unit = - // TODO: don't register such constraints unless a flag is on - currentEffect.foreach(e => cc.constrain(UnsafeEta, e)) - - def processBlock(b: Block)(using cc: ConstraintsCollector, blkRes: ConsStrat, currentEffect: Opt[StratVar]): Unit = - val instId = cc.instId - b match - case Return(res) => cc.constrain(processResult(res), blkRes) - case Throw(exc, annotations) => - if annotations.exists(_.isInstanceOf[Annot.Pure]) then - constrainOpaqueResult(exc)(using cc, N) - else - markUnsafeEta() - constrainOpaqueResult(exc) - case Match(scrut, arms, dflt, rest) => - val scrutStrat = processResult(scrut) - cc.constrain(scrutStrat, new Dtor(scrut.uid, instId)) - for case (Case.Cls(cls, path), _) <- arms do - cc.constrain(processResult(path), UnknownCons) - (arms.map(_._2) ++ dflt).foreach(processBlock) - processBlock(rest) - case Label(l, loop, body, rest) => - processBlock(body) - processBlock(rest) - case Break(label) => () - case Continue(label) => () - case Scoped(syms, body) => processBlock(body) - case Begin(sub, rest) => - processBlock(sub) - processBlock(rest) - case Assign(lhs, rhs, rest) => + private def processClsLikeDefn(cls: ClsLikeDefn)(using cc: ConstraintScope, ctx: Ctx): Unit = + cls.methodKeys.valuesIterator.foreach(constrainOpaqueResult) + cls.privateFields.foreach: sym => + cc.constrainOpaque(generatedVars(sym)) + cls.publicFields.foreach: (_, tsym) => + cc.constrainOpaque(generatedVars(tsym)) + cls.methods.foreach: fun => + cc.constrainOpaque(generatedVars(fun.dSym)) + fun.params.foreach(_.allParams.foreach(p => cc.constrainOpaque(generatedVars(p.sym)))) + processBlock(fun.body)(using cc, Ctx(UnknownCons, S(FunCtx(cc.variable(unknownEffect), fun.params)), fun.annotations.exists(_.isInstanceOf[Annot.Pure])) /* methods are always considered impure; we don't track their effects */) + processBlock(cls.preCtor)(using cc, Ctx(UnknownCons, S(FunCtx(cc.variable(unknownEffect), Nil)), suppressEffect = false) /* FIXME: ctor effects should NOT be discarded */) + processBlock(cls.ctor)(using cc, Ctx(UnknownCons, S(FunCtx(cc.variable(unknownEffect), Nil)), suppressEffect = false) /* FIXME: ctor effects should NOT be discarded */) + cls.companion.foreach: mod => + mod.methodKeys.valuesIterator.foreach(constrainOpaqueResult) + mod.privateFields.foreach: sym => + cc.constrainOpaque(generatedVars(sym)) + mod.publicFields.foreach: (_, tsym) => + cc.constrainOpaque(generatedVars(tsym)) + mod.methods.foreach: fun => + processFunctionDefn(fun) + processBlock(mod.ctor)(using cc, Ctx(UnknownCons, S(FunCtx(cc.variable(unknownEffect), Nil)), suppressEffect = false) /* FIXME: ctor effects should NOT be discarded */) + + private def constrainOpaqueResult(r: Result)(using cc: ConstraintScope, ctx: Ctx): Unit = + cc.constrain(processResult(r), UnknownCons) + + private def constrainEffect(prod: ProdStrat)(using cc: ConstraintScope, ctx: Ctx): Unit = + if !ctx.suppressEffect then + ctx.fun.foreach: fun => + fun.effect.foreach(effect => cc.constrain(prod, effect)) + + private def processBlock(b: Block)(using cc: ConstraintScope, ctx: Ctx): Unit = + val instId = cc.instId + b match + case Return(res) => cc.constrain(processResult(res), ctx.result) + case Throw(exc, annotations) => + if annotations.exists(_.isInstanceOf[Annot.Pure]) then + // TODO: the @pure annotation on throw also suppresses the effects of + // the result. This is due to a limitation that constructors' effects + // are not analyzed, so constructing an error object is always + // effectful. + constrainOpaqueResult(exc)(using cc, ctx.copy(suppressEffect = true)) + else + constrainEffect(UnsafeEta) + constrainOpaqueResult(exc) + case Match(scrut, arms, dflt, rest) => + val scrutStrat = processResult(scrut) + cc.constrain(scrutStrat, new Dtor(scrut.uid, instId)) + for case (Case.Cls(cls, path), _) <- arms do + cc.constrain(processResult(path), UnknownCons) + (arms.map(_._2) ++ dflt).foreach(processBlock) + processBlock(rest) + case Label(l, loop, body, rest) => + processBlock(body) + processBlock(rest) + case Break(label) => () + case Continue(label) => () + case Scoped(syms, body) => processBlock(body) + case Begin(sub, rest) => + processBlock(sub) + processBlock(rest) + case Assign(lhs, rhs, rest) => + val rhsStrat = processResult(rhs) + lhs.match + case NoSymbol => () + case lhs: (LocalVarSymbol | TermSymbol) => + if nonlocals.contains(lhs) then constrainEffect(UnsafeEta) + cc.constrain(rhsStrat, generatedVars(lhs)) + processBlock(rest) + case TryBlock(sub, finallyDo, rest) => + processBlock(sub) + processBlock(finallyDo) + processBlock(rest) + case AssignField(lhs, nme, rhs, rest) => + constrainEffect(UnsafeEta) + constrainOpaqueResult(lhs) + constrainOpaqueResult(rhs) + processBlock(rest) + case AssignDynField(lhs, fld, kind, rhs, rest) => + constrainEffect(UnsafeEta) + constrainOpaqueResult(lhs) + constrainOpaqueResult(fld) + constrainOpaqueResult(rhs) + processBlock(rest) + case Define(defn, rest) => + defn match + case ValDefn(tsym, sym, rhs) => val rhsStrat = processResult(rhs) - lhs.match - case NoSymbol => () - case lhs: (LocalVarSymbol | TermSymbol) => - if nonlocals.contains(lhs) then markUnsafeEta() - cc.constrain(rhsStrat, generatedVars(lhs)) - processBlock(rest) - case TryBlock(sub, finallyDo, rest) => - processBlock(sub) - processBlock(finallyDo) - processBlock(rest) - case AssignField(lhs, nme, rhs, rest) => - markUnsafeEta() - constrainOpaqueResult(lhs) - constrainOpaqueResult(rhs) - processBlock(rest) - case AssignDynField(lhs, fld, kind, rhs, rest) => - markUnsafeEta() - constrainOpaqueResult(lhs) - constrainOpaqueResult(fld) - constrainOpaqueResult(rhs) - processBlock(rest) - case Define(defn, rest) => - defn match - case ValDefn(tsym, sym, rhs) => - val rhsStrat = processResult(rhs) - cc.constrain(rhsStrat, generatedVars(tsym)) - case fun: FunDefn => - // The eta optimizer requires lifting. - // Other definitions are deliberately opaque. - markUnsafeEta() - processFunctionDefn(fun) - case cls: ClsLikeDefn => - markUnsafeEta() - processClsLikeDefn(cls) - processBlock(rest) - case End(msg) => () - case Unreachable(_) => () + cc.constrain(rhsStrat, generatedVars(tsym)) + case fun: FunDefn => + // The eta optimizer requires lifting. + // Other definitions are deliberately opaque. + constrainEffect(UnsafeEta) + processFunctionDefn(fun) + case cls: ClsLikeDefn => + constrainEffect(UnsafeEta) + processClsLikeDefn(cls) + processBlock(rest) + case End(msg) => () + case Unreachable(_) => () - def processResult(r: Result)(using cc: ConstraintsCollector, currentEffect: Opt[StratVar]): ProdStrat = - val instId = cc.instId + private def processResult(r: Result)(using cc: ConstraintScope, ctx: Ctx): ProdStrat = + val instId = cc.instId + def freshCallEffect(): StratVar = + val effect = freshVar("call_effect", cc.forFun) + // A call's effect contributes to the enclosing function's latent effect. + constrainEffect(effect) + effect + def constrainConstructorEffect(result: Result): Unit = + val unsafeEta = result match + case call: Call => !FlowAnalysis.hasPureAnnotation(call, call.argss.size - 1) + case _: Instantiate => true + case _ => false + // Constructor bodies are not modeled here; instantiation may have effects. + if unsafeEta then constrainEffect(UnsafeEta) + def handleCallLike(callId: CallId, fStrat: ProdStrat, args: List[Arg]): ProdStrat = + val argsStrat = args.map(a => processResult(a.value)) + if args.exists(_.spread.isDefined) then + // TODO: preserve callee-effect precision for spread calls + // * Needs to address [todo:effectful-spreads] + constrainEffect(UnsafeEta) + cc.constrain(fStrat, UnknownCons) + argsStrat.foreach(arg => cc.constrain(arg, UnknownCons)) + UnknownProd + else + val callRes = freshVar("call_res", cc.forFun) + val effect: EffectVar = callId.exprId.getResult match + case call: Call if FlowAnalysis.hasPureAnnotation(call, callId.whichArgList) => BottomEffect + case _ => freshCallEffect() + cc.constrain(fStrat, new ConsFun(callId, instId)( + argsStrat, + callRes, + effect, + )) + callRes + r match + case sel@TrackableSelect(from, field, owner) => + constrainEffect(UnsafeEta) + val fromStrat = processResult(from) + val selRes = freshVar("sel_res", cc.forFun) + cc.constrain( + fromStrat, + new FieldSel(sel.uid, instId)(field, owner, selRes)) + selRes + case c@CtorProducer(ctor, args, selectedFrom) if args.forall(_.spread.isEmpty) => + constrainConstructorEffect(c) + for qual <- selectedFrom do + cc.constrain(processResult(qual), UnknownCons) + val argsStrat = args.map: + case Arg(_, a) => processResult(a) + ctor match + case cls: ClassSymbol => + cls.tree.clsParams.size match + case 1 => + val clsParams = cls.tree.clsParams.head // TODO use irDefn here and elsewhere + // TODO: properly check the parameter lists, which may change after passes like lifting + // softTODO(argsStrat.size === clsParams.size, s"mismatched ctor arg and cls param sizes") + registerCtor(new Ctor(c.uid, instId)(ctor, clsParams.zip(argsStrat))) + case _ => + // - the size of 0 means we don't know the cls param symbols, + // so we constrain args with UnknownCons and this CtorProducer gives UnknownProd + // - if size > 1, we cannot handle multiple parameter class flow now, + // constrain args with UnknownCons and this CtorProducer gives UnknownProd + for a <- argsStrat do cc.constrain(a, UnknownCons) + UnknownProd + case _: ModuleOrObjectSymbol => registerCtor(new Ctor(c.uid, instId)(ctor, Nil)) + case tupSize: Int => + registerCtor(new Ctor(c.uid, instId)(tupSize, (0 until tupSize).zip(argsStrat).toList)) + case c@CtorProducer(_, args, selectedFrom) => + constrainEffect(UnsafeEta) + for qual <- selectedFrom do + cc.constrain(processResult(qual), UnknownCons) + args.foreach(arg => cc.constrain(processResult(arg.value), UnknownCons)) + UnknownProd + case c@Call(fun, argss) => + // each later arg list applies the result of the previous one + argss.iterator.zipWithIndex.foldLeft(processResult(fun)): + case (fStrat, (args, whichArgList)) => handleCallLike((c.uid, whichArgList), fStrat, args) + case i@Instantiate(_, cls, argss) => + constrainConstructorEffect(i) + constrainOpaqueResult(cls) + argss.flatten.foreach(a => constrainOpaqueResult(a.value)) + UnknownProd + case lam@Lambda(ps, body) => + processFunctionBody("lam_res", ps ne_:: Nil, body, lam.uid, Nil) + case _: Tuple => lastWords("should be handled in CtorProducer") + case Record(_, fields) => + if fields.exists(_.spread) then constrainEffect(UnsafeEta) + fields.foreach: + case RcdArg(idx, value) => + idx.foreach(p => cc.constrain(processResult(p), UnknownCons)) + cc.constrain(processResult(value), UnknownCons) + UnknownProd + case refSite @ FunRef(f, selectedFrom) => + // ? [todo:harry-0] + // ? It's a special case for module getters but I'm not sure if + // ? this is good enough. It just conservatively marks it unsafe. + if f.irFunDefn.fold(!f.isAccessPure)(_.params.isEmpty) then constrainEffect(UnsafeEta) + refSite match + case DynSelect(_, key, DynSelectKind.Member(_)) => cc.constrain(processResult(key), UnknownCons) + case SuperSelect(_, base, key) => + cc.constrain(processResult(base), UnknownCons) + cc.constrain(processResult(key), UnknownCons) + case _ => () + for qual <- selectedFrom do + cc.constrain(processResult(qual), UnknownCons) + funToScheme.get(f) match + case Some(fScheme) => + instantiate(fScheme, refSite.uid, f) + case None => generatedVars(f) + case s @ SuperSelect(qual, base, key) => + constrainEffect(UnsafeEta) + cc.constrain(processResult(key), UnknownCons) + cc.constrain(processResult(qual), UnknownCons) + cc.constrain(processResult(base), UnknownCons) + generatedVars(s.symbol) + case s @ Select(qual, name) => + constrainEffect(UnsafeEta) + cc.constrain(processResult(qual), UnknownCons) + s.symbol.fold(UnknownProd): selSym => + generatedVars(selSym) + case DynSelect(qual, fld, kind) => + constrainEffect(UnsafeEta) + cc.constrain(processResult(qual), UnknownCons) + cc.constrain(processResult(fld), UnknownCons) + kind match + case DynSelectKind.Member(sym) => generatedVars(sym) + case DynSelectKind.ArrayIdx | DynSelectKind.Unknown => UnknownProd // * A cast passes its operand through; a checked one may also throw. - def processCast(value: Result, check: Bool): ProdStrat = - if check then markUnsafeEta() + case Cast(value, _, check) => + if check then constrainEffect(UnsafeEta) val valueStrat = processResult(value) cc.constrain(valueStrat, UnknownCons) valueStrat - def freshCallEffect(): StratVar = - val effect = freshVar("call_effect", cc.forFun) - // A call's effect contributes to the enclosing function's latent effect. - currentEffect.foreach: enclosingEffect => - cc.constrain(effect, enclosingEffect) - effect - def constrainConstructorEffect(result: Result): Unit = - val unsafeEta = result match - case call: Call => !FlowAnalysis.isPureCall(call, call.argss.size - 1) - case _: Instantiate => true - case _ => false - // Constructor bodies are not modeled here; instantiation may have effects. - if unsafeEta then markUnsafeEta() - def handleCallLike(callId: CallId, fStrat: ProdStrat, args: List[Arg]): ProdStrat = - val argsStrat = args.map(a => processResult(a.value)) - if args.exists(_.spread.isDefined) then - // TODO: preserve callee-effect precision for spread calls - // * Needs to address [todo:effectful-spreads] - markUnsafeEta() - cc.constrain(fStrat, UnknownCons) - argsStrat.foreach(arg => cc.constrain(arg, UnknownCons)) - UnknownProd - else - val callRes = freshVar("call_res", cc.forFun) - val effect = freshCallEffect() - cc.constrain(fStrat, new ConsFun(callId, instId)( - argsStrat, - callRes, - effect, - )) - callRes - r match - case sel@TrackableSelect(from, field, owner) => - markUnsafeEta() - val fromStrat = processResult(from) - val selRes = freshVar("sel_res", cc.forFun) - cc.constrain( - fromStrat, - new FieldSel(sel.uid, instId)(field, owner, selRes)) - selRes - case c@CtorProducer(ctor, args, selectedFrom) if args.forall(_.spread.isEmpty) => - constrainConstructorEffect(c) - for qual <- selectedFrom do - cc.constrain(processResult(qual), UnknownCons) - val argsStrat = args.map: - case Arg(_, a) => processResult(a) - ctor match - case cls: ClassSymbol => - cls.tree.clsParams.size match - case 1 => - val clsParams = cls.tree.clsParams.head // TODO use irDefn here and elsewhere - // TODO: properly check the parameter lists, which may change after passes like lifting - // softTODO(argsStrat.size === clsParams.size, s"mismatched ctor arg and cls param sizes") - registerCtor(new Ctor(c.uid, instId)(ctor, clsParams.zip(argsStrat))) - case _ => - // - the size of 0 means we don't know the cls param symbols, - // so we constrain args with UnknownCons and this CtorProducer gives UnknownProd - // - if size > 1, we cannot handle multiple parameter class flow now, - // constrain args with UnknownCons and this CtorProducer gives UnknownProd - for a <- argsStrat do cc.constrain(a, UnknownCons) - UnknownProd - case _: ModuleOrObjectSymbol => registerCtor(new Ctor(c.uid, instId)(ctor, Nil)) - case tupSize: Int => - registerCtor(new Ctor(c.uid, instId)(tupSize, (0 until tupSize).zip(argsStrat).toList)) - case c@CtorProducer(_, args, selectedFrom) => - markUnsafeEta() - for qual <- selectedFrom do - cc.constrain(processResult(qual), UnknownCons) - args.foreach(arg => cc.constrain(processResult(arg.value), UnknownCons)) - UnknownProd - case c@Call(fun, argss) => - // each later arg list applies the result of the previous one - argss.iterator.zipWithIndex.foldLeft(processResult(fun)): - case (fStrat, (args, whichArgList)) => handleCallLike((c.uid, whichArgList), fStrat, args) - case i@Instantiate(_, cls, argss) => - constrainConstructorEffect(i) - constrainOpaqueResult(cls) - argss.flatten.foreach(a => constrainOpaqueResult(a.value)) - UnknownProd - case lam@Lambda(ps, body) => - mkFunProdStrat("lam_res", ps ne_:: Nil, body, lam.uid, Nil) - case _: Tuple => lastWords("should be handled in CtorProducer") - case Record(_, fields) => - if fields.exists(_.spread) then markUnsafeEta() - fields.foreach: - case RcdArg(idx, value) => - idx.foreach(p => cc.constrain(processResult(p), UnknownCons)) - cc.constrain(processResult(value), UnknownCons) - UnknownProd - case CastResult(value, _, check) => processCast(value, check) - case p: Path => - p match - case refSite @ FunRef(f, selectedFrom) => - // ? [todo:harry-0] - // ? It's a special case for module getters but I'm not sure if - // ? this is good enough. It just conservatively marks it unsafe. - if f.irFunDefn.fold(!f.isAccessPure)(_.params.isEmpty) then markUnsafeEta() - refSite match - case DynSelect(_, key, DynSelectKind.Member(_)) => cc.constrain(processResult(key), UnknownCons) - case SuperSelect(_, base, key) => - cc.constrain(processResult(base), UnknownCons) - cc.constrain(processResult(key), UnknownCons) - case _ => () - for qual <- selectedFrom do - cc.constrain(processResult(qual), UnknownCons) - funsToProdStratScheme.get(f) match - case Some(fScheme) => - fScheme.instantiate(refSite.uid, f) - case None => generatedVars(f) - case s @ SuperSelect(qual, base, key) => - markUnsafeEta() - cc.constrain(processResult(key), UnknownCons) - cc.constrain(processResult(qual), UnknownCons) - cc.constrain(processResult(base), UnknownCons) - generatedVars(s.symbol) - case s @ Select(qual, name) => - markUnsafeEta() - cc.constrain(processResult(qual), UnknownCons) - s.symbol.fold(UnknownProd): selSym => - generatedVars(selSym) - case DynSelect(qual, fld, kind) => - markUnsafeEta() - cc.constrain(processResult(qual), UnknownCons) - cc.constrain(processResult(fld), UnknownCons) - kind match - case DynSelectKind.Member(sym) => generatedVars(sym) - case DynSelectKind.ArrayIdx | DynSelectKind.Unknown => UnknownProd - case CastPath(value, _, check) => processCast(value, check) - case Value.MemberRef(_, disamb) => - disamb match - case _: ClassSymbol | _: PatternSymbol => - // Just accessing the binding is pure. - () - case sym: TermSymbol => - // (Accessing mutable fields is a Select) - if !sym.isAccessPure then markUnsafeEta() - case _: ModuleOrObjectSymbol => - lastWords("module/object references should be handled by CtorProducer") - case _: TypeAliasSymbol => - lastWords("type aliases should not occur as runtime member references") - generatedVars(disamb) - case Value.SimpleRef(sym) => - if nonlocals.contains(sym) then - // A special case to avoid regression in deforest/eta-expansion/basic.mls - // To be superseded by our capture analysis / masking. - val isParam = currentEffect.flatMap(_.sourceSymbol).exists: - case fun: TermSymbol => preAnalyzer.res.funSymToFunDefn.get(fun).exists: - _.params.exists(_.paramSyms.contains(sym)) - case _ => false - if !isParam then markUnsafeEta() - generatedVars(sym) - case Value.This(_) => UnknownProd - case Value.Lit(lit) => UnknownProd - } + case Value.MemberRef(_, disamb) => + disamb match + case _: ClassSymbol | _: PatternSymbol => + // Just accessing the binding is pure. + () + case sym: TermSymbol => + // (Accessing mutable fields is a Select) + if !sym.isAccessPure then constrainEffect(UnsafeEta) + case _: ModuleOrObjectSymbol => + lastWords("module/object references should be handled by CtorProducer") + case _: TypeAliasSymbol => + lastWords("type aliases should not occur as runtime member references") + generatedVars(disamb) + case Value.SimpleRef(sym) => + if nonlocals.contains(sym) then + if !ctx.fun.exists(_.isOwnParam(sym)) then + constrainEffect(UnsafeEta) + generatedVars(sym) + case Value.This(_) => UnknownProd + case Value.Lit(lit) => UnknownProd + + analyze() + collect() + end FlowConstraintsCollector -class FlowConstraintSolver(val collector: FlowConstraintsCollector): + +/** The solver has to solve constraints from different scopes (either SCCs or + * the global scope) separately. [[collector]] specifies the states and options + * for the whole program, and [[scope]] specifies the actual set of constraints + * to be solved. + */ +class FlowConstraintSolver( + val collector: FlowConstraintsCollector, +)( + val scope: collector.ConstraintScope +): given tl: TraceLogger = collector.tl given fState: FlowAnalysis.State = collector.fState given eState: Elaborator.State = collector.eState @@ -1251,21 +1513,34 @@ class FlowConstraintSolver(val collector: FlowConstraintsCollector): val consumersWithSrcs = mutable.Buffer.empty[ConcreteCtorConsumer] val prodFunsWithDests = mutable.Buffer.empty[ProdFun] val consFunsWithSrcs = mutable.Buffer.empty[ConsFun] - val functionEffectVars = LinkedHashMap.empty[ConcreteId[FunId], StratVar | NoProd] - val callEffectVars = LinkedHashMap.empty[ConcreteId[CallId], StratVar | NoProd] + + val functionEffectVars = LinkedHashMap.empty[ConcreteId[FunId], EffectVar] + val callEffectVars = LinkedHashMap.empty[ConcreteId[CallId], EffectVar] + + private val registered: mutable.Set[Strat] = MutSet.empty + + private def register(strat: Strat): Unit = strat match + case p: ProdFun => + if p.instantiationId.isDefined then functionEffectVars(p.concreteId) = p.effect + if p.dests.nonEmpty && registered.add(p) then prodFunsWithDests += p + register(p.res) // for curried functions + case c: ConsFun => + if c.instantiationId.isDefined then callEffectVars(c.concreteId) = c.effect + if c.srcs.nonEmpty && registered.add(c) then consFunsWithSrcs += c + case c: Ctor => + if c.dests.nonEmpty && registered.add(c) then ctorsWithDests += c + case d: ConcreteCtorConsumer => + if d.srcs.nonEmpty && registered.add(d) then consumersWithSrcs += d + case _ => () private def addCtorDest(c: Ctor, d: ConcreteCtorConsumer | MarkerConsStrat): Unit = - if c.dests.isEmpty then ctorsWithDests += c - c.dests += d + if c.dests.add(d) && c.dests.size == 1 then register(c) private def addDtorSrc(d: ConcreteCtorConsumer, p: Ctor | MarkerProdStrat): Unit = - if d.srcs.isEmpty then consumersWithSrcs += d - d.srcs += p + if d.srcs.add(p) && d.srcs.size == 1 then register(d) private def addFunDest(p: ProdFun, c: ConsFun | MarkerConsStrat): Unit = - if p.dests.isEmpty then prodFunsWithDests += p - p.dests += c + if p.dests.add(c) && p.dests.size == 1 then register(p) private def addFunSrc(c: ConsFun, p: ProdFun | MarkerProdStrat): Unit = - if c.srcs.isEmpty then consFunsWithSrcs += c - c.srcs += p + if c.srcs.add(p) && c.srcs.size == 1 then register(c) /** Whether instances of `cls` may have the fields of `ancestor` through inheritance. * A class whose parent chain is not known is assumed to possibly inherit them. @@ -1307,61 +1582,48 @@ class FlowConstraintSolver(val collector: FlowConstraintsCollector): computed(lb) - private def logNonAffineSyms: Unit = + private def logNonAffineSyms(stratVars: => Iterable[StratVar]): Unit = tl.scoped(FlowAnalysis.TraceScope.NonAffineSyms): - given ShowCfg = ShowCfg.internal - tl.log(">>> non-affine syms >>>") - val outputRes = - for - stratVar <- fState.stratVars - if AllUpperBounds(stratVar).contains(NonAffine) - yield summon[SymbolPrinter].printSymbol(stratVar) - for nonAffine <- outputRes.toSortedSet do - tl.log(nonAffine) - tl.log("<<< non-affine syms <<<") + if tl.isTracing then + given ShowCfg = ShowCfg.internal + tl.log(">>> non-affine syms >>>") + val outputRes = + for + stratVar <- stratVars + if AllUpperBounds(stratVar).contains(NonAffine) + yield summon[SymbolPrinter].printSymbol(stratVar) + for nonAffine <- outputRes.toSortedSet do + tl.log(nonAffine) + tl.log("<<< non-affine syms <<<") - private def logAccumulatorSyms: Unit = + private def logAccumulatorSyms(stratVars: => Iterable[StratVar]): Unit = tl.scoped(FlowAnalysis.TraceScope.AccumulatorSym): - given ShowCfg = ShowCfg.internal - tl.log(">>> accumulator syms >>>") - def showAccumulatorSym(stratVar: StratVar): Opt[Str] = - AllUpperBounds(stratVar) - .collectFirst: - case pAcc: PossibleAccumulator if pAcc.s is stratVar => stratVar - case iPrm: IntoParam if iPrm.s is stratVar => stratVar - .map(summon[SymbolPrinter].printSymbol) - val outputRes = - for - stratVar <- fState.stratVars - if AllUpperBounds(stratVar).contains(Accumulator) - accumulatorSym <- showAccumulatorSym(stratVar) - yield accumulatorSym - for accumulator <- outputRes.toSortedSet do - tl.log(accumulator) - tl.log("<<< accumulator syms <<<") + if tl.isTracing then + given ShowCfg = ShowCfg.internal + tl.log(">>> accumulator syms >>>") + def showAccumulatorSym(stratVar: StratVar): Opt[Str] = + AllUpperBounds(stratVar) + .collectFirst: + case pAcc: PossibleAccumulator if pAcc.s is stratVar => stratVar + case iPrm: IntoParam if iPrm.s is stratVar => stratVar + .map(summon[SymbolPrinter].printSymbol) + val outputRes = + for + stratVar <- stratVars + if AllUpperBounds(stratVar).contains(Accumulator) + accumulatorSym <- showAccumulatorSym(stratVar) + yield accumulatorSym + for accumulator <- outputRes.toSortedSet do + tl.log(accumulator) + tl.log("<<< accumulator syms <<<") - locally { - def hasConcreteInstantiationId(prodOrCons: ProdStrat | ConsStrat): Boolean = prodOrCons match - case s: StratWithOrigin[?] => s.instantiationId.isDefined - case _ => true - def registerFunctionEffects(prod: ProdStrat): Unit = prod match - case p: ProdFun => - functionEffectVars(p.concreteId) = p.effect - registerFunctionEffects(p.res) - case _ => () - def inferredCallEffect(c: ConsFun): StratVar | NoProd = - c.exprId.exprId.getResult match - case call: Call if FlowAnalysis.isPureCall(call, c.exprId.whichArgList) => NoProd - case _ => c.effect - def handle(constraint: ProdStrat -> ConsStrat): Unit = - assert: - val (prod, cons) = constraint - hasConcreteInstantiationId(prod) && - hasConcreteInstantiationId(cons) - registerFunctionEffects(constraint._1) - constraint._2 match - case c: ConsFun => callEffectVars(c.concreteId) = c.effect - case _ => () + def solve(): Unit = + scope.strategies.foreach(register) + + def handle(constraint: Constraint): Unit = + val (prod, cons) = constraint + register(prod) + register(cons) constraint match case (c: Ctor, d: Dtor) => addCtorDest(c, d) @@ -1410,10 +1672,10 @@ class FlowConstraintSolver(val collector: FlowConstraintsCollector): case v: StratVar => handle(arg, v.asIntoParam) case _ => () handle(p.res, c.res) - // The callee's latent effect becomes the effect observed at this call. - inferredCallEffect(c) match - case NoProd => () - case ce: StratVar => handle(p.effect, ce) + // propagate effects + p.effect.foreach: pe => + c.effect.foreach: ce => + handle(pe, ce) case (p: ProdFun, UnknownCons) => addFunDest(p, UnknownCons) for a <- p.params do handle(UnknownProd, a) @@ -1438,13 +1700,16 @@ class FlowConstraintSolver(val collector: FlowConstraintsCollector): for a <- c.params do handle(a, UnknownCons) handle(UnknownProd, c.res) // The conservative fallback. - inferredCallEffect(c) match - case NoProd => () - case ce: StratVar => handle(UnsafeEta, ce) + c.effect.foreach(ce => handle(UnsafeEta, ce)) case (p: StratVar, c: StratVar) => if p.upperBounds.add(c) then for l <- p.lowerBounds do handle(l, c) - for u <- c.upperBounds do handle(p, u) + for u <- c.upperBounds do u match + // ! given p <: c and c <: u, it follows that p <: u, + // ! so skip handling the case p <: u. + // ! this is an important optimization... + case u: StratVar if p.upperBounds.contains(u) => () + case u => handle(p, u) case (p: StratVar, c) => if p.upperBounds.add(c) then c match case pAcc: PossibleAccumulator if p is pAcc.s => @@ -1457,11 +1722,66 @@ class FlowConstraintSolver(val collector: FlowConstraintsCollector): case _ => () // ignore other cases end handle - for c <- collector.allConstraints do handle(c) - } + for c <- scope.constraints do handle(c) + end solve + + def log(): Unit = + lazy val stratVars = + val inScope = MutSet.empty[StratVar] + foreachStrategy: + case v: StratVar => inScope += v + case _ => () + // Select from this solver's graph while preserving the existing order of names. + fState.stratVars.view.filter(inScope) + logNonAffineSyms(stratVars) + logAccumulatorSyms(stratVars) - logNonAffineSyms - logAccumulatorSyms + private def foreachStrategy(f: Strat => Unit): Unit = + val visited = mutable.Set.empty[Strat] + def visit(strat: Strat): Unit = if visited.add(strat) then + f(strat) + strat match + case v: StratVar => + v.lowerBounds.foreach(visit) + v.upperBounds.foreach(visit) + case p: ProdFun => + p.params.foreach(visit) + p.restParam.foreach(visit) + visit(p.res) + visit(p.capturedVarUpperbound) + p.effect.foreach(visit) + p.dests.foreach(visit) + case c: ConsFun => + c.params.foreach(visit) + visit(c.res) + c.effect.foreach(visit) + c.srcs.foreach(visit) + case c: Ctor => + c.args.foreach((_, prod) => visit(prod)) + c.dests.foreach(visit) + case s: FieldSel => + visit(s.consVar) + s.srcs.foreach(visit) + case d: ConcreteCtorConsumer => + d.srcs.foreach(visit) + case p: PossibleAccumulator => visit(p.s) + case p: IntoParam => visit(p.s) + case _: MarkerProdStrat | _: MarkerConsStrat => () + registered.foreach(visit) + scope.strategies.foreach(visit) + for (prod, cons) <- scope.constraints do + visit(prod) + visit(cons) + + if scope.forScc.isEmpty then + collector.allRealCtors.foreach(visit) + def assertConcrete(): Unit = + foreachStrategy: + case s: StratWithOrigin[?] => + assert(s.instantiationId.isDefined, "abstract strategy escaped its scheme") + case _ => () + + end FlowConstraintSolver diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/basic.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/basic.mls index 0a5aeea25f..8235211e03 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/basic.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/basic.mls @@ -316,8 +316,8 @@ private fun f(a) = g(a) private fun g(x) = 3 f(1) //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > f²⁵#0 @ -> eliminable: {0} //│ dead-param-elim > g²#0 @ -> eliminable: {0} +//│ dead-param-elim > f²⁵#0 @ -> eliminable: {0} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 3 @@ -328,7 +328,7 @@ f(1) //│ dead-param-elim > >>> dead-param-elim results >>> //│ dead-param-elim > g²#0 @ f²⁵.g³ -> eliminable: {0} //│ dead-param-elim > g²#0 @ f²⁶.g³ -> eliminable: {0} -//│ dead-param-elim > f²⁷#0 @ f²⁶ -> eliminable: {0} +//│ dead-param-elim > f²⁷#0 @ f²⁵ -> eliminable: {0} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/clash.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/clash.mls index d1190147a6..ac67776a77 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/clash.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/clash.mls @@ -66,8 +66,8 @@ private fun choose(x)(y) = if false then x else y private fun m() = choose(f)(g)(1)(2) m() //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > g⁰#1 @ -> eliminable: {0} //│ dead-param-elim > f⁰#1 @ -> eliminable: {0} +//│ dead-param-elim > g⁰#1 @ -> eliminable: {0} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 1 @@ -81,8 +81,8 @@ private fun choose(x)(y) = if false then x else y private fun m() = choose(f)(g)(1, 2) m() //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > g⁰#0 @ -> eliminable: {1} //│ dead-param-elim > f⁰#0 @ -> eliminable: {1} +//│ dead-param-elim > g⁰#0 @ -> eliminable: {1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 1 @@ -108,8 +108,8 @@ private fun f(a, b, ...rest) = rest private fun g(a, b, c, ...rest) = rest (if false then f else g)(1,2,3,4,5) //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > g⁰#0 @ -> eliminable: {0, 1} //│ dead-param-elim > f⁰#0 @ -> eliminable: {0, 1} +//│ dead-param-elim > g⁰#0 @ -> eliminable: {0, 1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = [4, 5] @@ -119,8 +119,8 @@ private fun f(a, b, ...rest) = rest private fun g(a, b, c, ...rest) = rest (if true then f else g)(1,2,3,4,5) //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > g⁰#0 @ -> eliminable: {0, 1} //│ dead-param-elim > f⁰#0 @ -> eliminable: {0, 1} +//│ dead-param-elim > g⁰#0 @ -> eliminable: {0, 1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = [3, 4, 5] diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/dead-ref.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/dead-ref.mls index 095f6108ca..92bca3529c 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/dead-ref.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/dead-ref.mls @@ -13,8 +13,8 @@ private fun f(a) = private fun g(a) = 2 f(unsafe.undefined) //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > f⁰#0 @ -> eliminable: {0} //│ dead-param-elim > g⁰#0 @ -> eliminable: {0} +//│ dead-param-elim > f⁰#0 @ -> eliminable: {0} //│ dead-param-elim > <<< dead-param-elim results <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ @private diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/lambda.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/lambda.mls index ca15062346..80c598f514 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/lambda.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/lambda.mls @@ -110,10 +110,10 @@ private fun main(f1, f2) = private fun main1() = main((a) => 0, (a) => a + 1) main1() //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > apply⁰#0 @ main1⁰.main⁰.apply¹ -> eliminable: {1} //│ dead-param-elim > lambda⁰#0 @ main1⁰ -> eliminable: {0} -//│ dead-param-elim > apply⁰#0 @ main1¹.main⁰.apply¹ -> eliminable: {1} +//│ dead-param-elim > apply⁰#0 @ main1⁰.main⁰.apply¹ -> eliminable: {1} //│ dead-param-elim > lambda⁰#0 @ main1¹ -> eliminable: {0} +//│ dead-param-elim > apply⁰#0 @ main1¹.main⁰.apply¹ -> eliminable: {1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/recursive.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/recursive.mls index cbfd45840b..4688758bf6 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/recursive.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/recursive.mls @@ -161,10 +161,10 @@ fun ping(n, unused) = if n == 0 then 0 else pong(n - 1, unused) fun pong(n, unused) = if n == 0 then 0 else ping(n - 1, unused) ping(5, 99) //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > ping⁴#0 @ pong⁴ -> eliminable: {1} -//│ dead-param-elim > pong⁵#0 @ ping⁵ -> eliminable: {1} -//│ dead-param-elim > ping⁴#0 @ ping⁶ -> eliminable: {1} -//│ dead-param-elim > pong⁵#0 @ ping⁶ -> eliminable: {1} +//│ dead-param-elim > ping⁴#0 @ ping⁵ -> eliminable: {1} +//│ dead-param-elim > pong⁴#0 @ ping⁵ -> eliminable: {1} +//│ dead-param-elim > pong⁴#0 @ ping⁶ -> eliminable: {1} +//│ dead-param-elim > ping⁴#0 @ pong⁵ -> eliminable: {1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 0 diff --git a/hkmc2/shared/src/test/mlscript/deforest/append.mls b/hkmc2/shared/src/test/mlscript/deforest/append.mls index 133df84b88..7a0a3e7932 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/append.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/append.mls @@ -65,22 +65,22 @@ appendThree of //│ deforest > match: xs⁰ //│ deforest > fields: xs⁰.h¹ //│ deforest > fields: xs⁰.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ //│ deforest > Cons⁰(h², tmp¹) -> //│ deforest > match: xs⁰ //│ deforest > fields: xs⁰.h¹ //│ deforest > fields: xs⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: xs⁰ //│ deforest > Cons⁰(h⁰, tmp⁰) -> //│ deforest > match: xs⁰ //│ deforest > fields: xs⁰.h¹ //│ deforest > fields: xs⁰.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ //│ deforest > Cons⁰(h², tmp¹) -> //│ deforest > match: xs⁰ //│ deforest > fields: xs⁰.h¹ //│ deforest > fields: xs⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: xs⁰ //│ deforest > <<< fusing <<< //│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) diff --git a/hkmc2/shared/src/test/mlscript/deforest/basic.mls b/hkmc2/shared/src/test/mlscript/deforest/basic.mls index 2ca64c0f30..17c7f41e8b 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/basic.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/basic.mls @@ -175,9 +175,6 @@ fun c(x, d) = if x is X(a) then c(X(a), d) () => c(X(3), 2) //│ deforest > >>> fusing >>> -//│ deforest > X⁰(a²) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.a⁰ //│ deforest > X⁰(3) -> //│ deforest > match: x⁴ //│ deforest > fields: x⁴.a⁰ @@ -187,6 +184,9 @@ fun c(x, d) = if x is //│ deforest > X⁰(3) -> //│ deforest > match: x⁴ //│ deforest > fields: x⁴.a⁰ +//│ deforest > X⁰(a²) -> +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.a⁰ //│ deforest > <<< fusing <<< //│ = fun lambda$lambda @@ -301,10 +301,10 @@ fun f(x) = if x is A(x) then 2 f(A(2)) + f(A(3)) //│ deforest > >>> fusing >>> -//│ deforest > A⁰(3) -> +//│ deforest > A⁰(2) -> //│ deforest > match: x¹⁰ //│ deforest > fields: x¹⁰.x⁸ -//│ deforest > A⁰(2) -> +//│ deforest > A⁰(3) -> //│ deforest > match: x¹⁰ //│ deforest > fields: x¹⁰.x⁸ //│ deforest > <<< fusing <<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/cyclic.mls b/hkmc2/shared/src/test/mlscript/deforest/cyclic.mls index 79004e9403..efdd574b18 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/cyclic.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/cyclic.mls @@ -18,11 +18,11 @@ fun f(x) = if x is else 0 f(Nil) //│ deforest > >>> fusing >>> -//│ deforest > Nil⁰ -> -//│ deforest > match: x⁰ //│ deforest > A⁰(2) -> //│ deforest > match: x⁰ //│ deforest > fields: x⁰.x¹ +//│ deforest > Nil⁰ -> +//│ deforest > match: x⁰ //│ deforest > <<< fusing <<< //│ = 0 diff --git a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/basic.mls b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/basic.mls index f178abeab7..eeb0ecbb09 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/basic.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/basic.mls @@ -69,9 +69,9 @@ g(f(p())) //│ deforest > fields: y⁰.b⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> -//│ eta-expansion > f$f⁰#0 @ : [1, 2] -//│ eta-expansion > p$p⁰#0 @ : [0, 2, 2] //│ eta-expansion > f$x_X⁰#0 @ : [1, 2, 2] +//│ eta-expansion > p$p⁰#0 @ : [0, 2, 2] +//│ eta-expansion > f$f⁰#0 @ : [1, 2] //│ eta-expansion > <<< eta-expansion targets shapes <<< //│ = O @@ -111,18 +111,18 @@ fun main(x) = c(p(2)) main(3) //│ deforest > >>> fusing >>> -//│ deforest > X⁰(tmp³) -> -//│ deforest > match: x⁵ -//│ deforest > fields: x⁵.a⁰ -//│ deforest > Y⁰(tmp⁴) -> +//│ deforest > Y⁰(tmp³) -> //│ deforest > match: arg$X$0$¹ //│ deforest > fields: arg$X$0$¹.b⁰ -//│ deforest > X⁰(tmp³) -> +//│ deforest > X⁰(tmp⁴) -> //│ deforest > match: x⁵ //│ deforest > fields: x⁵.a⁰ -//│ deforest > Y⁰(tmp⁴) -> +//│ deforest > Y⁰(tmp³) -> //│ deforest > match: arg$X$0$¹ //│ deforest > fields: arg$X$0$¹.b⁰ +//│ deforest > X⁰(tmp⁴) -> +//│ deforest > match: x⁵ +//│ deforest > fields: x⁵.a⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > main_p$p⁰#0 @ : [2, 3] diff --git a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/recursive.mls b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/recursive.mls index ab0789f32b..fac53be17e 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/recursive.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/recursive.mls @@ -112,8 +112,8 @@ len(flatten(id((1 :: 2 :: Nil) :: (3 :: Nil) :: Nil))) //│ deforest > fields: ls¹.t⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> -//│ eta-expansion > flatten_append$append⁰#0 @ : [2, 5] //│ eta-expansion > flatten$flatten⁰#0 @ : [1, 5] +//│ eta-expansion > flatten_append$append⁰#0 @ : [2, 5] //│ eta-expansion > <<< eta-expansion targets shapes <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ let tmp, tmp1, tmp2, tmp3, tmp4, flatten$flatten, flatten_append$append$worker, flatten$flatten$worker; diff --git a/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls b/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls index 28a1dc5a5e..c246ac6532 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls @@ -97,12 +97,12 @@ affine_branched(AA(0), AA(1), (y) => if y is AA(x) then x + 2) //│ deforest > cap_ub_(term:lambda,0)_for_lambda⁰ //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(1) -> -//│ deforest > match: y⁰ -//│ deforest > fields: y⁰.x⁰ //│ deforest > AA⁰(0) -> //│ deforest > match: x² //│ deforest > fields: x².x⁰ +//│ deforest > AA⁰(1) -> +//│ deforest > match: y⁰ +//│ deforest > fields: y⁰.x⁰ //│ deforest > <<< fusing <<< //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls index a8e0e0047a..4e6d7836a6 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls @@ -26,67 +26,79 @@ fun lscomp1(ls) = if ls is else Nil lscomp1(zip(enumFromTo(1, 3), enumFromTo(2, 4))) //│ deforest > >>> fusing >>> -//│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> //│ deforest > match: xs_zip⁰ //│ deforest > fields: xs_zip⁰.h⁰ //│ deforest > fields: xs_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip⁰ +//│ deforest > match: xs_zip⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> //│ deforest > match: ys_zip⁰ //│ deforest > fields: ys_zip⁰.h⁰ //│ deforest > fields: ys_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls2⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ls2⁰ -//│ deforest > Cons⁰(tmp¹, tmp²) -> -//│ deforest > match: ls2⁰ -//│ deforest > fields: ls2⁰.h⁰ -//│ deforest > fields: ls2⁰.t⁰ +//│ deforest > match: ys_zip⁰ //│ deforest > Pair⁰(x⁰, y⁰) -> //│ deforest > match: arg$Cons$0$⁰ //│ deforest > fields: arg$Cons$0$⁰.a¹ //│ deforest > fields: arg$Cons$0$⁰.b⁰ +//│ deforest > Cons⁰(tmp¹, tmp²) -> +//│ deforest > match: ls2⁰ +//│ deforest > fields: ls2⁰.h⁰ +//│ deforest > fields: ls2⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip⁰ +//│ deforest > match: ls2⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls2⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> //│ deforest > match: xs_zip⁰ //│ deforest > fields: xs_zip⁰.h⁰ //│ deforest > fields: xs_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip⁰ +//│ deforest > match: xs_zip⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> //│ deforest > match: ys_zip⁰ //│ deforest > fields: ys_zip⁰.h⁰ //│ deforest > fields: ys_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip⁰ +//│ deforest > match: ys_zip⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> //│ deforest > match: xs_zip⁰ //│ deforest > fields: xs_zip⁰.h⁰ //│ deforest > fields: xs_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip⁰ +//│ deforest > match: xs_zip⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> //│ deforest > match: ys_zip⁰ //│ deforest > fields: ys_zip⁰.h⁰ //│ deforest > fields: ys_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls2⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ls2⁰ -//│ deforest > Cons⁰(tmp¹, tmp²) -> -//│ deforest > match: ls2⁰ -//│ deforest > fields: ls2⁰.h⁰ -//│ deforest > fields: ls2⁰.t⁰ +//│ deforest > match: ys_zip⁰ //│ deforest > Pair⁰(x⁰, y⁰) -> //│ deforest > match: arg$Cons$0$⁰ //│ deforest > fields: arg$Cons$0$⁰.a¹ //│ deforest > fields: arg$Cons$0$⁰.b⁰ //│ deforest > Cons⁰(tmp¹, tmp²) -> +//│ deforest > match: ls2⁰ +//│ deforest > fields: ls2⁰.h⁰ +//│ deforest > fields: ls2⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls2⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls2⁰ +//│ deforest > Cons⁰(a⁰, tmp⁰) -> +//│ deforest > match: xs_zip⁰ +//│ deforest > fields: xs_zip⁰.h⁰ +//│ deforest > fields: xs_zip⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: xs_zip⁰ +//│ deforest > Cons⁰(a⁰, tmp⁰) -> +//│ deforest > match: ys_zip⁰ +//│ deforest > fields: ys_zip⁰.h⁰ +//│ deforest > fields: ys_zip⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ys_zip⁰ +//│ deforest > Cons⁰(tmp¹, tmp²) -> //│ deforest > match: ls⁰ //│ deforest > fields: ls⁰.h⁰ //│ deforest > fields: ls⁰.t⁰ @@ -98,18 +110,6 @@ lscomp1(zip(enumFromTo(1, 3), enumFromTo(2, 4))) //│ deforest > match: ls⁰ //│ deforest > Nil⁰ -> //│ deforest > match: ls⁰ -//│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: ys_zip⁰ -//│ deforest > fields: ys_zip⁰.h⁰ -//│ deforest > fields: ys_zip⁰.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip⁰ -//│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: xs_zip⁰ -//│ deforest > fields: xs_zip⁰.h⁰ -//│ deforest > fields: xs_zip⁰.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip⁰ //│ deforest > <<< fusing <<< //│ = Cons(Pair(1, 3), Cons(Pair(2, 4), Cons(Pair(3, 5), Nil))) @@ -128,29 +128,41 @@ fun f1(a1) = if a1 is else Nil concatMap(f1, Pair(5, 10) :: Nil) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(tmp³, Nil⁰) -> -//│ deforest > match: xs⁰ -//│ deforest > fields: xs⁰.h⁰ -//│ deforest > fields: xs⁰.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ +//│ deforest > Pair⁰(1, 3) -> +//│ deforest > match: a2⁰ +//│ deforest > fields: a2⁰.a¹ +//│ deforest > fields: a2⁰.b⁰ +//│ deforest > Pair⁰(2, 3) -> +//│ deforest > match: a2⁰ +//│ deforest > fields: a2⁰.a¹ +//│ deforest > fields: a2⁰.b⁰ +//│ deforest > Pair⁰(a², b¹) -> +//│ deforest > match: a2⁰ +//│ deforest > fields: a2⁰.a¹ +//│ deforest > fields: a2⁰.b⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ -//│ deforest > Cons⁰(tmp⁴, tmp⁵) -> +//│ deforest > match: ls¹ +//│ deforest > Cons⁰(tmp³, Nil⁰) -> //│ deforest > match: ls¹ //│ deforest > fields: ls¹.h⁰ //│ deforest > fields: ls¹.t⁰ -//│ deforest > Cons⁰(tmp⁶, tmp⁷) -> +//│ deforest > Cons⁰(tmp⁴, tmp⁵) -> //│ deforest > match: ls¹ //│ deforest > fields: ls¹.h⁰ //│ deforest > fields: ls¹.t⁰ -//│ deforest > Cons⁰(tmp⁸, Nil⁰) -> +//│ deforest > Cons⁰(tmp⁶, tmp⁷) -> //│ deforest > match: ls¹ //│ deforest > fields: ls¹.h⁰ //│ deforest > fields: ls¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls¹ -//│ deforest > Pair⁰(a², b¹) -> +//│ deforest > match: xs⁰ +//│ deforest > Cons⁰(tmp⁸, Nil⁰) -> +//│ deforest > match: xs⁰ +//│ deforest > fields: xs⁰.h⁰ +//│ deforest > fields: xs⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: xs⁰ +//│ deforest > Pair⁰(1, 3) -> //│ deforest > match: a2⁰ //│ deforest > fields: a2⁰.a¹ //│ deforest > fields: a2⁰.b⁰ @@ -158,44 +170,30 @@ concatMap(f1, Pair(5, 10) :: Nil) //│ deforest > match: a2⁰ //│ deforest > fields: a2⁰.a¹ //│ deforest > fields: a2⁰.b⁰ -//│ deforest > Pair⁰(1, 3) -> +//│ deforest > Pair⁰(a², b¹) -> //│ deforest > match: a2⁰ //│ deforest > fields: a2⁰.a¹ //│ deforest > fields: a2⁰.b⁰ -//│ deforest > Cons⁰(tmp³, Nil⁰) -> -//│ deforest > match: xs⁰ -//│ deforest > fields: xs⁰.h⁰ -//│ deforest > fields: xs⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ -//│ deforest > Cons⁰(tmp⁴, tmp⁵) -> +//│ deforest > match: ls¹ +//│ deforest > Cons⁰(tmp³, Nil⁰) -> //│ deforest > match: ls¹ //│ deforest > fields: ls¹.h⁰ //│ deforest > fields: ls¹.t⁰ -//│ deforest > Cons⁰(tmp⁶, tmp⁷) -> +//│ deforest > Cons⁰(tmp⁴, tmp⁵) -> //│ deforest > match: ls¹ //│ deforest > fields: ls¹.h⁰ //│ deforest > fields: ls¹.t⁰ -//│ deforest > Cons⁰(tmp⁸, Nil⁰) -> +//│ deforest > Cons⁰(tmp⁶, tmp⁷) -> //│ deforest > match: ls¹ //│ deforest > fields: ls¹.h⁰ //│ deforest > fields: ls¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls¹ -//│ deforest > Pair⁰(a², b¹) -> -//│ deforest > match: a2⁰ -//│ deforest > fields: a2⁰.a¹ -//│ deforest > fields: a2⁰.b⁰ -//│ deforest > Pair⁰(2, 3) -> -//│ deforest > match: a2⁰ -//│ deforest > fields: a2⁰.a¹ -//│ deforest > fields: a2⁰.b⁰ -//│ deforest > Pair⁰(1, 3) -> -//│ deforest > match: a2⁰ -//│ deforest > fields: a2⁰.a¹ -//│ deforest > fields: a2⁰.b⁰ +//│ deforest > match: xs⁰ +//│ deforest > Cons⁰(tmp⁸, Nil⁰) -> +//│ deforest > match: xs⁰ +//│ deforest > fields: xs⁰.h⁰ +//│ deforest > fields: xs⁰.t⁰ //│ deforest > Nil⁰ -> //│ deforest > match: xs⁰ //│ deforest > Cons⁰(h¹, tmp⁹) -> @@ -204,16 +202,18 @@ concatMap(f1, Pair(5, 10) :: Nil) //│ deforest > fields: xs⁰.t⁰ //│ deforest > Nil⁰ -> //│ deforest > match: xs⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: xs⁰ //│ deforest > Cons⁰(tmp¹⁰, Nil⁰) -> //│ deforest > match: ls¹ //│ deforest > fields: ls¹.h⁰ //│ deforest > fields: ls¹.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ls¹ //│ deforest > Pair⁰(5, 10) -> //│ deforest > match: a1⁰ //│ deforest > fields: a1⁰.a¹ //│ deforest > fields: a1⁰.b⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls¹ //│ deforest > <<< fusing <<< //│ = Cons(Pair(5, 1), Cons(Pair(5, 2), Cons(Pair(5, 5), Nil))) @@ -250,6 +250,8 @@ if A is A then lscomp(1 :: 2 :: 3 :: Nil) B then Nil //│ deforest > >>> fusing >>> +//│ deforest > A⁰ -> +//│ deforest > match: scrut⁰ //│ deforest > Cons⁰(1, tmp¹²) -> //│ deforest > match: ls² //│ deforest > fields: ls².h⁰ @@ -264,8 +266,6 @@ if A is //│ deforest > fields: ls².t⁰ //│ deforest > Nil⁰ -> //│ deforest > match: ls² -//│ deforest > A⁰ -> -//│ deforest > match: scrut⁰ //│ deforest > <<< fusing <<< //│ = Cons(2, Cons(3, Cons(4, Nil))) @@ -280,76 +280,76 @@ fun h() = lscomp1(1 :: 2 :: 3 :: Nil) h() //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(1, tmp¹⁴) -> +//│ deforest > Nil⁰ -> +//│ deforest > match: ls³ +//│ deforest > Cons⁰(3, Nil⁰) -> //│ deforest > match: ls³ //│ deforest > fields: ls³.h⁰ //│ deforest > fields: ls³.t⁰ -//│ deforest > Cons⁰(2, tmp¹⁵) -> +//│ deforest > Cons⁰(2, tmp¹⁴) -> //│ deforest > match: ls³ //│ deforest > fields: ls³.h⁰ //│ deforest > fields: ls³.t⁰ -//│ deforest > Cons⁰(3, Nil⁰) -> +//│ deforest > Cons⁰(1, tmp¹⁵) -> //│ deforest > match: ls³ //│ deforest > fields: ls³.h⁰ //│ deforest > fields: ls³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls³ -//│ deforest > Cons⁰(1, tmp¹⁶) -> +//│ deforest > match: ls⁴ +//│ deforest > Cons⁰(3, Nil⁰) -> //│ deforest > match: ls⁴ //│ deforest > fields: ls⁴.h⁰ //│ deforest > fields: ls⁴.t⁰ -//│ deforest > Cons⁰(2, tmp¹⁷) -> +//│ deforest > Cons⁰(2, tmp¹⁶) -> //│ deforest > match: ls⁴ //│ deforest > fields: ls⁴.h⁰ //│ deforest > fields: ls⁴.t⁰ -//│ deforest > Cons⁰(3, Nil⁰) -> +//│ deforest > Cons⁰(1, tmp¹⁷) -> //│ deforest > match: ls⁴ //│ deforest > fields: ls⁴.h⁰ //│ deforest > fields: ls⁴.t⁰ //│ deforest > Nil⁰ -> //│ deforest > match: ls⁴ -//│ deforest > Cons⁰(1, tmp¹⁴) -> -//│ deforest > match: ls³ -//│ deforest > fields: ls³.h⁰ -//│ deforest > fields: ls³.t⁰ -//│ deforest > Cons⁰(2, tmp¹⁵) -> -//│ deforest > match: ls³ -//│ deforest > fields: ls³.h⁰ -//│ deforest > fields: ls³.t⁰ //│ deforest > Cons⁰(3, Nil⁰) -> -//│ deforest > match: ls³ -//│ deforest > fields: ls³.h⁰ -//│ deforest > fields: ls³.t⁰ +//│ deforest > match: ls⁴ +//│ deforest > fields: ls⁴.h⁰ +//│ deforest > fields: ls⁴.t⁰ +//│ deforest > Cons⁰(2, tmp¹⁶) -> +//│ deforest > match: ls⁴ +//│ deforest > fields: ls⁴.h⁰ +//│ deforest > fields: ls⁴.t⁰ +//│ deforest > Cons⁰(1, tmp¹⁷) -> +//│ deforest > match: ls⁴ +//│ deforest > fields: ls⁴.h⁰ +//│ deforest > fields: ls⁴.t⁰ //│ deforest > Nil⁰ -> //│ deforest > match: ls³ -//│ deforest > Cons⁰(1, tmp¹⁴) -> +//│ deforest > Cons⁰(3, Nil⁰) -> //│ deforest > match: ls³ //│ deforest > fields: ls³.h⁰ //│ deforest > fields: ls³.t⁰ -//│ deforest > Cons⁰(2, tmp¹⁵) -> +//│ deforest > Cons⁰(2, tmp¹⁴) -> //│ deforest > match: ls³ //│ deforest > fields: ls³.h⁰ //│ deforest > fields: ls³.t⁰ -//│ deforest > Cons⁰(3, Nil⁰) -> +//│ deforest > Cons⁰(1, tmp¹⁵) -> //│ deforest > match: ls³ //│ deforest > fields: ls³.h⁰ //│ deforest > fields: ls³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls³ -//│ deforest > Cons⁰(1, tmp¹⁶) -> +//│ deforest > match: ls⁴ +//│ deforest > Cons⁰(3, Nil⁰) -> //│ deforest > match: ls⁴ //│ deforest > fields: ls⁴.h⁰ //│ deforest > fields: ls⁴.t⁰ -//│ deforest > Cons⁰(2, tmp¹⁷) -> +//│ deforest > Cons⁰(2, tmp¹⁶) -> //│ deforest > match: ls⁴ //│ deforest > fields: ls⁴.h⁰ //│ deforest > fields: ls⁴.t⁰ -//│ deforest > Cons⁰(3, Nil⁰) -> +//│ deforest > Cons⁰(1, tmp¹⁷) -> //│ deforest > match: ls⁴ //│ deforest > fields: ls⁴.h⁰ //│ deforest > fields: ls⁴.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ls⁴ //│ deforest > <<< fusing <<< //│ = Cons( //│ [1, 1], diff --git a/hkmc2/shared/src/test/mlscript/deforest/module.mls b/hkmc2/shared/src/test/mlscript/deforest/module.mls index 413a3a9341..92dc12a744 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/module.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/module.mls @@ -39,8 +39,6 @@ module DeforestList with val res = DeforestList.sum(DeforestList.enum(100)) DeforestList.res + DeforestList.main() //│ deforest > >>> fusing >>> -//│ deforest > DeforestList⁰.this.Nil⁰ -> -//│ deforest > match: ls⁰ //│ deforest > DeforestList⁰.this.Cons⁰(n⁰, tmp¹) -> //│ deforest > match: ls⁰ //│ deforest > fields: ls⁰.h⁰ @@ -51,6 +49,8 @@ DeforestList.res + DeforestList.main() //│ deforest > match: ls⁰ //│ deforest > fields: ls⁰.h⁰ //│ deforest > fields: ls⁰.t⁰ +//│ deforest > DeforestList⁰.this.Nil⁰ -> +//│ deforest > match: ls⁰ //│ deforest > DeforestList⁰.this.Cons⁰(n⁰, tmp¹) -> //│ deforest > match: ls⁰ //│ deforest > fields: ls⁰.h⁰ @@ -78,8 +78,6 @@ module DeforestList with val res = DeforestList.sum(DeforestList.enum(100)) DeforestList.res + DeforestList.main() //│ deforest > >>> fusing >>> -//│ deforest > DeforestList¹.this.Nil¹ -> -//│ deforest > match: ls¹ //│ deforest > DeforestList¹.this.Cons¹(n¹, tmp²) -> //│ deforest > match: ls¹ //│ deforest > fields: ls¹.h¹ @@ -90,6 +88,8 @@ DeforestList.res + DeforestList.main() //│ deforest > match: ls¹ //│ deforest > fields: ls¹.h¹ //│ deforest > fields: ls¹.t¹ +//│ deforest > DeforestList¹.this.Nil¹ -> +//│ deforest > match: ls¹ //│ deforest > DeforestList¹.this.Cons¹(n¹, tmp²) -> //│ deforest > match: ls¹ //│ deforest > fields: ls¹.h¹ @@ -159,25 +159,25 @@ module M with C [M.f(3), M.g(true)] //│ deforest > >>> fusing >>> -//│ deforest > B⁰(0) -> -//│ deforest > match: scrut¹ -//│ deforest > D⁰ -> -//│ deforest > match: scrut¹ //│ deforest > A¹ -> -//│ deforest > match: scrut² +//│ deforest > match: scrut¹ //│ deforest > A¹ -> -//│ deforest > match: scrut² +//│ deforest > match: scrut¹ //│ deforest > C⁰ -> -//│ deforest > match: scrut² -//│ deforest > B⁰(0) -> //│ deforest > match: scrut¹ +//│ deforest > B⁰(0) -> +//│ deforest > match: scrut² //│ deforest > D⁰ -> -//│ deforest > match: scrut¹ -//│ deforest > A¹ -> //│ deforest > match: scrut² //│ deforest > A¹ -> -//│ deforest > match: scrut² +//│ deforest > match: scrut¹ +//│ deforest > A¹ -> +//│ deforest > match: scrut¹ //│ deforest > C⁰ -> +//│ deforest > match: scrut¹ +//│ deforest > B⁰(0) -> +//│ deforest > match: scrut² +//│ deforest > D⁰ -> //│ deforest > match: scrut² //│ deforest > <<< fusing <<< //│ = [D, A] diff --git a/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls b/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls index d1f48c4996..0620022a57 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls @@ -34,15 +34,15 @@ fun g(x) = if x is let a = AA(AA(3)) f(a, 2) + g(a) + f(BB(3), 2) + f(AA(AA(4)), 5) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(tmp⁰) -> +//│ deforest > BB⁰(3) -> //│ deforest > match: x⁰ //│ deforest > fields: x⁰.x¹ -//│ deforest > AA⁰(4) -> -//│ deforest > match: a⁰ -//│ deforest > fields: a⁰.x¹ -//│ deforest > BB⁰(3) -> +//│ deforest > AA⁰(tmp⁰) -> //│ deforest > match: x⁰ //│ deforest > fields: x⁰.x² +//│ deforest > AA⁰(4) -> +//│ deforest > match: a⁰ +//│ deforest > fields: a⁰.x² //│ deforest > <<< fusing <<< //│ = 37 //│ a = AA(AA(3)) @@ -60,16 +60,16 @@ f(AA(AA(3)), 9) + f(AA(AA(4)), 10) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp¹) -> //│ deforest > match: x³ -//│ deforest > fields: x³.x¹ -//│ deforest > AA⁰(4) -> +//│ deforest > fields: x³.x² +//│ deforest > AA⁰(3) -> //│ deforest > match: a¹ -//│ deforest > fields: a¹.x¹ +//│ deforest > fields: a¹.x² //│ deforest > AA⁰(tmp²) -> //│ deforest > match: x³ -//│ deforest > fields: x³.x¹ -//│ deforest > AA⁰(3) -> +//│ deforest > fields: x³.x² +//│ deforest > AA⁰(4) -> //│ deforest > match: a¹ -//│ deforest > fields: a¹.x¹ +//│ deforest > fields: a¹.x² //│ deforest > <<< fusing <<< //│ = 30 @@ -86,10 +86,10 @@ f(AA(AA(3))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp³) -> //│ deforest > match: x⁴ -//│ deforest > fields: x⁴.x¹ +//│ deforest > fields: x⁴.x² //│ deforest > AA⁰(3) -> //│ deforest > match: a² -//│ deforest > fields: a².x¹ +//│ deforest > fields: a².x² //│ deforest > <<< fusing <<< //│ = 5 @@ -104,16 +104,16 @@ f(AA(AA(A))) + f(AA(AA(A))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁴) -> //│ deforest > match: x⁵ -//│ deforest > fields: x⁵.x¹ +//│ deforest > fields: x⁵.x² //│ deforest > AA⁰(A⁰) -> //│ deforest > match: arg$AA$0$⁰ -//│ deforest > fields: arg$AA$0$⁰.x¹ +//│ deforest > fields: arg$AA$0$⁰.x² //│ deforest > AA⁰(tmp⁵) -> //│ deforest > match: x⁵ -//│ deforest > fields: x⁵.x¹ +//│ deforest > fields: x⁵.x² //│ deforest > AA⁰(A⁰) -> //│ deforest > match: arg$AA$0$⁰ -//│ deforest > fields: arg$AA$0$⁰.x¹ +//│ deforest > fields: arg$AA$0$⁰.x² //│ deforest > <<< fusing <<< //│ = "A3A3" @@ -126,10 +126,10 @@ c2(AA(AA(0))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁶) -> //│ deforest > match: x⁶ -//│ deforest > fields: x⁶.x¹ +//│ deforest > fields: x⁶.x² //│ deforest > AA⁰(0) -> //│ deforest > match: arg$AA$0$¹ -//│ deforest > fields: arg$AA$0$¹.x¹ +//│ deforest > fields: arg$AA$0$¹.x² //│ deforest > <<< fusing <<< //│ = 0 @@ -142,10 +142,10 @@ f(AA(BB(B))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁷) -> //│ deforest > match: a³ -//│ deforest > fields: a³.x¹ +//│ deforest > fields: a³.x² //│ deforest > BB⁰(B⁰) -> //│ deforest > match: arg$AA$0$² -//│ deforest > fields: arg$AA$0$².x² +//│ deforest > fields: arg$AA$0$².x¹ //│ deforest > B⁰ -> //│ deforest > match: arg$BB$0$⁰ //│ deforest > <<< fusing <<< @@ -162,24 +162,24 @@ fun test(x, y, z, i) = if x is m + n test(AA(1), AA(2), BB(3), 4) + test(AA(1), AA(2), BB(3), 4) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(3) -> -//│ deforest > match: z⁰ -//│ deforest > fields: z⁰.x² -//│ deforest > AA⁰(2) -> -//│ deforest > match: y⁰ -//│ deforest > fields: y⁰.x¹ //│ deforest > AA⁰(1) -> //│ deforest > match: x⁷ -//│ deforest > fields: x⁷.x¹ -//│ deforest > BB⁰(3) -> -//│ deforest > match: z⁰ -//│ deforest > fields: z⁰.x² +//│ deforest > fields: x⁷.x² //│ deforest > AA⁰(2) -> //│ deforest > match: y⁰ -//│ deforest > fields: y⁰.x¹ +//│ deforest > fields: y⁰.x² +//│ deforest > BB⁰(3) -> +//│ deforest > match: z⁰ +//│ deforest > fields: z⁰.x¹ //│ deforest > AA⁰(1) -> //│ deforest > match: x⁷ -//│ deforest > fields: x⁷.x¹ +//│ deforest > fields: x⁷.x² +//│ deforest > AA⁰(2) -> +//│ deforest > match: y⁰ +//│ deforest > fields: y⁰.x² +//│ deforest > BB⁰(3) -> +//│ deforest > match: z⁰ +//│ deforest > fields: z⁰.x¹ //│ deforest > <<< fusing <<< //│ = 10 @@ -200,12 +200,12 @@ f1(aa(BB(cc))) + f2(aa(BB(CC(0)))) + f3(cc) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(x⁸) -> //│ deforest > match: x⁹ -//│ deforest > AA⁰(x⁸) -> -//│ deforest > match: x¹⁰ -//│ deforest > fields: x¹⁰.x¹ +//│ deforest > fields: x⁹.x² //│ deforest > BB⁰(cc⁰) -> //│ deforest > match: x1⁰ -//│ deforest > fields: x1⁰.x² +//│ deforest > fields: x1⁰.x¹ +//│ deforest > AA⁰(x⁸) -> +//│ deforest > match: x¹⁰ //│ deforest > <<< fusing <<< //│ = "ccf2f3cc" //│ cc = CC("cc") @@ -223,7 +223,7 @@ let y = A in //│ deforest > >>> fusing >>> //│ deforest > AA⁰(2) -> //│ deforest > match: x¹¹ -//│ deforest > fields: x¹¹.x¹ +//│ deforest > fields: x¹¹.x² //│ deforest > A⁰ -> //│ deforest > match: y¹ //│ deforest > <<< fusing <<< @@ -242,8 +242,8 @@ let y = A in //│ deforest > >>> fusing >>> //│ deforest > AA⁰(2) -> //│ deforest > match: x¹² -//│ deforest > fields: x¹².x¹ -//│ deforest > fields: x¹².x¹ +//│ deforest > fields: x¹².x² +//│ deforest > fields: x¹².x² //│ deforest > <<< fusing <<< //│ = 9 @@ -262,7 +262,7 @@ test(p) + f(p) + test(AA(A)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(A⁰) -> //│ deforest > match: x¹³ -//│ deforest > fields: x¹³.x¹ +//│ deforest > fields: x¹³.x² //│ deforest > A⁰ -> //│ deforest > match: arg$AA$0$³ //│ deforest > <<< fusing <<< @@ -288,26 +288,26 @@ f(aa, 3) + f(aa, 4) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(tmp⁸) -> //│ deforest > match: tmp⁹ -//│ deforest > fields: tmp⁹.x² +//│ deforest > fields: tmp⁹.x¹ //│ deforest > AA⁰(2) -> //│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x¹ +//│ deforest > fields: scrut⁰.x² //│ deforest > AA⁰(3) -> //│ deforest > match: scrut¹ //│ deforest > BB⁰(tmp⁸) -> //│ deforest > match: tmp⁹ -//│ deforest > fields: tmp⁹.x² +//│ deforest > fields: tmp⁹.x¹ //│ deforest > AA⁰(2) -> //│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x¹ +//│ deforest > fields: scrut⁰.x² //│ deforest > AA⁰(3) -> //│ deforest > match: scrut¹ //│ deforest > BB⁰(tmp⁸) -> //│ deforest > match: tmp⁹ -//│ deforest > fields: tmp⁹.x² +//│ deforest > fields: tmp⁹.x¹ //│ deforest > AA⁰(2) -> //│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x¹ +//│ deforest > fields: scrut⁰.x² //│ deforest > AA⁰(3) -> //│ deforest > match: scrut¹ //│ deforest > <<< fusing <<< @@ -331,20 +331,20 @@ test(3, AA(2), AA(3)) + test(3, AA(2), AA(3)) //│ deforest > match: scrut² //│ deforest > AA⁰(0) -> //│ deforest > match: scrut² -//│ deforest > AA⁰(3) -> -//│ deforest > match: q⁰ -//│ deforest > fields: q⁰.x¹ -//│ deforest > AA⁰(2) -> -//│ deforest > match: p⁰ -//│ deforest > fields: p⁰.x¹ //│ deforest > AA⁰(0) -> //│ deforest > match: scrut² +//│ deforest > AA⁰(2) -> +//│ deforest > match: p⁰ +//│ deforest > fields: p⁰.x² //│ deforest > AA⁰(3) -> //│ deforest > match: q⁰ -//│ deforest > fields: q⁰.x¹ +//│ deforest > fields: q⁰.x² //│ deforest > AA⁰(2) -> //│ deforest > match: p⁰ -//│ deforest > fields: p⁰.x¹ +//│ deforest > fields: p⁰.x² +//│ deforest > AA⁰(3) -> +//│ deforest > match: q⁰ +//│ deforest > fields: q⁰.x² //│ deforest > <<< fusing <<< //│ = 16 @@ -367,18 +367,18 @@ 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) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(3) -> -//│ deforest > match: q¹ -//│ deforest > fields: q¹.x¹ //│ deforest > AA⁰(2) -> //│ deforest > match: p¹ -//│ deforest > fields: p¹.x¹ +//│ deforest > fields: p¹.x² //│ deforest > AA⁰(3) -> //│ deforest > match: q¹ -//│ deforest > fields: q¹.x¹ +//│ deforest > fields: q¹.x² //│ deforest > AA⁰(2) -> //│ deforest > match: p¹ -//│ deforest > fields: p¹.x¹ +//│ deforest > fields: p¹.x² +//│ deforest > AA⁰(3) -> +//│ deforest > match: q¹ +//│ deforest > fields: q¹.x² //│ deforest > <<< fusing <<< //│ = 13 //│ a = A @@ -397,24 +397,24 @@ fun test(x, y, z, i) = k + i test(AA(1), AA(2), BB(3), 4) + test(AA(1), AA(2), BB(3), 4) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(3) -> -//│ deforest > match: z¹ -//│ deforest > fields: z¹.x² -//│ deforest > AA⁰(2) -> -//│ deforest > match: y² -//│ deforest > fields: y².x¹ //│ deforest > AA⁰(1) -> //│ deforest > match: x¹⁴ -//│ deforest > fields: x¹⁴.x¹ -//│ deforest > BB⁰(3) -> -//│ deforest > match: z¹ -//│ deforest > fields: z¹.x² +//│ deforest > fields: x¹⁴.x² //│ deforest > AA⁰(2) -> //│ deforest > match: y² -//│ deforest > fields: y².x¹ +//│ deforest > fields: y².x² +//│ deforest > BB⁰(3) -> +//│ deforest > match: z¹ +//│ deforest > fields: z¹.x¹ //│ deforest > AA⁰(1) -> //│ deforest > match: x¹⁴ -//│ deforest > fields: x¹⁴.x¹ +//│ deforest > fields: x¹⁴.x² +//│ deforest > AA⁰(2) -> +//│ deforest > match: y² +//│ deforest > fields: y².x² +//│ deforest > BB⁰(3) -> +//│ deforest > match: z¹ +//│ deforest > fields: z¹.x¹ //│ deforest > <<< fusing <<< //│ = 18 @@ -432,24 +432,24 @@ fun test(x, y, z, i) = k + i test(AA(1), AA(2), BB(3), 4) + test(AA(1), BB(2), BB(3), 4) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(3) -> -//│ deforest > match: z² -//│ deforest > fields: z².x² -//│ deforest > BB⁰(2) -> -//│ deforest > match: y³ -//│ deforest > fields: y³.x² //│ deforest > AA⁰(1) -> //│ deforest > match: x¹⁵ -//│ deforest > fields: x¹⁵.x¹ -//│ deforest > BB⁰(3) -> -//│ deforest > match: z² -//│ deforest > fields: z².x² +//│ deforest > fields: x¹⁵.x² //│ deforest > AA⁰(2) -> //│ deforest > match: y³ -//│ deforest > fields: y³.x¹ +//│ deforest > fields: y³.x² +//│ deforest > BB⁰(3) -> +//│ deforest > match: z² +//│ deforest > fields: z².x¹ //│ deforest > AA⁰(1) -> //│ deforest > match: x¹⁵ -//│ deforest > fields: x¹⁵.x¹ +//│ deforest > fields: x¹⁵.x² +//│ deforest > BB⁰(2) -> +//│ deforest > match: y³ +//│ deforest > fields: y³.x¹ +//│ deforest > BB⁰(3) -> +//│ deforest > match: z² +//│ deforest > fields: z².x¹ //│ deforest > <<< fusing <<< //│ = 10 @@ -467,18 +467,18 @@ test(p(BB(3))) + test(p(CC(3))) + c(p(A)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(x¹⁶) -> //│ deforest > match: x¹⁷ +//│ deforest > fields: x¹⁷.x² +//│ deforest > BB⁰(3) -> +//│ deforest > match: arg$AA$0$⁴ +//│ deforest > fields: arg$AA$0$⁴.x¹ //│ deforest > AA⁰(x¹⁶) -> -//│ deforest > match: x¹⁸ -//│ deforest > fields: x¹⁸.x¹ +//│ deforest > match: x¹⁷ +//│ deforest > fields: x¹⁷.x² //│ deforest > CC⁰(3) -> //│ deforest > match: arg$AA$0$⁴ -//│ deforest > fields: arg$AA$0$⁴.x¹⁹ +//│ deforest > fields: arg$AA$0$⁴.x¹⁸ //│ deforest > AA⁰(x¹⁶) -> -//│ deforest > match: x¹⁸ -//│ deforest > fields: x¹⁸.x¹ -//│ deforest > BB⁰(3) -> -//│ deforest > match: arg$AA$0$⁴ -//│ deforest > fields: arg$AA$0$⁴.x² +//│ deforest > match: x¹⁹ //│ deforest > <<< fusing <<< //│ = 12 @@ -502,41 +502,41 @@ f(AA(3), 3, true, 10) + f(AA(5), 4, false, 20) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(tmp¹⁰) -> //│ deforest > match: tmp¹¹ -//│ deforest > fields: tmp¹¹.x² +//│ deforest > fields: tmp¹¹.x¹ //│ deforest > BB⁰(tmp¹²) -> //│ deforest > match: tmp¹¹ -//│ deforest > fields: tmp¹¹.x² +//│ deforest > fields: tmp¹¹.x¹ //│ deforest > AA⁰(2) -> //│ deforest > match: scrut³ -//│ deforest > fields: scrut³.x¹ +//│ deforest > fields: scrut³.x² //│ deforest > AA⁰(3) -> //│ deforest > match: scrut⁴ //│ deforest > BB⁰(tmp¹⁰) -> //│ deforest > match: tmp¹¹ -//│ deforest > fields: tmp¹¹.x² +//│ deforest > fields: tmp¹¹.x¹ //│ deforest > BB⁰(tmp¹²) -> //│ deforest > match: tmp¹¹ -//│ deforest > fields: tmp¹¹.x² +//│ deforest > fields: tmp¹¹.x¹ //│ deforest > AA⁰(2) -> //│ deforest > match: scrut³ -//│ deforest > fields: scrut³.x¹ +//│ deforest > fields: scrut³.x² //│ deforest > AA⁰(3) -> //│ deforest > match: scrut⁴ -//│ deforest > AA⁰(5) -> -//│ deforest > match: x²⁰ //│ deforest > BB⁰(tmp¹⁰) -> //│ deforest > match: tmp¹¹ -//│ deforest > fields: tmp¹¹.x² +//│ deforest > fields: tmp¹¹.x¹ //│ deforest > BB⁰(tmp¹²) -> //│ deforest > match: tmp¹¹ -//│ deforest > fields: tmp¹¹.x² +//│ deforest > fields: tmp¹¹.x¹ //│ deforest > AA⁰(2) -> //│ deforest > match: scrut³ -//│ deforest > fields: scrut³.x¹ +//│ deforest > fields: scrut³.x² //│ deforest > AA⁰(3) -> //│ deforest > match: scrut⁴ //│ deforest > AA⁰(3) -> //│ deforest > match: x²⁰ +//│ deforest > AA⁰(5) -> +//│ deforest > match: x²⁰ //│ deforest > <<< fusing <<< //│ = 41 @@ -571,14 +571,14 @@ fun test(a, b) = print(x) test(B, B) + test(A, A) //│ deforest > >>> fusing >>> -//│ deforest > A⁰ -> -//│ deforest > match: a⁴ -//│ deforest > A⁰ -> -//│ deforest > match: b⁰ //│ deforest > B⁰ -> //│ deforest > match: a⁴ //│ deforest > B⁰ -> //│ deforest > match: b⁰ +//│ deforest > A⁰ -> +//│ deforest > match: a⁴ +//│ deforest > A⁰ -> +//│ deforest > match: b⁰ //│ deforest > <<< fusing <<< //│ > B //│ > 1 diff --git a/hkmc2/shared/src/test/mlscript/deforest/recursive.mls b/hkmc2/shared/src/test/mlscript/deforest/recursive.mls index 026e050c4c..9466b97165 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/recursive.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/recursive.mls @@ -130,18 +130,6 @@ fun cons1(a1) = if a1 is cons(f(0)) + cons1(f(1)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: a1⁰ -//│ deforest > Cons⁰(x⁰, tmp⁶) -> -//│ deforest > match: a1⁰ -//│ deforest > fields: a1⁰.h⁰ -//│ deforest > fields: a1⁰.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: a1⁰ -//│ deforest > Cons⁰(y⁰, tmp⁷) -> -//│ deforest > match: a1⁰ -//│ deforest > fields: a1⁰.h⁰ -//│ deforest > fields: a1⁰.t⁰ -//│ deforest > Nil⁰ -> //│ deforest > match: a¹ //│ deforest > Cons⁰(x⁰, tmp⁶) -> //│ deforest > match: a¹ @@ -153,6 +141,18 @@ cons(f(0)) + cons1(f(1)) //│ deforest > match: a¹ //│ deforest > fields: a¹.h⁰ //│ deforest > fields: a¹.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: a1⁰ +//│ deforest > Cons⁰(x⁰, tmp⁶) -> +//│ deforest > match: a1⁰ +//│ deforest > fields: a1⁰.h⁰ +//│ deforest > fields: a1⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: a1⁰ +//│ deforest > Cons⁰(y⁰, tmp⁷) -> +//│ deforest > match: a1⁰ +//│ deforest > fields: a1⁰.h⁰ +//│ deforest > fields: a1⁰.t⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -187,13 +187,7 @@ fun succ(x) = x + 1 fun double(x) = x * 2 map(map(1 :: 2 :: Nil, succ), double) //│ deforest > >>> fusing >>> -//│ deforest > Nil⁰ -> -//│ deforest > match: ls⁴ -//│ deforest > Cons⁰(tmp⁹, tmp¹⁰) -> -//│ deforest > match: ls⁴ -//│ deforest > fields: ls⁴.h⁰ -//│ deforest > fields: ls⁴.t⁰ -//│ deforest > Cons⁰(1, tmp¹¹) -> +//│ deforest > Cons⁰(1, tmp⁹) -> //│ deforest > match: ls⁴ //│ deforest > fields: ls⁴.h⁰ //│ deforest > fields: ls⁴.t⁰ @@ -203,6 +197,12 @@ map(map(1 :: 2 :: Nil, succ), double) //│ deforest > fields: ls⁴.t⁰ //│ deforest > Nil⁰ -> //│ deforest > match: ls⁴ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls⁴ +//│ deforest > Cons⁰(tmp¹⁰, tmp¹¹) -> +//│ deforest > match: ls⁴ +//│ deforest > fields: ls⁴.h⁰ +//│ deforest > fields: ls⁴.t⁰ //│ deforest > <<< fusing <<< //│ = Cons(4, Cons(6, Nil)) @@ -217,18 +217,18 @@ fun f2(ls) = if ls is fun wrap(n) = to(n) f1(wrap(4)) + f2(wrap(5)) //│ deforest > >>> fusing >>> -//│ deforest > Nil⁰ -> -//│ deforest > match: ls⁵ //│ deforest > Cons⁰(n², tmp¹²) -> //│ deforest > match: ls⁵ //│ deforest > fields: ls⁵.h⁰ //│ deforest > fields: ls⁵.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁶ +//│ deforest > match: ls⁵ //│ deforest > Cons⁰(n², tmp¹²) -> //│ deforest > match: ls⁶ //│ deforest > fields: ls⁶.h⁰ //│ deforest > fields: ls⁶.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls⁶ //│ deforest > <<< fusing <<< //│ = 10 @@ -242,24 +242,24 @@ fun f2(ls, acc) = if ls is h :: t then f2(t, acc + h + 1) f1(to(4, Nil), 0) + f2(to(5, Nil), 0) + f1(to(6, Nil), 0) //│ deforest > >>> fusing >>> -//│ deforest > Nil⁰ -> -//│ deforest > match: ls⁷ //│ deforest > Cons⁰(n³, acc⁰) -> //│ deforest > match: ls⁷ //│ deforest > fields: ls⁷.h⁰ //│ deforest > fields: ls⁷.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁸ +//│ deforest > match: ls⁷ //│ deforest > Cons⁰(n³, acc⁰) -> //│ deforest > match: ls⁸ //│ deforest > fields: ls⁸.h⁰ //│ deforest > fields: ls⁸.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁷ +//│ deforest > match: ls⁸ //│ deforest > Cons⁰(n³, acc⁰) -> //│ deforest > match: ls⁷ //│ deforest > fields: ls⁷.h⁰ //│ deforest > fields: ls⁷.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls⁷ //│ deforest > <<< fusing <<< //│ = 51 @@ -272,28 +272,28 @@ fun double(x) = x * 2 fun triple(x) = x * 3 map(map(map(1 :: 2 :: Nil, succ), double), triple) //│ deforest > >>> fusing >>> -//│ deforest > Nil⁰ -> +//│ deforest > Cons⁰(1, tmp¹³) -> //│ deforest > match: ls⁹ -//│ deforest > Cons⁰(tmp¹³, tmp¹⁴) -> +//│ deforest > fields: ls⁹.h⁰ +//│ deforest > fields: ls⁹.t⁰ +//│ deforest > Cons⁰(2, Nil⁰) -> //│ deforest > match: ls⁹ //│ deforest > fields: ls⁹.h⁰ //│ deforest > fields: ls⁹.t⁰ //│ deforest > Nil⁰ -> //│ deforest > match: ls⁹ -//│ deforest > Cons⁰(tmp¹³, tmp¹⁴) -> +//│ deforest > Nil⁰ -> //│ deforest > match: ls⁹ -//│ deforest > fields: ls⁹.h⁰ -//│ deforest > fields: ls⁹.t⁰ -//│ deforest > Cons⁰(1, tmp¹⁵) -> +//│ deforest > Cons⁰(tmp¹⁴, tmp¹⁵) -> //│ deforest > match: ls⁹ //│ deforest > fields: ls⁹.h⁰ //│ deforest > fields: ls⁹.t⁰ -//│ deforest > Cons⁰(2, Nil⁰) -> +//│ deforest > Nil⁰ -> +//│ deforest > match: ls⁹ +//│ deforest > Cons⁰(tmp¹⁴, tmp¹⁵) -> //│ deforest > match: ls⁹ //│ deforest > fields: ls⁹.h⁰ //│ deforest > fields: ls⁹.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ls⁹ //│ deforest > <<< fusing <<< //│ = Cons(12, Cons(18, Nil)) @@ -421,12 +421,12 @@ fun rev(xs_rev, acc) = if xs_rev is Nil then acc map(incr, rev(id(mk(5)), Nil)) //│ deforest > >>> fusing >>> -//│ deforest > Nil⁰ -> -//│ deforest > match: xs_map⁰ //│ deforest > Cons⁰(x¹, acc¹) -> //│ deforest > match: xs_map⁰ //│ deforest > fields: xs_map⁰.h⁰ //│ deforest > fields: xs_map⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: xs_map⁰ //│ deforest > <<< fusing <<< //│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) @@ -516,12 +516,6 @@ fun rev(a, ys) = if ys is Nil then a count(0, rev(Nil, 1 :: 2 :: Nil)) //│ deforest > >>> fusing >>> -//│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ -//│ deforest > Cons⁰(h¹, a⁷) -> -//│ deforest > match: xs⁰ -//│ deforest > fields: xs⁰.h⁰ -//│ deforest > fields: xs⁰.t⁰ //│ deforest > Cons⁰(1, tmp²⁶) -> //│ deforest > match: ys⁰ //│ deforest > fields: ys⁰.h⁰ @@ -532,6 +526,12 @@ count(0, rev(Nil, 1 :: 2 :: Nil)) //│ deforest > fields: ys⁰.t⁰ //│ deforest > Nil⁰ -> //│ deforest > match: ys⁰ +//│ deforest > Cons⁰(h¹, a⁷) -> +//│ deforest > match: xs⁰ +//│ deforest > fields: xs⁰.h⁰ +//│ deforest > fields: xs⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: xs⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -593,23 +593,23 @@ c(T(A, T(B, L, L), T(A, L, L))) //│ deforest > fields: x⁴.r⁰ //│ deforest > A⁰ -> //│ deforest > match: n⁵ -//│ deforest > T⁰(A⁰, L⁰, L⁰) -> +//│ deforest > T⁰(B⁰, L⁰, L⁰) -> //│ deforest > match: x⁴ //│ deforest > fields: x⁴.n⁴ //│ deforest > fields: x⁴.l⁰ //│ deforest > fields: x⁴.r⁰ -//│ deforest > A⁰ -> +//│ deforest > B⁰ -> //│ deforest > match: n⁵ //│ deforest > L⁰ -> //│ deforest > match: x⁴ //│ deforest > L⁰ -> //│ deforest > match: x⁴ -//│ deforest > T⁰(B⁰, L⁰, L⁰) -> +//│ deforest > T⁰(A⁰, L⁰, L⁰) -> //│ deforest > match: x⁴ //│ deforest > fields: x⁴.n⁴ //│ deforest > fields: x⁴.l⁰ //│ deforest > fields: x⁴.r⁰ -//│ deforest > B⁰ -> +//│ deforest > A⁰ -> //│ deforest > match: n⁵ //│ deforest > L⁰ -> //│ deforest > match: x⁴ @@ -973,11 +973,7 @@ f(3) //│ deforest > fields: a⁸.x⁹ //│ deforest > <<< fusing <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let f$a_A, f$a_A1, f$g, f$f, f$g1; -//│ @private -//│ define f$g as fun f$g⁰(a) { -//│ return a(undefined, undefined, undefined) -//│ }; +//│ let f$a_A, f$a_A1, f$f, f$g, f$g1; //│ @private //│ define f$f as fun f$f⁰(x) { //│ let scrut, tmp; @@ -987,22 +983,26 @@ f(3) //│ return 0 //│ else //│ set tmp = f$a_A⁰(x); -//│ return f$g¹(tmp) +//│ return f$g⁰(tmp) //│ end //│ }; //│ @private +//│ define f$g as fun f$g⁰(a) { +//│ return a(undefined, undefined, undefined) +//│ }; +//│ @private //│ define f$g1 as fun f$g¹(a) { //│ return a(undefined, undefined, undefined) //│ }; //│ @affine(1) @private -//│ define f$a_A as fun f$a_A¹(A_x)(fv_arg$A$0$, fv_y, fv_tmp) { +//│ define f$a_A as fun f$a_A⁰(A_x)(fv_arg$A$0$, fv_y, fv_tmp) { //│ set fv_tmp = -⁰(A_x, 1); -//│ return f⁰(fv_tmp) +//│ return f$f⁰(fv_tmp) //│ }; //│ @affine(1) @private -//│ define f$a_A1 as fun f$a_A⁰(A_x)(fv_arg$A$0$, fv_y, fv_tmp) { +//│ define f$a_A1 as fun f$a_A¹(A_x)(fv_arg$A$0$, fv_y, fv_tmp) { //│ set fv_tmp = -⁰(A_x, 1); -//│ return f$f⁰(fv_tmp) +//│ return f⁰(fv_tmp) //│ }; //│ define f¹ as fun f⁰(x) { //│ let scrut, tmp; @@ -1012,7 +1012,7 @@ f(3) //│ return 0 //│ else //│ set tmp = f$a_A¹(x); -//│ return f$g⁰(tmp) +//│ return f$g¹(tmp) //│ end //│ }; //│ define g⁰ as fun g¹(a) { diff --git a/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls b/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls index 40b1061ad4..77f63b962d 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls @@ -46,11 +46,11 @@ fun c(x, y) = if x is A then x.AA#x c(AA(2), A) //│ deforest > >>> fusing >>> -//│ deforest > A⁰ -> -//│ deforest > match: y⁰ //│ deforest > AA⁰(2) -> //│ deforest > match: x² //│ deforest > fields: x².x¹ +//│ deforest > A⁰ -> +//│ deforest > match: y⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -64,11 +64,11 @@ fun c(x, y) = if x is fun p() = AA(2) c(p(), A) //│ deforest > >>> fusing >>> -//│ deforest > A⁰ -> -//│ deforest > match: y¹ //│ deforest > AA⁰(2) -> //│ deforest > match: x³ //│ deforest > fields: x³.x¹ +//│ deforest > A⁰ -> +//│ deforest > match: y¹ //│ deforest > <<< fusing <<< //│ = 2 @@ -84,11 +84,11 @@ fun c(x, y) = if x is fun p() = AA(2) c(p(), A) //│ deforest > >>> fusing >>> -//│ deforest > A⁰ -> -//│ deforest > match: y² //│ deforest > AA⁰(2) -> //│ deforest > match: x⁴ //│ deforest > fields: x⁴.x¹ +//│ deforest > A⁰ -> +//│ deforest > match: y² //│ deforest > <<< fusing <<< //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/deforest/simple.mls b/hkmc2/shared/src/test/mlscript/deforest/simple.mls index 2b89872cb6..a1efffe298 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/simple.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/simple.mls @@ -47,10 +47,10 @@ if x is A then 1 B then 2 //│ deforest > >>> fusing >>> -//│ deforest > B⁰ -> -//│ deforest > match: x¹ //│ deforest > A⁰ -> //│ deforest > match: x¹ +//│ deforest > B⁰ -> +//│ deforest > match: x¹ //│ deforest > <<< fusing <<< //│ = 1 @@ -60,12 +60,12 @@ if x is AA(x) then x BB(x) then x //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(B⁰) -> -//│ deforest > match: x² -//│ deforest > fields: x².bb⁰ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: x² //│ deforest > fields: x².aa⁰ +//│ deforest > BB⁰(B⁰) -> +//│ deforest > match: x² +//│ deforest > fields: x².bb⁰ //│ deforest > <<< fusing <<< //│ = A @@ -79,16 +79,16 @@ if x is AA(x) then f(x) BB(x) then f(x) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(B⁰) -> -//│ deforest > match: x³ -//│ deforest > fields: x³.bb⁰ -//│ deforest > B⁰ -> -//│ deforest > match: a⁰ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: x³ //│ deforest > fields: x³.aa⁰ +//│ deforest > BB⁰(B⁰) -> +//│ deforest > match: x³ +//│ deforest > fields: x³.bb⁰ //│ deforest > A⁰ -> //│ deforest > match: a⁰ +//│ deforest > B⁰ -> +//│ deforest > match: a⁰ //│ deforest > <<< fusing <<< //│ = 1 @@ -108,15 +108,15 @@ if x is AA then f1(x.AA#aa) BB then f2(x.BB#bb) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(B⁰) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.bb⁰ -//│ deforest > B⁰ -> -//│ deforest > match: a¹ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: x⁴ //│ deforest > fields: x⁴.aa⁰ +//│ deforest > BB⁰(B⁰) -> +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.bb⁰ //│ deforest > A⁰ -> +//│ deforest > match: a¹ +//│ deforest > B⁰ -> //│ deforest > match: a² //│ deforest > <<< fusing <<< //│ = 1 @@ -164,10 +164,10 @@ if x is if y is B then 2 //│ deforest > >>> fusing >>> -//│ deforest > B⁰ -> -//│ deforest > match: y⁰ //│ deforest > A⁰ -> //│ deforest > match: x⁶ +//│ deforest > B⁰ -> +//│ deforest > match: y⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -181,10 +181,10 @@ fun c(a) = x c(A) + c(B) //│ deforest > >>> fusing >>> -//│ deforest > B⁰ -> -//│ deforest > match: a³ //│ deforest > A⁰ -> //│ deforest > match: a³ +//│ deforest > B⁰ -> +//│ deforest > match: a³ //│ deforest > <<< fusing <<< //│ > 1 //│ > 2 @@ -246,10 +246,10 @@ if x is if y is B then 2 //│ deforest > >>> fusing >>> -//│ deforest > B⁰ -> -//│ deforest > match: y¹ //│ deforest > A⁰ -> //│ deforest > match: x⁸ +//│ deforest > B⁰ -> +//│ deforest > match: y¹ //│ deforest > <<< fusing <<< //│ = 2 @@ -263,19 +263,19 @@ fun f(x, y) = a + 3 f(AAA(1, 3), 1) + f(BBB(2, 3), 2) + f(AAA(3, 2), 4) + f(BBB(4, 6), 0) //│ deforest > >>> fusing >>> -//│ deforest > BBB⁰(4, 6) -> +//│ deforest > AAA⁰(1, 3) -> //│ deforest > match: x⁹ //│ deforest > fields: x⁹.x¹⁰ //│ deforest > fields: x⁹.y² -//│ deforest > AAA⁰(3, 2) -> +//│ deforest > BBB⁰(2, 3) -> //│ deforest > match: x⁹ //│ deforest > fields: x⁹.x¹¹ //│ deforest > fields: x⁹.y³ -//│ deforest > BBB⁰(2, 3) -> +//│ deforest > AAA⁰(3, 2) -> //│ deforest > match: x⁹ //│ deforest > fields: x⁹.x¹⁰ //│ deforest > fields: x⁹.y² -//│ deforest > AAA⁰(1, 3) -> +//│ deforest > BBB⁰(4, 6) -> //│ deforest > match: x⁹ //│ deforest > fields: x⁹.x¹¹ //│ deforest > fields: x⁹.y³ @@ -311,11 +311,11 @@ fun c(x, y) = if x is A then a c(AA(2), A) //│ deforest > >>> fusing >>> -//│ deforest > A⁰ -> -//│ deforest > match: y⁴ //│ deforest > AA⁰(2) -> //│ deforest > match: x¹² //│ deforest > fields: x¹².aa⁰ +//│ deforest > A⁰ -> +//│ deforest > match: y⁴ //│ deforest > <<< fusing <<< //│ = 2 @@ -328,20 +328,20 @@ fun f(x, y) = a + 3 f(AAA(1, 3), 1) + f(CCC(2), 2) + f(AAA(3, 2), 4) + f(CCC(4), 0) //│ deforest > >>> fusing >>> -//│ deforest > CCC⁰(4) -> +//│ deforest > AAA⁰(1, 3) -> +//│ deforest > match: x¹³ +//│ deforest > fields: x¹³.x¹⁰ +//│ deforest > fields: x¹³.y² +//│ deforest > CCC⁰(2) -> //│ deforest > match: x¹³ //│ deforest > fields: x¹³.c⁰ //│ deforest > AAA⁰(3, 2) -> //│ deforest > match: x¹³ -//│ deforest > fields: x¹³.x¹¹ -//│ deforest > fields: x¹³.y³ -//│ deforest > CCC⁰(2) -> +//│ deforest > fields: x¹³.x¹⁰ +//│ deforest > fields: x¹³.y² +//│ deforest > CCC⁰(4) -> //│ deforest > match: x¹³ //│ deforest > fields: x¹³.c⁰ -//│ deforest > AAA⁰(1, 3) -> -//│ deforest > match: x¹³ -//│ deforest > fields: x¹³.x¹¹ -//│ deforest > fields: x¹³.y³ //│ deforest > <<< fusing <<< //│ = 24 @@ -356,15 +356,15 @@ fun f(x, y) = a + 3 f(AAA(1, 3), 1) + f(CCC(2), 2) + f(AAA(3, 2), 4) //│ deforest > >>> fusing >>> -//│ deforest > AAA⁰(3, 2) -> +//│ deforest > AAA⁰(1, 3) -> //│ deforest > match: x¹⁴ -//│ deforest > fields: x¹⁴.y³ +//│ deforest > fields: x¹⁴.y² //│ deforest > CCC⁰(2) -> //│ deforest > match: x¹⁴ //│ deforest > fields: x¹⁴.c⁰ -//│ deforest > AAA⁰(1, 3) -> +//│ deforest > AAA⁰(3, 2) -> //│ deforest > match: x¹⁴ -//│ deforest > fields: x¹⁴.y³ +//│ deforest > fields: x¹⁴.y² //│ deforest > <<< fusing <<< //│ = 22 @@ -446,8 +446,8 @@ mapHead(x => x, AAA(1, AAA(2, None))) //│ deforest > >>> fusing >>> //│ deforest > AAA⁰(1, tmp⁴) -> //│ deforest > match: x¹⁸ -//│ deforest > fields: x¹⁸.x¹¹ -//│ deforest > fields: x¹⁸.y³ +//│ deforest > fields: x¹⁸.x¹⁰ +//│ deforest > fields: x¹⁸.y² //│ deforest > <<< fusing <<< //│ = 1 @@ -471,10 +471,10 @@ fun test(a) = if a is else 2 test(B) + test(C) //│ deforest > >>> fusing >>> -//│ deforest > C⁰ -> -//│ deforest > match: a⁵ //│ deforest > B⁰ -> //│ deforest > match: a⁵ +//│ deforest > C⁰ -> +//│ deforest > match: a⁵ //│ deforest > <<< fusing <<< //│ = 4 @@ -491,12 +491,12 @@ fun test(x, y, z) = if x is m + n test(AA(1), AA(2), AA(3)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(2) -> -//│ deforest > match: y⁵ -//│ deforest > fields: y⁵.aa⁰ //│ deforest > AA⁰(1) -> //│ deforest > match: x¹⁹ //│ deforest > fields: x¹⁹.aa⁰ +//│ deforest > AA⁰(2) -> +//│ deforest > match: y⁵ +//│ deforest > fields: y⁵.aa⁰ //│ deforest > <<< fusing <<< //│ = "2AA(3)NaN" @@ -511,18 +511,18 @@ fun test(x) = let p = if true then AA(CCC(AA)) else BB(CCC(AA)) in... test(p) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(tmp⁵) -> -//│ deforest > match: x²⁰ -//│ deforest > fields: x²⁰.bb⁰ -//│ deforest > CCC⁰(AA⁰) -> -//│ deforest > match: arg$BB$0$⁰ -//│ deforest > fields: arg$BB$0$⁰.c⁰ -//│ deforest > AA⁰(tmp⁶) -> +//│ deforest > AA⁰(tmp⁵) -> //│ deforest > match: x²⁰ //│ deforest > fields: x²⁰.aa⁰ //│ deforest > CCC⁰(AA⁰) -> //│ deforest > match: arg$AA$0$¹ //│ deforest > fields: arg$AA$0$¹.c⁰ +//│ deforest > BB⁰(tmp⁶) -> +//│ deforest > match: x²⁰ +//│ deforest > fields: x²⁰.bb⁰ +//│ deforest > CCC⁰(AA⁰) -> +//│ deforest > match: arg$BB$0$⁰ +//│ deforest > fields: arg$BB$0$⁰.c⁰ //│ deforest > <<< fusing <<< //│ = 141 @@ -538,12 +538,12 @@ fun test(x, y) = let p = if true then AA(CCC) else BB(CCC) in... test(p, id(CCC(123))) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(CCC⁰) -> -//│ deforest > match: x²¹ -//│ deforest > fields: x²¹.bb⁰ //│ deforest > AA⁰(CCC⁰) -> //│ deforest > match: x²¹ //│ deforest > fields: x²¹.aa⁰ +//│ deforest > BB⁰(CCC⁰) -> +//│ deforest > match: x²¹ +//│ deforest > fields: x²¹.bb⁰ //│ deforest > <<< fusing <<< //│ = 141 @@ -598,11 +598,11 @@ fun g(x) = if x is BB then B f(g(AA(1))) //│ deforest > >>> fusing >>> -//│ deforest > A⁰ -> +//│ deforest > AA⁰(1) -> //│ deforest > match: x²³ +//│ deforest > A⁰ -> +//│ deforest > match: x²⁴ //│ deforest > B⁰ -> -//│ deforest > match: x²³ -//│ deforest > AA⁰(1) -> //│ deforest > match: x²⁴ //│ deforest > <<< fusing <<< //│ = 0 @@ -633,14 +633,14 @@ fun c2(x) = if x is A then 0 c1(app(AA)) + c1(AA(2)) + c2(AA(3)) + c2(A) //│ deforest > >>> fusing >>> -//│ deforest > A⁰ -> -//│ deforest > match: x²⁵ -//│ deforest > AA⁰(3) -> -//│ deforest > match: x²⁵ -//│ deforest > fields: x²⁵.aa⁰ //│ deforest > AA⁰(2) -> //│ deforest > match: x1⁰ //│ deforest > fields: x1⁰.aa⁰ +//│ deforest > AA⁰(3) -> +//│ deforest > match: x²⁵ +//│ deforest > fields: x²⁵.aa⁰ +//│ deforest > A⁰ -> +//│ deforest > match: x²⁵ //│ deforest > <<< fusing <<< //│ = 6 @@ -659,8 +659,8 @@ c(B) //│ deforest > match: scrut² //│ deforest > A⁰ -> //│ deforest > match: scrut² -//│ deforest > B⁰ -> -//│ deforest > match: x²⁶ +//│ deforest > A⁰ -> +//│ deforest > match: scrut² //│ deforest > A⁰ -> //│ deforest > match: scrut² //│ deforest > new AA¹(2) -> @@ -669,14 +669,14 @@ c(B) //│ deforest > BB⁰(3) -> //│ deforest > match: x²⁶ //│ deforest > fields: x²⁶.bb⁰ -//│ deforest > A⁰ -> -//│ deforest > match: scrut² //│ deforest > new AA¹(2) -> //│ deforest > match: x²⁶ //│ deforest > fields: x²⁶.aa⁰ //│ deforest > BB⁰(3) -> //│ deforest > match: x²⁶ //│ deforest > fields: x²⁶.bb⁰ +//│ deforest > B⁰ -> +//│ deforest > match: x²⁶ //│ deforest > <<< fusing <<< //│ > 5 //│ ═══[RUNTIME ERROR] Error: e1 @@ -687,11 +687,11 @@ fun f(a, b) = if a is AA(x) then x + b.BB#bb f(AA(1), BB(2)) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(2) -> -//│ deforest > select: bb //│ deforest > AA⁰(1) -> //│ deforest > match: a⁷ //│ deforest > fields: a⁷.aa⁰ +//│ deforest > BB⁰(2) -> +//│ deforest > select: bb //│ deforest > <<< fusing <<< //│ = 3 @@ -760,10 +760,10 @@ fun f1(a1) = if a1 is AA then 1 fun f2(a2) = if a2 is AA then 2 f1(id(AA(1))) + f2(id(AA(2))) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(2) -> -//│ deforest > match: a2⁰ //│ deforest > AA⁰(1) -> //│ deforest > match: a1⁰ +//│ deforest > AA⁰(2) -> +//│ deforest > match: a2⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -818,9 +818,9 @@ fun f2(a2) = if a2 is AA then 2 f1(wrap(1)) + f2(wrap(2)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(x³¹) -> -//│ deforest > match: a2¹ -//│ deforest > AA⁰(x³¹) -> //│ deforest > match: a1¹ +//│ deforest > AA⁰(x³¹) -> +//│ deforest > match: a2¹ //│ deforest > <<< fusing <<< //│ = 3 @@ -853,25 +853,25 @@ fun g(b) = C [f(3), g(true)] //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(0) -> -//│ deforest > match: scrut³ -//│ deforest > D⁰ -> -//│ deforest > match: scrut³ //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut⁴ +//│ deforest > match: scrut³ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut⁴ +//│ deforest > match: scrut³ //│ deforest > C⁰ -> -//│ deforest > match: scrut⁴ -//│ deforest > BB⁰(0) -> //│ deforest > match: scrut³ +//│ deforest > BB⁰(0) -> +//│ deforest > match: scrut⁴ //│ deforest > D⁰ -> -//│ deforest > match: scrut³ -//│ deforest > AA⁰(0) -> //│ deforest > match: scrut⁴ +//│ deforest > AA⁰(0) -> +//│ deforest > match: scrut³ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut⁴ +//│ deforest > match: scrut³ //│ deforest > C⁰ -> +//│ deforest > match: scrut³ +//│ deforest > BB⁰(0) -> +//│ deforest > match: scrut⁴ +//│ deforest > D⁰ -> //│ deforest > match: scrut⁴ //│ deforest > <<< fusing <<< //│ = [D, AA(1)] @@ -944,20 +944,20 @@ fun f5(a5) = if a5 is AA(aa) then aa f1(p(1)) + f2(p(2)) + f3(p(3)) + f4(p(4)) + f5(p(5)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(x³⁶) -> -//│ deforest > match: a5⁰ -//│ deforest > fields: a5⁰.aa⁰ +//│ deforest > match: a1² +//│ deforest > fields: a1².aa⁰ //│ deforest > AA⁰(x³⁶) -> -//│ deforest > match: a4⁰ -//│ deforest > fields: a4⁰.aa⁰ +//│ deforest > match: a2² +//│ deforest > fields: a2².aa⁰ //│ deforest > AA⁰(x³⁶) -> //│ deforest > match: a3⁰ //│ deforest > fields: a3⁰.aa⁰ //│ deforest > AA⁰(x³⁶) -> -//│ deforest > match: a2² -//│ deforest > fields: a2².aa⁰ +//│ deforest > match: a4⁰ +//│ deforest > fields: a4⁰.aa⁰ //│ deforest > AA⁰(x³⁶) -> -//│ deforest > match: a1² -//│ deforest > fields: a1².aa⁰ +//│ deforest > match: a5⁰ +//│ deforest > fields: a5⁰.aa⁰ //│ deforest > <<< fusing <<< //│ = 15 @@ -985,17 +985,11 @@ fun f4(ls4) = if ls4 is f1(to(4)) + f2(to(5)) + f3(to(6)) + f4(to(7)) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(n⁰, tmp⁸) -> -//│ deforest > match: ls4⁰ -//│ deforest > fields: ls4⁰.h⁰ -//│ deforest > fields: ls4⁰.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ls4⁰ -//│ deforest > Cons⁰(n⁰, tmp⁸) -> -//│ deforest > match: ls3⁰ -//│ deforest > fields: ls3⁰.h⁰ -//│ deforest > fields: ls3⁰.t⁰ +//│ deforest > match: ls1⁰ +//│ deforest > fields: ls1⁰.h⁰ +//│ deforest > fields: ls1⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls3⁰ +//│ deforest > match: ls1⁰ //│ deforest > Cons⁰(n⁰, tmp⁸) -> //│ deforest > match: ls2⁰ //│ deforest > fields: ls2⁰.h⁰ @@ -1003,11 +997,17 @@ f1(to(4)) + f2(to(5)) + f3(to(6)) + f4(to(7)) //│ deforest > Nil⁰ -> //│ deforest > match: ls2⁰ //│ deforest > Cons⁰(n⁰, tmp⁸) -> -//│ deforest > match: ls1⁰ -//│ deforest > fields: ls1⁰.h⁰ -//│ deforest > fields: ls1⁰.t⁰ +//│ deforest > match: ls3⁰ +//│ deforest > fields: ls3⁰.h⁰ +//│ deforest > fields: ls3⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls1⁰ +//│ deforest > match: ls3⁰ +//│ deforest > Cons⁰(n⁰, tmp⁸) -> +//│ deforest > match: ls4⁰ +//│ deforest > fields: ls4⁰.h⁰ +//│ deforest > fields: ls4⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls4⁰ //│ deforest > <<< fusing <<< //│ = 88 @@ -1061,10 +1061,10 @@ fun f1(a) = if a is AA(i) then i fun f2(a) = if a is AA(i) then i + 1 f1(id(AA(1))) + f2(id(AA(2))) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(2) -> +//│ deforest > AA⁰(1) -> //│ deforest > match: a⁹ //│ deforest > fields: a⁹.aa⁰ -//│ deforest > AA⁰(1) -> +//│ deforest > AA⁰(2) -> //│ deforest > match: a¹⁰ //│ deforest > fields: a¹⁰.aa⁰ //│ deforest > <<< fusing <<< @@ -1081,10 +1081,10 @@ fun wrap(a) = a f1(wrap(p(2))) + f2(p(1)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(x³⁹) -> -//│ deforest > match: a2³ -//│ deforest > fields: a2³.aa⁰ -//│ deforest > AA⁰(x³⁹) -> //│ deforest > match: a1³ //│ deforest > fields: a1³.aa⁰ +//│ deforest > AA⁰(x³⁹) -> +//│ deforest > match: a2³ +//│ deforest > fields: a2³.aa⁰ //│ deforest > <<< fusing <<< //│ = 6 diff --git a/hkmc2/shared/src/test/mlscript/deforest/todos.mls b/hkmc2/shared/src/test/mlscript/deforest/todos.mls index 27108fc1ba..417ffdcb4b 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/todos.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/todos.mls @@ -35,8 +35,6 @@ fun f(a) = if a is let p = AA(AA(AA(10))) test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ deforest > >>> fusing >>> -//│ deforest > B⁰ -> -//│ deforest > match: x⁰ //│ deforest > AA⁰(tmp⁰) -> //│ deforest > match: x⁰ //│ deforest > fields: x⁰.x¹ @@ -46,23 +44,20 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ deforest > AA⁰(10) -> //│ deforest > match: arg$AA$0$¹ //│ deforest > fields: arg$AA$0$¹.x¹ +//│ deforest > B⁰ -> +//│ deforest > match: x⁰ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, test$x_B, test$x_rest, test$x_AA, test$x_rest1, test$arg$AA$0$_AA, test$arg$AA$0$_rest, test$arg$AA$0$_AA1, test$arg$AA$0$_rest1, test$test, test$test1, AA_x, AA_x1, AA_x2, deforest$lam; +//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, test$x_AA, test$x_rest, test$arg$AA$0$_AA, test$arg$AA$0$_rest, test$arg$AA$0$_AA1, test$arg$AA$0$_rest1, test$x_B, test$x_rest1, test$test, test$test1, AA_x, AA_x1, AA_x2, deforest$lam; //│ @private //│ define test$test as fun test$test⁰(x) { //│ let t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; -//│ return x(tmp11, t, tmp12, tmp13, tmp14) +//│ return x(arg$AA$0$, arg$AA$0$1, arg$AA$0$2, a, tmp11, t, tmp12, tmp13, tmp14) //│ }; //│ @private //│ define test$test1 as fun test$test¹(x) { //│ let t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; -//│ return x(arg$AA$0$, arg$AA$0$1, arg$AA$0$2, a, tmp11, t, tmp12, tmp13, tmp14) -//│ }; -//│ @affine(1) @private -//│ define test$x_B as fun test$x_B⁰()(fv_tmp, fv_t, fv_tmp1, fv_tmp2, fv_tmp3) { -//│ set fv_tmp = 4; -//│ return test$x_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) +//│ return x(tmp11, t, tmp12, tmp13, tmp14) //│ }; //│ @affine(1) @private //│ define test$x_AA as fun test$x_AA⁰(AA_x3)(fv_arg$AA$0$, fv_arg$AA$0$1, fv_arg$AA$0$2, fv_a, fv_tmp, fv_t, fv_tmp1, fv_tmp2, fv_tmp3) { @@ -81,16 +76,13 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ set fv_tmp = fv_a; //│ return test$arg$AA$0$_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) //│ }; -//│ @private -//│ define test$x_rest as fun test$x_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { -//│ set fv_t = fv_tmp; -//│ set fv_tmp1 = +⁰(fv_t, 20); -//│ set fv_tmp2 = -⁰(fv_tmp1, 3); -//│ set fv_tmp3 = +⁰(fv_tmp2, 2); -//│ return -⁰(fv_tmp3, 1) +//│ @affine(1) @private +//│ define test$x_B as fun test$x_B⁰()(fv_tmp, fv_t, fv_tmp1, fv_tmp2, fv_tmp3) { +//│ set fv_tmp = 4; +//│ return test$x_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) //│ }; //│ @private -//│ define test$x_rest1 as fun test$x_rest¹(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { +//│ define test$x_rest as fun test$x_rest¹(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { //│ set fv_t = fv_tmp; //│ set fv_tmp1 = +⁰(fv_t, 20); //│ set fv_tmp2 = -⁰(fv_tmp1, 3); @@ -105,6 +97,14 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ define test$arg$AA$0$_rest1 as fun test$arg$AA$0$_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { //│ return test$arg$AA$0$_rest¹(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) //│ }; +//│ @private +//│ define test$x_rest1 as fun test$x_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { +//│ set fv_t = fv_tmp; +//│ set fv_tmp1 = +⁰(fv_t, 20); +//│ set fv_tmp2 = -⁰(fv_tmp1, 3); +//│ set fv_tmp3 = +⁰(fv_tmp2, 2); +//│ return -⁰(fv_tmp3, 1) +//│ }; //│ define test⁰ as fun test¹(x) { //│ let t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; //│ match x @@ -163,10 +163,10 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ set tmp6 = test$arg$AA$0$_AA⁰(AA_x1); //│ set AA_x2 = tmp6; //│ set tmp7 = test$x_AA⁰(AA_x2); -//│ set tmp8 = test$test¹(tmp7); +//│ set tmp8 = test$test⁰(tmp7); //│ set tmp9 = +⁰(tmp4, tmp8); //│ set deforest$lam = test$x_B⁰(); -//│ set tmp10 = test$test⁰(deforest$lam); +//│ set tmp10 = test$test¹(deforest$lam); //│ return +⁰(tmp9, tmp10) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 78 diff --git a/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls b/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls index 8feafe7a0d..2da9136d30 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls @@ -19,30 +19,30 @@ 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) //│ deforest > >>> fusing >>> -//│ deforest > Nil⁰ -> -//│ deforest > match: ls_unzip⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ls_unzip⁰ -//│ deforest > Cons⁰(tmp⁰, tmp¹) -> -//│ deforest > match: ls_unzip⁰ -//│ deforest > fields: ls_unzip⁰.h⁰ -//│ deforest > fields: ls_unzip⁰.t⁰ //│ deforest > Pair⁰(x⁰, y⁰) -> //│ deforest > match: arg$Cons$0$⁰ //│ deforest > fields: arg$Cons$0$⁰.a⁰ //│ deforest > fields: arg$Cons$0$⁰.b⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ls_unzip⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ls_unzip⁰ //│ deforest > Cons⁰(tmp⁰, tmp¹) -> //│ deforest > match: ls_unzip⁰ //│ deforest > fields: ls_unzip⁰.h⁰ //│ deforest > fields: ls_unzip⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls_unzip⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls_unzip⁰ //│ deforest > Pair⁰(x⁰, y⁰) -> //│ deforest > match: arg$Cons$0$⁰ //│ deforest > fields: arg$Cons$0$⁰.a⁰ //│ deforest > fields: arg$Cons$0$⁰.b⁰ +//│ deforest > Cons⁰(tmp⁰, tmp¹) -> +//│ deforest > match: ls_unzip⁰ +//│ deforest > fields: ls_unzip⁰.h⁰ +//│ deforest > fields: ls_unzip⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls_unzip⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls_unzip⁰ //│ deforest > <<< fusing <<< //│ = Pair(Cons(1, Cons(2, Nil)), Cons(2, Cons(3, Nil))) @@ -60,30 +60,30 @@ fun testZipUnzip(n) = if unzip(id(makeZippedList(n))) is Pair(xs, ys) then zip(xs, ys) testZipUnzip(3) //│ deforest > >>> fusing >>> -//│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip⁰ //│ deforest > Cons⁰(a¹, atail⁰) -> //│ deforest > match: xs_zip⁰ //│ deforest > fields: xs_zip⁰.h⁰ //│ deforest > fields: xs_zip⁰.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip⁰ //│ deforest > Cons⁰(b¹, btail⁰) -> //│ deforest > match: ys_zip⁰ //│ deforest > fields: ys_zip⁰.h⁰ //│ deforest > fields: ys_zip⁰.t⁰ //│ deforest > Nil⁰ -> //│ deforest > match: xs_zip⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ys_zip⁰ //│ deforest > Cons⁰(a¹, atail⁰) -> //│ deforest > match: xs_zip⁰ //│ deforest > fields: xs_zip⁰.h⁰ //│ deforest > fields: xs_zip⁰.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip⁰ //│ deforest > Cons⁰(b¹, btail⁰) -> //│ deforest > match: ys_zip⁰ //│ deforest > fields: ys_zip⁰.h⁰ //│ deforest > fields: ys_zip⁰.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: xs_zip⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ys_zip⁰ //│ deforest > <<< fusing <<< //│ = Cons(Pair(3, 4), Cons(Pair(2, 3), Cons(Pair(1, 2), Nil))) @@ -106,54 +106,54 @@ fun testZipMapBothUnzip(n) = if unzip(id(makeZippedList(n))) is ) testZipMapBothUnzip(4) //│ deforest > >>> fusing >>> -//│ deforest > Nil⁰ -> -//│ deforest > match: ls_map⁰ //│ deforest > Cons⁰(a², atail¹) -> //│ deforest > match: ls_map⁰ //│ deforest > fields: ls_map⁰.h⁰ //│ deforest > fields: ls_map⁰.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ls_map⁰ //│ deforest > Cons⁰(b², btail¹) -> //│ deforest > match: ls_map⁰ //│ deforest > fields: ls_map⁰.h⁰ //│ deforest > fields: ls_map⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip¹ +//│ deforest > match: ls_map⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls_map⁰ //│ deforest > Cons⁰(tmp², tmp³) -> //│ deforest > match: xs_zip¹ //│ deforest > fields: xs_zip¹.h⁰ //│ deforest > fields: xs_zip¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip¹ +//│ deforest > match: xs_zip¹ //│ deforest > Cons⁰(tmp², tmp³) -> //│ deforest > match: ys_zip¹ //│ deforest > fields: ys_zip¹.h⁰ //│ deforest > fields: ys_zip¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls_map⁰ +//│ deforest > match: ys_zip¹ //│ deforest > Cons⁰(a², atail¹) -> //│ deforest > match: ls_map⁰ //│ deforest > fields: ls_map⁰.h⁰ //│ deforest > fields: ls_map⁰.t⁰ -//│ deforest > Nil⁰ -> -//│ deforest > match: ls_map⁰ //│ deforest > Cons⁰(b², btail¹) -> //│ deforest > match: ls_map⁰ //│ deforest > fields: ls_map⁰.h⁰ //│ deforest > fields: ls_map⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip¹ +//│ deforest > match: ls_map⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls_map⁰ //│ deforest > Cons⁰(tmp², tmp³) -> //│ deforest > match: xs_zip¹ //│ deforest > fields: xs_zip¹.h⁰ //│ deforest > fields: xs_zip¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip¹ +//│ deforest > match: xs_zip¹ //│ deforest > Cons⁰(tmp², tmp³) -> //│ deforest > match: ys_zip¹ //│ deforest > fields: ys_zip¹.h⁰ //│ deforest > fields: ys_zip¹.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ys_zip¹ //│ deforest > <<< fusing <<< //│ = Cons( //│ Pair(5, 25), diff --git a/hkmc2/shared/src/test/mlscript/effect-analysis/Basic.mls b/hkmc2/shared/src/test/mlscript/effect-analysis/Basic.mls index 8df5e3f1b0..df436e7d19 100644 --- a/hkmc2/shared/src/test/mlscript/effect-analysis/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/effect-analysis/Basic.mls @@ -18,8 +18,8 @@ fun node_pure(x) = leaf_pure(x) //│ flow-analysis > >>> effect-analysis results >>> //│ flow-analysis > function leaf_pure⁰#0 @ -> Pure //│ flow-analysis > function node_pure⁰#0 @ -> Pure -//│ flow-analysis > call +⁰@result¹#0 @ -> Pure -//│ flow-analysis > call leaf_pure⁰@result⁰#0 @ -> Pure +//│ flow-analysis > call +⁰@result⁰#0 @ -> Pure +//│ flow-analysis > call leaf_pure⁰@result¹#0 @ -> Pure //│ flow-analysis > <<< effect-analysis results <<< @@ -51,8 +51,8 @@ fun node_unsafe(x) = leaf_unsafe(x) //│ flow-analysis > >>> effect-analysis results >>> //│ flow-analysis > function leaf_unsafe⁰#0 @ -> UnsafeEta //│ flow-analysis > function node_unsafe⁰#0 @ -> UnsafeEta -//│ flow-analysis > call leaf_unsafe⁰@result⁰#0 @ -> UnsafeEta -//│ flow-analysis > call unsafe@result¹#0 @ -> UnsafeEta +//│ flow-analysis > call leaf_unsafe⁰@result¹#0 @ -> UnsafeEta +//│ flow-analysis > call unsafe@result⁰#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< @@ -62,8 +62,8 @@ fun node_unsafe(x) = leaf_unsafe(x) //│ flow-analysis > >>> effect-analysis results >>> //│ flow-analysis > function leaf_unsafe⁰#0 @ -> UnsafeEta //│ flow-analysis > function node_unsafe⁰#0 @ -> UnsafeEta -//│ flow-analysis > call leaf_unsafe⁰@result⁰#0 @ -> UnsafeEta -//│ flow-analysis > call print@result¹#0 @ -> UnsafeEta +//│ flow-analysis > call leaf_unsafe⁰@result¹#0 @ -> UnsafeEta +//│ flow-analysis > call print@result⁰#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< @@ -85,14 +85,14 @@ fun node() = //│ flow-analysis > function leaf_unsafe_private⁰#0 @ -> UnsafeEta //│ flow-analysis > function leaf_unsafe_public⁰#0 @ -> UnsafeEta //│ flow-analysis > function node⁰#0 @ -> UnsafeEta -//│ flow-analysis > call leaf_pure_private⁰@result²#0 @ -> Pure -//│ flow-analysis > call leaf_pure_public⁰@result³#0 @ -> UnsafeEta -//│ flow-analysis > call leaf_unsafe_private⁰@result⁰#0 @ -> UnsafeEta -//│ flow-analysis > call leaf_unsafe_public⁰@result¹#0 @ -> UnsafeEta -//│ flow-analysis > call x²@result⁶#0 @ -> Pure -//│ flow-analysis > call x³@result⁷#0 @ -> UnsafeEta -//│ flow-analysis > call x¹@result⁵#0 @ -> UnsafeEta -//│ flow-analysis > call x⁰@result⁴#0 @ -> UnsafeEta +//│ flow-analysis > call leaf_pure_private⁰@result⁵#0 @ -> Pure +//│ flow-analysis > call leaf_pure_public⁰@result⁴#0 @ -> UnsafeEta +//│ flow-analysis > call leaf_unsafe_private⁰@result⁷#0 @ -> UnsafeEta +//│ flow-analysis > call leaf_unsafe_public⁰@result⁶#0 @ -> UnsafeEta +//│ flow-analysis > call x²@result²#0 @ -> UnsafeEta +//│ flow-analysis > call x³@result³#0 @ -> UnsafeEta +//│ flow-analysis > call x¹@result¹#0 @ -> Pure +//│ flow-analysis > call x⁰@result⁰#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< // * Recursive calls propagate pure and unsafe. @@ -101,9 +101,9 @@ fun node_recursive_unsafe() = unsafe(); node_recursive_unsafe() //│ flow-analysis > >>> effect-analysis results >>> //│ flow-analysis > function node_recursive_pure⁰#0 @ -> Pure //│ flow-analysis > function node_recursive_unsafe⁰#0 @ -> UnsafeEta -//│ flow-analysis > call ,⁰@result⁰#0 @ -> Pure +//│ flow-analysis > call ,⁰@result²#0 @ -> Pure //│ flow-analysis > call node_recursive_unsafe⁰@result¹#0 @ -> UnsafeEta -//│ flow-analysis > call unsafe@result²#0 @ -> UnsafeEta +//│ flow-analysis > call unsafe@result⁰#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< @@ -113,8 +113,8 @@ fun node_pure() = leaf_pure() //│ flow-analysis > >>> effect-analysis results >>> //│ flow-analysis > function leaf_pure⁰#0 @ -> Pure //│ flow-analysis > function node_pure⁰#0 @ -> Pure -//│ flow-analysis > call leaf_pure⁰@result⁰#0 @ -> Pure -//│ flow-analysis > call unsafe@result¹#0 @ -> UnsafeEta +//│ flow-analysis > call leaf_pure⁰@result¹#0 @ -> Pure +//│ flow-analysis > call unsafe@result⁰#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< @@ -125,9 +125,9 @@ fun node_pure() = leaf_pure()() //│ flow-analysis > function leaf_pure⁰#0 @ -> Pure //│ flow-analysis > function leaf_pure⁰#1 @ -> Pure //│ flow-analysis > function node_pure⁰#0 @ -> Pure -//│ flow-analysis > call leaf_pure⁰@result⁰#0 @ -> Pure -//│ flow-analysis > call leaf_pure⁰@result⁰#1 @ -> Pure -//│ flow-analysis > call unsafe@result¹#0 @ -> UnsafeEta +//│ flow-analysis > call leaf_pure⁰@result¹#0 @ -> Pure +//│ flow-analysis > call leaf_pure⁰@result¹#1 @ -> Pure +//│ flow-analysis > call unsafe@result⁰#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< @@ -156,9 +156,9 @@ fun box_unsafe(box: Box) = box.unsafe() //│ flow-analysis > function box_instantiation⁰#0 @ -> UnsafeEta //│ flow-analysis > function box_pure⁰#0 @ -> UnsafeEta //│ flow-analysis > function box_unsafe⁰#0 @ -> UnsafeEta -//│ flow-analysis > call pure@result²#0 @ -> UnsafeEta +//│ flow-analysis > call pure@result⁰#0 @ -> UnsafeEta +//│ flow-analysis > call unsafe@result²#0 @ -> UnsafeEta //│ flow-analysis > call unsafe@result¹#0 @ -> UnsafeEta -//│ flow-analysis > call unsafe@result⁰#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< @@ -192,8 +192,8 @@ fun node_pure_4(xs) = leaf_pure_2(1, 2, 3) //│ flow-analysis > function node_pure_3⁰#0 @ -> Pure //│ flow-analysis > function node_pure_4⁰#0 @ -> Pure //│ flow-analysis > call leaf_pure_1⁰@result¹#0 @ -> Pure -//│ flow-analysis > call leaf_pure_2⁰@result⁰#0 @ -> Pure -//│ flow-analysis > call unsafe@result²#0 @ -> UnsafeEta +//│ flow-analysis > call leaf_pure_2⁰@result²#0 @ -> Pure +//│ flow-analysis > call unsafe@result⁰#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< @@ -230,30 +230,30 @@ fun node() = leaf_unsafe_public(unsafe) leaf_unsafe_private(unsafe) //│ flow-analysis > >>> effect-analysis results >>> -//│ flow-analysis > function answer⁰#0 @ answer³ -> Pure -//│ flow-analysis > function answer⁰#0 @ node⁰.answer² -> Pure -//│ flow-analysis > function answer⁰#0 @ node⁰.answer¹ -> Pure -//│ flow-analysis > function leaf_pure_private⁰#0 @ leaf_pure_private² -> UnsafeEta -//│ flow-analysis > function leaf_pure_private⁰#0 @ node⁰.leaf_pure_private¹ -> Pure -//│ flow-analysis > function leaf_pure_public⁰#0 @ leaf_pure_public² -> UnsafeEta -//│ flow-analysis > function leaf_pure_public⁰#0 @ node⁰.leaf_pure_public¹ -> Pure -//│ flow-analysis > function leaf_unsafe_private⁰#0 @ leaf_unsafe_private² -> UnsafeEta -//│ flow-analysis > function leaf_unsafe_private⁰#0 @ node⁰.leaf_unsafe_private¹ -> UnsafeEta -//│ flow-analysis > function leaf_unsafe_public⁰#0 @ leaf_unsafe_public² -> UnsafeEta -//│ flow-analysis > function leaf_unsafe_public⁰#0 @ node⁰.leaf_unsafe_public¹ -> UnsafeEta -//│ flow-analysis > function node¹#0 @ node⁰ -> UnsafeEta -//│ flow-analysis > call leaf_pure_private⁰@result³#0 @ node⁰ -> Pure -//│ flow-analysis > call leaf_pure_public⁰@result¹#0 @ node⁰ -> Pure -//│ flow-analysis > call leaf_unsafe_private⁰@result⁷#0 @ node⁰ -> UnsafeEta -//│ flow-analysis > call leaf_unsafe_public⁰@result⁵#0 @ node⁰ -> UnsafeEta -//│ flow-analysis > call x²@result⁴#0 @ leaf_unsafe_public² -> UnsafeEta -//│ flow-analysis > call x²@result⁴#0 @ node⁰.leaf_unsafe_public¹ -> UnsafeEta -//│ flow-analysis > call x³@result⁶#0 @ leaf_unsafe_private² -> UnsafeEta -//│ flow-analysis > call x³@result⁶#0 @ node⁰.leaf_unsafe_private¹ -> UnsafeEta -//│ flow-analysis > call x¹@result²#0 @ leaf_pure_private² -> UnsafeEta -//│ flow-analysis > call x¹@result²#0 @ node⁰.leaf_pure_private¹ -> Pure -//│ flow-analysis > call x⁰@result⁰#0 @ leaf_pure_public² -> UnsafeEta -//│ flow-analysis > call x⁰@result⁰#0 @ node⁰.leaf_pure_public¹ -> Pure +//│ flow-analysis > function answer⁰#0 @ answer¹ -> Pure +//│ flow-analysis > function answer⁰#0 @ node¹.answer² -> Pure +//│ flow-analysis > function answer⁰#0 @ node¹.answer³ -> Pure +//│ flow-analysis > function leaf_pure_private⁰#0 @ leaf_pure_private¹ -> UnsafeEta +//│ flow-analysis > function leaf_pure_private⁰#0 @ node¹.leaf_pure_private² -> Pure +//│ flow-analysis > function leaf_pure_public⁰#0 @ leaf_pure_public¹ -> UnsafeEta +//│ flow-analysis > function leaf_pure_public⁰#0 @ node¹.leaf_pure_public² -> Pure +//│ flow-analysis > function leaf_unsafe_private⁰#0 @ leaf_unsafe_private¹ -> UnsafeEta +//│ flow-analysis > function leaf_unsafe_private⁰#0 @ node¹.leaf_unsafe_private² -> UnsafeEta +//│ flow-analysis > function leaf_unsafe_public⁰#0 @ leaf_unsafe_public¹ -> UnsafeEta +//│ flow-analysis > function leaf_unsafe_public⁰#0 @ node¹.leaf_unsafe_public² -> UnsafeEta +//│ flow-analysis > function node⁰#0 @ node¹ -> UnsafeEta +//│ flow-analysis > call leaf_pure_private⁰@result⁵#0 @ node¹ -> Pure +//│ flow-analysis > call leaf_pure_public⁰@result⁴#0 @ node¹ -> Pure +//│ flow-analysis > call leaf_unsafe_private⁰@result⁷#0 @ node¹ -> UnsafeEta +//│ flow-analysis > call leaf_unsafe_public⁰@result⁶#0 @ node¹ -> UnsafeEta +//│ flow-analysis > call x²@result²#0 @ leaf_unsafe_public¹ -> UnsafeEta +//│ flow-analysis > call x²@result²#0 @ node¹.leaf_unsafe_public² -> UnsafeEta +//│ flow-analysis > call x³@result³#0 @ leaf_unsafe_private¹ -> UnsafeEta +//│ flow-analysis > call x³@result³#0 @ node¹.leaf_unsafe_private² -> UnsafeEta +//│ flow-analysis > call x¹@result¹#0 @ leaf_pure_private¹ -> UnsafeEta +//│ flow-analysis > call x¹@result¹#0 @ node¹.leaf_pure_private² -> Pure +//│ flow-analysis > call x⁰@result⁰#0 @ leaf_pure_public¹ -> UnsafeEta +//│ flow-analysis > call x⁰@result⁰#0 @ node¹.leaf_pure_public² -> Pure //│ flow-analysis > <<< effect-analysis results <<< @@ -267,6 +267,6 @@ fun node_unsafe() = M.leaf_unsafe //│ flow-analysis > >>> effect-analysis results >>> //│ flow-analysis > function node_pure⁰#0 @ node_pure¹ -> UnsafeEta //│ flow-analysis > function node_unsafe⁰#0 @ node_unsafe¹ -> UnsafeEta -//│ flow-analysis > call unsafe@result⁰#0 @ leaf_unsafe⁰ -> UnsafeEta -//│ flow-analysis > call unsafe@result⁰#0 @ node_unsafe¹.leaf_unsafe¹ -> UnsafeEta +//│ flow-analysis > call unsafe@result⁰#0 @ leaf_unsafe¹ -> UnsafeEta +//│ flow-analysis > call unsafe@result⁰#0 @ node_unsafe¹.leaf_unsafe⁰ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< diff --git a/hkmc2/shared/src/test/mlscript/effect-analysis/Imports.mls b/hkmc2/shared/src/test/mlscript/effect-analysis/Imports.mls index a3ef085f77..7204961b4a 100644 --- a/hkmc2/shared/src/test/mlscript/effect-analysis/Imports.mls +++ b/hkmc2/shared/src/test/mlscript/effect-analysis/Imports.mls @@ -29,9 +29,9 @@ fun pure_callback(x) = invoke(y => y, x) //│ flow-analysis > function invoke⁰#0 @ -> UnsafeEta //│ flow-analysis > function lambda⁰#0 @ -> Pure //│ flow-analysis > function pure_callback⁰#0 @ -> UnsafeEta -//│ flow-analysis > call f⁰@result²#0 @ -> UnsafeEta +//│ flow-analysis > call f⁰@result⁰#0 @ -> UnsafeEta +//│ flow-analysis > call invoke⁰@result²#0 @ -> UnsafeEta //│ flow-analysis > call invoke⁰@result¹#0 @ -> UnsafeEta -//│ flow-analysis > call invoke⁰@result⁰#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< @@ -44,14 +44,14 @@ fun leaf(x) = print(x) fun node(x) = leaf(x) fun qualified(x) = Predef.print(x) //│ flow-analysis > >>> effect-analysis results >>> -//│ flow-analysis > function leaf⁰#0 @ leaf² -> UnsafeEta -//│ flow-analysis > function leaf⁰#0 @ node⁰.leaf¹ -> UnsafeEta -//│ flow-analysis > function node¹#0 @ node⁰ -> UnsafeEta +//│ flow-analysis > function leaf⁰#0 @ leaf¹ -> UnsafeEta +//│ flow-analysis > function leaf⁰#0 @ node¹.leaf² -> UnsafeEta +//│ flow-analysis > function node⁰#0 @ node¹ -> UnsafeEta //│ flow-analysis > function qualified⁰#0 @ qualified¹ -> UnsafeEta -//│ flow-analysis > call leaf⁰@result²#0 @ node⁰ -> UnsafeEta -//│ flow-analysis > call print@result¹#0 @ leaf² -> UnsafeEta -//│ flow-analysis > call print@result¹#0 @ node⁰.leaf¹ -> UnsafeEta -//│ flow-analysis > call print@result⁰#0 @ qualified¹ -> UnsafeEta +//│ flow-analysis > call leaf⁰@result¹#0 @ node¹ -> UnsafeEta +//│ flow-analysis > call print@result²#0 @ qualified¹ -> UnsafeEta +//│ flow-analysis > call print@result⁰#0 @ leaf¹ -> UnsafeEta +//│ flow-analysis > call print@result⁰#0 @ node¹.leaf² -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< @@ -60,16 +60,16 @@ private fun invoke(f, x) = f(x) fun indirect(x) = invoke(print, x) fun pure_callback(x) = invoke(y => y, x) //│ flow-analysis > >>> effect-analysis results >>> -//│ flow-analysis > function indirect¹#0 @ indirect⁰ -> UnsafeEta -//│ flow-analysis > function invoke⁰#0 @ indirect⁰.invoke² -> UnsafeEta -//│ flow-analysis > function invoke⁰#0 @ invoke³ -> UnsafeEta -//│ flow-analysis > function invoke⁰#0 @ pure_callback⁰.invoke¹ -> Pure -//│ flow-analysis > function lambda⁰#0 @ lambda² -> Pure -//│ flow-analysis > function lambda⁰#0 @ pure_callback⁰.lambda¹ -> Pure -//│ flow-analysis > function pure_callback¹#0 @ pure_callback⁰ -> Pure -//│ flow-analysis > call f⁰@result⁰#0 @ indirect⁰.invoke² -> UnsafeEta -//│ flow-analysis > call f⁰@result⁰#0 @ invoke³ -> UnsafeEta -//│ flow-analysis > call f⁰@result⁰#0 @ pure_callback⁰.invoke¹ -> Pure -//│ flow-analysis > call invoke⁰@result²#0 @ indirect⁰ -> UnsafeEta -//│ flow-analysis > call invoke⁰@result¹#0 @ pure_callback⁰ -> Pure +//│ flow-analysis > function indirect⁰#0 @ indirect¹ -> UnsafeEta +//│ flow-analysis > function invoke⁰#0 @ indirect¹.invoke² -> UnsafeEta +//│ flow-analysis > function invoke⁰#0 @ invoke¹ -> UnsafeEta +//│ flow-analysis > function invoke⁰#0 @ pure_callback¹.invoke³ -> Pure +//│ flow-analysis > function lambda⁰#0 @ lambda¹ -> Pure +//│ flow-analysis > function lambda⁰#0 @ pure_callback¹.lambda² -> Pure +//│ flow-analysis > function pure_callback⁰#0 @ pure_callback¹ -> Pure +//│ flow-analysis > call f⁰@result⁰#0 @ indirect¹.invoke² -> UnsafeEta +//│ flow-analysis > call f⁰@result⁰#0 @ invoke¹ -> UnsafeEta +//│ flow-analysis > call f⁰@result⁰#0 @ pure_callback¹.invoke³ -> Pure +//│ flow-analysis > call invoke⁰@result²#0 @ pure_callback¹ -> Pure +//│ flow-analysis > call invoke⁰@result¹#0 @ indirect¹ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< diff --git a/hkmc2/shared/src/test/mlscript/effect-analysis/Mutation.mls b/hkmc2/shared/src/test/mlscript/effect-analysis/Mutation.mls index e2e9be3a36..74fd7f638e 100644 --- a/hkmc2/shared/src/test/mlscript/effect-analysis/Mutation.mls +++ b/hkmc2/shared/src/test/mlscript/effect-analysis/Mutation.mls @@ -45,8 +45,8 @@ fun mutate_captured() = //│ flow-analysis > function lambda$⁰#1 @ -> UnsafeEta //│ flow-analysis > function lambda⁰#0 @ -> UnsafeEta //│ flow-analysis > function mutate_captured⁰#0 @ -> UnsafeEta -//│ flow-analysis > call lambda$⁰@result⁰#0 @ -> Pure -//│ flow-analysis > call lambda⁰@result¹#0 @ -> UnsafeEta +//│ flow-analysis > call lambda$⁰@result¹#0 @ -> Pure +//│ flow-analysis > call lambda⁰@result⁰#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< @@ -137,8 +137,8 @@ fun observe_captured() = //│ flow-analysis > function lambda$⁰#1 @ -> Pure //│ flow-analysis > function lambda⁰#0 @ -> Pure //│ flow-analysis > function observe_captured⁰#0 @ -> Pure -//│ flow-analysis > call lambda$⁰@result⁰#0 @ -> Pure -//│ flow-analysis > call lambda⁰@result¹#0 @ -> Pure +//│ flow-analysis > call lambda$⁰@result¹#0 @ -> Pure +//│ flow-analysis > call lambda⁰@result⁰#0 @ -> Pure //│ flow-analysis > <<< effect-analysis results <<< // * Observing a mutable state is an effect. @@ -245,10 +245,10 @@ fun shared_captures() = //│ flow-analysis > function observe$⁰#1 @ -> UnsafeEta //│ flow-analysis > function observe⁰#0 @ -> UnsafeEta //│ flow-analysis > function shared_captures⁰#0 @ -> UnsafeEta -//│ flow-analysis > call mutate$⁰@result⁰#0 @ -> Pure -//│ flow-analysis > call mutate⁰@result²#0 @ -> UnsafeEta -//│ flow-analysis > call observe$⁰@result¹#0 @ -> Pure -//│ flow-analysis > call observe⁰@result³#0 @ -> UnsafeEta +//│ flow-analysis > call mutate$⁰@result³#0 @ -> Pure +//│ flow-analysis > call mutate⁰@result¹#0 @ -> UnsafeEta +//│ flow-analysis > call observe$⁰@result²#0 @ -> Pure +//│ flow-analysis > call observe⁰@result⁰#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< diff --git a/hkmc2/shared/src/test/mlscript/effect-analysis/Regression.mls b/hkmc2/shared/src/test/mlscript/effect-analysis/Regression.mls index 671c6f313d..bafe96a4be 100644 --- a/hkmc2/shared/src/test/mlscript/effect-analysis/Regression.mls +++ b/hkmc2/shared/src/test/mlscript/effect-analysis/Regression.mls @@ -38,21 +38,21 @@ len(flatten(id((1 :: 2 :: Nil) :: (3 :: Nil) :: Nil))) //│ flow-analysis > function len$ls_Nil⁰#1 @ -> Pure //│ flow-analysis > function len$ls_rest⁰#0 @ -> Pure //│ flow-analysis > function len⁰#0 @ -> UnsafeEta -//│ flow-analysis > call +⁰@result³#0 @ -> Pure -//│ flow-analysis > call +⁰@result⁸#0 @ -> Pure -//│ flow-analysis > call append⁰@result⁵#0 @ -> UnsafeEta -//│ flow-analysis > call append⁰@result⁷#0 @ -> UnsafeEta +//│ flow-analysis > call +⁰@result¹²#0 @ -> Pure +//│ flow-analysis > call +⁰@result⁷#0 @ -> Pure +//│ flow-analysis > call append⁰@result¹⁰#0 @ -> UnsafeEta +//│ flow-analysis > call append⁰@result⁸#0 @ -> UnsafeEta //│ flow-analysis > call flatten$flatten⁰@result¹#0 @ -> UnsafeEta //│ flow-analysis > call flatten$flatten⁰@result¹⁴#0 @ -> UnsafeEta -//│ flow-analysis > call flatten_append$append⁰@result¹³#0 @ -> UnsafeEta -//│ flow-analysis > call flatten_append$append⁰@result¹¹#0 @ -> UnsafeEta -//│ flow-analysis > call flatten⁰@result⁶#0 @ -> UnsafeEta -//│ flow-analysis > call id@result²#0 @ -> UnsafeEta -//│ flow-analysis > call len$len⁰@result⁰#0 @ -> Pure -//│ flow-analysis > call len$len⁰@result⁹#0 @ -> Pure -//│ flow-analysis > call len$ls_Cons⁰@result¹⁰#0 @ -> Pure -//│ flow-analysis > call len$ls_Nil⁰@result¹⁵#0 @ -> Pure -//│ flow-analysis > call len⁰@result⁴#0 @ -> UnsafeEta -//│ flow-analysis > call ls⁰@result¹²#0 @ -> Pure +//│ flow-analysis > call flatten_append$append⁰@result²#0 @ -> UnsafeEta +//│ flow-analysis > call flatten_append$append⁰@result⁴#0 @ -> UnsafeEta +//│ flow-analysis > call flatten⁰@result⁹#0 @ -> UnsafeEta +//│ flow-analysis > call id@result¹³#0 @ -> UnsafeEta +//│ flow-analysis > call len$len⁰@result¹⁵#0 @ -> Pure +//│ flow-analysis > call len$len⁰@result⁶#0 @ -> Pure +//│ flow-analysis > call len$ls_Cons⁰@result⁵#0 @ -> Pure +//│ flow-analysis > call len$ls_Nil⁰@result⁰#0 @ -> Pure +//│ flow-analysis > call len⁰@result¹¹#0 @ -> UnsafeEta +//│ flow-analysis > call ls⁰@result³#0 @ -> Pure //│ flow-analysis > <<< effect-analysis results <<< //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/opt/EtaEffects.mls b/hkmc2/shared/src/test/mlscript/opt/EtaEffects.mls index 808b311afe..2dc08d6eb7 100644 --- a/hkmc2/shared/src/test/mlscript/opt/EtaEffects.mls +++ b/hkmc2/shared/src/test/mlscript/opt/EtaEffects.mls @@ -31,9 +31,9 @@ node()() //│ flow-analysis > function leaf⁰#0 @ -> Pure //│ flow-analysis > function node⁰#0 @ -> Pure //│ flow-analysis > function pure⁰#0 @ -> Pure -//│ flow-analysis > call baseCall⁰@result⁰#0 @ -> Pure +//│ flow-analysis > call baseCall⁰@result²#0 @ -> Pure //│ flow-analysis > call node⁰@result¹#0 @ -> Pure -//│ flow-analysis > call pure⁰@result²#0 @ -> Pure +//│ flow-analysis > call pure⁰@result⁰#0 @ -> Pure //│ flow-analysis > <<< effect-analysis results <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > node⁰#0 @ : [0, 0] @@ -51,8 +51,8 @@ node()() //│ flow-analysis > >>> effect-analysis results >>> //│ flow-analysis > function leaf⁰#0 @ -> Pure //│ flow-analysis > function node⁰#0 @ -> UnsafeEta -//│ flow-analysis > call baseCall⁰@result⁰#0 @ -> Pure -//│ flow-analysis > call effect⁰@result²#0 @ -> UnsafeEta +//│ flow-analysis > call baseCall⁰@result²#0 @ -> Pure +//│ flow-analysis > call effect⁰@result⁰#0 @ -> UnsafeEta //│ flow-analysis > call node⁰@result¹#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> @@ -75,10 +75,10 @@ node()()() //│ flow-analysis > function leaf_2⁰#0 @ -> Pure //│ flow-analysis > function node⁰#0 @ -> Pure //│ flow-analysis > function pure⁰#0 @ -> Pure -//│ flow-analysis > call baseCall⁰@result¹#0 @ -> Pure -//│ flow-analysis > call callPrefix⁰@result⁰#0 @ -> Pure -//│ flow-analysis > call node⁰@result²#0 @ -> Pure -//│ flow-analysis > call pure⁰@result³#0 @ -> Pure +//│ flow-analysis > call baseCall⁰@result²#0 @ -> Pure +//│ flow-analysis > call callPrefix⁰@result³#0 @ -> Pure +//│ flow-analysis > call node⁰@result¹#0 @ -> Pure +//│ flow-analysis > call pure⁰@result⁰#0 @ -> Pure //│ flow-analysis > <<< effect-analysis results <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > node⁰#0 @ : [0, 0, 0] @@ -99,10 +99,10 @@ node()()() //│ flow-analysis > function leaf_1⁰#0 @ -> UnsafeEta //│ flow-analysis > function leaf_2⁰#0 @ -> Pure //│ flow-analysis > function node⁰#0 @ -> Pure -//│ flow-analysis > call baseCall⁰@result¹#0 @ -> UnsafeEta -//│ flow-analysis > call callPrefix⁰@result⁰#0 @ -> Pure -//│ flow-analysis > call effect⁰@result³#0 @ -> UnsafeEta -//│ flow-analysis > call node⁰@result²#0 @ -> Pure +//│ flow-analysis > call baseCall⁰@result²#0 @ -> UnsafeEta +//│ flow-analysis > call callPrefix⁰@result³#0 @ -> Pure +//│ flow-analysis > call effect⁰@result⁰#0 @ -> UnsafeEta +//│ flow-analysis > call node⁰@result¹#0 @ -> Pure //│ flow-analysis > <<< effect-analysis results <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > node⁰#0 @ : [0, 0] diff --git a/hkmc2/shared/src/test/mlscript/opt/EtaExpansion.mls b/hkmc2/shared/src/test/mlscript/opt/EtaExpansion.mls index 0ba51102bf..87193532a9 100644 --- a/hkmc2/shared/src/test/mlscript/opt/EtaExpansion.mls +++ b/hkmc2/shared/src/test/mlscript/opt/EtaExpansion.mls @@ -260,8 +260,8 @@ app(returnRest2(0)) //│ eta-expansion > returnRest2⁰#0 @ : [1, 1+rest] //│ eta-expansion > <<< eta-expansion targets shapes <<< //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > returnRest2⁰#0 @ -> eliminable: {0} //│ dead-param-elim > rest2⁰#0 @ -> eliminable: {0} +//│ dead-param-elim > returnRest2⁰#0 @ -> eliminable: {0} //│ dead-param-elim > rest2⁰#1 @ -> eliminable: {0} //│ dead-param-elim > <<< dead-param-elim results <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— @@ -370,11 +370,7 @@ fun f(k, x) = h(2) f(k1, 1) + f(k2, 5) //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let a, b, tmp, tmp1, f_a_b$b, f_a$a, f$f; -//│ @private -//│ define f_a_b$b as fun f_a_b$b⁰(k, y)(eta$0$0) { -//│ return k(y)(eta$0$0) -//│ }; +//│ let a, b, tmp, tmp1, f_a$a, f$f, f_a_b$b; //│ @private //│ define f_a$a as fun f_a$a⁰(k, y)(eta$0$0) { //│ return f_a_b$b⁰(k, y)(eta$0$0) @@ -383,6 +379,10 @@ f(k1, 1) + f(k2, 5) //│ define f$f as fun f$f⁰(k, x) { //│ return f_a$a⁰(k, x)(2) //│ }; +//│ @private +//│ define f_a_b$b as fun f_a_b$b⁰(k, y)(eta$0$0) { +//│ return k(y)(eta$0$0) +//│ }; //│ define a as fun a⁰(k, y) { //│ return b⁰(k, y) //│ }; diff --git a/hkmc2/shared/src/test/mlscript/slop/CallableSignatures.mls b/hkmc2/shared/src/test/mlscript/slop/CallableSignatures.mls index c56a7544ab..eae0dd3bb1 100644 --- a/hkmc2/shared/src/test/mlscript/slop/CallableSignatures.mls +++ b/hkmc2/shared/src/test/mlscript/slop/CallableSignatures.mls @@ -141,21 +141,21 @@ fun capturedGenerator(value: Int) = generate("unused", 2) capturedGenerator(40).next().value //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let tmp: ?, tmp1: ?, capturedGenerator_generate$generate, capturedGenerator_generate$generate1, capturedGenerator$capturedGenerator; +//│ let tmp: ?, tmp1: ?, capturedGenerator_generate$generate, capturedGenerator$capturedGenerator, capturedGenerator_generate$generate1; //│ @private @generator //│ define capturedGenerator_generate$generate as fun capturedGenerator_generate$generate⁰(value: Int⁰, n: Int⁰): generator Int⁰ { //│ return +⁰(value, n) //│ }; +//│ @private +//│ define capturedGenerator$capturedGenerator as fun capturedGenerator$capturedGenerator⁰(value: Int⁰): ? { +//│ return capturedGenerator_generate$generate⁰(value, 2) +//│ }; //│ @private @generator //│ define capturedGenerator_generate$generate1 as fun capturedGenerator_generate$generate¹(value: Int⁰, n: Int⁰): generator Int⁰ { //│ return +⁰(value, n) //│ }; -//│ @private -//│ define capturedGenerator$capturedGenerator as fun capturedGenerator$capturedGenerator⁰(value: Int⁰): ? { -//│ return capturedGenerator_generate$generate¹(value, 2) -//│ }; //│ define capturedGenerator⁰ as fun capturedGenerator¹(value: Int⁰): ? { -//│ return capturedGenerator_generate$generate⁰(value, 2) +//│ return capturedGenerator_generate$generate¹(value, 2) //│ }; //│ set tmp = capturedGenerator$capturedGenerator⁰(40); //│ set tmp1 = tmp.next⁰(); diff --git a/hkmc2/shared/src/test/mlscript/slop/ConstructorEffects.mls b/hkmc2/shared/src/test/mlscript/slop/ConstructorEffects.mls index 3e074e4f97..44c8a03507 100644 --- a/hkmc2/shared/src/test/mlscript/slop/ConstructorEffects.mls +++ b/hkmc2/shared/src/test/mlscript/slop/ConstructorEffects.mls @@ -50,10 +50,10 @@ fun callback(run: () -> Int) = Callback(run) //│ flow-analysis > function observed⁰#0 @ -> UnsafeEta //│ flow-analysis > function update⁰#0 @ -> UnsafeEta //│ flow-analysis > function written⁰#0 @ -> UnsafeEta -//│ flow-analysis > call +⁰@result³#0 @ -> Pure -//│ flow-analysis > call +⁰@result¹#0 @ -> Pure -//│ flow-analysis > call run@result⁰#0 @ -> UnsafeEta -//│ flow-analysis > call update⁰@result²#0 @ -> UnsafeEta +//│ flow-analysis > call +⁰@result²#0 @ -> Pure +//│ flow-analysis > call +⁰@result⁰#0 @ -> Pure +//│ flow-analysis > call run@result³#0 @ -> UnsafeEta +//│ flow-analysis > call update⁰@result¹#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< //│ state = 0 @@ -75,8 +75,8 @@ class Child() extends Parent() fun inherited() = Child() //│ flow-analysis > >>> effect-analysis results >>> //│ flow-analysis > function inherited⁰#0 @ -> UnsafeEta -//│ flow-analysis > call print@result¹#0 @ -> UnsafeEta -//│ flow-analysis > call super⁰@result⁰#0 @ -> UnsafeEta +//│ flow-analysis > call print@result⁰#0 @ -> UnsafeEta +//│ flow-analysis > call super⁰@result¹#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< @@ -95,10 +95,10 @@ val saved = make() //│ flow-analysis > function keep⁰#0 @ -> Pure //│ flow-analysis > function keep⁰#1 @ -> UnsafeEta //│ flow-analysis > function make⁰#0 @ -> UnsafeEta -//│ flow-analysis > call +⁰@result²#0 @ -> Pure -//│ flow-analysis > call keep⁰@result³#0 @ -> Pure -//│ flow-analysis > call make⁰@result¹#0 @ -> UnsafeEta -//│ flow-analysis > call saved⁰@result⁰#0 @ -> UnsafeEta +//│ flow-analysis > call +⁰@result¹#0 @ -> Pure +//│ flow-analysis > call keep⁰@result⁰#0 @ -> Pure +//│ flow-analysis > call make⁰@result²#0 @ -> UnsafeEta +//│ flow-analysis > call saved⁰@result³#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< //│ = [1, 7] //│ calls = 1 @@ -118,8 +118,8 @@ saved_snapshot() //│ flow-analysis > function keep_snapshot⁰#0 @ -> Pure //│ flow-analysis > function keep_snapshot⁰#1 @ -> UnsafeEta //│ flow-analysis > function snapshot⁰#0 @ -> UnsafeEta -//│ flow-analysis > call keep_snapshot⁰@result²#0 @ -> Pure -//│ flow-analysis > call saved_snapshot⁰@result⁰#0 @ -> UnsafeEta +//│ flow-analysis > call keep_snapshot⁰@result⁰#0 @ -> Pure +//│ flow-analysis > call saved_snapshot⁰@result²#0 @ -> UnsafeEta //│ flow-analysis > call snapshot⁰@result¹#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< //│ = 41 @@ -142,10 +142,10 @@ build(3).value //│ flow-analysis > function build⁰#0 @ build² -> UnsafeEta //│ flow-analysis > function build⁰#0 @ build³ -> UnsafeEta //│ flow-analysis > function build⁰#0 @ build¹ -> UnsafeEta -//│ flow-analysis > call -⁰@result²#0 @ -> Pure -//│ flow-analysis > call >⁰@result³#0 @ -> Pure -//│ flow-analysis > call build⁰@result¹#0 @ -> UnsafeEta -//│ flow-analysis > call build⁰@result⁰#0 @ -> UnsafeEta +//│ flow-analysis > call -⁰@result¹#0 @ -> Pure +//│ flow-analysis > call >⁰@result⁰#0 @ -> Pure +//│ flow-analysis > call build⁰@result²#0 @ -> UnsafeEta +//│ flow-analysis > call build⁰@result³#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< //│ = 3 @@ -159,10 +159,10 @@ fun build_effectful(n) = RecursiveEffect(n) //│ flow-analysis > >>> effect-analysis results >>> //│ flow-analysis > function build_effectful⁰#0 @ build_effectful² -> UnsafeEta //│ flow-analysis > function build_effectful⁰#0 @ build_effectful¹ -> UnsafeEta -//│ flow-analysis > call +⁰@result³#0 @ -> Pure -//│ flow-analysis > call -⁰@result¹#0 @ -> Pure -//│ flow-analysis > call >⁰@result²#0 @ -> Pure -//│ flow-analysis > call build_effectful⁰@result⁰#0 @ -> UnsafeEta +//│ flow-analysis > call +⁰@result⁰#0 @ -> Pure +//│ flow-analysis > call -⁰@result²#0 @ -> Pure +//│ flow-analysis > call >⁰@result¹#0 @ -> Pure +//│ flow-analysis > call build_effectful⁰@result³#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< //│ recursive_effects = 0 diff --git a/hkmc2/shared/src/test/mlscript/slop/ConstructorSafety.mls b/hkmc2/shared/src/test/mlscript/slop/ConstructorSafety.mls index 1dfdb01c9f..c255049cf4 100644 --- a/hkmc2/shared/src/test/mlscript/slop/ConstructorSafety.mls +++ b/hkmc2/shared/src/test/mlscript/slop/ConstructorSafety.mls @@ -33,12 +33,12 @@ data class Optional(value?) private fun consume(x) = x.Optional#value is null [consume(Optional()), consume(Optional(()))] //│ deforest > >>> fusing >>> +//│ deforest > Optional⁰(null) -> +//│ deforest > select: value //│ deforest > Optional⁰(optionalArg⁰) -> //│ deforest > select: value //│ deforest > runtime⁰.Unit⁰ -> //│ deforest > match: scrut⁰ -//│ deforest > Optional⁰(null) -> -//│ deforest > select: value //│ deforest > <<< fusing <<< //│ = [true, false] @@ -76,10 +76,10 @@ data class Other private fun consume(x) = if x is Other then 1 else 3 [consume(One(7)), consume(Two(8, 9))] //│ deforest > >>> fusing >>> -//│ deforest > Two⁰(8, 9) -> -//│ deforest > match: x¹ //│ deforest > One⁰(7) -> //│ deforest > match: x¹ +//│ deforest > Two⁰(8, 9) -> +//│ deforest > match: x¹ //│ deforest > <<< fusing <<< //│ = [3, 3] diff --git a/hkmc2/shared/src/test/mlscript/slop/DeforestBindingMutability.mls b/hkmc2/shared/src/test/mlscript/slop/DeforestBindingMutability.mls index d93e84cfd8..3b47466d57 100644 --- a/hkmc2/shared/src/test/mlscript/slop/DeforestBindingMutability.mls +++ b/hkmc2/shared/src/test/mlscript/slop/DeforestBindingMutability.mls @@ -51,7 +51,7 @@ updateInitialized(40) //│ return box(arg$MutableInputBox$0$, value, tmp, current) //│ }; //│ @affine(1) @private -//│ define updateInitialized$box_MutableInputBox as fun updateInitialized$box_MutableInputBox¹(MutableInputBox_value)(fv_arg$MutableInputBox$0$, fv_value, fv_tmp, fv_current) { +//│ define updateInitialized$box_MutableInputBox as fun updateInitialized$box_MutableInputBox⁰(MutableInputBox_value)(fv_arg$MutableInputBox$0$, fv_value, fv_tmp, fv_current) { //│ set fv_arg$MutableInputBox$0$ = MutableInputBox_value; //│ set fv_value = fv_arg$MutableInputBox$0$; //│ set fv_tmp = +⁰(fv_current, fv_value); @@ -59,7 +59,7 @@ updateInitialized(40) //│ return fv_current //│ }; //│ @affine(1) @private -//│ define updateInitialized$box_MutableInputBox1 as fun updateInitialized$box_MutableInputBox⁰(MutableInputBox_value)(fv_arg$MutableInputBox$0$, fv_value, fv_tmp, fv_current) { +//│ define updateInitialized$box_MutableInputBox1 as fun updateInitialized$box_MutableInputBox¹(MutableInputBox_value)(fv_arg$MutableInputBox$0$, fv_value, fv_tmp, fv_current) { //│ set fv_arg$MutableInputBox$0$ = MutableInputBox_value; //│ set fv_value = fv_arg$MutableInputBox$0$; //│ set fv_tmp = +⁰(fv_current, fv_value); diff --git a/hkmc2/shared/src/test/mlscript/slop/EffectValueFlows.mls b/hkmc2/shared/src/test/mlscript/slop/EffectValueFlows.mls index 0558726e4b..9373523be9 100644 --- a/hkmc2/shared/src/test/mlscript/slop/EffectValueFlows.mls +++ b/hkmc2/shared/src/test/mlscript/slop/EffectValueFlows.mls @@ -22,9 +22,6 @@ private fun consume(box: EffectValueBox): Int = val result = consume(produce(CountedGetter.value)) [result, CountedGetter.calls] //│ deforest > >>> fusing >>> -//│ deforest > EffectValueBox⁰(tmp⁰) -> -//│ deforest > match: box⁰ -//│ deforest > fields: box⁰.value⁰ //│ deforest > <<< fusing <<< //│ > getter //│ = [42, 1] diff --git a/hkmc2/shared/src/test/mlscript/slop/ImmutableFieldEffects.mls b/hkmc2/shared/src/test/mlscript/slop/ImmutableFieldEffects.mls index f1abb98d30..26dcf2a464 100644 --- a/hkmc2/shared/src/test/mlscript/slop/ImmutableFieldEffects.mls +++ b/hkmc2/shared/src/test/mlscript/slop/ImmutableFieldEffects.mls @@ -68,7 +68,7 @@ set fields.changing = 42 [read_param(fields), read_body(new BodyFields), read_unknown(fields)] //│ flow-analysis > >>> effect-analysis results >>> //│ flow-analysis > call read_body⁰@result¹#0 @ -> UnsafeEta -//│ flow-analysis > call read_param⁰@result²#0 @ -> UnsafeEta -//│ flow-analysis > call read_unknown⁰@result⁰#0 @ -> UnsafeEta +//│ flow-analysis > call read_param⁰@result⁰#0 @ -> UnsafeEta +//│ flow-analysis > call read_unknown⁰@result²#0 @ -> UnsafeEta //│ flow-analysis > <<< effect-analysis results <<< //│ = [41, 41, 41] diff --git a/hkmc2/shared/src/test/mlscript/slop/OptionalEtaExpansion.mls b/hkmc2/shared/src/test/mlscript/slop/OptionalEtaExpansion.mls index a1c0caa70f..8dc80543c6 100644 --- a/hkmc2/shared/src/test/mlscript/slop/OptionalEtaExpansion.mls +++ b/hkmc2/shared/src/test/mlscript/slop/OptionalEtaExpansion.mls @@ -30,7 +30,7 @@ private fun nested(b) = if b then firstRequired else firstOptional [nested(true)(10)(2), nested(false)(10)(2)] //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > nested⁰#0 @ : [1, 1] -//│ eta-expansion > firstOptional⁰#0 @ : [1, 1(1 optional)] //│ eta-expansion > firstRequired⁰#0 @ : [1, 1] +//│ eta-expansion > firstOptional⁰#0 @ : [1, 1(1 optional)] //│ eta-expansion > <<< eta-expansion targets shapes <<< //│ = [12, 12] diff --git a/hkmc2/shared/src/test/mlscript/slop/SharedFunctionSignatures.mls b/hkmc2/shared/src/test/mlscript/slop/SharedFunctionSignatures.mls index f884269363..ba36713917 100644 --- a/hkmc2/shared/src/test/mlscript/slop/SharedFunctionSignatures.mls +++ b/hkmc2/shared/src/test/mlscript/slop/SharedFunctionSignatures.mls @@ -32,8 +32,8 @@ private fun partial(unused: Str, value: Int): Int -> Int = add(value) //│ define partial⁰ as fun partial¹(unused: Str⁰, value: Int⁰)(eta$0$0: Int⁰): Int⁰ { //│ return add⁰(value)(eta$0$0) //│ }; -//│ set tmp = partial$partial¹(40)(2); -//│ set tmp1 = partial$partial⁰(41)(2); +//│ set tmp = partial$partial⁰(40)(2); +//│ set tmp1 = partial$partial¹(41)(2); //│ return [tmp, tmp1] //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = [42, 43] @@ -127,8 +127,8 @@ private fun nested(unused: Str, value: Int): Int = //│ }; //│ return inner²(value) //│ }; -//│ set tmp = nested$nested¹(41); -//│ set tmp1 = nested$nested⁰(42); +//│ set tmp = nested$nested⁰(41); +//│ set tmp1 = nested$nested¹(42); //│ return [tmp, tmp1] //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = [42, 43] @@ -146,10 +146,10 @@ private fun backward(unused: Str, count: Int, value: Int): Int = if count == 0 then value else forward(unused, count - 1, value) [forward("first", 2, 42), backward("second", 3, 43)] //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let tmp: Int⁰, tmp1: Int⁰, backward$forward, forward$backward, backward$forward1, backward$backward, forward$forward, forward$backward1; +//│ let tmp: Int⁰, tmp1: Int⁰, forward$forward, forward$backward, backward$forward, backward$backward, forward$backward1, backward$forward1; //│ @private @private -//│ define backward$forward as fun backward$forward⁰(count: Int⁰, value: Int⁰): Int⁰ { -//│ return backward⁰(undefined, count, value) +//│ define forward$forward as fun forward$forward⁰(count: Int⁰, value: Int⁰): Int⁰ { +//│ return forward$backward⁰(count, value) //│ }; //│ @private @private //│ define forward$backward as fun forward$backward⁰(count: Int⁰, value: Int⁰): Int⁰ { @@ -160,11 +160,11 @@ private fun backward(unused: Str, count: Int, value: Int): Int = //│ return value //│ else //│ set tmp2 = -⁰(count, 1); -//│ return forward⁰(undefined, tmp2, value) +//│ return forward$forward⁰(tmp2, value) //│ end //│ }; //│ @private @private -//│ define backward$forward1 as fun backward$forward¹(count: Int⁰, value: Int⁰): Int⁰ { +//│ define backward$forward as fun backward$forward⁰(count: Int⁰, value: Int⁰): Int⁰ { //│ return backward$backward⁰(count, value) //│ }; //│ @private @private @@ -176,14 +176,10 @@ private fun backward(unused: Str, count: Int, value: Int): Int = //│ return value //│ else //│ set tmp2 = -⁰(count, 1); -//│ return backward$forward¹(tmp2, value) +//│ return backward$forward⁰(tmp2, value) //│ end //│ }; //│ @private @private -//│ define forward$forward as fun forward$forward⁰(count: Int⁰, value: Int⁰): Int⁰ { -//│ return forward$backward¹(count, value) -//│ }; -//│ @private @private //│ define forward$backward1 as fun forward$backward¹(count: Int⁰, value: Int⁰): Int⁰ { //│ let scrut: ?, tmp2: Int⁰; //│ set scrut = Predef⁰.equals⁰(count, 0); @@ -192,12 +188,16 @@ private fun backward(unused: Str, count: Int, value: Int): Int = //│ return value //│ else //│ set tmp2 = -⁰(count, 1); -//│ return forward$forward⁰(tmp2, value) +//│ return forward⁰(undefined, tmp2, value) //│ end //│ }; +//│ @private @private +//│ define backward$forward1 as fun backward$forward¹(count: Int⁰, value: Int⁰): Int⁰ { +//│ return backward⁰(undefined, count, value) +//│ }; //│ @private //│ define forward¹ as fun forward⁰(unused: Str⁰, count: Int⁰, value: Int⁰): Int⁰ { -//│ return forward$backward⁰(count, value) +//│ return forward$backward¹(count, value) //│ }; //│ @private //│ define backward¹ as fun backward⁰(unused: Str⁰, count: Int⁰, value: Int⁰): Int⁰ { @@ -208,7 +208,7 @@ private fun backward(unused: Str, count: Int, value: Int): Int = //│ return value //│ else //│ set tmp2 = -⁰(count, 1); -//│ return backward$forward⁰(tmp2, value) +//│ return backward$forward¹(tmp2, value) //│ end //│ }; //│ set tmp = forward$forward⁰(2, 42); diff --git a/hkmc2/shared/src/test/mlscript/slop/SolvedSchemeEnvironment.mls b/hkmc2/shared/src/test/mlscript/slop/SolvedSchemeEnvironment.mls new file mode 100644 index 0000000000..3927466860 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/slop/SolvedSchemeEnvironment.mls @@ -0,0 +1,67 @@ +:. +:js + +#config(inlining: None, liftDefns: Some(LiftDefns), flowBasedOpt: Some(FlowBasedOpt(mono: false))) + + +// Publishing a closure connects its scheme's local parameters to callers in +// other schemes. Both installations must retain their captured arguments. +:checkIR +:expect [42, 43] +let published = (value: Int) => value +private fun publish(base: Int) = + set published = (value: Int) => base + value +private fun install(base: Int) = publish(base) +private fun invokePublished(value: Int) = published(value) +install(40) +let first = invokePublished(2) +install(41) +[first, invokePublished(2)] +//│ = [42, 43] +//│ first = 42 +//│ published = fun + + +// The callback's effect reaches runCallback through its argument only after +// instantiation. Eta expansion must keep that effect before the read. +:checkIR +:expect [1, 42, 1] +let calls = 0 +let callback = () => () +private fun installCallback(action) = set callback = action +private fun effectfulCallback() = set calls += 1 +@affine(0) private fun answer() = 42 +private fun runCallback(action) = + action() + answer +installCallback(effectfulCallback) +let saved = runCallback(callback) +let before = calls +[before, saved(), calls] +//│ = [1, 42, 1] +//│ before = 1 +//│ callback = fun effectfulCallback +//│ calls = 1 +//│ saved = fun answer + + +// Values flow out of a recursive scheme into one shared slot and back into a +// separate scheme. Solving the functions before the assignments must preserve +// both constructor alternatives and their fields. +:checkIR +:noTailRec +:expect [20, 22] +data class PublishedBox(value: Int) +let publishedBox = PublishedBox(0) +private fun publishBox(count: Int, value: Int) = + if count == 0 then set publishedBox = PublishedBox(value) + else publishBox(count - 1, value) +private fun readBox() = + if publishedBox is PublishedBox(value) then value else -1 +publishBox(2, 20) +let firstBox = readBox() +publishBox(1, 22) +[firstBox, readBox()] +//│ = [20, 22] +//│ firstBox = 20 +//│ publishedBox = PublishedBox(22) diff --git a/hkmc2/shared/src/test/mlscript/slop/SolvedSchemeLocalEffects.mls b/hkmc2/shared/src/test/mlscript/slop/SolvedSchemeLocalEffects.mls new file mode 100644 index 0000000000..e0a6509550 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/slop/SolvedSchemeLocalEffects.mls @@ -0,0 +1,25 @@ +:. +:js + +#config(inlining: None, liftDefns: None, flowBasedOpt: Some(FlowBasedOpt(mono: false))) + + +// Keep the class inside its function scheme: solving method and constructor +// effects must not write abstract strategies into the shared effect slot. +:checkIR +:expect [43, 2] +let localCalls = 0 +private fun useLocal(callback) = + class Local with + val value = callback() + fun invoke() = callback() + let local = new Local() + local.invoke() +private fun localCallback() = + set localCalls += 1 + 21 +let localResult = useLocal(localCallback) + useLocal(() => 22) +[localResult, localCalls] +//│ = [43, 2] +//│ localCalls = 2 +//│ localResult = 43 diff --git a/hkmc2/shared/src/test/mlscript/slop/SolvedStrategySchemes.mls b/hkmc2/shared/src/test/mlscript/slop/SolvedStrategySchemes.mls new file mode 100644 index 0000000000..308c66057b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/slop/SolvedStrategySchemes.mls @@ -0,0 +1,151 @@ +:. +:js +:noInline +:lift +:deforest + +data class SchemeBox(value: Int) +//│ deforest > >>> fusing >>> +//│ deforest > <<< fusing <<< + + +// The boxes stay inside their callees. Their fusion metadata and local slot +// types must survive both instantiation steps, even though only Ints escape. +:checkIR +:soir +:siret +:expect 41 +private fun localField(n: Int): Int = + let box = SchemeBox(n) + box.value +private fun localMatch(n: Int): Int = + let box = SchemeBox(n) + if box is SchemeBox(value) then value else 0 +private fun nested(n: Int): Int = localField(n) + localMatch(n + 1) +nested(20) +//│ deforest > >>> fusing >>> +//│ deforest > SchemeBox⁰(n⁰) -> +//│ deforest > select: value +//│ deforest > SchemeBox⁰(n¹) -> +//│ deforest > match: box⁰ +//│ deforest > fields: box⁰.value⁰ +//│ deforest > SchemeBox⁰(n⁰) -> +//│ deforest > select: value +//│ deforest > SchemeBox⁰(n¹) -> +//│ deforest > match: box⁰ +//│ deforest > fields: box⁰.value⁰ +//│ deforest > SchemeBox⁰(n⁰) -> +//│ deforest > select: value +//│ deforest > SchemeBox⁰(n¹) -> +//│ deforest > match: box⁰ +//│ deforest > fields: box⁰.value⁰ +//│ deforest > <<< fusing <<< +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ let nested_localMatch$box_SchemeBox, localMatch$box_SchemeBox, nested_localMatch$box_SchemeBox1, nested_localField$localField, nested$nested, nested_localMatch$localMatch, nested_localField$localField1, nested_localMatch$localMatch1; +//│ @private @private +//│ define nested_localField$localField as fun nested_localField$localField⁰(n: Int⁰): Int⁰ { +//│ return n +//│ }; +//│ @private @private +//│ define nested$nested as fun nested$nested⁰(n: Int⁰): Int⁰ { +//│ let tmp: Int⁰, tmp1: Int⁰, tmp2: Int⁰; +//│ set tmp = nested_localField$localField⁰(n); +//│ set tmp1 = +⁰(n, 1); +//│ set tmp2 = nested_localMatch$localMatch⁰(tmp1); +//│ return +⁰(tmp, tmp2) +//│ }; +//│ @private @private +//│ define nested_localMatch$localMatch as fun nested_localMatch$localMatch⁰(n: Int⁰): Int⁰ { +//│ return (nested_localMatch$box_SchemeBox⁰(n)(undefined, undefined) as! Int⁰) +//│ }; +//│ @private @private +//│ define nested_localField$localField1 as fun nested_localField$localField¹(n: Int⁰): Int⁰ { +//│ return n +//│ }; +//│ @private @private +//│ define nested_localMatch$localMatch1 as fun nested_localMatch$localMatch¹(n: Int⁰): Int⁰ { +//│ return (nested_localMatch$box_SchemeBox¹(n)(undefined, undefined) as! Int⁰) +//│ }; +//│ @affine(1) @private +//│ define nested_localMatch$box_SchemeBox as fun nested_localMatch$box_SchemeBox⁰(SchemeBox_value: Int⁰)(fv_arg$SchemeBox$0$: ?, fv_value: ?): ? { +//│ return SchemeBox_value +//│ }; +//│ @affine(1) @private +//│ define localMatch$box_SchemeBox as fun localMatch$box_SchemeBox⁰(SchemeBox_value: Int⁰)(fv_arg$SchemeBox$0$: ?, fv_value: ?): ? { +//│ return SchemeBox_value +//│ }; +//│ @affine(1) @private +//│ define nested_localMatch$box_SchemeBox1 as fun nested_localMatch$box_SchemeBox¹(SchemeBox_value: Int⁰)(fv_arg$SchemeBox$0$: ?, fv_value: ?): ? { +//│ return SchemeBox_value +//│ }; +//│ @private +//│ define localField⁰ as fun localField¹(n: Int⁰): Int⁰ { +//│ return n +//│ }; +//│ @private +//│ define localMatch⁰ as fun localMatch¹(n: Int⁰): Int⁰ { +//│ return (localMatch$box_SchemeBox⁰(n)(undefined, undefined) as! Int⁰) +//│ }; +//│ @private +//│ define nested⁰ as fun nested¹(n: Int⁰): Int⁰ { +//│ let tmp: Int⁰, tmp1: Int⁰, tmp2: Int⁰; +//│ set tmp = nested_localField$localField¹(n); +//│ set tmp1 = +⁰(n, 1); +//│ set tmp2 = nested_localMatch$localMatch¹(tmp1); +//│ return +⁰(tmp, tmp2) +//│ }; +//│ return nested$nested⁰(20) +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 41 + + +// Recursive members share one solved scheme graph, while separate references +// to that group still get independent instantiations. +:checkIR +:expect 42 +private fun evenBox(n: Int): SchemeBox = + if n == 0 then SchemeBox(21) else oddBox(n - 1) +private fun oddBox(n: Int): SchemeBox = evenBox(n) +let fromMatch = if oddBox(1) is SchemeBox(value) then value else 0 +evenBox(2).value + fromMatch +//│ deforest > >>> fusing >>> +//│ deforest > <<< fusing <<< +//│ = 42 +//│ fromMatch = 21 + + +// Bounds crossing into a shared top-level value must remain connected when +// several function schemes are instantiated in different contexts. +:checkIR +:expect [40, 20] +val sharedSchemeBox = SchemeBox(20) +private fun sharedField(): Int = sharedSchemeBox.value +private fun sharedMatch(): Int = + if sharedSchemeBox is SchemeBox(value) then value else 0 +private fun sumShared(): Int = sharedField() + sharedMatch() +[sumShared(), sharedField()] +//│ deforest > >>> fusing >>> +//│ deforest > <<< fusing <<< +//│ = [40, 20] +//│ sharedSchemeBox = SchemeBox(20) + + +// A higher-order call is instantiated with both a pure and an effectful +// callback. The effectful callback must still run exactly once. +:checkIR +:expect [20, 21, 1] +let calls = 0 +private fun applyCallback(callback, value: Int) = callback(value) +private fun pureCallback(value: Int): Int = value +private fun effectfulCallback(value: Int): Int = + set calls += 1 + value + 1 +let first = applyCallback(pureCallback, 20) +let second = applyCallback(effectfulCallback, 20) +[first, second, calls] +//│ deforest > >>> fusing >>> +//│ deforest > <<< fusing <<< +//│ = [20, 21, 1] +//│ calls = 1 +//│ first = 20 +//│ second = 21 diff --git a/hkmc2/shared/src/test/mlscript/slop/strictres/OptionalBinding.mls b/hkmc2/shared/src/test/mlscript/slop/strictres/OptionalBinding.mls index b9115e0d9c..af222e98be 100644 --- a/hkmc2/shared/src/test/mlscript/slop/strictres/OptionalBinding.mls +++ b/hkmc2/shared/src/test/mlscript/slop/strictres/OptionalBinding.mls @@ -471,11 +471,11 @@ data class Data(value?) private fun read(item) = item.value [read(Data(unsafe.undefined)), read(Data()), read(Data(11))] //│ deforest > >>> fusing >>> -//│ deforest > Data⁰(11) -> +//│ deforest > Data⁰(undefined) -> //│ deforest > select: value //│ deforest > Data⁰(null) -> //│ deforest > select: value -//│ deforest > Data⁰(undefined) -> +//│ deforest > Data⁰(11) -> //│ deforest > select: value //│ deforest > <<< fusing <<< //│ = [undefined, null, 11]