From 0819eecb1f13aff17f8732cb24c16c8f844451c5 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 18 Mar 2025 17:25:29 +0800 Subject: [PATCH] WIP: Distinguish assignment from reassignment in the MIR --- .../src/main/scala/hkmc2/codegen/Block.scala | 77 ++++++++---- .../hkmc2/codegen/BlockTransformer.scala | 13 +- .../scala/hkmc2/codegen/BlockTraverser.scala | 5 +- .../scala/hkmc2/codegen/HandlerLowering.scala | 29 +++-- .../src/main/scala/hkmc2/codegen/Lifter.scala | 17 +-- .../main/scala/hkmc2/codegen/Lowering.scala | 21 ++-- .../main/scala/hkmc2/codegen/Printer.scala | 5 +- .../hkmc2/codegen/StackSafeTransform.scala | 20 +-- .../scala/hkmc2/codegen/UsedVarAnalyzer.scala | 4 +- .../scala/hkmc2/codegen/js/JSBuilder.scala | 13 +- .../scala/hkmc2/codegen/llir/Builder.scala | 9 +- .../test/mlscript/codegen/BlockPrinter.mls | 20 +-- .../test/mlscript/handlers/StackSafety.mls | 34 ++--- .../src/test/mlscript/lifter/ClassInFun.mls | 20 +-- .../src/test/mlscript/lifter/FunInFun.mls | 66 +++++++--- .../test/mlscript/lifter/StackSafetyLift.mls | 116 +++++++++++------- 16 files changed, 294 insertions(+), 175 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 19a432bb8d..266f5e0994 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -26,13 +26,24 @@ sealed abstract class Block extends Product with AutoLocated: protected def children: Ls[Located] = ??? // Maybe extending AutoLocated is unnecessary + /** Returns all local variables defined in this block through an `Assign` instruction. + * Does not look inside nested definitions. */ lazy val definedVars: Set[Local] = this match case _: Return | _: Throw => Set.empty case Begin(sub, rst) => sub.definedVars ++ rst.definedVars + + // * Currently, `TermSymbol` is used to represent private fields, + // * such as `let` bindings in object scopes and non-`val` class parameters. + // * Assignments to such fields are not defining a variable of the IR, + // * even though they're represented using the same Assign instruction. + // * TODO: It may be much cleaner to just have the elaborator use explicit selections and ReassignField instead. + // * TODO: Confusingly, we also have the case of `Define` being used to define a `val` without an owner (see below). We should simplify all this ad-hoc logic. case Assign(l: TermSymbol, r, rst) => rst.definedVars case Assign(l, r, rst) => rst.definedVars + l - case AssignField(l, n, r, rst) => rst.definedVars - case AssignDynField(l, n, ai, r, rst) => rst.definedVars + + case Reassign(l, r, rst) => rst.definedVars + case ReassignField(l, n, r, rst) => rst.definedVars + case ReassignDynField(l, n, ai, r, rst) => rst.definedVars case Match(scrut, arms, dflt, rst) => arms.flatMap(_._2.definedVars).toSet ++ dflt.toList.flatMap(_.definedVars) ++ rst.definedVars case End(_) => Set.empty @@ -49,8 +60,9 @@ sealed abstract class Block extends Product with AutoLocated: case _: Return | _: Throw | _: End | _: Break | _: Continue => 1 case Begin(sub, rst) => sub.size + rst.size case Assign(_, _, rst) => 1 + rst.size - case AssignField(_, _, _, rst) => 1 + rst.size - case AssignDynField(_, _, _, _, rst) => 1 + rst.size + case Reassign(_, _, rst) => 1 + rst.size + case ReassignField(_, _, _, rst) => 1 + rst.size + case ReassignDynField(_, _, _, _, rst) => 1 + rst.size case Match(_, arms, dflt, rst) => 1 + arms.map(_._2.size).sum + dflt.map(_.size).getOrElse(0) + rst.size case Define(_, rst) => 1 + rst.size @@ -63,6 +75,7 @@ sealed abstract class Block extends Product with AutoLocated: case b: BlockTail => f(b) case Begin(sub, rst) => Begin(sub, rst.mapTail(f)) case Assign(lhs, rhs, rst) => Assign(lhs, rhs, rst.mapTail(f)) + case Reassign(lhs, rhs, rst) => Reassign(lhs, rhs, rst.mapTail(f)) case Define(defn, rst) => Define(defn, rst.mapTail(f)) case HandleBlock(lhs, res, par, args, cls, handlers, body, rest) => HandleBlock(lhs, res, par, args, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, h.body)), body, rest.mapTail(f)) @@ -71,10 +84,10 @@ sealed abstract class Block extends Product with AutoLocated: case Match(scrut, arms, dflt, rst) => Match(scrut, arms, dflt, rst.mapTail(f)) case Label(label, body, rest) => Label(label, body, rest.mapTail(f)) - case af @ AssignField(lhs, nme, rhs, rest) => - AssignField(lhs, nme, rhs, rest.mapTail(f))(af.symbol) - case adf @ AssignDynField(lhs, fld, arrayIdx, rhs, rest) => - AssignDynField(lhs, fld, arrayIdx, rhs, rest.mapTail(f)) + case af @ ReassignField(lhs, nme, rhs, rest) => + ReassignField(lhs, nme, rhs, rest.mapTail(f))(af.symbol) + case adf @ ReassignDynField(lhs, fld, arrayIdx, rhs, rest) => + ReassignDynField(lhs, fld, arrayIdx, rhs, rest.mapTail(f)) case tb @ TryBlock(sub, fin, rest) => TryBlock(sub, fin, rest.mapTail(f)) @@ -90,9 +103,10 @@ sealed abstract class Block extends Product with AutoLocated: case Continue(label) => Set(label) case Begin(sub, rest) => sub.freeVars ++ rest.freeVars case TryBlock(sub, finallyDo, rest) => sub.freeVars ++ finallyDo.freeVars ++ rest.freeVars - case Assign(lhs, rhs, rest) => Set(lhs) ++ rhs.freeVars ++ rest.freeVars - case AssignField(lhs, nme, rhs, rest) => lhs.freeVars ++ rhs.freeVars ++ rest.freeVars - case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => lhs.freeVars ++ fld.freeVars ++ rhs.freeVars ++ rest.freeVars + case Assign(lhs, rhs, rest) => rhs.freeVars ++ rest.freeVars + case Reassign(lhs, rhs, rest) => rhs.freeVars ++ rest.freeVars + lhs + case ReassignField(lhs, nme, rhs, rest) => lhs.freeVars ++ rhs.freeVars ++ rest.freeVars + case ReassignDynField(lhs, fld, arrayIdx, rhs, rest) => lhs.freeVars ++ fld.freeVars ++ rhs.freeVars ++ rest.freeVars case Define(defn, rest) => defn.freeVars ++ rest.freeVars case HandleBlock(lhs, res, par, args, cls, hdr, bod, rst) => (bod.freeVars - lhs) ++ rst.freeVars ++ hdr.flatMap(_.freeVars) @@ -113,9 +127,10 @@ sealed abstract class Block extends Product with AutoLocated: case Continue(label) => Set(label) case Begin(sub, rest) => sub.freeVarsLLIR ++ rest.freeVarsLLIR case TryBlock(sub, finallyDo, rest) => sub.freeVarsLLIR ++ finallyDo.freeVarsLLIR ++ rest.freeVarsLLIR - case Assign(lhs, rhs, rest) => Set(lhs) ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR - case AssignField(lhs, nme, rhs, rest) => lhs.freeVarsLLIR ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR - case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => lhs.freeVarsLLIR ++ fld.freeVarsLLIR ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR + case Assign(lhs, rhs, rest) => rhs.freeVarsLLIR ++ rest.freeVarsLLIR + case Reassign(lhs, rhs, rest) => Set(lhs) ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR + case ReassignField(lhs, nme, rhs, rest) => lhs.freeVarsLLIR ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR + case ReassignDynField(lhs, fld, arrayIdx, rhs, rest) => lhs.freeVarsLLIR ++ fld.freeVarsLLIR ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR case Define(defn, rest) => defn.freeVarsLLIR ++ rest.freeVarsLLIR case HandleBlock(lhs, res, par, args, cls, hdr, bod, rst) => (bod.freeVarsLLIR - lhs) ++ rst.freeVarsLLIR ++ hdr.flatMap(_.freeVars) @@ -126,8 +141,9 @@ sealed abstract class Block extends Product with AutoLocated: case Begin(sub, rest) => sub :: rest :: Nil case TryBlock(sub, finallyDo, rest) => sub :: finallyDo :: rest :: Nil case Assign(_, rhs, rest) => rhs.subBlocks ::: rest :: Nil - case AssignField(_, _, rhs, rest) => rhs.subBlocks ::: rest :: Nil - case AssignDynField(_, _, _, rhs, rest) => rhs.subBlocks ::: rest :: Nil + case Reassign(_, rhs, rest) => rhs.subBlocks ::: rest :: Nil + case ReassignField(_, _, rhs, rest) => rhs.subBlocks ::: rest :: Nil + case ReassignDynField(_, _, _, rhs, rest) => rhs.subBlocks ::: rest :: Nil case Define(d, rest) => d.subBlocks ::: rest :: Nil case HandleBlock(_, _, par, args, _, handlers, body, rest) => par.subBlocks ++ args.flatMap(_.subBlocks) ++ handlers.map(_.body) :+ body :+ rest case Label(_, body, rest) => body :: rest :: Nil @@ -198,18 +214,24 @@ sealed abstract class Block extends Product with AutoLocated: if newRest is rest then this else Assign(lhs, rhs, newRest) + + case Reassign(lhs, rhs, rest) => + val newRest = rest.flatten(k) + if newRest is rest + then this + else Reassign(lhs, rhs, newRest) - case a@AssignField(lhs, nme, rhs, rest) => + case a@ReassignField(lhs, nme, rhs, rest) => val newRest = rest.flatten(k) if newRest is rest then this - else AssignField(lhs, nme, rhs, newRest)(a.symbol) + else ReassignField(lhs, nme, rhs, newRest)(a.symbol) - case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => + case ReassignDynField(lhs, fld, arrayIdx, rhs, rest) => val newRest = rest.flatten(k) if newRest is rest then this - else AssignDynField(lhs, fld, arrayIdx, rhs, newRest) + else ReassignDynField(lhs, fld, arrayIdx, rhs, newRest) case Define(defn, rest) => val newDefn = defn match @@ -271,12 +293,18 @@ case class Begin(sub: Block, rest: Block) extends Block with ProductWithTail case class TryBlock(sub: Block, finallyDo: Block, rest: Block) extends Block with ProductWithTail +// * Assigns the initial value of a variable or private field symbol. +// * Invariant: any given symbol may be assigned a value at most once, in the same block +// * (and specifically not in nested definitions), +// * and may only be read or reassigned after it has been assigned first. case class Assign(lhs: Local, rhs: Result, rest: Block) extends Block with ProductWithTail -// case class Assign(lhs: Path, rhs: Result, rest: Block) extends Block with ProductWithTail -case class AssignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(val symbol: Opt[FieldSymbol]) extends Block with ProductWithTail +// * Reassigns the value of a variable or private field symbol. +case class Reassign(lhs: Local, rhs: Result, rest: Block) extends Block with ProductWithTail + +case class ReassignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(val symbol: Opt[FieldSymbol]) extends Block with ProductWithTail -case class AssignDynField(lhs: Path, fld: Path, arrayIdx: Bool, rhs: Result, rest: Block) extends Block with ProductWithTail +case class ReassignDynField(lhs: Path, fld: Path, arrayIdx: Bool, rhs: Result, rest: Block) extends Block with ProductWithTail case class Define(defn: Defn, rest: Block) extends Block with ProductWithTail @@ -466,7 +494,8 @@ extension (k: Block => Block) def transform(f: (Block => Block) => (Block => Block)) = f(k) def assign(l: Local, r: Result) = k.chain(Assign(l, r, _)) - def assignFieldN(lhs: Path, nme: Tree.Ident, rhs: Result) = k.chain(AssignField(lhs, nme, rhs, _)(N)) + def reassign(l: Local, r: Result) = k.chain(Reassign(l, r, _)) + def reassignFieldN(lhs: Path, nme: Tree.Ident, rhs: Result) = k.chain(ReassignField(lhs, nme, rhs, _)(N)) def break(l: Local): Block = k.rest(Break(l)) def continue(l: Local): Block = k.rest(Continue(l)) def define(defn: Defn) = k.chain(Define(defn, _)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index f54a4cb510..8551894ff8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -60,13 +60,18 @@ class BlockTransformer(subst: SymbolSubst): val l2 = applyLocal(l) val rst2 = applySubBlock(rst) if (l2 is l) && (r2 is r) && (rst2 is rst) then b else Assign(l2, r2, rst2) - case b @ AssignField(l, n, r, rst) => + case Reassign(l, r, rst) => + applyResult2(r): r2 => + val l2 = applyLocal(l) + val rst2 = applySubBlock(rst) + if (l2 is l) && (r2 is r) && (rst2 is rst) then b else Reassign(l2, r2, rst2) + case b @ ReassignField(l, n, r, rst) => applyResult2(r): r2 => val l2 = applyPath(l) val rst2 = applySubBlock(rst) val sym = b.symbol.mapConserve(_.subst) if (l2 is l) && (r2 is r) && (rst2 is rst) && (sym is b.symbol) - then b else AssignField(l2, n, r2, rst2)(sym) + then b else ReassignField(l2, n, r2, rst2)(sym) case Define(defn, rst) => val defn2 = applyDefn(defn) val rst2 = applySubBlock(rst) @@ -83,14 +88,14 @@ class BlockTransformer(subst: SymbolSubst): if (l2 is l) && (res2 is res) && (par2 is par) && (args2 is args) && (cls2 is cls) && (hdr2 is hdr) && (bod2 is bod) && (rst2 is rst) then b else HandleBlock(l2, res2, par2, args2, cls2, hdr2, bod2, rst2) - case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => + case ReassignDynField(lhs, fld, arrayIdx, rhs, rest) => applyResult2(rhs): rhs2 => val lhs2 = applyPath(lhs) val fld2 = applyPath(fld) val rest2 = applySubBlock(rest) if (lhs2 is lhs) && (fld2 is fld) && (rhs2 is rhs) && (rest2 is rest) then b - else AssignDynField(lhs2, fld2, arrayIdx, rhs2, rest2) + else ReassignDynField(lhs2, fld2, arrayIdx, rhs2, rest2) def applyResult2(r: Result)(k: Result => Block): Block = k(applyResult(r)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala index 558db75709..d8e93148e1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala @@ -35,7 +35,8 @@ class BlockTraverser: case Begin(sub, rst) => applySubBlock(sub); applySubBlock(rst) case TryBlock(sub, fin, rst) => applySubBlock(sub); applySubBlock(fin); applySubBlock(rst) case Assign(l, r, rst) => applyLocal(l); applyResult(r); applySubBlock(rst) - case b @ AssignField(l, n, r, rst) => + case Reassign(l, r, rst) => applyLocal(l); applyResult(r); applySubBlock(rst) + case b @ ReassignField(l, n, r, rst) => applyPath(l); applyResult(r); applySubBlock(rst); b.symbol.foreach(_.traverse) case Define(defn, rst) => applyDefn(defn); applySubBlock(rst) case HandleBlock(l, res, par, args, cls, hdr, bod, rst) => @@ -46,7 +47,7 @@ class BlockTraverser: hdr.foreach(applyHandler) applySubBlock(bod) applySubBlock(rst) - case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => + case ReassignDynField(lhs, fld, arrayIdx, rhs, rest) => applyPath(lhs) applyResult(rhs) applyPath(fld) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index fb45847495..a158648eff 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -67,10 +67,10 @@ class HandlerLowering(paths: HandlerPaths)(using TL, Raise, Elaborator.State, El private def funcLikeHandlerCtx(ctorThis: Option[Path], isHandlerMtd: Bool, nme: Str) = HandlerCtx(false, false, nme, ctorThis, state => blockBuilder - .assignFieldN(state.res.contTrace.last, nextIdent, Instantiate( + .reassignFieldN(state.res.contTrace.last, nextIdent, Instantiate( state.cls.selN(Tree.Ident("class")), Value.Lit(Tree.IntLit(state.uid)) :: Nil)) - .assignFieldN(state.res.contTrace, lastIdent, state.res.contTrace.last.next) + .reassignFieldN(state.res.contTrace, lastIdent, state.res.contTrace.last.next) .ret(state.res)) private def functionHandlerCtx(nme: Str) = funcLikeHandlerCtx(N, false, nme) private def topLevelCtx(nme: Str) = HandlerCtx(true, false, nme, N, _ => rtThrowMsg("Unhandled effects")) @@ -270,12 +270,15 @@ class HandlerLowering(paths: HandlerPaths)(using TL, Raise, Elaborator.State, El case Assign(lhs, rhs, rest) => val PartRet(head, parts) = go(rest) PartRet(Assign(lhs, rhs, head), parts) - case blk @ AssignField(lhs, nme, rhs, rest) => + case Reassign(lhs, rhs, rest) => val PartRet(head, parts) = go(rest) - PartRet(AssignField(lhs, nme, rhs, head)(blk.symbol), parts) - case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => + PartRet(Reassign(lhs, rhs, head), parts) + case blk @ ReassignField(lhs, nme, rhs, rest) => val PartRet(head, parts) = go(rest) - PartRet(AssignDynField(lhs, fld, arrayIdx, rhs, head), parts) + PartRet(ReassignField(lhs, nme, rhs, head)(blk.symbol), parts) + case ReassignDynField(lhs, fld, arrayIdx, rhs, rest) => + val PartRet(head, parts) = go(rest) + PartRet(ReassignDynField(lhs, fld, arrayIdx, rhs, head), parts) case Return(_, _) => PartRet(blk, Nil) // ignored cases case TryBlock(sub, finallyDo, rest) => ??? // ignore @@ -385,7 +388,7 @@ class HandlerLowering(paths: HandlerPaths)(using TL, Raise, Elaborator.State, El val handlerBody = translateBlock(h.body, HandlerCtx(false, true, s"Cont$$handleBlock$$${h.lhs.nme}$$", N, state => blockBuilder - .assignFieldN(state.res.contTrace.last, nextIdent, PureCall(state.cls, Value.Lit(Tree.IntLit(state.uid)) :: Nil)) + .reassignFieldN(state.res.contTrace.last, nextIdent, PureCall(state.cls, Value.Lit(Tree.IntLit(state.uid)) :: Nil)) .ret(PureCall(paths.handleBlockImplPath, state.res :: h.lhs.asPath :: Nil)))) val handlerMtds = h.handlers.map: handler => @@ -460,13 +463,13 @@ class HandlerLowering(paths: HandlerPaths)(using TL, Raise, Elaborator.State, El override def applyBlock(b: Block): Block = b match case ReturnCont(res, uid) => blockBuilder - .assign(pcSymbol, Value.Lit(Tree.IntLit(uid))) - .assignFieldN(res.asPath.contTrace.last, nextIdent, clsSym.asPath) - .assignFieldN(res.asPath.contTrace, lastIdent, clsSym.asPath) + .reassign(pcSymbol, Value.Lit(Tree.IntLit(uid))) + .reassignFieldN(res.asPath.contTrace.last, nextIdent, clsSym.asPath) + .reassignFieldN(res.asPath.contTrace, lastIdent, clsSym.asPath) .ret(res.asPath) case StateTransition(uid) => blockBuilder - .assign(pcSymbol, Value.Lit(Tree.IntLit(uid))) + .reassign(pcSymbol, Value.Lit(Tree.IntLit(uid))) .continue(loopLbl) case FnEnd() => blockBuilder.break(loopLbl) @@ -486,7 +489,7 @@ class HandlerLowering(paths: HandlerPaths)(using TL, Raise, Elaborator.State, El val resumedVal = VarSymbol(Tree.Ident("value$")) - def createAssignment(sym: Local) = Assign(sym, resumedVal.asPath, End()) + def createAssignment(sym: Local) = Reassign(sym, resumedVal.asPath, End()) // TODO: or is this `Assign`? val assignedResumedCases = for b <- parts @@ -531,7 +534,7 @@ class HandlerLowering(paths: HandlerPaths)(using TL, Raise, Elaborator.State, El Assign(freshTmp(), PureCall( Value.Ref(State.builtinOpsMap("super")), // refers to runtime.FunctionContFrame which is pure Value.Lit(Tree.UnitLit(true)) :: Nil), End()), - AssignField( + ReassignField( clsSym.asPath, pcVar.id, Value.Ref(pcVar), diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index 5c52cba9fe..d1d49c8800 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -601,24 +601,27 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): case (blk, (bms, local)) => val initial = blk.assign(local, createCall(bms, ctx)) ctx.defns(bms) match - case c: ClsLikeDefn => initial.assignFieldN(local.asPath, Tree.Ident("class"), bms.asPath) + case c: ClsLikeDefn => initial.reassignFieldN(local.asPath, Tree.Ident("class"), bms.asPath) case _ => initial val remaining = rewritten match - case Assign(lhs: InnerSymbol, rhs, rest) => ctx.getIsymPath(lhs) match + + // TODO (LP:) not sure about the semantics of these "Reassign" cases + + case Reassign(lhs: InnerSymbol, rhs, rest) => ctx.getIsymPath(lhs) match case Some(value) if !belongsToCtor(lhs) => - Assign(value, applyResult(rhs), applyBlock(rest)) + Reassign(value, applyResult(rhs), applyBlock(rest)) case _ => super.applyBlock(rewritten) - case Assign(t: TermSymbol, rhs, rest) if t.owner.isDefined => + case Reassign(t: TermSymbol, rhs, rest) if t.owner.isDefined => ctx.getIsymPath(t.owner.get) match case Some(value) if !belongsToCtor(t.owner.get) => - AssignField(value.asPath, t.id, applyResult(rhs), applyBlock(rest))(N) + ReassignField(value.asPath, t.id, applyResult(rhs), applyBlock(rest))(N) case _ => super.applyBlock(rewritten) - case Assign(lhs, rhs, rest) => ctx.getLocalCaptureSym(lhs) match + case Reassign(lhs, rhs, rest) => ctx.getLocalCaptureSym(lhs) match case Some(captureSym) => - AssignField(ctx.getLocalClosPath(lhs).get, captureSym.id, applyResult(rhs), applyBlock(rest))(N) + ReassignField(ctx.getLocalClosPath(lhs).get, captureSym.id, applyResult(rhs), applyBlock(rest))(N) case None => ctx.getLocalPath(lhs) match case None => super.applyBlock(rewritten) case Some(value) => Assign(value, applyResult(rhs), applyBlock(rest)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 26ec18d512..0a4b4bba52 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -350,20 +350,20 @@ class Lowering()(using Config, TL, Raise, State, Ctx): lhs match case Ref(sym) => subTerm(rhs): r => - Assign(sym, r, k(unit)) + Reassign(sym, r, k(unit)) case sel @ SynthSel(prefix, nme) => subTerm(prefix): p => subTerm_nonTail(rhs): r => - AssignField(p, nme, r, k(unit))(sel.sym) + ReassignField(p, nme, r, k(unit))(sel.sym) case sel @ Sel(prefix, nme) => subTerm(prefix): p => subTerm_nonTail(rhs): r => - AssignField(p, nme, r, k(unit))(sel.sym) + ReassignField(p, nme, r, k(unit))(sel.sym) case sel @ DynSel(prefix, fld, ai) => subTerm(prefix): p => subTerm_nonTail(fld): f => subTerm_nonTail(rhs): r => - AssignDynField(p, f, ai, r, k(unit)) + ReassignDynField(p, f, ai, r, k(unit)) case st.Lam(params, body) => warnStmt @@ -541,7 +541,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): case SetRef(lhs, rhs) => subTerm(lhs): ref => subTerm_nonTail(rhs): value => - AssignField(ref, Tree.Ident("value"), value, k(value))(N) + ReassignField(ref, Tree.Ident("value"), value, k(value))(N) case Rcd(stats) => block(stats, L(Nil))(k) @@ -682,7 +682,6 @@ class Lowering()(using Config, TL, Raise, State, Ctx): def setupSelection(prefix: Term, nme: Tree.Ident, sym: Opt[FieldSymbol])(k: Result => Block)(using Subst): Block = subTerm(prefix): p => - val selRes = TempSymbol(N, "selRes") k(Select(p, nme)(sym)) final def setupFunctionOrByNameDef(paramLists: List[ParamList], bodyTerm: Term, name: Option[Str]) @@ -811,7 +810,9 @@ trait LoweringTraceLog(instrument: Bool)(using TL, Raise, State) object TrivialStatementsAndMatch: + def unapply(b: Block): Opt[(Opt[Block => Block], Match)] = + def handleAssignAndMatch( assign: Block => Block, m: Match, @@ -826,10 +827,10 @@ object TrivialStatementsAndMatch: case m: Match => S(N, m) case Assign(lhs, rhs: Path, TrivialStatementsAndMatch(k, m)) => handleAssignAndMatch(r => Assign(lhs, rhs, r), m, k) - case a@AssignField(lhs, nme, rhs: Path, TrivialStatementsAndMatch(k, m)) => - handleAssignAndMatch(r => AssignField(lhs, nme, rhs, r)(a.symbol), m, k) - case AssignDynField(lhs, fld, arrayIdx, rhs: Path, TrivialStatementsAndMatch(k, m)) => - handleAssignAndMatch(r => AssignDynField(lhs, fld, arrayIdx, rhs, r), m, k) + case a@ReassignField(lhs, nme, rhs: Path, TrivialStatementsAndMatch(k, m)) => + handleAssignAndMatch(r => ReassignField(lhs, nme, rhs, r)(a.symbol), m, k) + case ReassignDynField(lhs, fld, arrayIdx, rhs: Path, TrivialStatementsAndMatch(k, m)) => + handleAssignAndMatch(r => ReassignDynField(lhs, fld, arrayIdx, rhs, r), m, k) case Define(defn, TrivialStatementsAndMatch(k, m)) => handleAssignAndMatch(r => Define(defn, r), m, k) case _ => N diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala index e4be496c17..2bd5841e43 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala @@ -46,9 +46,12 @@ object Printer: case TryBlock(sub, finallyDo, rest) => doc"try #{ # ${mkDocument(sub)} # #} finally # #{ ${mkDocument(finallyDo)} in # #} ${mkDocument(rest)}" case Assign(lhs, rhs, rest) => + val docLhs = summon[Scope].lookup(lhs).getOrElse(summon[Scope].allocateName(lhs)) + doc"$docLhs = ${mkDocument(rhs)} in # ${mkDocument(rest)}" + case Reassign(lhs, rhs, rest) => val docLhs = summon[Scope].lookup(lhs).getOrElse(summon[Scope].allocateName(lhs)) doc"set $docLhs = ${mkDocument(rhs)} in # ${mkDocument(rest)}" - case AssignField(lhs, nme, rhs, rest) => + case ReassignField(lhs, nme, rhs, rest) => doc"set ${mkDocument(lhs)}.${nme.name} = ${mkDocument(rhs)} in # ${mkDocument(rest)}" case Define(defn, rest) => { doc"define ${mkDocument(defn)} in # ${mkDocument(rest)}" diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index bc835c1c27..80dbdfe938 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -32,13 +32,13 @@ class StackSafeTransform(depthLimit: Int, paths: HandlerPaths)(using State): def extractRes(res: Result, isTailCall: Bool, f: Result => Block, sym: Option[Symbol], curDepth: => Symbol) = if isTailCall then blockBuilder - .assignFieldN(runtimePath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) + .reassignFieldN(runtimePath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) .ret(res) else val tmp = sym getOrElse TempSymbol(None, "tmp") val offsetGtDepth = TempSymbol(None, "offsetGtDepth") blockBuilder - .assignFieldN(runtimePath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) + .reassignFieldN(runtimePath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) .assign(tmp, res) .assign(tmp, Call(resetDepthPath, tmp.asPath.asArg :: curDepth.asPath.asArg :: Nil)(true, false)) .rest(f(tmp.asPath)) @@ -64,18 +64,18 @@ class StackSafeTransform(depthLimit: Int, paths: HandlerPaths)(using State): resume() */ blockBuilder - .assignFieldN(runtimePath, STACK_OFFSET_IDENT, stackDepthPath) + .reassignFieldN(runtimePath, STACK_OFFSET_IDENT, stackDepthPath) .ret(Call(Value.Ref(resumeSym), Nil)(true, true)) ) :: Nil, blockBuilder - .assignFieldN(runtimePath, STACK_LIMIT_IDENT, intLit(depthLimit)) // set stackLimit before call - .assignFieldN(runtimePath, STACK_OFFSET_IDENT, intLit(0)) // set stackOffset = 0 before call - .assignFieldN(runtimePath, STACK_DEPTH_IDENT, intLit(1)) // set stackDepth = 1 before call - .assignFieldN(runtimePath, STACK_HANDLER_IDENT, handlerSym.asPath) // assign stack handler + .reassignFieldN(runtimePath, STACK_LIMIT_IDENT, intLit(depthLimit)) // set stackLimit before call + .reassignFieldN(runtimePath, STACK_OFFSET_IDENT, intLit(0)) // set stackOffset = 0 before call + .reassignFieldN(runtimePath, STACK_DEPTH_IDENT, intLit(1)) // set stackDepth = 1 before call + .reassignFieldN(runtimePath, STACK_HANDLER_IDENT, handlerSym.asPath) // assign stack handler .rest(body), blockBuilder // reset the stack safety values - .assignFieldN(runtimePath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 after call - .assignFieldN(runtimePath, STACK_HANDLER_IDENT, Value.Lit(Tree.UnitLit(true))) // set stackHandler = null + .reassignFieldN(runtimePath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 after call + .reassignFieldN(runtimePath, STACK_HANDLER_IDENT, Value.Lit(Tree.UnitLit(true))) // set stackHandler = null .rest(rest) ) @@ -104,7 +104,7 @@ class StackSafeTransform(depthLimit: Int, paths: HandlerPaths)(using State): case Return(res, implct) if usesStack(res) => extract(applyResult(res), true, Return(_, implct), N, curDepth) // Optimization to avoid generation of unnecessary variables - case Assign(lhs, r, rest) => + case Reassign(lhs, r, rest) => if usesStack(r) then extract(applyResult(r), false, _ => applyBlock(rest), S(lhs), curDepth) else diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala index a436aef450..a29b4d7b7f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala @@ -127,7 +127,7 @@ class UsedVarAnalyzer(b: Block, handlerPaths: Opt[HandlerPaths])(using State): applyBlock(b) override def applyBlock(b: Block): Unit = b match - case Assign(lhs, rhs, rest) => + case Reassign(lhs, rhs, rest) => accessed = accessed.addMutated(lhs) applyResult(rhs) applyBlock(rest) @@ -282,7 +282,7 @@ class UsedVarAnalyzer(b: Block, handlerPaths: Opt[HandlerPaths])(using State): new BlockTraverserShallow: applyBlock(b) override def applyBlock(b: Block): Unit = b match - case Assign(lhs, rhs, rest) => + case Reassign(lhs, rhs, rest) => applyResult(rhs) if hasReader.contains(lhs) || hasMutator.contains(lhs) then reqCapture += lhs applyBlock(rest) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala index 8cc077a4ef..66d07f8213 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -158,11 +158,13 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: t match case _: HandleBlock => errStmt(msg"This code requires effect handler instrumentation but was compiled without it.") - case Assign(l, r, rst) => + case Assign(l, r, rst) => // * TODO sanity check the LHS is undefined doc" # ${getVar(l)} = ${result(r)};${returningTerm(rst, endSemi)}" - case AssignField(p, n, r, rst) => + case Reassign(l, r, rst) => + doc" # ${getVar(l)} = ${result(r)};${returningTerm(rst, endSemi)}" + case ReassignField(p, n, r, rst) => doc" # ${result(p)}.${n.name} = ${result(r)};${returningTerm(rst, endSemi)}" - case AssignDynField(p, f, ai, r, rst) => + case ReassignDynField(p, f, ai, r, rst) => doc" # ${result(p)}[${result(f)}] = ${result(r)};${returningTerm(rst, endSemi)}" case Define(defn, rst) => def mkThis(sym: InnerSymbol): Document = @@ -455,8 +457,13 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: blockPreamble(p.main) -> (imps.mkDocument(doc" # ") :/: returningTerm(p.main, endSemi = false).stripBreaks) def blockPreamble(t: Block)(using Raise, Scope): Document = + // TODO document: mutable var assnts require the lookup val vars = t.definedVars.toSeq.filter(scope.lookup(_).isEmpty).sortBy(_.uid).iterator.map(l => + + // TODO: now should be possible to just write: (currently causes problems to handlers) + // val vars = t.definedVars.toSeq.sortBy(_.uid).iterator.map(l => + l -> scope.allocateName(l)) if vars.isEmpty then doc"" else doc" # let " :: vars.map: (_, nme) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala index 7705fa01a3..3a40468e75 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala @@ -278,8 +278,8 @@ final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: definedVars.foreach(allocIfNew) bBlock(sub): x => bBlock(rest)(k) - case Assign(lhs, rhs, rest2) => - bBlock(Assign(lhs, rhs, Begin(rest2, rest)))(k) + case Reassign(lhs, rhs, rest2) => + bBlock(Reassign(lhs, rhs, Begin(rest2, rest)))(k) case Begin(sub, rest2) => bBlock(Begin(sub, Begin(rest2, rest)))(k) case Define(defn, rest2) => @@ -291,7 +291,10 @@ final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: case Assign(lhs, rhs, rest) => val name = allocIfNew(lhs) bBind(S(name), rhs, rest)(k) - case AssignField(lhs, nme, rhs, rest) => TODO("AssignField not supported") + case Reassign(lhs, rhs, rest) => + val name = allocIfNew(lhs) + bBind(S(name), rhs, rest)(k) + case ReassignField(lhs, nme, rhs, rest) => TODO("AssignField not supported") case Define(fd @ FunDefn(_own, sym, params, body), rest) => val f = bFunDef(fd) ctx.def_acc += f diff --git a/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls b/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls index 636650519a..713ed71ce1 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls @@ -4,7 +4,7 @@ let x = 1 x + 1 //│ Pretty Lowered: -//│ set x = 1 in set block$res1 = +(x, 1) in return undefined +//│ x = 1 in block$res1 = +(x, 1) in return undefined //│ = 2 //│ x = 1 @@ -12,7 +12,7 @@ x + 1 fun incr(n) = n + 1 fun (|>) pipe(x, f) = f(x) //│ Pretty Lowered: -//│ define fun incr(n) { return +(n, 1) } in define fun pipe(x1, f) { return f(x1) } in set block$res2 = undefined in end +//│ define fun incr(n) { return +(n, 1) } in define fun pipe(x1, f) { return f(x1) } in block$res2 = undefined in end :slot let x = 1 @@ -20,19 +20,19 @@ let x = if x == 0 then 1 else 0 let x = x + 1 //│ Pretty Lowered: //│ -//│ set x2 = 1 in -//│ set scrut = ==(x2, 0) in +//│ x2 = 1 in +//│ scrut = ==(x2, 0) in //│ match scrut //│ true => -//│ set tmp = 1 in +//│ tmp = 1 in //│ end //│ else -//│ set tmp = 0 in +//│ tmp = 0 in //│ end //│ in -//│ set x3 = tmp in -//│ set tmp1 = +(x3, 1) in -//│ set x4 = tmp1 in -//│ set block$res3 = undefined in +//│ x3 = tmp in +//│ tmp1 = +(x3, 1) in +//│ x4 = tmp1 in +//│ block$res3 = undefined in //│ end //│ x = 1 diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 11906a537a..818f8bea9f 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -144,21 +144,21 @@ sum(10000) //│ JS (unsanitized): //│ let sum1, res1, handleBlock$1; //│ sum1 = function sum(n) { -//│ let scrut, tmp, tmp1, curDepth, stackDelayRes, Cont$func$sum$StackSafety$_mls_L139_4_57$1; +//│ let scrut, tmp, tmp1, tmp2, curDepth, stackDelayRes, Cont$func$sum$StackSafety$_mls_L139_4_57$1; //│ Cont$func$sum$StackSafety$_mls_L139_4_57$1 = function Cont$func$sum$StackSafety$_mls_L139_4_57$(pc1) { //│ return new Cont$func$sum$StackSafety$_mls_L139_4_57$.class(pc1); //│ }; //│ Cont$func$sum$StackSafety$_mls_L139_4_57$1.class = class Cont$func$sum$StackSafety$_mls_L139_4_57$ extends runtime.FunctionContFrame.class { //│ constructor(pc) { -//│ let tmp2; -//│ tmp2 = super(null); +//│ let tmp3; +//│ tmp3 = super(null); //│ this.pc = pc; //│ } //│ resume(value$) { //│ if (this.pc === 0) { //│ stackDelayRes = value$; //│ } else if (this.pc === 1) { -//│ tmp1 = value$; +//│ tmp2 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 0) { @@ -176,17 +176,18 @@ sum(10000) //│ break contLoop; //│ } else if (this.pc === 3) { //│ runtime.stackDepth = runtime.stackDepth + 1; -//│ tmp1 = sum1(tmp); -//│ if (tmp1 instanceof runtime.EffectSig.class) { +//│ tmp2 = sum1(tmp); +//│ if (tmp2 instanceof runtime.EffectSig.class) { //│ this.pc = 1; -//│ tmp1.contTrace.last.next = this; -//│ tmp1.contTrace.last = this; -//│ return tmp1 +//│ tmp2.contTrace.last.next = this; +//│ tmp2.contTrace.last = this; +//│ return tmp2 //│ } //│ this.pc = 1; //│ continue contLoop; //│ } else if (this.pc === 1) { -//│ tmp1 = runtime.resetDepth(tmp1, curDepth); +//│ tmp2 = runtime.resetDepth(tmp2, curDepth); +//│ tmp1 = tmp2; //│ return n + tmp1 //│ } //│ break; @@ -207,13 +208,14 @@ sum(10000) //│ } else { //│ tmp = n - 1; //│ runtime.stackDepth = runtime.stackDepth + 1; -//│ tmp1 = sum1(tmp); -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ tmp1.contTrace.last.next = new Cont$func$sum$StackSafety$_mls_L139_4_57$1.class(1); -//│ tmp1.contTrace.last = tmp1.contTrace.last.next; -//│ return tmp1 +//│ tmp2 = sum1(tmp); +//│ if (tmp2 instanceof runtime.EffectSig.class) { +//│ tmp2.contTrace.last.next = new Cont$func$sum$StackSafety$_mls_L139_4_57$1.class(1); +//│ tmp2.contTrace.last = tmp2.contTrace.last.next; +//│ return tmp2 //│ } -//│ tmp1 = runtime.resetDepth(tmp1, curDepth); +//│ tmp2 = runtime.resetDepth(tmp2, curDepth); +//│ tmp1 = tmp2; //│ return n + tmp1 //│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls index 37bd126bd6..03f4d61dea 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls @@ -70,6 +70,7 @@ f() + f() + f() //│ } //│ } //│ resume(value$) { +//│ let tmp1, tmp2, tmp3, tmp4; //│ if (this.pc === 2) { //│ this.tmp$1 = value$; //│ } else if (this.pc === 3) { @@ -79,7 +80,7 @@ f() + f() + f() //│ } //│ contLoop: while (true) { //│ if (this.pc === 7) { -//│ this.tmp$1 = f$(this.h$0); +//│ tmp1 = f$(this.h$0); //│ if (this.tmp$1 instanceof runtime.EffectSig.class) { //│ this.pc = 2; //│ this.tmp$1.contTrace.last.next = this; @@ -92,7 +93,7 @@ f() + f() + f() //│ this.pc = 6; //│ continue contLoop; //│ } else if (this.pc === 6) { -//│ this.tmp$2 = f$(this.h$0); +//│ tmp2 = f$(this.h$0); //│ if (this.tmp$2 instanceof runtime.EffectSig.class) { //│ this.pc = 3; //│ this.tmp$2.contTrace.last.next = this; @@ -102,11 +103,11 @@ f() + f() + f() //│ this.pc = 3; //│ continue contLoop; //│ } else if (this.pc === 3) { -//│ this.tmp$3 = this.tmp$1 + this.tmp$2; +//│ tmp3 = this.tmp$1 + this.tmp$2; //│ this.pc = 5; //│ continue contLoop; //│ } else if (this.pc === 5) { -//│ this.tmp$4 = f$(this.h$0); +//│ tmp4 = f$(this.h$0); //│ if (this.tmp$4 instanceof runtime.EffectSig.class) { //│ this.pc = 4; //│ this.tmp$4.contTrace.last.next = this; @@ -152,12 +153,13 @@ f() + f() + f() //│ } //│ } //│ resume(value$) { +//│ let tmp1; //│ if (this.pc === 0) { //│ this.tmp$1 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 1) { -//│ this.tmp$1 = runtime.safeCall(this.h$0.perform()); +//│ tmp1 = runtime.safeCall(this.h$0.perform()); //│ if (this.tmp$1 instanceof runtime.EffectSig.class) { //│ this.pc = 0; //│ this.tmp$1.contTrace.last.next = this; @@ -215,7 +217,7 @@ f() + f() + f() //│ throw new this.Error("Unhandled effects"); //│ } //│ tmp -//│ = 3 +//│ = NaN :expect 1 :sjs @@ -571,12 +573,12 @@ f().foo() //│ toString() { return "f$capture(" + globalThis.Predef.render(this.w0$) + ")"; } //│ }; //│ f6 = function f() { -//│ let x, y, z, tmp6, tmp7, capture; +//│ let x, y, z, w, tmp6, tmp7, capture; //│ capture = new f$capture3(null); //│ x = 1; //│ y = 10; //│ z = 10; -//│ capture.w0$ = 1000; +//│ w = 1000; //│ tmp6 = Bad$(capture); //│ tmp7 = runtime.safeCall(tmp6.foo()); //│ return Good$(x, y, z, capture) @@ -623,7 +625,7 @@ fun sum(n) = else n + sum(n - 1) sum(100) -//│ = 5050 +//│ = NaN // instance checks diff --git a/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls index afe6b839fa..b238086cce 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls @@ -189,9 +189,9 @@ x + y //│ toString() { return "f$capture(" + globalThis.Predef.render(this.a0$) + ")"; } //│ }; //│ f5 = function f() { -//│ let capture, g$this, h$this; +//│ let a, capture, g$this, h$this; //│ capture = new f$capture1(null); -//│ capture.a0$ = 0; +//│ a = 0; //│ g$this = runtime.safeCall(g5(capture)); //│ h$this = runtime.safeCall(h(capture)); //│ return Tuple1(g$this, h$this) @@ -218,12 +218,27 @@ x + y //│ tmp5 = runtime.safeCall(tmp4.toString()); //│ y = tmp5; //│ x + y -//│ = "01" +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:145) +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of null (reading 'toString') +//│ at REPL23:1:2232 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:598:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:927:10) +//│ at REPLServer.emit (node:events:518:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: processTerm (JSBackendDiffMaker.scala:191) +//│ ═══[RUNTIME ERROR] Expected: '"01"', got: 'undefined' //│ f1 = [function] //│ f2 = [function] //│ ret = Tuple([function], [function]) -//│ x = "0" -//│ y = "1" +//│ x = undefined +//│ y = undefined data class Tuple(a, b) @@ -341,7 +356,22 @@ fun f() = fun g() = y x() f() -//│ = 2 +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:145) +//│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. +//│ at Runtime.checkCall (file:///Users/parreaux/work/Research/code/mlscript/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:99:24) +//│ at REPL49:1:355 +//│ at f (REPL49:1:549) +//│ at REPL49:1:590 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:598:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:927:10) +//│ at REPLServer.emit (node:events:518:28) +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: processTerm (JSBackendDiffMaker.scala:191) +//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' :expect 2 fun f() = @@ -475,9 +505,9 @@ g()(1) //│ toString() { return "g$capture(" + globalThis.Predef.render(this.y0$) + ")"; } //│ }; //│ g13 = function g() { -//│ let capture; +//│ let y1, capture; //│ capture = new g$capture1(null); -//│ capture.y0$ = 0; +//│ y1 = 0; //│ return runtime.safeCall(f15(capture)) //│ }; //│ tmp15 = g13(); @@ -561,32 +591,32 @@ fun f() = x f() //│ JS (unsanitized): -//│ let f19, g16, f20, f$2, f$capture15; +//│ let f19, g16, f20, f$2, f$capture13; //│ g16 = function g() { //│ return 1 //│ }; -//│ f$2 = function f$(f$capture16) { -//│ f$capture16.x0$ = 1; +//│ f$2 = function f$(f$capture14) { +//│ f$capture14.x0$ = 1; //│ return runtime.Unit //│ }; -//│ f19 = function f(f$capture16) { +//│ f19 = function f(f$capture14) { //│ return () => { -//│ return f$2(f$capture16) +//│ return f$2(f$capture14) //│ } //│ }; -//│ f$capture15 = function f$capture(x0$1) { +//│ f$capture13 = function f$capture(x0$1) { //│ return new f$capture.class(x0$1); //│ }; -//│ f$capture15.class = class f$capture14 { +//│ f$capture13.class = class f$capture12 { //│ constructor(x0$) { //│ this.x0$ = x0$; //│ } //│ toString() { return "f$capture(" + globalThis.Predef.render(this.x0$) + ")"; } //│ }; //│ f20 = function f() { -//│ let tmp19, capture; -//│ capture = new f$capture15(null); -//│ capture.x0$ = 1; +//│ let x1, tmp19, capture; +//│ capture = new f$capture13(null); +//│ x1 = 1; //│ tmp19 = f$2(capture); //│ return capture.x0$ //│ }; diff --git a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls index 33d415aea4..9725c7c084 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls @@ -53,16 +53,17 @@ hi(0) //│ } //│ } //│ resume(value$) { +//│ let scrut, tmp; //│ if (this.pc === 0) { //│ this.stackDelayRes$3 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 0) { -//│ this.scrut$1 = this.n$0 == 0; +//│ scrut = this.n$0 == 0; //│ if (this.scrut$1 === true) { //│ return 0 //│ } else { -//│ this.tmp$2 = this.n$0 - 1; +//│ tmp = this.n$0 - 1; //│ this.pc = 2; //│ continue contLoop; //│ } @@ -190,52 +191,54 @@ fun sum(n) = n + sum(n - 1) sum(10000) //│ JS (unsanitized): -//│ let sum1, res1, Cont$func$sum$StackSafetyLift$_mls_L187_4_57$1, handleBlock$1, Cont$handleBlock$stackHandler$3, StackDelay$3, lambda1, Cont$func$sum$StackSafetyLift$_mls_L187_4_57$$ctor, Cont$func$sum$StackSafetyLift$_mls_L187_4_57$$, Cont$handleBlock$stackHandler$$ctor1, Cont$handleBlock$stackHandler$$1, lambda$1; -//│ Cont$func$sum$StackSafetyLift$_mls_L187_4_57$$ = function Cont$func$sum$StackSafetyLift$_mls_L187_4_57$$(n$0, scrut$1, tmp$2, tmp$3, curDepth$4, stackDelayRes$5, pc) { +//│ let sum1, res1, Cont$func$sum$StackSafetyLift$_mls_L188_4_57$1, handleBlock$1, Cont$handleBlock$stackHandler$3, StackDelay$3, lambda1, Cont$func$sum$StackSafetyLift$_mls_L188_4_57$$ctor, Cont$func$sum$StackSafetyLift$_mls_L188_4_57$$, Cont$handleBlock$stackHandler$$ctor1, Cont$handleBlock$stackHandler$$1, lambda$1; +//│ Cont$func$sum$StackSafetyLift$_mls_L188_4_57$$ = function Cont$func$sum$StackSafetyLift$_mls_L188_4_57$$(n$0, scrut$1, tmp$2, tmp$3, tmp$4, curDepth$5, stackDelayRes$6, pc) { //│ let tmp; -//│ tmp = new Cont$func$sum$StackSafetyLift$_mls_L187_4_57$1.class(pc); -//│ return tmp(n$0, scrut$1, tmp$2, tmp$3, curDepth$4, stackDelayRes$5) +//│ tmp = new Cont$func$sum$StackSafetyLift$_mls_L188_4_57$1.class(pc); +//│ return tmp(n$0, scrut$1, tmp$2, tmp$3, tmp$4, curDepth$5, stackDelayRes$6) //│ }; -//│ Cont$func$sum$StackSafetyLift$_mls_L187_4_57$$ctor = function Cont$func$sum$StackSafetyLift$_mls_L187_4_57$$ctor(n$0, scrut$1, tmp$2, tmp$3, curDepth$4, stackDelayRes$5) { +//│ Cont$func$sum$StackSafetyLift$_mls_L188_4_57$$ctor = function Cont$func$sum$StackSafetyLift$_mls_L188_4_57$$ctor(n$0, scrut$1, tmp$2, tmp$3, tmp$4, curDepth$5, stackDelayRes$6) { //│ return (pc) => { //│ let tmp; -//│ tmp = new Cont$func$sum$StackSafetyLift$_mls_L187_4_57$1.class(pc); -//│ return tmp(n$0, scrut$1, tmp$2, tmp$3, curDepth$4, stackDelayRes$5) +//│ tmp = new Cont$func$sum$StackSafetyLift$_mls_L188_4_57$1.class(pc); +//│ return tmp(n$0, scrut$1, tmp$2, tmp$3, tmp$4, curDepth$5, stackDelayRes$6) //│ } //│ }; -//│ Cont$func$sum$StackSafetyLift$_mls_L187_4_57$1 = function Cont$func$sum$StackSafetyLift$_mls_L187_4_57$(pc1) { -//│ return (n$01, scrut$11, tmp$21, tmp$31, curDepth$41, stackDelayRes$51) => { -//│ return new Cont$func$sum$StackSafetyLift$_mls_L187_4_57$.class(pc1)(n$01, scrut$11, tmp$21, tmp$31, curDepth$41, stackDelayRes$51); +//│ Cont$func$sum$StackSafetyLift$_mls_L188_4_57$1 = function Cont$func$sum$StackSafetyLift$_mls_L188_4_57$(pc1) { +//│ return (n$01, scrut$11, tmp$21, tmp$31, tmp$41, curDepth$51, stackDelayRes$61) => { +//│ return new Cont$func$sum$StackSafetyLift$_mls_L188_4_57$.class(pc1)(n$01, scrut$11, tmp$21, tmp$31, tmp$41, curDepth$51, stackDelayRes$61); //│ } //│ }; -//│ Cont$func$sum$StackSafetyLift$_mls_L187_4_57$1.class = class Cont$func$sum$StackSafetyLift$_mls_L187_4_57$ extends runtime.FunctionContFrame.class { +//│ Cont$func$sum$StackSafetyLift$_mls_L188_4_57$1.class = class Cont$func$sum$StackSafetyLift$_mls_L188_4_57$ extends runtime.FunctionContFrame.class { //│ constructor(pc) { -//│ return (n$0, scrut$1, tmp$2, tmp$3, curDepth$4, stackDelayRes$5) => { +//│ return (n$0, scrut$1, tmp$2, tmp$3, tmp$4, curDepth$5, stackDelayRes$6) => { //│ let tmp; //│ tmp = super(null); //│ this.n$0 = n$0; //│ this.scrut$1 = scrut$1; //│ this.tmp$2 = tmp$2; //│ this.tmp$3 = tmp$3; -//│ this.curDepth$4 = curDepth$4; -//│ this.stackDelayRes$5 = stackDelayRes$5; +//│ this.tmp$4 = tmp$4; +//│ this.curDepth$5 = curDepth$5; +//│ this.stackDelayRes$6 = stackDelayRes$6; //│ this.pc = pc; //│ return this; //│ } //│ } //│ resume(value$) { +//│ let scrut, tmp, tmp1, tmp2; //│ if (this.pc === 0) { -//│ this.stackDelayRes$5 = value$; +//│ this.stackDelayRes$6 = value$; //│ } else if (this.pc === 1) { -//│ this.tmp$3 = value$; +//│ this.tmp$4 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 0) { -//│ this.scrut$1 = this.n$0 == 0; +//│ scrut = this.n$0 == 0; //│ if (this.scrut$1 === true) { //│ return 0 //│ } else { -//│ this.tmp$2 = this.n$0 - 1; +//│ tmp = this.n$0 - 1; //│ this.pc = 3; //│ continue contLoop; //│ } @@ -245,30 +248,31 @@ sum(10000) //│ break contLoop; //│ } else if (this.pc === 3) { //│ runtime.stackDepth = runtime.stackDepth + 1; -//│ this.tmp$3 = sum1(this.tmp$2); -//│ if (this.tmp$3 instanceof runtime.EffectSig.class) { +//│ tmp2 = sum1(this.tmp$2); +//│ if (this.tmp$4 instanceof runtime.EffectSig.class) { //│ this.pc = 1; -//│ this.tmp$3.contTrace.last.next = this; -//│ this.tmp$3.contTrace.last = this; -//│ return this.tmp$3 +//│ this.tmp$4.contTrace.last.next = this; +//│ this.tmp$4.contTrace.last = this; +//│ return this.tmp$4 //│ } //│ this.pc = 1; //│ continue contLoop; //│ } else if (this.pc === 1) { -//│ this.tmp$3 = runtime.resetDepth(this.tmp$3, this.curDepth$4); +//│ tmp2 = runtime.resetDepth(this.tmp$4, this.curDepth$5); +//│ tmp1 = this.tmp$4; //│ return this.n$0 + this.tmp$3 //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$func$sum$StackSafetyLift$_mls_L187_4_57$(" + globalThis.Predef.render(this.pc) + ")"; } +//│ toString() { return "Cont$func$sum$StackSafetyLift$_mls_L188_4_57$(" + globalThis.Predef.render(this.pc) + ")"; } //│ }; //│ sum1 = function sum(n) { -//│ let scrut, tmp, tmp1, curDepth, stackDelayRes; +//│ let scrut, tmp, tmp1, tmp2, curDepth, stackDelayRes; //│ curDepth = runtime.stackDepth; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ stackDelayRes.contTrace.last.next = Cont$func$sum$StackSafetyLift$_mls_L187_4_57$$(n, scrut, tmp, tmp1, curDepth, stackDelayRes, 0); +//│ stackDelayRes.contTrace.last.next = Cont$func$sum$StackSafetyLift$_mls_L188_4_57$$(n, scrut, tmp, tmp1, tmp2, curDepth, stackDelayRes, 0); //│ stackDelayRes.contTrace.last = stackDelayRes.contTrace.last.next; //│ return stackDelayRes //│ } @@ -278,13 +282,14 @@ sum(10000) //│ } else { //│ tmp = n - 1; //│ runtime.stackDepth = runtime.stackDepth + 1; -//│ tmp1 = sum1(tmp); -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ tmp1.contTrace.last.next = Cont$func$sum$StackSafetyLift$_mls_L187_4_57$$(n, scrut, tmp, tmp1, curDepth, stackDelayRes, 1); -//│ tmp1.contTrace.last = tmp1.contTrace.last.next; -//│ return tmp1 +//│ tmp2 = sum1(tmp); +//│ if (tmp2 instanceof runtime.EffectSig.class) { +//│ tmp2.contTrace.last.next = Cont$func$sum$StackSafetyLift$_mls_L188_4_57$$(n, scrut, tmp, tmp1, tmp2, curDepth, stackDelayRes, 1); +//│ tmp2.contTrace.last = tmp2.contTrace.last.next; +//│ return tmp2 //│ } -//│ tmp1 = runtime.resetDepth(tmp1, curDepth); +//│ tmp2 = runtime.resetDepth(tmp2, curDepth); +//│ tmp1 = tmp2; //│ return n + tmp1 //│ } //│ }; @@ -370,7 +375,10 @@ sum(10000) //│ runtime.stackDepth = 0; //│ runtime.stackHandler = null; //│ res1 -//│ = 50005000 +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: processTerm (JSBackendDiffMaker.scala:191) +//│ ═══[RUNTIME ERROR] Expected: '50005000', got: 'NaN' +//│ = NaN // stack-overflows without :stackSafe :re @@ -391,8 +399,20 @@ fun foo(f) = set ctr += 1 dummy(f(f)) foo(foo) -//│ = 0 -//│ ctr = 10001 +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:145) +//│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. +//│ at Runtime.checkCall (file:///Users/parreaux/work/Research/code/mlscript/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:99:24) +//│ at Cont$func$foo$StackSafetyLift$_mls_L394_37_111$.resume (REPL20:1:3459) +//│ at Runtime.resumeContTrace (file:///Users/parreaux/work/Research/code/mlscript/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:412:38) +//│ at file:///Users/parreaux/work/Research/code/mlscript/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:403:22 +//│ at lambda$ (REPL20:1:5675) +//│ at EffectSig.handlerFun (REPL20:1:5967) +//│ at Runtime.handleEffect (file:///Users/parreaux/work/Research/code/mlscript/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:369:34) +//│ at Runtime.handleBlockImpl (file:///Users/parreaux/work/Research/code/mlscript/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:308:24) +//│ at handleBlock$ (REPL20:1:8689) +//│ at REPL20:1:8771 +//│ ctr = NaN :stackSafe 1000 :effectHandlers @@ -403,8 +423,11 @@ val foo = else n + f(n-1) f(10000) foo -//│ = 50005000 -//│ foo = 50005000 +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: processTerm (JSBackendDiffMaker.scala:191) +//│ ═══[RUNTIME ERROR] Expected: '50005000', got: 'NaN' +//│ = NaN +//│ foo = NaN :re fun foo() = @@ -432,7 +455,9 @@ handle h = Eff with else n + f(n-1) resume(f(10000)) foo(h) -//│ = 50005000 +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: processTerm (JSBackendDiffMaker.scala:191) +//│ ═══[RUNTIME ERROR] Expected: '50005000', got: '()' // function call and defn inside handler :effectHandlers @@ -448,7 +473,9 @@ handle h = Eff with in fun foo(h) = h.perform foo(h) -//│ = 50005000 +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: processTerm (JSBackendDiffMaker.scala:191) +//│ ═══[RUNTIME ERROR] Expected: '50005000', got: '()' :re :effectHandlers @@ -504,5 +531,8 @@ fun sum(n) = n + sum(n - 1) fun bad() = sum(10000) + sum(10000) bad() -//│ = 100010000 +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: processTerm (JSBackendDiffMaker.scala:191) +//│ ═══[RUNTIME ERROR] Expected: '100010000', got: 'NaN' +//│ = NaN