diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index 3a4fb35ae2..807764b2cb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -75,7 +75,7 @@ class BlockTransformer(subst: SymbolSubst): applyDefn(defn): defn2 => val rst2 = applySubBlock(rst) if (defn2 is defn) && (rst2 is rst) then b else Define(defn2, rst2) - case HandleBlock(l, res, par, args, cls, hdr, bod, rst) => + case h @ HandleBlock(l, res, par, args, cls, hdr, bod, rst) => val l2 = applyLocal(l) val res2 = applyLocal(res) applyPath(par): par2 => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala index 98aada13fd..febede7677 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala @@ -126,6 +126,7 @@ class BlockTraverser: cls.traverse applyPath(path) case Case.Tup(len, inf) => () + case Case.Field(_, _) => () def applyHandler(hdr: Handler): Unit = hdr.sym.traverse diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 761abd1b04..b9408c5bc1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -13,6 +13,9 @@ import semantics.* import semantics.Elaborator.ctx import semantics.Elaborator.State import hkmc2.Config.EffectHandlers +import scala.collection.mutable +import scala.util.boundary +import hkmc2.codegen.js.JSBuilder object HandlerLowering: @@ -20,6 +23,12 @@ object HandlerLowering: private val nextIdent: Tree.Ident = Tree.Ident("next") private val lastIdent: Tree.Ident = Tree.Ident("last") private val contTraceIdent: Tree.Ident = Tree.Ident("contTrace") + private def unit = Value.Lit(Tree.UnitLit(true)) + private def intLit(i: BigInt) = Value.Lit(Tree.IntLit(i)) + + private def locToStr(loc: Loc) = + val (line, _, col) = loc.origin.fph.getLineColAt(loc.spanStart) + Value.Lit(Tree.StrLit(s"${loc.origin.fileName.last}:${line + loc.origin.startLineNum - 1}:$col")) extension (p: Path) def pc = p.selN(pcIdent) @@ -27,43 +36,49 @@ object HandlerLowering: def next = p.selN(nextIdent) def last = p.selN(lastIdent) def contTrace = p.selN(contTraceIdent) - - extension (b: Block) def userDefinedVars: Set[Local] = b.definedVars.collect: - case s: VarSymbol => s - + private case class LinkState(res: Local, cls: Path, uid: Path) type FnOrCls = Either[BlockMemberSymbol, DefinitionSymbol[? <: ClassLikeDef] & InnerSymbol] - // isTopLevel: - // whether the current block is the top level block, as we do not emit code for continuation class on the top level - // since we cannot return an effect signature on the top level (we are not in a function so return statement are invalid) - // contName: the name of the continuation class - // ctorThis: the path to `this` in the constructor, this is used to insert `return this;` at the end of constructor. - // linkAndHandle: - // a function that takes a LinkState and returns a block that links the continuation class and handles the effect - // this is a convenience function which initializes the continuation class in function context or throw an error in top level + // currentFun: path to the current function for resumption, none if not instrumented like top level or constructor + // thisPath: path to `this` binding if the function is a method, `this` will be rebinded on resumption + // plCnt: how many times to call this function for resumption, as we have arbitrary number of parameter lists + // currentLocals: All locals to be saved and reloaded, this cannot include any variables in outer scopes + // currentStackSafetySym: The symbol to be used for stack safety private case class HandlerCtx( - isTopLevel: Bool, - isHandlerBody: Bool, - contName: Str, - ctorThis: Option[Path], + currentFun: Option[Path], + thisPath: Option[Path], + plCnt: Int, + currentLocals: List[Local], + currentStackSafetySym: Option[FnOrCls], debugInfo: DebugInfo, - linkAndHandle: LinkState => Block, ): - def nestDebugScope(locals: Set[Local], localsFn: Path) = copy(debugInfo = debugInfo.copy(inScopeLocals = - debugInfo.inScopeLocals ++ locals, prevLocalsFn = S(localsFn))) + def isTopLevel = currentFun.isEmpty + def doUnwind(path: Path, loc: Value, stateId: BigInt, restoreList: List[Local])(using paths: HandlerPaths) = + Return(Call(paths.unwindPath, ( + path :: + intLit(plCnt) :: + currentFun.get :: + debugInfo.debugInfoPath :: + loc :: + intLit(stateId) :: + thisPath.getOrElse(unit) :: + intLit(restoreList.length) :: + restoreList.map(_.asPath) + ).map(_.asArg))(true, true), false) - // inScopeLocals: Local variables that are in scope. - // prevLocalsFn: The function that gets the outer function's locals. + // inScopeLocals: All variables that are in scope, including those that come from outer scope. private case class DebugInfo( debugNme: Str, - inScopeLocals: Set[Local], - prevLocalsFn: Opt[Path], - ) + debugInfoPath: Path, + inScopeLocals: Set[Local], // TODO: Remove this after scoped block is implemented. + ): + def nest(debugNme: Str, debugInfoPath: Path, locals: List[Local]) = copy( + debugNme = debugNme, debugInfoPath, inScopeLocals = inScopeLocals ++ locals) private object DebugInfo: - def topLevel(debugNme: Str) = DebugInfo(debugNme, Set.empty, N) + def topLevel(debugNme: Str, locals: Set[Local]) = DebugInfo(debugNme, Value.Lit(Tree.UnitLit(true)), locals) type StateId = BigInt @@ -78,36 +93,18 @@ class HandlerPaths(using Elaborator.State): val handleBlockImplPath: Path = runtimePath.selSN("handleBlockImpl") val stackDelayClsPath: Path = runtimePath.selSN("StackDelay") val topLevelEffectPath: Path = runtimePath.selSN("topLevelEffect") - - def isHandlerClsPath(p: Path) = - (p eq contClsPath) || (p eq stackDelayClsPath) || (p eq effectSigPath) + val enterHandleBlockPath: Path = runtimePath.selSN("enterHandleBlock") + val stackDepthIdent = new Tree.Ident("stackDepth") + val stackDepthPath: Path = runtimePath.selN(stackDepthIdent) + val fnLocalsPath: Path = runtimePath.selSN("FnLocalsInfo").selSN("class") + val localVarInfoPath: Path = runtimePath.selSN("LocalVarInfo").selSN("class") + val unwindPath: Path = runtimePath.selSN("unwind") + val isResuming: Path = runtimePath.selSN("isResuming") + val resumePc: Path = runtimePath.selSN("resumePc") + val resumeValueIdent = new Tree.Ident("resumeValue") + val resumeValue: Path = runtimePath.selN(resumeValueIdent) class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, Elaborator.State, Elaborator.Ctx): - - private def funcLikeHandlerCtx(ctorThis: Option[Path], isHandlerMtd: Bool, contNme: Str, debugNme: Str)(using h: HandlerCtx) = - HandlerCtx(false, false, contNme, ctorThis, h.debugInfo.copy(debugNme), state => - blockBuilder - .assignFieldN(state.res.asPath.contTrace.last, nextIdent, Instantiate( - mut = true, - state.cls, - state.uid.asArg :: Nil)) - .assignFieldN(state.res.asPath.contTrace, lastIdent, state.res.asPath.contTrace.last.next) - .ret(state.res.asPath)) - private def functionHandlerCtx(nme: Str, debugNme: Str)(using HandlerCtx) = funcLikeHandlerCtx(N, false, nme, debugNme) - private def topLevelCall(state: LinkState) = Call( - paths.topLevelEffectPath, - state.res.asPath.asArg :: Value.Lit(Tree.BoolLit(opt.debug)).asArg :: Nil - )(true, false) - private def topLevelCtx(nme: Str, debugNme: Str) = HandlerCtx( - true, false, nme, N, DebugInfo.topLevel(debugNme), - state => Assign( - state.res, - topLevelCall(state), - End()) - ) - private def ctorCtx(ctorThis: Path, nme: Str, debugNme: Str)(using HandlerCtx) = funcLikeHandlerCtx(S(ctorThis), false, nme, debugNme) - private def handlerMtdCtx(nme: Str, debugNme: Str)(using HandlerCtx) = funcLikeHandlerCtx(N, true, nme, debugNme) - private def handlerCtx(using HandlerCtx): HandlerCtx = summon private def freshTmp(dbgNme: Str = "tmp") = new TempSymbol(N, dbgNme) @@ -127,41 +124,6 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, .map((fun, _)) case _ => N - object ResumptionPoint: - private val resumptionSymbol = freshTmp("resumptionPoint") - def apply(res: Local, uid: StateId, rest: Block) = - Assign(res, PureCall(Value.Ref(resumptionSymbol), List(Value.Lit(Tree.IntLit(uid)))), rest) - def unapply(blk: Block) = blk match - case Assign(res, PureCall(Value.Ref(`resumptionSymbol`, _), List(Value.Lit(Tree.IntLit(uid)))), rest) => - Some(res, uid, rest) - case _ => None - - object ReturnCont: - private val returnContSymbol = freshTmp("returnCont") - def apply(res: Local, uid: StateId) = - Assign(res, PureCall(Value.Ref(returnContSymbol), List(Value.Lit(Tree.IntLit(uid)))), End("")) - def unapply(blk: Block) = blk match - case Assign(res, PureCall(Value.Ref(`returnContSymbol`, _), List(Value.Lit(Tree.IntLit(uid)))), _) => - Some(res, uid) - case _ => None - - // placeholder for effectful Results, such as Call, Instantiate and anything else that could - // return a continuation - object ResultPlaceholder: - private val callSymbol = freshTmp("resultPlaceholder") - def apply(res: Local, uid: StateId, r: Result, rest: Block) = - Assign( - res, - PureCall(Value.Ref(callSymbol), List(Value.Lit(Tree.IntLit(uid)))), - Assign(res, r, rest)) - def unapply(blk: Block) = blk match - case Assign( - res, - PureCall(Value.Ref(`callSymbol`, _), List(Value.Lit(Tree.IntLit(uid)))), - Assign(_, c, rest)) => - Some(res, uid, c, rest) - case _ => None - object StateTransition: private val transitionSymbol = freshTmp("transition") def apply(uid: StateId) = @@ -170,190 +132,121 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, case Return(PureCall(Value.Ref(`transitionSymbol`, _), List(Value.Lit(Tree.IntLit(uid)))), false) => S(uid) case _ => N - - object FnEnd: - private val fnEndSymbol = freshTmp("fnEnd") - def apply() = Return(PureCall(Value.Ref(fnEndSymbol), Nil), false) + + object Unwind: + private val unwindSymbol = freshTmp("unwind") + def apply(uid: StateId, loc: Value) = + Return(PureCall(Value.Ref(unwindSymbol), List(Value.Lit(Tree.IntLit(uid)), loc)), false) def unapply(blk: Block) = blk match - case Return(PureCall(Value.Ref(`fnEndSymbol`, _), Nil), false) => true - case _ => false + case Return(PureCall(Value.Ref(`unwindSymbol`, _), List(Value.Lit(Tree.IntLit(uid)), loc: Value)), false) => + S(uid, loc) + case _ => N + + abstract class LazyId: + private var id: Opt[StateId] = N + protected def getImpl: StateId + def get: StateId = id match + case S(value) => value + case N => + val value = getImpl + id = S(value) + value + def isUsed: Bool = id.isDefined + def transitionOrBlk(blk: => Block) = + if isUsed then StateTransition(get) else blk - private class FreshId: - // IMPORTANT: this must be >= 1 otherwise we get state ID collions with the "entry" state 0. - var id: Int = 1 + private class IdAllocator: + var id: Int = 0 def apply() = val tmp = id id += 1 tmp - private val freshId = FreshId() - // id: the id of the current state // blk: the block of code within this state - // sym: the variable to which the resumed value should set - case class BlockState(id: StateId, blk: Block, sym: Opt[Local]) - - // Tries to remove states that jump directly to other states - // Note: Currently it doesn't seem to do anything, so it's not used. Maybe the states are already pretty optimal. - /* - def optParts(entryState: BlockState, states: Ls[BlockState]): (BlockState, Ls[BlockState]) = - val statesMap = (entryState :: states).map(state => state.id -> state).toMap - def findEdges(state: BlockState) = - var edges: List[BlockState] = Nil - new BlockTraverser: - applyBlock(state.blk) - override def applyBlock(b: Block): Unit = b match - case StateTransition(id) => edges ::= statesMap(id) - case _ => super.applyBlock(b) - state.id -> edges - // build edges - val edges = (entryState :: states).map(findEdges).toMap - // assume that all states are reachable from the entry point - var dests: Map[StateId, StateId] = Map.empty - var visited: Set[StateId] = Set.empty - - // whether a state purely jumps to another state, and if so, which state it jumps to - def getJmp(state: BlockState): Opt[StateId] = - if state.sym.isDefined then N - else state.blk match - case StateTransition(id) => S(id) - case _ => N - - // build the `dests` map by doing a dfs from the entry state - def dfs(state: BlockState): Unit = - visited += state.id - getJmp(state) match - case None => () - case Some(value) => - dests += (state.id -> value) - for e <- edges(state.id) do - if !visited.contains(e.id) then - dfs(e) - dfs(entryState) - - // cycles should be impossible -- if there are, just don't bother - val sorted = - try topologicalSort(dests).toList - catch case c: CyclicGraphError => - return (entryState, states) - - var finalDests: Map[StateId, StateId] = Map.empty - def dp(state: StateId): StateId = finalDests.get(state) match - case Some(value) => value - case None => - val ret = dests.get(state) match - case None => state - case Some(dest) => dp(dest) - finalDests += (state -> ret) - ret - - val transformer = new BlockTransformer(SymbolSubst()): - override def applyBlock(b: Block): Block = b match - case StateTransition(uid) => StateTransition(dp(uid)) - case _ => super.applyBlock(b) - - def rewriteState(s: BlockState) = s.copy(blk = transformer.applyBlock(s.blk)) - - val rewrittenEntry = rewriteState(entryState) - val rewrittenStates = states.map(rewriteState) - - (rewrittenEntry, rewrittenStates) - */ - - // removes states that are not reachable from any resumption point - def removeUselessStates(states: Ls[BlockState], resumptionPoints: Ls[StateId]): Ls[BlockState] = - def findEdges(state: BlockState) = - var edges: Set[StateId] = Set.empty - new BlockTraverser: - applyBlock(state.blk) - override def applyBlock(b: Block): Unit = b match - case StateTransition(id) => edges += id - case _ => super.applyBlock(b) - state.id -> edges - // build edges - val edges = states.map(findEdges).toMap - - var visited: Set[StateId] = Set.empty - var remaining: Set[StateId] = resumptionPoints.toSet + case class BlockPartition(blk: Block, resumable: Bool) + case class PartitionedBlock(entry: StateId, states: Map[StateId, BlockPartition]) - def dfs(state: StateId): Unit = - visited += state - remaining -= state - for e <- edges(state) do - if !visited.contains(e) then dfs(e) - - while !remaining.isEmpty do - dfs(remaining.head) - - states.filter(state => visited.contains(state.id)) - - def partitionBlock(blk: Block, inclEntryPoint: Bool, labelIds: Map[Symbol, (StateId, StateId)] = Map.empty): Ls[BlockState] = - // for readability :) - case class PartRet(head: Block, states: Ls[BlockState]) - - var resumptionPoints: List[StateId] = List.empty + object EffectfulResult: + def unapply(r: Result) = r match + case c: Call if c.mayRaiseEffects => S(r) + case _: Instantiate => S(r) + case _ => N + + private def partitionBlock(blk: Block)(using h: HandlerCtx): PartitionedBlock = + val result = mutable.HashMap.empty[StateId, BlockPartition] + val allocId = new IdAllocator() - // * returns (truncated input block, child block states) // * blk: The block to transform + // * partitioned: whether we are already in a partitioned state + // * if we are not partitioned, we do not need to jump to afterEnd, + // * this is because we are still in the original block, which shares + // * the same code path. // * labelIds: maps label IDs to the state at the start of the label and the state after the label - // * afterEnd: what state End should jump to, if at all + // * afterEnd: what state End should jump to, if at all // TODO: don't split within Match, Begin and Labels when not needed, ideally keep it intact. // Need careful analysis for this. - def go(blk: Block)(implicit labelIds: Map[Symbol, (StateId, StateId)], afterEnd: Option[StateId]): PartRet = + def go(blk: Block)(using labelIds: Map[Symbol, (LazyId, LazyId)], afterEnd: Option[LazyId], partitioned: Bool): Block = boundary: + // First check if the current block contain any non trivial call, if so we need a partition + + def forceId(blk: Block, resumable: Bool): StateId = blk match + case StateTransition(uid) => + if !result(uid).resumable && resumable then + result(uid) = BlockPartition(result(uid).blk, true) + uid + case _ => + val id = allocId() + result(id) = BlockPartition(blk, resumable) + id + + // sym: the local that stores the result + def doNewEffectPartition(res: Result, rst: Block) = + val stateId = forceId(go(rst)(using partitioned = true), true) + val newBlock = blockBuilder + .assignFieldN(paths.runtimePath, paths.resumeValueIdent, res) + .ifthen( + paths.resumeValue, + Case.Cls(paths.effectSigSym, paths.effectSigPath), + Unwind(stateId, res.toLoc.fold(unit)(locToStr(_))) + ) + .rest(StateTransition(stateId)) + boundary.break(newBlock) + class RestLazyId(rst: Block) extends LazyId: + def getImpl: StateId = forceId(go(rst)(using partitioned = true), false) + def transitionSoft: Block = transitionOrBlk(go(rst)) + + val nonTrivialBlockChecker = new BlockDataTransformer(SymbolSubst()): + override def applyBlock(b: Block) = b match + // Special handling for tail calls + case Return(c @ Call(fun, args), false) => b // Prevents the recursion into applyResult + case _ => super.applyBlock(b) + override def applyResult(r: Result)(k: Result => Block) = r match + case EffectfulResult(r) => + // Fallback case, this may lead to unnecessary vars if it is assign-like + // FIXME: This fall back case might be not needed at all. + doNewEffectPartition(r, k(paths.resumeValue)) + case _ => super.applyResult(r)(k) + + // If current block contains direct effectful result the following call will early exit. + nonTrivialBlockChecker.applyBlock(blk) + blk match - case ResumptionPoint(result, uid, rest) => - resumptionPoints ::= uid - val PartRet(head, states) = go(rest) - PartRet(StateTransition(uid), BlockState(uid, head, S(result)) :: states) - - case Match(scrut, arms, dflt, rest) => - val restParts = go(rest) - val restId: StateId = restParts.head match - case StateTransition(uid) => uid - case _ => freshId() - - val armsParts = arms.map((cse, blkk) => (cse, go(blkk)(using afterEnd = S(restId)))) - val dfltParts = dflt.map(blkk => go(blkk)(using afterEnd = S(restId))) - - val states_ = restParts.states ::: armsParts.flatMap(_._2.states) - val states = dfltParts match - case N => states_ - case S(value) => value.states ::: states_ - - val newArms = armsParts.map((cse, partRet) => (cse, partRet.head)) - - restParts.head match - case StateTransition(_) => - PartRet( - Match(scrut, newArms, dfltParts.map(_.head), StateTransition(restId)), - states - ) - case _ => - PartRet( - Match(scrut, newArms, dfltParts.map(_.head), StateTransition(restId)), - BlockState(restId, restParts.head, N) :: states - ) - case l @ Label(label, loop, body, rest) => - val startId = freshId() // start of body - - val PartRet(restNew, restParts) = go(rest) - - val endId: StateId = restNew match // start of rest - case StateTransition(uid) => uid - case _ => freshId() - - val PartRet(bodyNew, parts) = go(body)(using labelIds + (label -> (startId, endId)), S(endId)) - - restNew match - case StateTransition(_) => - PartRet( - StateTransition(startId), - BlockState(startId, bodyNew, N) :: parts ::: restParts - ) - case _ => - PartRet( - StateTransition(startId), - BlockState(startId, bodyNew, N) :: BlockState(endId, restNew, N) :: parts ::: restParts - ) + + case Match(scrut, arms, dflt, rest) => + val restId = RestLazyId(rest) + val newArms = arms.map((cse, blkk) => (cse, go(blkk)(using afterEnd = S(restId)))) + val newDflt = dflt.map(blkk => go(blkk)(using afterEnd = S(restId))) + Match(scrut, newArms, newDflt, restId.transitionSoft) + + case Label(label, loop, body, rest) => + val restId = RestLazyId(rest) + val startId = new LazyId: + def getImpl = allocId() + val newBody = go(body)(using labelIds + (label -> (startId, restId)), S(restId)) + if startId.isUsed then + result(startId.get) = BlockPartition(Begin(newBody, restId.transitionSoft), false) + StateTransition(startId.get) + else + Label(label, loop, newBody, restId.transitionSoft) case Break(label) => val (start, end) = labelIds.get(label) match @@ -361,9 +254,12 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, msg"Could not find label '${label.nme}'" -> label.toLoc :: Nil, source = Diagnostic.Source.Compilation)) - return PartRet(blk, Nil) + return blk case S(value) => value - PartRet(StateTransition(end), Nil) + if partitioned then + StateTransition(end.get) + else + Break(label) case Continue(label) => val (start, end) = labelIds.get(label) match @@ -371,286 +267,253 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, msg"Could not find label '${label.nme}'" -> label.toLoc :: Nil, source = Diagnostic.Source.Compilation)) - return PartRet(blk, Nil) + return blk case S(value) => value - PartRet(StateTransition(start), Nil) - - // for some reason, blocks sometimes start with Begin(End, ...) - case Begin(End(_), blk) => go(blk) - - case Begin(sub, rest) => - val PartRet(restNew, restParts) = go(rest) - restNew match - case StateTransition(uid) => - val PartRet(subNew, subParts) = go(sub)(using afterEnd = S(uid)) - PartRet(subNew, subParts ::: restParts) - case _ => - val restId = freshId() - val PartRet(subNew, subParts) = go(sub)(using afterEnd = S(restId)) - PartRet(subNew, BlockState(restId, restNew, N) :: subParts ::: restParts) - - case Define(defn, rest) => - val PartRet(head, parts) = go(rest) - PartRet(Define(defn, head), parts) - - // implicit returns is used inside constructors when call occur in tail position, - // which may transition to `return this;` (inserted in second pass) after the implicit return - case End(_) | Return(_, true) => afterEnd match - case None => PartRet(FnEnd(), Nil) - case Some(value) => PartRet(StateTransition(value), Nil) + if partitioned then + StateTransition(start.get) + else + Continue(label) - // identity cases - case Assign(lhs, rhs, rest) => - val PartRet(head, parts) = go(rest) - PartRet(Assign(lhs, rhs, head), parts) + case Begin(sub, rest) => + val restId = RestLazyId(rest) + val newSub = go(sub)(using afterEnd = S(restId)) + Begin(newSub, restId.transitionSoft) + + case End(_) => + if partitioned then + afterEnd.fold(blk)(id => StateTransition(id.get)) + else + blk - case blk @ AssignField(lhs, nme, rhs, rest) => - val PartRet(head, parts) = go(rest) - PartRet(AssignField(lhs, nme, rhs, head)(blk.symbol), parts) + // Currently, implicit returns are only used in top level and tail call of constructor + // The former case never enters the partitioning function, so it must be the later case here. + // We no longer handle the later case, hence we can ignore this case. + // case Return(_, true) => afterEnd match + // case None => End() + // case Some(id) => StateTransition(id) - case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => - val PartRet(head, parts) = go(rest) - PartRet(AssignDynField(lhs, fld, arrayIdx, rhs, head), parts) + // identity cases - case Return(_, _) => PartRet(blk, Nil) + case Define(defn, rest) => Define(defn, go(rest)) + case Assign(lhs, rhs, rest) => Assign(lhs, rhs, go(rest)) + case blk @ AssignField(lhs, nme, rhs, rest) => AssignField(lhs, nme, rhs, go(rest))(blk.symbol) + case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => AssignDynField(lhs, fld, arrayIdx, rhs, go(rest)) + case _: Return => blk // ignored cases case TryBlock(sub, finallyDo, rest) => ??? // ignore - case Throw(_) => PartRet(blk, Nil) + case Throw(_) => blk case _: HandleBlock => lastWords("unexpected handleBlock") // already translated at this point - val PartRet(head, states) = go(blk)(using labelIds, N) - - if inclEntryPoint then BlockState(0, head, N) :: states - else removeUselessStates(states, resumptionPoints) - - private val runtimePath = State.runtimeSymbol.asPath - private val stackDepthIdent = new Tree.Ident("stackDepth") - private val stackDepthPath: Path = runtimePath.selN(stackDepthIdent) - private val fnLocalsPath: Path = runtimePath.selSN("FnLocalsInfo").selSN("class") - private val localVarInfoPath: Path = runtimePath.selSN("LocalVarInfo").selSN("class") - private def createGetLocalsFn(b: Block, extraLocals: Set[Local])(using h: HandlerCtx) = - val locals = (b.userDefinedVars ++ extraLocals) -- h.debugInfo.inScopeLocals - val localsInfo = locals.toList.sortBy(_.uid).map: s => - FlowSymbol(s.nme) -> Instantiate(mut = true, localVarInfoPath, - Value.Lit(Tree.StrLit(s.nme)).asArg :: s.asPath.asArg :: Nil - ) - val startSym = FlowSymbol("prev") - val thisInfo = FlowSymbol("thisInfo") - val arrSym = TempSymbol(N, "arr") - - val body = blockBuilder - .assign(startSym, h.debugInfo.prevLocalsFn match - case None => Tuple(mut = true, Nil) - case Some(value) => PureCall(value, Nil) - ) - .foldLeft(localsInfo): - case (acc, (sym, res)) => acc.assign(sym, res) - .assign(arrSym, Tuple(mut = false, localsInfo.map(v => v._1.asPath.asArg))) - .assign(thisInfo, Instantiate(mut = true, fnLocalsPath, - Value.Lit(Tree.StrLit(h.debugInfo.debugNme)).asArg - :: Value.Ref(arrSym).asArg - :: Nil - )) - .assign(TempSymbol(N, ""), Call(startSym.asPath.selSN("push"), thisInfo.asPath.asArg :: Nil)(false, false)) - .ret(startSym.asPath) - - FunDefn(N, BlockMemberSymbol("getLocals", Nil), PlainParamList(Nil) :: Nil, body) - - var doUnwindMap: Map[FnOrCls, Path] = Map.empty + val initId = allocId() + // Note: initial part will only be resumed if stack safety is on. + val initPart = BlockPartition(go(blk)(using Map(), N, false), opt.stackSafety.isDefined) + result(initId) = initPart + PartitionedBlock(initId, Map.from(result)) + + private def computeRestoreList(parts: PartitionedBlock)(using HandlerCtx): List[Local] = + val localSet = summon[HandlerCtx].currentLocals.toSet + val result = mutable.HashSet.empty[Local] + + def traverseEntry(stateId: StateId) = + val traversed = mutable.HashSet.empty[StateId] + var initialized = Set.empty[Local] + + new BlockTraverserShallow(): + traversed += stateId + applyBlock(parts.states(stateId).blk) + override def applyBlock(blk: Block): Unit = blk match + case Unwind(uid, loc) => () + case StateTransition(uid) => + if !traversed.contains(uid) && !parts.states(uid).resumable then + traversed += stateId + applyBlock(parts.states(uid).blk) + case Assign(lhs, rhs, rest) => + applyResult(rhs) + val saved = initialized + initialized += lhs + applyBlock(rest) + initialized = saved + case Define(defn: ValDefn, rest) => + applyPath(defn.rhs) + val saved = initialized + initialized += defn.sym + applyBlock(rest) + initialized = saved + case Define(defn, rest) => + val saved = initialized + initialized += defn.sym + applyBlock(rest) + initialized = saved + case _ => super.applyBlock(blk) + override def applySymbol(l: Symbol): Unit = + if localSet.contains(l) && !initialized.contains(l) then + result += l + + parts.states.foreach: (stateId, part) => + if part.resumable then traverseEntry(stateId) + + result.toList + + val doUnwindMap: mutable.Map[FnOrCls, Path => Return] = mutable.HashMap.empty /** * The actual translation: - * 1. add call markers, transform class, function, lambda and sub handler blocks - * 2. - * a) generate continuation class - * b) generate normal function body - * 3. float out definitions + * 1. add call markers. rewrite handler blocks in terms of classes and functions + * 2. add debug methods + * 3. class lifter + * 4. state machine transformation of all functions. add unwind and resume state + * generate normal function body */ - // callSelf allows the continuation class of the current block (belonging to some function f or class C) - // to call itself f or C in state 0 in case a stack delay effect was raised, which saves us from duplicating - // all the code in the first state - private def translateBlock(b: Block, extraLocals: Set[Local], callSelf: Opt[Result], fnOrCls: FnOrCls, h: HandlerCtx): Block = - val getLocalsFn = createGetLocalsFn(b, extraLocals)(using h) - given HandlerCtx = h.nestDebugScope(b.userDefinedVars ++ extraLocals, getLocalsFn.sym.asPath) - val stage1 = firstPass(b) - val stage2 = secondPass(stage1, fnOrCls, callSelf, getLocalsFn) - if h.isTopLevel then stage2 else thirdPass(stage2) - - private def firstPass(b: Block)(using HandlerCtx): Block = - val getLocalsSym = ctx.builtins.debug.getLocals - val transformer = new BlockTransformerShallow(SymbolSubst()): - // FIXME: there is a HUGE amount of error-prone, maintenance-heavy manually duplicated code in there to refactor + + private def translateBlock(blk: Block, h: HandlerCtx): Block = + // TODO: add getLocal, similar mechanism as determine resumption. + // All the defined variables are masked away from the inner scope (TODO: Scoped) + given HandlerCtx = h + + def translateFunLike(fun: FunDefn, funcPath: Path, thisPath: Option[Path], debugNme: Str) = + val varList = (fun.body.definedVars ++ fun.params.flatMap(_.params.map(_.sym))) + .filterNot(h.debugInfo.inScopeLocals(_)).toList.sortBy(_.uid) + val debugInfo = Value.Lit(Tree.StrLit(debugNme)).asArg :: varList.zipWithIndex.filter(_._1.isInstanceOf[VarSymbol]) + .flatMap: (sym, idx) => + List(intLit(idx), Value.Lit(Tree.StrLit(sym.nme))) + .map(_.asArg) + val debugInfoSym = freshTmp(s"$debugNme$$debugInfo") + val newCtx = HandlerCtx(S(funcPath), thisPath, fun.params.length, varList, S(L(fun.sym)), + h.debugInfo.nest(debugNme, if opt.debug then debugInfoSym.asPath else unit, varList)) + val bod2 = translateBlock(fun.body, newCtx) + val fun2 = if fun.body is bod2 then fun else + FunDefn(fun.owner, fun.sym, fun.params, bod2) + (debugInfoSym, debugInfo, fun2) + + val subblockTransform = new BlockTransformer(SymbolSubst()): + override def applyDefn(defn: Defn)(k: Defn => Block): Block = defn match + case fun: FunDefn => + if !h.isTopLevel then + raise(WarningReport(msg"Unexpected nested function: lambdas may not function correctly." -> fun.sym.toLoc :: Nil, source = Diagnostic.Source.Compilation)) + val (debugInfoSym, debugInfo, fun2) = translateFunLike(fun, Value.Ref(fun.sym, N), N, fun.sym.nme) + if opt.debug then Assign(debugInfoSym, Tuple(false, debugInfo), k(fun2)) else k(fun2) + case ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, methods, privateFields, publicFields, preCtor, ctor, companion, bufferable) => + if !h.isTopLevel then + raise(WarningReport(msg"Unexpected nested class: lambdas may not function correctly." -> isym.toLoc :: Nil, source = Diagnostic.Source.Compilation)) + val debugInfos = mutable.ArrayBuffer.empty[(Local, List[Arg])] + val newMtds = methods.map: f => + val (debugInfoSym, debugInfo, fun2) = translateFunLike(f, Value.Ref(isym).sel(new Tree.Ident(f.sym.nme), f.sym.asTrm.get), + S(Value.Ref(isym)), s"${sym.nme}#${f.sym.nme}") + debugInfos += debugInfoSym -> debugInfo + fun2 + val companion2 = companion.map: bod => + val newMtds = bod.methods.map: f => + val (debugInfoSym, debugInfo, fun2) = translateFunLike(f, Value.Ref(bod.isym).sel(new Tree.Ident(f.sym.nme), f.sym.asTrm.get), + S(Value.Ref(bod.isym)), s"${sym.nme}.${f.sym.nme}") + debugInfos += debugInfoSym -> debugInfo + fun2 + // We cannot use this bc there is no subblock transform... + // val newCtor = translateTrivialOrTopLevel(bod.ctor) + // TODO: Companion's ctor is more well behaved, handle this properly. + val newCtor = translateCtorLike(bod.ctor) + tl.log(s"companion name: ${bod.isym.nme}") + ClsLikeBody(bod.isym, newMtds, bod.privateFields, bod.publicFields, newCtor) + val c2 = ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, newMtds, privateFields, publicFields, translateCtorLike(preCtor), translateCtorLike(ctor), companion2, bufferable) + if opt.debug then + debugInfos.foldRight(k(c2)): (elem, blk) => + Assign(elem._1, Tuple(false, elem._2), blk) + else k(c2) + case _ => super.applyDefn(defn)(k) + val b = subblockTransform.applyBlock(blk) + if h.isTopLevel then + return translateTrivialOrTopLevel(b) + val parts = partitionBlock(b) + if parts.states.size <= 1 && opt.stackSafety.isEmpty then + return translateTrivialOrTopLevel(b) + val vars = if opt.debug then h.currentLocals else computeRestoreList(parts) + h.currentStackSafetySym.foreach: fnOrCls => + doUnwindMap += + fnOrCls -> (res => h.doUnwind(res, fnOrCls.fold(_.toLoc, _.toLoc).fold(unit)(locToStr(_)), parts.entry, vars)(using paths)) + + val pcVar = freshTmp("pc") + val mainLoopLbl = freshTmp("main") + + val postTransform = new BlockTransformerShallow(SymbolSubst()): override def applyBlock(b: Block) = b match - case b: HandleBlock => - val rest = applyBlock(b.rest) - translateHandleBlock(b.copy(rest = rest)) - // This block optimizes tail-calls in the handler transformation. We do not optimize implicit returns. - // Implicit returns are used in top level and constructor: - // For top level, this correspond to the last statement which should also be checked for effect. - // For constructor, we will append `return this;` after the implicit return so it is not a tail call. - case Return(c @ Call(fun, args), false) if !handlerCtx.isHandlerBody => - applyPath(fun): fun2 => - applyArgs(args): args2 => - val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun, c.mayRaiseEffects) - if c2 is c then b else Return(c2, false) - // Optimization to avoid generation of unnecessary variables - case Assign(lhs, c @ Call(fun, args), rest) if c.mayRaiseEffects => - applyPath(fun): fun2 => - applyArgs(args): args2 => - val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun, c.mayRaiseEffects) - ResultPlaceholder(lhs, freshId(), c2, applyBlock(rest)) - case Assign(lhs, c @ Instantiate(mut, cls, args), rest) => - applyPath(cls): cls2 => - applyArgs(args): args2 => - val c2 = if (cls2 is cls) && (args2 is args) then c else Instantiate(mut, cls2, args2) - ResultPlaceholder(lhs, freshId(), c2, applyBlock(rest)) + case StateTransition(uid) => + Assign(pcVar, Value.Lit(Tree.IntLit(uid)), Continue(mainLoopLbl)) + case Unwind(uid, loc) => + h.doUnwind(paths.resumeValue, loc, uid, vars)(using paths) case _ => super.applyBlock(b) - override def applyResult(r: Result)(k: Result => Block): Block = r match - case c @ Call(fun, args) if c.mayRaiseEffects => - val res = freshTmp("res") - applyPath(fun): fun2 => - applyArgs(args): args2 => - val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun, c.mayRaiseEffects) - ResultPlaceholder(res, freshId(), c2, k(Value.Ref(res))) - case c @ Instantiate(mut, cls, args) => - val res = freshTmp("res") - applyPath(cls): cls2 => - applyArgs(args): args2 => - val c2 = if (cls2 is cls) && (args2 is args) then c else Instantiate(mut, cls2, args2) - ResultPlaceholder(res, freshId(), c2, k(Value.Ref(res))) - case r => super.applyResult(r)(k) - override def applyPath(p: Path)(k: Path => Block): Block = p match - case Value.Ref(`getLocalsSym`, _) => k(handlerCtx.debugInfo.prevLocalsFn.get) - case _ => super.applyPath(p)(k) - override def applyLam(lam: Lambda): Lambda = - // This should normally be unreachable due to prior desugaring of lambda - raise(InternalError(msg"Unexpected lambda during handler lowering" -> lam.toLoc :: Nil, - source = Diagnostic.Source.Compilation)) - Lambda(lam.params, translateBlock(lam.body, lam.params.paramSyms.toSet, N, L(BlockMemberSymbol("", Nil, false)), functionHandlerCtx(s"Cont$$lambda$$", "‹lambda›"))) - override def applyDefn(defn: Defn)(k: Defn => Block): Block = defn match - case f: FunDefn => k(translateFun(f)) - case c: ClsLikeDefn => k(translateCls(c)) - case _: ValDefn => super.applyDefn(defn)(k) - transformer.applyBlock(b) - - private def secondPass(b: Block, fnOrCls: FnOrCls, callSelf: Opt[Result], getLocalsFn: FunDefn)(using h: HandlerCtx): Block = - val cls = if handlerCtx.isTopLevel then N else genContClass(b, callSelf) - - val ret = cls match - case None => genNormalBody(b, BlockMemberSymbol("", Nil), N) - case Some(cls) => - // create the doUnwind function - val doUnwindSym = BlockMemberSymbol("doUnwind", Nil, true) - doUnwindMap += fnOrCls -> doUnwindSym.asPath - val pcSym = VarSymbol(Tree.Ident("pc")) - val resSym = VarSymbol(Tree.Ident("res")) - val doUnwindBlk = h.linkAndHandle( - LinkState(resSym, cls.sym.asPath, pcSym.asPath) - ) - val doUnwindDef = FunDefn( - N, doUnwindSym, - PlainParamList(Param.simple(resSym) :: Param.simple(pcSym) :: Nil) :: Nil, - doUnwindBlk - ) - val doUnwindLazy = Lazy(doUnwindSym.asPath) - val rst = genNormalBody(b, cls.sym, S(doUnwindLazy)) - - if doUnwindLazy.isEmpty && opt.stackSafety.isEmpty then - blockBuilder - .define(cls) - .rest(rst) - else - blockBuilder - .define(cls) - .define(doUnwindDef) - .rest(rst) - if opt.debug then - Define(getLocalsFn, ret) - else - ret - - // moves definitions to the top level of the block - private def thirdPass(b: Block): Block = - // to ensure the fun and class references in the continuation class are properly scoped, - // we move all function defns to the top level of the handler block - val (blk, defns) = b.floatOutDefns() - defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) - - private def locToStr(l: Loc): Str = - Scope.replaceInvalidCharacters(l.origin.fileName.last + "_L" + l.origin.startLineNum + "_" + l.spanStart + "_" + l.spanEnd) - - private def symToStr(s: Symbol): Str = - s"${Scope.replaceInvalidCharacters(s.nme)}" - - private def translateFun(f: FunDefn)(using HandlerCtx): FunDefn = - val callSelf = f.params match - case pList :: Nil => - val params = pList.params.map(p => p.sym.asPath.asArg) - f.owner match - case None => S(Call(f.sym.asPath, params)(true, true)) - case Some(owner) => - S(Call(Select(owner.asPath, Tree.Ident(f.sym.nme))(N), params)(true, true)) - case _ => None // TODO: more than one plist - - FunDefn(f.owner, f.sym, f.params, translateBlock(f.body, - f.params.flatMap(_.paramSyms).toSet, - callSelf, - L(f.sym), - functionHandlerCtx(s"Cont$$func$$${symToStr(f.sym)}$$", f.sym.nme)) - ) - - private def translateBody(cls: ClsLikeBody, sym: BlockMemberSymbol)(using HandlerCtx): ClsLikeBody = - val curCtorCtx = - if handlerCtx.isTopLevel - then - topLevelCtx(s"Cont$$modCtor$$${symToStr(sym)}$$", s"‹constructor of ${sym.nme}›") - else ctorCtx( - cls.isym.asPath, - s"Cont$$ctor$$${symToStr(sym)}$$", s"‹constructor of ${sym.nme}›") - ClsLikeBody( - cls.isym, - cls.methods.map(translateFun), - cls.privateFields, - cls.publicFields, - translateBlock(cls.ctor, Set.empty, N, R(cls.isym), curCtorCtx), - ) + + val arms = parts.states.toList.map: (id, part) => + Case.Lit(Tree.IntLit(id)) -> + postTransform.applyBlock(part.blk) + + val mainLoop = Label(mainLoopLbl, true, Match(Value.Ref(pcVar), arms, N, End()), End()) + + val getSavedTmp = freshTmp("saveOffset") + def getSaved(off: BigInt): (Block => Block, Path) = + if off == 0 then + return (id, DynSelect(paths.runtimePath.selSN("resumeArr"), paths.runtimePath.selSN("resumeIdx"), true)) + val computeOff = Assign(getSavedTmp, Call(State.builtinOpsMap("+").asPath, paths.runtimePath.selSN("resumeIdx").asArg :: intLit(off).asArg :: Nil)(false, false), _) + (computeOff, DynSelect(paths.runtimePath.selSN("resumeArr"), getSavedTmp.asPath, true)) + + val restoreVars = vars.zipWithIndex.foldLeft(blockBuilder.assign(pcVar, paths.resumePc)): + case (builder, (local, idx)) => + val (computeOff, savePath) = getSaved(idx) + builder.chain(computeOff).assign(local, savePath) + + Match( + paths.isResuming, + Case.Lit(Tree.BoolLit(true)) -> + restoreVars + .assignFieldN(paths.runtimePath, new Tree.Ident("isResuming"), Value.Lit(Tree.BoolLit(false))).end :: Nil, + S(Assign(pcVar, intLit(parts.entry), End())), + mainLoop) - private def translateCls(cls: ClsLikeDefn)(using HandlerCtx): ClsLikeDefn = - val curCtorCtx = ctorCtx( - cls.isym.asPath, - s"Cont$$ctor$$${symToStr(cls.sym)}$$", s"‹constructor of ${cls.sym.nme}›") - cls.copy(methods = cls.methods.map(translateFun), - ctor = translateBlock(cls.ctor, Set.empty, N, R(cls.isym), curCtorCtx), - companion = cls.companion.map(translateBody(_, cls.sym))) // TODO: callSelf + private def translateCtorLike(b: Block)(using h: HandlerCtx): Block = + translateBlock(b, HandlerCtx(N, N, 0, b.definedVars.filterNot(h.debugInfo.inScopeLocals(_)).toList, N, + h.debugInfo.nest("ctor-like block", unit, b.definedVars.toList))) + + private def translateTrivialOrTopLevel(b: Block)(using HandlerCtx): Block = + def topLevelCheck(l: Local, r: Result, rst: Block): Block = + blockBuilder + .assign(l, r) + .ifthen( + l.asPath, + Case.Cls(paths.effectSigSym, paths.effectSigPath), + Assign(l, Call(paths.topLevelEffectPath, l.asPath.asArg :: Value.Lit(Tree.BoolLit(opt.debug)).asArg :: Nil)(true, false), End()), + N) + .rest(rst) + val trivialTransform = new BlockTransformerShallow(SymbolSubst()): + override def applyBlock(b: Block) = b match + // Important: trivial block that contain tail call also pass through this, important to not treat those call as top level + // TODO: separate trivial logic (debugging) and top level (sanity checks and print stack effect) + case Return(EffectfulResult(r), false) => b + case Assign(lhs, EffectfulResult(r), rest) => + // Optimization to reuse lhs instead of fresh local + topLevelCheck(lhs, r, applyBlock(rest)) + case _ => super.applyBlock(b) + override def applyResult(r: Result)(k: Result => Block) = r match + case EffectfulResult(r) => + // Fallback case, this may lead to unnecessary vars if it is assign-like + // FIXME: This fall back case might be not needed at all. + val l = freshTmp() + topLevelCheck(l, r, k(Value.Ref(l))) + case _ => super.applyResult(r)(k) + trivialTransform.applyBlock(b) - // Handle block becomes a FunDefn and CallPlaceholder - private def translateHandleBlock(h: HandleBlock)(using HandlerCtx): Block = - val sym = BlockMemberSymbol(s"handleBlock$$", Nil) - val lbl = freshTmp("handlerBody") - val lblLoop = freshTmp("handlerLoop") - - val handlerBody = translateBlock( - h.body, Set.empty, S(Call(sym.asPath, Nil)(true, false)), L(sym), - HandlerCtx( - false, true, - s"Cont$$handleBlock$$${symToStr(h.lhs)}$$", N, - handlerCtx.debugInfo.copy(debugNme = s"‹handler body of ${h.lhs.nme}›"), - state => blockBuilder - .assignFieldN(state.res.asPath.contTrace.last, nextIdent, Instantiate(true, state.cls, state.uid.asArg :: Nil)) - .ret(PureCall(paths.handleBlockImplPath, state.res.asPath :: h.lhs.asPath :: Nil)) - ) - ) + // Handle block is rewritten into: + // 1. Instantiation of the handler + // 2. An effectful call to enterHandleBlock + private def translateHandleBlockShallow(h: HandleBlock): Block = + val sym = new BlockMemberSymbol("handleBlock$", Nil, false) + + val bodyDefn = FunDefn(N, sym, PlainParamList(Nil) :: Nil, h.body) val handlerMtds = h.handlers.map: handler => - val sym = BlockMemberSymbol("hdlrFun", Nil, true) - val mtdBdy = translateBlock(handler.body, - handler.params.flatMap(_.paramSyms).toSet, N, L(sym), // TODO: callSelf - handlerMtdCtx(s"Cont$$handler$$${symToStr(h.lhs)}$$${symToStr(handler.sym)}$$", handler.sym.nme)) + val sym = BlockMemberSymbol(h.cls.nme + handler.sym.nme, Nil, true) val fDef = FunDefn( N, sym, PlainParamList(Param(FldFlags.empty, handler.resumeSym, N, Modulefulness.none) :: Nil) :: Nil, - mtdBdy + handler.body ) FunDefn( S(h.cls), @@ -658,18 +521,11 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, Define( fDef, Return(PureCall(paths.mkEffectPath, h.cls.asPath :: Value.Ref(sym, N) :: Nil), false))) - + // Some limited handling of effects extending classes and having access to their fields. // Currently does not support super() raising effects. - val tmp = freshTmp() - val ctor = blockBuilder - .assign(tmp, Call(Value.Ref(State.builtinOpsMap("super")), h.args.map(_.asArg))(true, true)) - .ret(h.cls.asPath) - - val ctorT = translateBlock(ctor, Set.empty, N, R(h.cls), // TODO: callSelf - ctorCtx(h.cls.asPath, s"Cont$$ctor$$${symToStr(sym)}$$", s"‹constructor of ${sym.nme}›") - ) - + // TODO: prob fixed (?) ^^^ + val clsDefn = ClsLikeDefn( N, // no owner h.cls, @@ -677,273 +533,38 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, syntax.Cls, N, Nil, S(h.par), handlerMtds, Nil, Nil, - Assign(freshTmp(), Call(Value.Ref(State.builtinOpsMap("super")), h.args.map(_.asArg))(true, true), End()), + // Apparently, the lifter is not happy with any assignment in the preCtor... + Return(Call(Value.Ref(State.builtinOpsMap("super")), h.args.map(_.asArg))(true, true), true), End(), N, - N, // TODO: bufferable? - ) // TODO: handle effect in super call - // NOTE: the super call is inside the preCtor - // during resumption we need to resume both the this.x = x bindings done in JSBuilder and the ctor - - val body = blockBuilder + N, + ) + + blockBuilder .define(clsDefn) .assign(h.lhs, Instantiate(mut = true, Value.Ref(clsDefn.sym, S(h.cls)), Nil)) - .rest(handlerBody) - - val defn = FunDefn( - N, // no owner - sym, PlainParamList(Nil) :: Nil, body) - - val result = blockBuilder - .define(defn) - .rest( - ResultPlaceholder(h.res, freshId(), Call(sym.asPath, Nil)(true, true), h.rest) - ) - result + .define(bodyDefn) + .assign(h.res, Call(paths.enterHandleBlockPath, List(h.lhs.asPath.asArg, Value.Ref(sym, N).asArg))(true, true)) + .rest(h.rest) - private def genContClass(b: Block, callSelf: Opt[Result])(using h: HandlerCtx): Opt[ClsLikeDefn] = - val clsSym = ClassSymbol( - Tree.DummyTypeDef(syntax.Cls), - Tree.Ident(handlerCtx.contName) - ) - - val pcVar = VarSymbol(pcIdent) - - val loopLbl = freshTmp("contLoop") - val pcSymbol = TermSymbol(ParamBind, S(clsSym), pcIdent) - - // This maps each state id to an optional location - // Note that the value is an Option, and None must be inserted even if the location is not known - // so that we can use the same map to enumerate all possible state id and check if there is any state id - val pcToLoc = collection.mutable.Map.empty[StateId, Option[Loc]] - var containsCall = false - - // Create the DoUnwind function - doUnwindMap += R(clsSym) -> Select(clsSym.asPath, Tree.Ident("doUnwind"))( - N /* this refers to the method defined in Runtime.FunctionContFrame */ - ) - val newPcSym = VarSymbol(Tree.Ident("newPc")) - val resSym = VarSymbol(Tree.Ident("res")) - val doUnwindBlk = blockBuilder - .assign(pcSymbol, newPcSym.asPath) - .assignFieldN(resSym.asPath.contTrace.last, nextIdent, clsSym.asPath) - .assignFieldN(resSym.asPath.contTrace, lastIdent, clsSym.asPath) - .ret(resSym.asPath) - - // Replaces ResultPlaceholders to check for effects and link the effect trace - def prepareBlock(b: Block): Block = - val transform = new BlockTransformerShallow(SymbolSubst()): - override def applyResult(r: Result)(k: Result => Block): Block = - r match - case c @ Call(Value.Ref(s: BuiltinSymbol, _), _) => () - case c: Call if !c.mayRaiseEffects => () - case _: Call | _: Instantiate => containsCall = true - case _ => () - super.applyResult(r)(k) - - override def applyBlock(b: Block): Block = b match - case Define(_: (ClsLikeDefn | FunDefn), rst) => applyBlock(rst) - case ResultPlaceholder(res, uid, c, rest) => - pcToLoc(uid) = c.toLoc - containsCall = true - blockBuilder - .assign(res, c) - .ifthen( - res.asPath, - Case.Cls(paths.effectSigSym, paths.effectSigPath), - ReturnCont(res, uid) - ) - .chain(ResumptionPoint(res, uid, _)) - .rest(applyBlock(rest)) - case _ => super.applyBlock(b) - transform.applyBlock(b) - val actualBlock = handlerCtx.ctorThis match - case N => prepareBlock(b) - case S(thisPath) => Begin(prepareBlock(b), Return(thisPath, false)) - // If there is no state id found during prepareBlock, the block is trivial. - val trivial = pcToLoc.isEmpty - - // there are three types of functions: - // (1) functions that have no calls, indicated by `containsCall` - // (2) functions that have only tail calls, indicated by `trivial` - // (3) all other functions - // - // Here, (2) and (3) need a continuation class when stack safety is enabled, otherwise only (3) needs it - // If (2) and stack safety is enabled, we can just create a continuation class with one state - - if trivial && opt.stackSafety.isEmpty then return N // case (1) or (2) if no stack safety - if !containsCall then return N // case (1) - - val depthSym = freshTmp("curDepth") - val resumedVal = VarSymbol(Tree.Ident("value$")) - - def createResumeBod = - val parts = - if opt.stackSafety.isDefined then callSelf match - case None => partitionBlock(actualBlock, true) - case Some(value) => - val someParts = partitionBlock(actualBlock, false) - BlockState(0, Return(value, false), N) :: someParts - else - partitionBlock(actualBlock, false) - - def transformPart(blk: Block): Block = - val transform = new BlockTransformerShallow(SymbolSubst()): - override def applyBlock(b: Block): Block = b match - case ReturnCont(res, uid) => Return(Call( - Select(clsSym.asPath, Tree.Ident("doUnwind"))( - N /* this refers to the method defined in Runtime.FunctionContFrame */ - ), - res.asPath.asArg :: Value.Lit(Tree.IntLit(uid)).asArg :: Nil)(true, false), - false - ) - case StateTransition(uid) => - blockBuilder - .assign(pcSymbol, Value.Lit(Tree.IntLit(uid))) - .continue(loopLbl) - case FnEnd() => - blockBuilder.break(loopLbl) - case _ => super.applyBlock(b) - transform.applyBlock(blk) - - // match block representing the function body - val mainMatchCases = parts.toList.map(b => (Case.Lit(Tree.IntLit(b.id)), transformPart(b.blk))) - val mainMatchBlk = Match( - pcSymbol.asPath, - mainMatchCases, - N, - End() - ) - - val tmp = freshTmp() - val withResetDepth = - if opt.stackSafety.isDefined && !trivial then - AssignField(runtimePath, stackDepthIdent, depthSym.asPath, mainMatchBlk)(N) - else mainMatchBlk - - val lbl = blockBuilder.label(loopLbl, loop = true, withResetDepth).rest(End()) - - def createAssignment(sym: Local) = Assign(sym, resumedVal.asPath, End()) - - val assignedResumedCases = for - b <- parts - sym <- b.sym - yield Case.Lit(Tree.IntLit(b.id)) -> createAssignment(sym) // NOTE: assume sym is in localsMap - - // assigns the resumed value - val body = - if assignedResumedCases.isEmpty then - lbl - else - Match( - pcSymbol.asPath, - assignedResumedCases, - N, - lbl - ) - - // assign cur depth - if opt.stackSafety.isDefined && !trivial then - Assign(depthSym, stackDepthPath, body) - else - body - - val resumeBody = - if trivial then callSelf match - case None => actualBlock - case Some(value) => Return(value, false) - else createResumeBod - - - val resumeSym = BlockMemberSymbol("resume", List()) - val resumeFnDef = FunDefn( - S(clsSym), // owner - resumeSym, - List(PlainParamList(List(Param(FldFlags.empty, resumedVal, N, Modulefulness.none)))), - resumeBody - ) + def translateHandleBlocks(b: Block): Block = - val debugMtds = if !opt.debug then Nil else - - val getLocalsSym = BlockMemberSymbol("getLocals", List()) - - val localsRes = h.debugInfo.prevLocalsFn match - case Some(value) => PureCall(value, Nil) - case None => Tuple(mut = true, Nil) - - val getLocalsFnDef = FunDefn( - S(clsSym), - getLocalsSym, - List(), - Return(localsRes, false) - ) - - val getLocSym = BlockMemberSymbol("getLoc", List()) - val getLocFnDef = FunDefn( - S(clsSym), - getLocSym, - List(), - Match(pcSymbol.asPath, pcToLoc.toSortedMap.iterator.map: (stateId, loc) => - Case.Lit(Tree.IntLit(stateId)) -> Return(Value.Lit(loc.fold(Tree.UnitLit(true)): loc => - val (line, _, col) = loc.origin.fph.getLineColAt(loc.spanStart) - Tree.StrLit(s"${loc.origin.fileName.last}:${line + loc.origin.startLineNum - 1}:$col") - ), false) - .toList, N, End()), - ) - - getLocalsFnDef :: getLocFnDef :: Nil - - val mtds = resumeFnDef :: debugMtds - - S(ClsLikeDefn( - N, // no owner - clsSym, - BlockMemberSymbol(clsSym.nme, Nil), - syntax.Cls, - N, - PlainParamList({ - val p = Param(FldFlags.empty.copy(isVal = true), pcVar, N, Modulefulness.none) - pcVar.decl = S(p) - p - } :: Nil) :: Nil, - S(paths.contClsPath), - mtds, - Nil, - Nil, - Assign(freshTmp(), PureCall( - Value.Ref(State.builtinOpsMap("super")), // refers to runtime.FunctionContFrame which is pure - Value.Lit(Tree.UnitLit(true)) :: Nil), End()), - AssignField( - clsSym.asPath, - pcVar.id, - Value.Ref(pcVar), - End() - )(S(pcSymbol)), - N, - N, // TODO: bufferable? - )) - - private def genNormalBody(b: Block, clsSym: BlockMemberSymbol, doUnwind: Opt[Lazy[Path]])(using HandlerCtx): Block = - val transform = new BlockTransformerShallow(SymbolSubst()): - override def applyBlock(b: Block): Block = b match - case ResultPlaceholder(res, uid, c, rest) => - val doUnwindBlk = doUnwind match - case None => Assign(res, topLevelCall(LinkState(res, clsSym.asPath, Value.Lit(Tree.IntLit(uid)))), End()) - case Some(doUnwind) => Return(PureCall(doUnwind.get_!, res.asPath :: Value.Lit(Tree.IntLit(uid)) :: Nil), false) - blockBuilder - .assign(res, c) - .ifthen( - res.asPath, - Case.Cls(paths.effectSigSym, paths.effectSigPath), - doUnwindBlk - ) - .rest(applyBlock(rest)) + val transform = new BlockTransformer(SymbolSubst()): + override def applyBlock(b: Block) = b match + case HandleBlock(lhs, res, par, args, cls, hdr, bod, rst) => + val hdr2 = hdr.map(applyHandler) + val bod2 = applyBlock(bod) + val rst2 = applyBlock(rst) + translateHandleBlockShallow(HandleBlock(lhs, res, par, args, cls, hdr2, bod2, rst2)) case _ => super.applyBlock(b) - transform.applyBlock(b) - def translateTopLevel(b: Block): (Block, Map[FnOrCls, Path]) = - doUnwindMap = Map.empty - val transformed = translateBlock(b, Set.empty, N, L(BlockMemberSymbol("", Nil)), topLevelCtx(s"Cont$$topLevel$$BAD", "‹top level›")) + def translateTopLevel(b: Block): (Block, collection.Map[FnOrCls, Path => Return]) = + doUnwindMap.clear() + val ctx = HandlerCtx(N, N, 0, b.definedVars.toList, N, DebugInfo.topLevel( + "‹top level›", + b.definedVars + )) + val transformed = translateBlock(b, ctx) (transformed, doUnwindMap) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index ce08029229..a6e07cb8cf 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -123,7 +123,7 @@ object Lifter: * Lifts classes and functions to the top-level. Also automatically rewrites lambdas. * Assumes the input block does not have any `HandleBlock`s. */ -class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): +class Lifter()(using State, Raise): import Lifter.* /** @@ -247,9 +247,10 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): case PubField(isym, sym) => Select(isym.asPath, Tree.Ident(sym.nme))(d) - def isHandlerClsPath(p: Path) = handlerPaths match - case None => false - case Some(paths) => paths.isHandlerClsPath(p) + + val ignoredSet = Set(State.globalThisSymbol.asPath.selSN("Object"), State.runtimeSymbol.asPath.selSN("NonLocalReturn")) + + def isIgnoredPath(p: Path) = ignoredSet.contains(p) /** * Creates a capture class for a function consisting of its mutable (and possibly immutable) local variables. @@ -476,7 +477,7 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): // If B extends A, then A -> B is an edge parentPath match case None => () - case Some(path) if isHandlerClsPath(path) => () + case Some(path) if isIgnoredPath(path) => () case Some(Select(RefOfBms(s, _), Tree.Ident("class"))) => if clsSyms.contains(s) then extendsGraph += (s -> defn.sym) case Some(RefOfBms(s, _)) => @@ -639,14 +640,14 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): case Some(c: ClsLikeDefn) => Value.Lit(Tree.BoolLit(false)).asArg :: getCallArgs(l, ctx) case _ => getCallArgs(l, ctx) applyListOf(args, applyArg(_)(_)): newArgs => - k(Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(c.isMlsFun, false)) + k(Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(c.isMlsFun, c.mayRaiseEffects)) case _ => super.applyResult(r)(k) case c @ Instantiate(mut, InstSel(l), args) => ctx.bmsReqdInfo.get(l) match case Some(info) if !ctx.isModOrObj(l) => val extraArgs = Value.Lit(Tree.BoolLit(mut)).asArg :: getCallArgs(l, ctx) applyListOf(args, applyArg(_)(_)): newArgs => - k(Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(true, false)) + k(Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(true, true)) case _ => super.applyResult(r)(k) // LEGACY CODE: We previously directly created the closure and assigned it to the // variable here. But, since this closure may be re-used later, this doesn't work @@ -944,7 +945,7 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): val args2 = headPlistCopy.params.map(p => p.sym.asPath.asArg) val bdy = blockBuilder - .ret(Call(singleCallBms.asPath, args1 ++ args2)(true, false)) // TODO: restParams not considered + .ret(Call(singleCallBms.asPath, args1 ++ args2)(true, true)) // TODO: restParams not considered val mainDefn = FunDefn(f.owner, f.sym, PlainParamList(extraParamsCpy) :: headPlistCopy :: Nil, bdy) val auxDefn = FunDefn(N, singleCallBms, flatPlist, lifted.body) @@ -1035,7 +1036,7 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): ) for ps <- newAuxSyms do - val call = Call(curSym.asPath, ps.map(_.asPath.asArg))(true, false) + val call = Call(curSym.asPath, ps.map(_.asPath.asArg))(true, true) curSym = TempSymbol(None, "tmp") val thisSym = curSym acc = acc.assign(thisSym, call) @@ -1266,7 +1267,7 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): // so we need to desugar them again val blk = LambdaRewriter.desugar(_blk) - val analyzer = UsedVarAnalyzer(blk, handlerPaths) + val analyzer = UsedVarAnalyzer(blk) val ctx = LifterCtx .withLocals(analyzer.findUsedLocals) .withDefns(analyzer.defnsMap) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 739e9765f4..de98338b51 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -512,14 +512,6 @@ class Lowering()(using Config, TL, Raise, State, Ctx): t.toLoc :: Nil, source = Diagnostic.Source.Compilation) conclude(Value.Ref(State.runtimeSymbol).selSN("raisePrintStackEffect").withLocOf(f)) - case t if instantiatedResolvedBms.exists(_ is ctx.builtins.debug.getLocals) => - if !config.effectHandlers.exists(_.debug) then - return fail: - ErrorReport( - msg"Debugging functions are not enabled" -> - t.toLoc :: Nil, - source = Diagnostic.Source.Compilation) - conclude(Value.Ref(ctx.builtins.debug.getLocals, N).withLocOf(f)) // * Due to whacky JS semantics, we need to make sure that selections leading to a call // * are preserved in the call and not moved to a temporary variable. case sel @ Sel(prefix, nme) => @@ -950,21 +942,24 @@ class Lowering()(using Config, TL, Raise, State, Ctx): val desug = LambdaRewriter.desugar(blk) val handlerPaths = new HandlerPaths + + val withHandlers1 = config.effectHandlers.fold(desug): opt => + HandlerLowering(handlerPaths, opt).translateHandleBlocks(desug) - val (withHandlers, doUnwindPaths) = config.effectHandlers.fold((desug, Map.empty)): opt => - HandlerLowering(handlerPaths, opt).translateTopLevel(desug) + val lifted = + if lift then Lifter().transform(withHandlers1) + else withHandlers1 + + val (withHandlers2, doUnwindPaths) = config.effectHandlers.fold((lifted, Map.empty)): opt => + HandlerLowering(handlerPaths, opt).translateTopLevel(lifted) val stackSafe = config.stackSafety match - case N => withHandlers - case S(sts) => StackSafeTransform(sts.stackLimit, handlerPaths, doUnwindPaths).transformTopLevel(withHandlers) + case N => withHandlers2 + case S(sts) => StackSafeTransform(sts.stackLimit, handlerPaths, doUnwindPaths).transformTopLevel(withHandlers2) val flattened = stackSafe.flattened - val lifted = - if lift then Lifter(S(handlerPaths)).transform(flattened) - else flattened - - val bufferable = BufferableTransform().transform(lifted) + val bufferable = BufferableTransform().transform(flattened) val merged = MergeMatchArmTransformer.applyBlock(bufferable) @@ -1004,7 +999,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): trait LoweringSelSanityChecks(using Config, TL, Raise, State) extends Lowering: - private val instrument: Bool = config.sanityChecks.isDefined + private val instrument: Bool = config.sanityChecks.isDefined && config.effectHandlers.isEmpty override def setupSelection(prefix: st, nme: Tree.Ident, disamb: Opt[DefinitionSymbol[?]])(k: Result => Block)(using LoweringCtx): Block = if !instrument then return super.setupSelection(prefix, nme, disamb)(k) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index df9f4130eb..d28167b1fa 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -9,13 +9,8 @@ import hkmc2.semantics.* import hkmc2.syntax.Tree import hkmc2.codegen.HandlerLowering.FnOrCls -class StackSafeTransform(depthLimit: Int, paths: HandlerPaths, doUnwindMap: Map[FnOrCls, Path])(using State): +class StackSafeTransform(depthLimit: Int, paths: HandlerPaths, doUnwindMap: collection.Map[FnOrCls, Path => Return])(using State): private val STACK_DEPTH_IDENT: Tree.Ident = Tree.Ident("stackDepth") - - val doUnwindFns = doUnwindMap.values.collect: - case s: Select if s.symbol.isDefined => s.symbol.get - case Value.Ref(sym, _) => sym - .toSet private val runtimePath: Path = State.runtimeSymbol.asPath private val checkDepthPath: Path = runtimePath.selN(Tree.Ident("checkDepth")) @@ -135,44 +130,32 @@ class StackSafeTransform(depthLimit: Int, paths: HandlerPaths, doUnwindMap: Map[ usedDepth = true TempSymbol(None, "curDepth") - val doUnwindPath = doUnwindMap.get(fnOrCls) + val doUnwind = doUnwindMap.get(fnOrCls) val newBody = transform(blk, curDepth) if isTrivial(blk) then newBody - else if doUnwindPath.isEmpty then - val resSym = TempSymbol(None, "stackDelayRes") + else if doUnwind.isEmpty then + // The current function is not instrumented and we cannot provide stack safety. + // TODO: shouldn't we just return the old blk? blockBuilder .staticif(usedDepth, _.assign(curDepth, stackDepthPath)) .rest(newBody) else val resSym = TempSymbol(None, "stackDelayRes") - val rewritten = blockBuilder + blockBuilder .staticif(usedDepth, _.assign(curDepth, stackDepthPath)) .assignFieldN(runtimePath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(increment))) .assign(resSym, Call(checkDepthPath, Nil)(true, true)) .ifthen( resSym.asPath, Case.Cls(paths.effectSigSym, paths.effectSigPath), - Return( - Call(doUnwindPath.get, resSym.asPath.asArg :: intLit(0).asArg :: Nil)(true, false), - false - ) + doUnwind.get(resSym.asPath) ) .rest(newBody) - // Float out defns, including the doUnwind function, so that they appear at the top of the block - // This is because the doUnwind function must appear before the checks inserted by the stack - // safety pass. - // However, due to how tightly coupled the stack safety and handler lowering are, it might be - // better to simply merge the two passes in the future. - val (blk, defns) = doUnwindPath.get match - case Value.Ref(sym, _) => rewritten.floatOutDefns() - case _ => (rewritten, Nil) - defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) def rewriteFn(defn: FunDefn) = - if doUnwindFns.contains(defn.sym) then defn - else FunDefn(defn.owner, defn.sym, defn.params, rewriteBlk(defn.body, L(defn.sym), 1)) + FunDefn(defn.owner, defn.sym, defn.params, rewriteBlk(defn.body, L(defn.sym), 1)) def transformTopLevel(b: Block) = transform(b, TempSymbol(N), true) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala index 31432b48e3..bcb2c8bcb8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala @@ -19,7 +19,7 @@ import scala.collection.mutable.Map as MutMap * * Assumes the input trees have no lambdas. */ -class UsedVarAnalyzer(b: Block, handlerPaths: Opt[HandlerPaths])(using State): +class UsedVarAnalyzer(b: Block)(using State): import Lifter.* private case class DefnMetadata( @@ -120,10 +120,6 @@ class UsedVarAnalyzer(b: Block, handlerPaths: Opt[HandlerPaths])(using State): val DefnMetadata(definedLocals, defnsMap, existingVars, inScopeDefns, nestedDefns, nestedDeep, nestedIn, companionMap) = createMetadata - def isHandlerClsPath(p: Path) = handlerPaths match - case None => false - case Some(paths) => paths.isHandlerClsPath(p) - private val blkMutCache: MutMap[Local, AccessInfo] = MutMap.empty private def blkAccessesShallow(b: Block, cacheId: Opt[Local] = N): AccessInfo = cacheId.flatMap(blkMutCache.get) match @@ -173,7 +169,7 @@ class UsedVarAnalyzer(b: Block, handlerPaths: Opt[HandlerPaths])(using State): blkAccessesShallow(f.body).withoutLocals(fVars) case c: ClsLikeDefn => val methodSyms = c.methods.map(_.sym).toSet - val ret = c.methods.foldLeft(blkAccessesShallow(c.preCtor) ++ blkAccessesShallow(c.ctor)): + c.methods.foldLeft(blkAccessesShallow(c.preCtor) ++ blkAccessesShallow(c.ctor)): case (acc, fDefn) => // class methods do not need to be lifted, so we don't count calls to their methods. // a previous reference to this class's block member symbol is enough to assume any @@ -182,11 +178,6 @@ class UsedVarAnalyzer(b: Block, handlerPaths: Opt[HandlerPaths])(using State): // however, we must keep references to the class itself! val defnAccess = findAccessesShallow(fDefn) acc ++ defnAccess.withoutBms(methodSyms) - if c.parentPath.isDefined && isHandlerClsPath(c.parentPath.get) then - // for continuation classes, treat them like they only read variables - AccessInfo(ret.accessed ++ ret.mutated, Set.empty, ret.refdDefns) - else - ret case _: ValDefn => AccessInfo.empty accessedCache.getOrElseUpdate(defn.sym, create) 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 f934d160d2..432d3b429d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -303,8 +303,7 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: val privs = mkPrivs(pubFlds, privFlds, mtdPrefix, isym) - val preCtorCode = body(preCtor, true) - val ctorCode = doc"$preCtorCode${body(ctor, endSemi = true)}${ + val ctorCode = doc"${body(Begin(preCtor, ctor), endSemi = true)}${ kind match case syntax.Obj => doc" # ${defineProperty(doc"this", "class", doc"${scope.lookup_!(isym, isym.toLoc)}")}" @@ -447,6 +446,16 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: case S(el) => returningTerm(el, endSemi = true) case N => doc"" e :: returningTerm(rest, endSemi) + case Match(scrut, arms, els, rest) if arms.forall(_._1.isInstanceOf[Case.Lit]) => + val l = arms.foldLeft(doc""): (acc, arm) => + acc :: doc" # case ${arm._1.asInstanceOf[Case.Lit].lit.idStr}: #{ ${ + returningTerm(arm._2, endSemi = true) + } # break; #} " + val e = els match + case S(el) => + doc" # default: #{ ${ returningTerm(el, endSemi = true) } # break; #} " + case N => doc"" + doc" # switch (${result(scrut)}) { #{ ${l :: e} #} # }" :: returningTerm(rest, endSemi) case Match(scrut, hd :: tl, els, rest) => val sd = result(scrut) def cond(cse: Case) = cse match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index cdafc57c6b..c68de4a378 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -181,7 +181,6 @@ object Elaborator: val plus_impl = assumeObject("plus_impl") object debug extends VirtualModule(assumeBuiltinMod("debug")): val printStack = assumeObject("printStack") - val getLocals = assumeObject("getLocals") object annotations extends VirtualModule(assumeBuiltinMod("annotations")): val compile = assumeObject("compile") val buffered = assumeObject("buffered") diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index a4ce900a78..a70d824bed 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -127,27 +127,33 @@ globalThis.Object.freeze(class Predef { let len, scrut, i, init, scrut1, tmp, tmp1, tmp2, tmp3; len = rest.length; scrut = len == 0; - if (scrut === true) { - return first - } else { - i = len - 1; - init = runtime.safeCall(rest.at(i)); - tmp4: while (true) { - scrut1 = i > 0; - if (scrut1 === true) { - tmp = i - 1; - i = tmp; - tmp1 = runtime.safeCall(rest.at(i)); - tmp2 = runtime.safeCall(f(tmp1, init)); - init = tmp2; - tmp3 = runtime.Unit; - continue tmp4 - } else { - tmp3 = runtime.Unit; + switch (scrut) { + case true: + return first; + break; + default: + i = len - 1; + init = runtime.safeCall(rest.at(i)); + tmp4: while (true) { + scrut1 = i > 0; + switch (scrut1) { + case true: + tmp = i - 1; + i = tmp; + tmp1 = runtime.safeCall(rest.at(i)); + tmp2 = runtime.safeCall(f(tmp1, init)); + init = tmp2; + tmp3 = runtime.Unit; + continue tmp4; + break; + default: + tmp3 = runtime.Unit; + break; + } + break; } + return runtime.safeCall(f(first, init)); break; - } - return runtime.safeCall(f(first, init)) } } } diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index d5cc8a4258..d1a73ebcb1 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -14,10 +14,25 @@ globalThis.Object.freeze(class Runtime { constructor() { runtime.Unit; } + static #isResuming; + static #resumeValue; + static #resumeArr; + static #resumeIdx; + static #resumePc; static #stackLimit; static #stackDepth; static #stackHandler; static #stackResume; + static get isResuming() { return Runtime.#isResuming; } + static set isResuming(value) { Runtime.#isResuming = value; } + static get resumeValue() { return Runtime.#resumeValue; } + static set resumeValue(value) { Runtime.#resumeValue = value; } + static get resumeArr() { return Runtime.#resumeArr; } + static set resumeArr(value) { Runtime.#resumeArr = value; } + static get resumeIdx() { return Runtime.#resumeIdx; } + static set resumeIdx(value) { Runtime.#resumeIdx = value; } + static get resumePc() { return Runtime.#resumePc; } + static set resumePc(value) { Runtime.#resumePc = value; } static get stackLimit() { return Runtime.#stackLimit; } static set stackLimit(value) { Runtime.#stackLimit = value; } static get stackDepth() { return Runtime.#stackDepth; } @@ -142,17 +157,23 @@ globalThis.Object.freeze(class Runtime { static get(xs, i) { let scrut, scrut1, tmp, tmp1, tmp2; scrut = i >= xs.length; - if (scrut === true) { - throw globalThis.RangeError("Tuple.get: index out of bounds") - } else { - tmp = runtime.Unit; + switch (scrut) { + case true: + throw globalThis.RangeError("Tuple.get: index out of bounds"); + break; + default: + tmp = runtime.Unit; + break; } tmp1 = - xs.length; scrut1 = i < tmp1; - if (scrut1 === true) { - throw globalThis.RangeError("Tuple.get: negative index out of bounds") - } else { - tmp2 = runtime.Unit; + switch (scrut1) { + case true: + throw globalThis.RangeError("Tuple.get: negative index out of bounds"); + break; + default: + tmp2 = runtime.Unit; + break; } return xs.at(i) } @@ -175,10 +196,13 @@ globalThis.Object.freeze(class Runtime { static get(string, i) { let scrut; scrut = i >= string.length; - if (scrut === true) { - throw globalThis.RangeError("Str.get: index out of bounds") - } else { - return runtime.safeCall(string.at(i)) + switch (scrut) { + case true: + throw globalThis.RangeError("Str.get: index out of bounds"); + break; + default: + return runtime.safeCall(string.at(i)); + break; } } static take(string, n) { @@ -211,42 +235,56 @@ globalThis.Object.freeze(class Runtime { static indent() { let scrut, prev, tmp; scrut = TraceLogger.enabled; - if (scrut === true) { - prev = TraceLogger.indentLvl; - tmp = prev + 1; - TraceLogger.indentLvl = tmp; - return prev - } else { - return runtime.Unit + switch (scrut) { + case true: + prev = TraceLogger.indentLvl; + tmp = prev + 1; + TraceLogger.indentLvl = tmp; + return prev; + break; + default: + return runtime.Unit; + break; } } static resetIndent(n) { let scrut; scrut = TraceLogger.enabled; - if (scrut === true) { - TraceLogger.indentLvl = n; - return runtime.Unit - } else { - return runtime.Unit + switch (scrut) { + case true: + TraceLogger.indentLvl = n; + return runtime.Unit; + break; + default: + return runtime.Unit; + break; } } static log(msg) { let scrut, tmp, tmp1, tmp2, tmp3, tmp4; scrut = TraceLogger.enabled; - if (scrut === true) { - tmp = runtime.safeCall("| ".repeat(TraceLogger.indentLvl)); - tmp1 = runtime.safeCall(" ".repeat(TraceLogger.indentLvl)); - tmp2 = "\n" + tmp1; - tmp3 = msg.replaceAll("\n", tmp2); - tmp4 = tmp + tmp3; - return runtime.safeCall(globalThis.console.log(tmp4)) - } else { - return runtime.Unit + switch (scrut) { + case true: + tmp = runtime.safeCall("| ".repeat(TraceLogger.indentLvl)); + tmp1 = runtime.safeCall(" ".repeat(TraceLogger.indentLvl)); + tmp2 = "\n" + tmp1; + tmp3 = msg.replaceAll("\n", tmp2); + tmp4 = tmp + tmp3; + return runtime.safeCall(globalThis.console.log(tmp4)); + break; + default: + return runtime.Unit; + break; } } toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "TraceLogger"]; }); + this.isResuming = false; + this.resumeValue = null; + this.resumeArr = null; + this.resumeIdx = null; + this.resumePc = null; globalThis.Object.freeze(class FatalEffect { static { Runtime.FatalEffect = globalThis.Object.freeze(new this) @@ -271,13 +309,96 @@ globalThis.Object.freeze(class Runtime { toString() { return runtime.render(this); } static [definitionMetadata] = ["object", "PrintStackEffect"]; }); - this.FunctionContFrame = function FunctionContFrame(next) { - return globalThis.Object.freeze(new FunctionContFrame.class(next)); + this.FunctionContFrame = function FunctionContFrame(next, saved) { + return globalThis.Object.freeze(new FunctionContFrame.class(next, saved)); }; globalThis.Object.freeze(class FunctionContFrame { static { Runtime.FunctionContFrame.class = this } + constructor(next, saved) { + this.next = next; + this.saved = saved; + } + resume(value) { + let scrut, f, scrut1, tmp, tmp1, tmp2, tmp3, tmp4; + scrut = this.saved.at(0) == 0; + switch (scrut) { + case true: + tmp = runtime.safeCall(globalThis.console.log("cannot resume getters")); + break; + default: + tmp = runtime.Unit; + break; + } + f = this.saved.at(1); + tmp5: while (true) { + scrut1 = this.saved.at(0) > 1; + switch (scrut1) { + case true: + tmp1 = runtime.safeCall(f()); + f = tmp1; + tmp2 = this.saved.at(0) - 1; + this.saved[0] = tmp2; + tmp3 = runtime.Unit; + continue tmp5; + break; + default: + tmp3 = runtime.Unit; + break; + } + break; + } + Runtime.isResuming = true; + Runtime.resumeValue = value; + Runtime.resumeArr = this.saved; + Runtime.resumeIdx = 7; + Runtime.resumePc = this.saved.at(4); + tmp4 = globalThis.Object.freeze([]); + return f.apply(this.saved.at(5), tmp4) + } + get getLocal() { + let debugInfo, res, i, scrut, tmp, tmp1, tmp2, tmp3, tmp4, tmp5; + debugInfo = this.saved.at(2); + res = []; + i = 1; + tmp6: while (true) { + scrut = i < debugInfo.length; + switch (scrut) { + case true: + tmp = i + 1; + tmp1 = 7 + debugInfo.at(i); + tmp2 = globalThis.Object.freeze(new Runtime.LocalVarInfo.class(debugInfo.at(tmp), this.saved.at(tmp1))); + tmp3 = runtime.safeCall(res.push(tmp2)); + tmp4 = i + 2; + i = tmp4; + tmp5 = runtime.Unit; + continue tmp6; + break; + default: + tmp5 = runtime.Unit; + break; + } + break; + } + return res; + } + get getNme() { + return this.saved.at(2).at(0); + } + get getLoc() { + return this.saved.at(3); + } + toString() { return runtime.render(this); } + static [definitionMetadata] = ["class", "FunctionContFrame", ["next", "saved"]]; + }); + this.FunctionContFrameOld = function FunctionContFrameOld(next) { + return globalThis.Object.freeze(new FunctionContFrameOld.class(next)); + }; + globalThis.Object.freeze(class FunctionContFrameOld { + static { + Runtime.FunctionContFrameOld.class = this + } constructor(next) { this.next = next; } @@ -288,7 +409,7 @@ globalThis.Object.freeze(class Runtime { return res1 } toString() { return runtime.render(this); } - static [definitionMetadata] = ["class", "FunctionContFrame", ["next"]]; + static [definitionMetadata] = ["class", "FunctionContFrameOld", ["next"]]; }); this.HandlerContFrame = function HandlerContFrame(next, nextHandler, handler) { return globalThis.Object.freeze(new HandlerContFrame.class(next, nextHandler, handler)); @@ -373,6 +494,22 @@ globalThis.Object.freeze(class Runtime { toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "LocalVarInfo", ["localName", "value"]]; }); + this.FakeError = function FakeError(stack) { + return globalThis.Object.freeze(new FakeError.class(stack)); + }; + globalThis.Object.freeze(class FakeError { + static { + Runtime.FakeError.class = this + } + constructor(stack) { + this.stack = stack; + } + toString() { + return this.stack + } + [prettyPrint]() { return this.toString(); } + static [definitionMetadata] = ["class", "FakeError", ["stack"]]; + }); this.stackLimit = 0; this.stackDepth = 0; this.stackHandler = null; @@ -437,51 +574,69 @@ globalThis.Object.freeze(class Runtime { return runtime.short_and(isUB, lambda1) }); scrut = runtime.short_or(tmp, lambda); - if (scrut === true) { - scrut1 = functionName.length > 0; - if (scrut1 === true) { - tmp1 = " '" + functionName; - tmp2 = tmp1 + "'"; - } else { - tmp2 = ""; - } - name = tmp2; - tmp3 = "Function" + name; - tmp4 = tmp3 + " expected "; - if (isUB === true) { - tmp5 = ""; - } else { - tmp5 = "at least "; - } - tmp6 = tmp4 + tmp5; - tmp7 = tmp6 + expected; - tmp8 = tmp7 + " argument"; - scrut2 = expected === 1; - if (scrut2 === true) { - tmp9 = ""; - } else { - tmp9 = "s"; - } - tmp10 = tmp8 + tmp9; - tmp11 = tmp10 + " but got "; - tmp12 = tmp11 + got; - throw globalThis.Error(tmp12) - } else { - return runtime.Unit + switch (scrut) { + case true: + scrut1 = functionName.length > 0; + switch (scrut1) { + case true: + tmp1 = " '" + functionName; + tmp2 = tmp1 + "'"; + break; + default: + tmp2 = ""; + break; + } + name = tmp2; + tmp3 = "Function" + name; + tmp4 = tmp3 + " expected "; + switch (isUB) { + case true: + tmp5 = ""; + break; + default: + tmp5 = "at least "; + break; + } + tmp6 = tmp4 + tmp5; + tmp7 = tmp6 + expected; + tmp8 = tmp7 + " argument"; + scrut2 = expected === 1; + switch (scrut2) { + case true: + tmp9 = ""; + break; + default: + tmp9 = "s"; + break; + } + tmp10 = tmp8 + tmp9; + tmp11 = tmp10 + " but got "; + tmp12 = tmp11 + got; + throw globalThis.Error(tmp12); + break; + default: + return runtime.Unit; + break; } } static safeCall(x) { - if (x === undefined) { - return runtime.Unit - } else { - return x + switch (x) { + case undefined: + return runtime.Unit; + break; + default: + return x; + break; } } static checkCall(x) { - if (x === undefined) { - throw globalThis.Error("MLscript call unexpectedly returned `undefined`, the forbidden value.") - } else { - return x + switch (x) { + case undefined: + throw globalThis.Error("MLscript call unexpectedly returned `undefined`, the forbidden value."); + break; + default: + return x; + break; } } static deboundMethod(mtdName, clsName) { @@ -514,122 +669,148 @@ globalThis.Object.freeze(class Runtime { return Runtime.mkEffect(Runtime.PrintStackEffect, showLocals) } static topLevelEffect(tr, debug) { - let scrut, tmp, tmp1, tmp2, tmp3, tmp4, tmp5; - tmp6: while (true) { + let scrut, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; + tmp7: while (true) { scrut = tr.handler === Runtime.PrintStackEffect; - if (scrut === true) { - tmp = Runtime.showStackTrace("Stack Trace:", tr, debug, tr.handlerFun); - tmp1 = runtime.safeCall(globalThis.console.log(tmp)); - tmp2 = Runtime.resume(tr.contTrace); - tmp3 = runtime.safeCall(tmp2(runtime.Unit)); - tr = tmp3; - tmp4 = runtime.Unit; - continue tmp6 - } else { - tmp4 = runtime.Unit; + switch (scrut) { + case true: + tmp = Runtime.showStackTrace("Stack Trace:", tr, debug, tr.handlerFun); + tmp1 = runtime.safeCall(globalThis.console.log(tmp)); + tmp2 = Runtime.resume(tr.contTrace); + tmp3 = runtime.safeCall(tmp2(runtime.Unit)); + tr = tmp3; + tmp4 = runtime.Unit; + continue tmp7; + break; + default: + tmp4 = runtime.Unit; + break; } break; } if (tr instanceof Runtime.EffectSig.class) { tmp5 = "Error: Unhandled effect " + tr.handler.constructor.name; - throw Runtime.showStackTrace(tmp5, tr, debug, false) + tmp6 = Runtime.showStackTrace(tmp5, tr, debug, false); + throw Runtime.FakeError(tmp6) } else { return tr } } static showStackTrace(header, tr, debug, showLocals) { - let msg, curHandler, atTail, scrut, cur, scrut1, locals, curLocals, loc, loc1, localsMsg, scrut2, scrut3, tmp, tmp1, lambda, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17, tmp18; + let msg, curHandler, atTail, scrut, cur, scrut1, curLocals, loc, loc1, localsMsg, scrut2, scrut3, tmp, lambda, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17; msg = header; curHandler = tr.contTrace; atTail = true; - if (debug === true) { - tmp19: while (true) { - scrut = curHandler !== null; - if (scrut === true) { - cur = curHandler.next; - tmp20: while (true) { - scrut1 = cur !== null; - if (scrut1 === true) { - locals = cur.getLocals; - tmp = locals.length - 1; - curLocals = runtime.safeCall(locals.at(tmp)); - loc = cur.getLoc; - if (loc === null) { - tmp1 = "pc=" + cur.pc; - } else { - tmp1 = loc; - } - loc1 = tmp1; - split_root$: { - split_1$: { - if (showLocals === true) { - scrut2 = curLocals.locals.length > 0; - if (scrut2 === true) { - lambda = (undefined, function (l) { - let tmp21, tmp22; - tmp21 = l.localName + "="; - tmp22 = Rendering.render(l.value); - return tmp21 + tmp22 - }); - tmp2 = runtime.safeCall(curLocals.locals.map(lambda)); - tmp3 = runtime.safeCall(tmp2.join(", ")); - tmp4 = " with locals: " + tmp3; - break split_root$ - } else { - break split_1$ + switch (debug) { + case true: + tmp18: while (true) { + scrut = curHandler !== null; + switch (scrut) { + case true: + cur = curHandler.next; + tmp19: while (true) { + scrut1 = cur !== null; + switch (scrut1) { + case true: + curLocals = cur.getLocal; + loc = cur.getLoc; + switch (loc) { + case null: + tmp = "pc=" + cur.pc; + break; + default: + tmp = loc; + break; } - } else { - break split_1$ - } + loc1 = tmp; + split_root$: { + split_1$: { + switch (showLocals) { + case true: + scrut2 = curLocals.length > 0; + switch (scrut2) { + case true: + lambda = (undefined, function (l) { + let tmp20, tmp21; + tmp20 = l.localName + "="; + tmp21 = Rendering.render(l.value); + return tmp20 + tmp21 + }); + tmp1 = runtime.safeCall(curLocals.map(lambda)); + tmp2 = runtime.safeCall(tmp1.join(", ")); + tmp3 = " with locals: " + tmp2; + break split_root$; + break; + default: + break split_1$; + break; + } + break; + default: + break split_1$; + break; + } + } + tmp3 = ""; + } + localsMsg = tmp3; + tmp4 = "\n\tat " + cur.getNme; + tmp5 = tmp4 + " ("; + tmp6 = tmp5 + loc1; + tmp7 = tmp6 + ")"; + tmp8 = msg + tmp7; + msg = tmp8; + tmp9 = msg + localsMsg; + msg = tmp9; + cur = cur.next; + atTail = false; + tmp10 = runtime.Unit; + continue tmp19; + break; + default: + tmp10 = runtime.Unit; + break; } - tmp4 = ""; + break; } - localsMsg = tmp4; - tmp5 = "\n\tat " + curLocals.fnName; - tmp6 = tmp5 + " ("; - tmp7 = tmp6 + loc1; - tmp8 = tmp7 + ")"; - tmp9 = msg + tmp8; - msg = tmp9; - tmp10 = msg + localsMsg; - msg = tmp10; - cur = cur.next; - atTail = false; - tmp11 = runtime.Unit; - continue tmp20 - } else { - tmp11 = runtime.Unit; - } - break; - } - curHandler = curHandler.nextHandler; - scrut3 = curHandler !== null; - if (scrut3 === true) { - tmp12 = "\n\twith handler " + curHandler.handler.constructor.name; - tmp13 = msg + tmp12; - msg = tmp13; - atTail = false; - tmp14 = runtime.Unit; - } else { - tmp14 = runtime.Unit; + curHandler = curHandler.nextHandler; + scrut3 = curHandler !== null; + switch (scrut3) { + case true: + tmp11 = "\n\twith handler " + curHandler.handler.constructor.name; + tmp12 = msg + tmp11; + msg = tmp12; + atTail = false; + tmp13 = runtime.Unit; + break; + default: + tmp13 = runtime.Unit; + break; + } + tmp14 = tmp13; + continue tmp18; + break; + default: + tmp14 = runtime.Unit; + break; } - tmp15 = tmp14; - continue tmp19 - } else { - tmp15 = runtime.Unit; + break; } + switch (atTail) { + case true: + tmp15 = msg + "\n\tat tail position"; + msg = tmp15; + tmp16 = runtime.Unit; + break; + default: + tmp16 = runtime.Unit; + break; + } + tmp17 = tmp16; break; - } - if (atTail === true) { - tmp16 = msg + "\n\tat tail position"; - msg = tmp16; + default: tmp17 = runtime.Unit; - } else { - tmp17 = runtime.Unit; - } - tmp18 = tmp17; - } else { - tmp18 = runtime.Unit; + break; } return msg } @@ -641,41 +822,53 @@ globalThis.Object.freeze(class Runtime { lambda = (undefined, function (m, marker) { let scrut3, tmp8, tmp9; scrut3 = runtime.safeCall(m.has(cont)); - if (scrut3 === true) { - tmp8 = ", " + marker; - tmp9 = result + tmp8; - result = tmp9; - return runtime.Unit - } else { - return runtime.Unit + switch (scrut3) { + case true: + tmp8 = ", " + marker; + tmp9 = result + tmp8; + result = tmp9; + return runtime.Unit; + break; + default: + return runtime.Unit; + break; } }); tmp1 = runtime.safeCall(hl.forEach(lambda)); scrut = runtime.safeCall(vis.has(cont)); - if (scrut === true) { - tmp2 = reps + 1; - reps = tmp2; - scrut1 = reps > 10; - if (scrut1 === true) { - throw globalThis.Error("10 repeated continuation frame (loop?)") - } else { - tmp3 = runtime.Unit; - } - tmp4 = result + ", REPEAT"; - result = tmp4; - tmp5 = runtime.Unit; - } else { - tmp5 = runtime.safeCall(vis.add(cont)); + switch (scrut) { + case true: + tmp2 = reps + 1; + reps = tmp2; + scrut1 = reps > 10; + switch (scrut1) { + case true: + throw globalThis.Error("10 repeated continuation frame (loop?)"); + break; + default: + tmp3 = runtime.Unit; + break; + } + tmp4 = result + ", REPEAT"; + result = tmp4; + tmp5 = runtime.Unit; + break; + default: + tmp5 = runtime.safeCall(vis.add(cont)); + break; } tmp6 = result + ") -> "; tmp7 = Runtime.showFunctionContChain(cont.next, hl, vis, reps); return tmp6 + tmp7 } else { scrut2 = cont === null; - if (scrut2 === true) { - return "(null)" - } else { - return "(NOT CONT)" + switch (scrut2) { + case true: + return "(null)"; + break; + default: + return "(NOT CONT)"; + break; } } } @@ -686,41 +879,53 @@ globalThis.Object.freeze(class Runtime { lambda = (undefined, function (m, marker) { let scrut3, tmp7, tmp8; scrut3 = runtime.safeCall(m.has(cont)); - if (scrut3 === true) { - tmp7 = ", " + marker; - tmp8 = result + tmp7; - result = tmp8; - return runtime.Unit - } else { - return runtime.Unit + switch (scrut3) { + case true: + tmp7 = ", " + marker; + tmp8 = result + tmp7; + result = tmp8; + return runtime.Unit; + break; + default: + return runtime.Unit; + break; } }); tmp = runtime.safeCall(hl.forEach(lambda)); scrut = runtime.safeCall(vis.has(cont)); - if (scrut === true) { - tmp1 = reps + 1; - reps = tmp1; - scrut1 = reps > 10; - if (scrut1 === true) { - throw globalThis.Error("10 repeated continuation frame (loop?)") - } else { - tmp2 = runtime.Unit; - } - tmp3 = result + ", REPEAT"; - result = tmp3; - tmp4 = runtime.Unit; - } else { - tmp4 = runtime.safeCall(vis.add(cont)); + switch (scrut) { + case true: + tmp1 = reps + 1; + reps = tmp1; + scrut1 = reps > 10; + switch (scrut1) { + case true: + throw globalThis.Error("10 repeated continuation frame (loop?)"); + break; + default: + tmp2 = runtime.Unit; + break; + } + tmp3 = result + ", REPEAT"; + result = tmp3; + tmp4 = runtime.Unit; + break; + default: + tmp4 = runtime.safeCall(vis.add(cont)); + break; } tmp5 = result + " -> "; tmp6 = Runtime.showFunctionContChain(cont.next, hl, vis, reps); return tmp5 + tmp6 } else { scrut2 = cont === null; - if (scrut2 === true) { - return "(null)" - } else { - return "(NOT HANDLER CONT)" + switch (scrut2) { + case true: + return "(null)"; + break; + default: + return "(NOT HANDLER CONT)"; + break; } } } @@ -743,16 +948,22 @@ globalThis.Object.freeze(class Runtime { if (contTrace instanceof Runtime.ContTrace.class) { tmp = globalThis.console.log("resumed: ", contTrace.resumed); scrut = contTrace.last === contTrace; - if (scrut === true) { - tmp1 = runtime.safeCall(globalThis.console.log("")); - } else { - tmp1 = runtime.Unit; + switch (scrut) { + case true: + tmp1 = runtime.safeCall(globalThis.console.log("")); + break; + default: + tmp1 = runtime.Unit; + break; } scrut1 = contTrace.lastHandler === contTrace; - if (scrut1 === true) { - tmp2 = runtime.safeCall(globalThis.console.log("")); - } else { - tmp2 = runtime.Unit; + switch (scrut1) { + case true: + tmp2 = runtime.safeCall(globalThis.console.log("")); + break; + default: + tmp2 = runtime.Unit; + break; } vis = globalThis.Object.freeze(new globalThis.Set()); hl = globalThis.Object.freeze(new globalThis.Map()); @@ -771,14 +982,17 @@ globalThis.Object.freeze(class Runtime { cur = contTrace.nextHandler; tmp15: while (true) { scrut2 = cur !== null; - if (scrut2 === true) { - tmp11 = Runtime.showHandlerContChain(cur, hl, vis, 0); - tmp12 = runtime.safeCall(globalThis.console.log(tmp11)); - cur = cur.nextHandler; - tmp13 = runtime.Unit; - continue tmp15 - } else { - tmp13 = runtime.Unit; + switch (scrut2) { + case true: + tmp11 = Runtime.showHandlerContChain(cur, hl, vis, 0); + tmp12 = runtime.safeCall(globalThis.console.log(tmp11)); + cur = cur.nextHandler; + tmp13 = runtime.Unit; + continue tmp15; + break; + default: + tmp13 = runtime.Unit; + break; } break; } @@ -800,6 +1014,13 @@ globalThis.Object.freeze(class Runtime { return runtime.safeCall(globalThis.console.log(eff)) } } + static unwind(cur, ...saved) { + let tmp; + tmp = new Runtime.FunctionContFrame.class(null, saved); + cur.contTrace.last.next = tmp; + cur.contTrace.last = cur.contTrace.last.next; + return cur + } static mkEffect(handler, handlerFun) { let res, tmp; tmp = new Runtime.ContTrace.class(null, null, null, null, false); @@ -831,11 +1052,14 @@ globalThis.Object.freeze(class Runtime { if (cur instanceof Runtime.EffectSig.class) { nxt = Runtime.handleEffect(cur); scrut = cur === nxt; - if (scrut === true) { - return cur - } else { - cur = nxt; - tmp = runtime.Unit; + switch (scrut) { + case true: + return cur; + break; + default: + cur = nxt; + tmp = runtime.Unit; + break; } tmp1 = tmp; continue tmp2 @@ -853,17 +1077,23 @@ globalThis.Object.freeze(class Runtime { split_root$: { split_1$: { scrut = prevHandlerFrame.nextHandler !== null; - if (scrut === true) { - scrut1 = prevHandlerFrame.nextHandler.handler !== cur.handler; - if (scrut1 === true) { - prevHandlerFrame = prevHandlerFrame.nextHandler; - tmp = runtime.Unit; - continue tmp6 - } else { - break split_1$ - } - } else { - break split_1$ + switch (scrut) { + case true: + scrut1 = prevHandlerFrame.nextHandler.handler !== cur.handler; + switch (scrut1) { + case true: + prevHandlerFrame = prevHandlerFrame.nextHandler; + tmp = runtime.Unit; + continue tmp6; + break; + default: + break split_1$; + break; + } + break; + default: + break split_1$; + break; } } tmp = runtime.Unit; @@ -871,10 +1101,13 @@ globalThis.Object.freeze(class Runtime { break; } scrut2 = prevHandlerFrame.nextHandler === null; - if (scrut2 === true) { - return cur - } else { - tmp1 = runtime.Unit; + switch (scrut2) { + case true: + return cur; + break; + default: + tmp1 = runtime.Unit; + break; } handlerFrame = prevHandlerFrame.nextHandler; saved = new Runtime.ContTrace.class(handlerFrame.next, cur.contTrace.last, handlerFrame.nextHandler, cur.contTrace.lastHandler, false); @@ -887,20 +1120,26 @@ globalThis.Object.freeze(class Runtime { cur = tmp3; if (cur instanceof Runtime.EffectSig.class) { scrut3 = saved.next !== null; - if (scrut3 === true) { - cur.contTrace.last.next = saved.next; - cur.contTrace.last = saved.last; - tmp4 = runtime.Unit; - } else { - tmp4 = runtime.Unit; + switch (scrut3) { + case true: + cur.contTrace.last.next = saved.next; + cur.contTrace.last = saved.last; + tmp4 = runtime.Unit; + break; + default: + tmp4 = runtime.Unit; + break; } scrut4 = saved.nextHandler !== null; - if (scrut4 === true) { - cur.contTrace.lastHandler.nextHandler = saved.nextHandler; - cur.contTrace.lastHandler = saved.lastHandler; - tmp5 = runtime.Unit; - } else { - tmp5 = runtime.Unit; + switch (scrut4) { + case true: + cur.contTrace.lastHandler.nextHandler = saved.nextHandler; + cur.contTrace.lastHandler = saved.lastHandler; + tmp5 = runtime.Unit; + break; + default: + tmp5 = runtime.Unit; + break; } return cur } else { @@ -911,10 +1150,13 @@ globalThis.Object.freeze(class Runtime { return (value) => { let scrut, tmp, tmp1; scrut = contTrace.resumed; - if (scrut === true) { - throw globalThis.Error("Multiple resumption") - } else { - tmp = runtime.Unit; + switch (scrut) { + case true: + throw globalThis.Error("Multiple resumption"); + break; + default: + tmp = runtime.Unit; + break; } contTrace.resumed = true; tmp1 = Runtime.resumeContTrace(contTrace, value); @@ -935,18 +1177,24 @@ globalThis.Object.freeze(class Runtime { value.contTrace.last.next = cont.next; value.contTrace.lastHandler.nextHandler = handlerCont; scrut = contTrace.last !== cont; - if (scrut === true) { - value.contTrace.last = contTrace.last; - tmp1 = runtime.Unit; - } else { - tmp1 = runtime.Unit; + switch (scrut) { + case true: + value.contTrace.last = contTrace.last; + tmp1 = runtime.Unit; + break; + default: + tmp1 = runtime.Unit; + break; } scrut1 = handlerCont !== null; - if (scrut1 === true) { - value.contTrace.lastHandler = contTrace.lastHandler; - tmp2 = runtime.Unit; - } else { - tmp2 = runtime.Unit; + switch (scrut1) { + case true: + value.contTrace.lastHandler = contTrace.lastHandler; + tmp2 = runtime.Unit; + break; + default: + tmp2 = runtime.Unit; + break; } return value } else { @@ -976,10 +1224,13 @@ globalThis.Object.freeze(class Runtime { return Runtime.stackHandler !== null }); scrut = runtime.short_and(tmp, lambda); - if (scrut === true) { - return runtime.safeCall(Runtime.stackHandler.delay()) - } else { - return runtime.Unit + switch (scrut) { + case true: + return runtime.safeCall(Runtime.stackHandler.delay()); + break; + default: + return runtime.Unit; + break; } } static runStackSafe(limit, f) { @@ -991,16 +1242,19 @@ globalThis.Object.freeze(class Runtime { Runtime.stackDepth = 1; tmp2: while (true) { scrut = Runtime.stackResume !== null; - if (scrut === true) { - saved = Runtime.stackResume; - Runtime.stackResume = null; - tmp = runtime.safeCall(saved()); - result = tmp; - Runtime.stackDepth = 1; - tmp1 = runtime.Unit; - continue tmp2 - } else { - tmp1 = runtime.Unit; + switch (scrut) { + case true: + saved = Runtime.stackResume; + Runtime.stackResume = null; + tmp = runtime.safeCall(saved()); + result = tmp; + Runtime.stackDepth = 1; + tmp1 = runtime.Unit; + continue tmp2; + break; + default: + tmp1 = runtime.Unit; + break; } break; } diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index f00d0d458f..92f882256d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -121,10 +121,46 @@ module TraceLogger with // Private definitions for algebraic effects +// layout: +// | callCnt | f | debugInfo | pcStr | pc | this | localCnt | locals... | + +mut val isResuming = false +mut val resumeValue = null +mut val resumeArr = null +mut val resumeIdx = null +mut val resumePc = null + object FatalEffect object PrintStackEffect -data abstract class FunctionContFrame(next) with +data class FunctionContFrame(next, saved) with + fun resume(value) = + if saved.[0] == 0 do + console.log("cannot resume getters") + let f = saved.[1] + while saved.[0] > 1 do + set + f = f() + saved.[0] -= 1 + set + isResuming = true + resumeValue = value + resumeArr = saved + resumeIdx = 7 + resumePc = saved.[4] + f.apply(saved.[5], []) + fun getLocal = + let debugInfo = saved.[2] + let res = mut [] + let i = 1 + while i < debugInfo.length do + res.push(new LocalVarInfo(debugInfo.[i + 1], saved.[7 + debugInfo.[i]])) + set i += 2 + res + fun getNme = saved.[2].[0] + fun getLoc = saved.[3] + +data abstract class FunctionContFrameOld(next) with fun resume(value) fun doUnwind(res1, newPc) = set @@ -142,6 +178,8 @@ class NonLocalReturn with data class FnLocalsInfo(fnName, locals) data class LocalVarInfo(localName, value) +data class FakeError(stack) with + fun toString() = stack fun raisePrintStackEffect(showLocals) = @@ -152,7 +190,7 @@ fun topLevelEffect(tr, debug) = console.log(showStackTrace("Stack Trace:", tr, debug, tr.handlerFun)) set tr = resume(tr.contTrace)(()) if tr is EffectSig then - throw showStackTrace("Error: Unhandled effect " + tr.handler.constructor.name, tr, debug, false) + throw FakeError(showStackTrace("Error: Unhandled effect " + tr.handler.constructor.name, tr, debug, false)) else tr @@ -165,16 +203,15 @@ fun showStackTrace(header, tr, debug, showLocals) = while curHandler !== null do let cur = curHandler.next while cur !== null do - let locals = cur.getLocals - let curLocals = locals.at(locals.length - 1) + let curLocals = cur.getLocal let loc = cur.getLoc let loc = if loc is null then "pc=" + cur.pc else loc - let localsMsg = if showLocals and curLocals.locals.length > 0 then - " with locals: " + curLocals.locals.map(l => l.localName + "=" + Rendering.render(l.value)).join(", ") + let localsMsg = if showLocals and curLocals.length > 0 then + " with locals: " + curLocals.map(l => l.localName + "=" + Rendering.render(l.value)).join(", ") else "" set - msg += "\n\tat " + curLocals.fnName + " (" + loc + ")" + msg += "\n\tat " + cur.getNme + " (" + loc + ")" msg += localsMsg cur = cur.next atTail = false @@ -256,6 +293,12 @@ fun debugEff(eff) = console.log(eff) // runtime implementations +fun unwind(cur, ...saved) = + set + cur.contTrace.last.next = new mut FunctionContFrame(null, saved) + cur.contTrace.last = cur.contTrace.last.next + cur + fun mkEffect(handler, handlerFun) = let res = new mut EffectSig(new mut ContTrace(null, null, null, null, false), handler, handlerFun) set diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index ef38e8b363..92bb309da8 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -7,6 +7,3 @@ :global // :d // :todo - - - diff --git a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls index 26fb4b1450..b7e9505255 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls @@ -22,14 +22,17 @@ fun buildPalindrome = case //│ let lambda; //│ lambda = (undefined, function (caseScrut) { //│ let n, tmp3, tmp4, tmp5; -//│ if (caseScrut === 0) { -//│ return globalThis.Object.freeze([]) -//│ } else { -//│ n = caseScrut; -//│ tmp3 = buildPalindrome(); -//│ tmp4 = n - 1; -//│ tmp5 = tmp3(tmp4); -//│ return globalThis.Object.freeze(runtime.Tuple.lazyConcat(n, runtime.Tuple.split, tmp5, n)) +//│ switch (caseScrut) { +//│ case 0: +//│ return globalThis.Object.freeze([]); +//│ break; +//│ default: +//│ n = caseScrut; +//│ tmp3 = buildPalindrome(); +//│ tmp4 = n - 1; +//│ tmp5 = tmp3(tmp4); +//│ return globalThis.Object.freeze(runtime.Tuple.lazyConcat(n, runtime.Tuple.split, tmp5, n)); +//│ break; //│ } //│ }); //│ return lambda @@ -80,13 +83,13 @@ sum2(arr) :e fun f(..xs) = xs //│ ╔══[ERROR] Lazy spread parameters not allowed. -//│ ║ l.81: fun f(..xs) = xs +//│ ║ l.84: fun f(..xs) = xs //│ ╙── ^^^^ :ge sum2(..arr) //│ ╔══[COMPILATION ERROR] Lazy spreads are not supported in call arguments -//│ ║ l.87: sum2(..arr) +//│ ║ l.90: sum2(..arr) //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 2 diff --git a/hkmc2/shared/src/test/mlscript/basics/ModuleMethods.mls b/hkmc2/shared/src/test/mlscript/basics/ModuleMethods.mls index 9df8d9038b..c787548422 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ModuleMethods.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ModuleMethods.mls @@ -124,9 +124,11 @@ fun f3() = (() => M)() :todo :e :effectHandlers +:lift +:noSanityCheck fun f3() = (() => return M)() //│ ╔══[ERROR] Unexpected moduleful reference of type M. -//│ ║ l.127: fun f3() = (() => return M)() +//│ ║ l.129: fun f3() = (() => return M)() //│ ╙── ^ @@ -136,7 +138,7 @@ fun f3() = (() => return M)() :e fun f(module m: M) = m //│ ╔══[ERROR] Unexpected moduleful reference of type M. -//│ ║ l.137: fun f(module m: M) = m +//│ ║ l.139: fun f(module m: M) = m //│ ║ ^ //│ ╙── Function must be marked as returning a 'module' in order to return a module. @@ -149,7 +151,7 @@ fun f(module m: M): module M = m :e val v = M //│ ╔══[ERROR] Unexpected moduleful reference of type M. -//│ ║ l.150: val v = M +//│ ║ l.152: val v = M //│ ║ ^ //│ ╙── Value must be marked as returning a 'module' in order to return a module. @@ -172,8 +174,8 @@ class C with fun foo: module M = M val bar: module M = M //│ ╔══[ERROR] Function returning modules should not be a class member. -//│ ║ l.172: fun foo: module M = M +//│ ║ l.174: fun foo: module M = M //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[ERROR] Value returning modules should not be a class member. -//│ ║ l.173: val bar: module M = M +//│ ║ l.175: val bar: module M = M //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls b/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls index 5025320ce4..530565d1d6 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls @@ -83,9 +83,14 @@ if 1 + 2 //│ let scrut, tmp17; //│ tmp17 = 2 * 3; //│ scrut = 1 + tmp17; -//│ if (scrut === true) { -//│ 0 -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (scrut) { +//│ case true: +//│ 0; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ ═══[RUNTIME ERROR] Error: match error :pt @@ -116,23 +121,23 @@ if 1 + 2 * 3 then 0 + 4 then 1 //│ ╔══[PARSE ERROR] Operator cannot be used inside this operator split -//│ ║ l.117: + 4 then 1 +//│ ║ l.122: + 4 then 1 //│ ║ ^ //│ ╟── as it has lower precedence than the splitting operator here -//│ ║ l.116: * 3 then 0 +//│ ║ l.121: * 3 then 0 //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected 'then' keyword in this operator split inner position -//│ ║ l.117: + 4 then 1 +//│ ║ l.122: + 4 then 1 //│ ║ ^^^^ //│ ╟── Note: the operator split starts here -//│ ║ l.116: * 3 then 0 +//│ ║ l.121: * 3 then 0 //│ ╙── ^ //│ ╔══[ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.115: if 1 + 2 +//│ ║ l.120: if 1 + 2 //│ ║ ^ -//│ ║ l.116: * 3 then 0 +//│ ║ l.121: * 3 then 0 //│ ║ ^^^^^^^^^^^^ -//│ ║ l.117: + 4 then 1 +//│ ║ l.122: + 4 then 1 //│ ╙── ^^^^^ :pt diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls b/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls index 19e96e6493..4b2d8bbebb 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls @@ -112,7 +112,7 @@ inc(41) :sjs if 1 == 2 then 0 else 42 //│ JS (unsanitized): -//│ let scrut; scrut = 1 == 2; if (scrut === true) { 0 } else { 42 } +//│ let scrut; scrut = 1 == 2; switch (scrut) { case true: 0; break; default: 42; break; } //│ = 42 //│ Type: Int @@ -152,14 +152,17 @@ fun pow(x) = case //│ let lambda1; //│ lambda1 = (undefined, function (caseScrut) { //│ let n, tmp, tmp1, tmp2; -//│ if (caseScrut === 0) { -//│ return 1 -//│ } else { -//│ n = caseScrut; -//│ tmp = pow(x1); -//│ tmp1 = n - 1; -//│ tmp2 = runtime.safeCall(tmp(tmp1)); -//│ return x1 * tmp2 +//│ switch (caseScrut) { +//│ case 0: +//│ return 1; +//│ break; +//│ default: +//│ n = caseScrut; +//│ tmp = pow(x1); +//│ tmp1 = n - 1; +//│ tmp2 = runtime.safeCall(tmp(tmp1)); +//│ return x1 * tmp2; +//│ break; //│ } //│ }); //│ return lambda1 @@ -176,12 +179,16 @@ fun nott = case //│ nott = function nott() { //│ let lambda1; //│ lambda1 = (undefined, function (caseScrut) { -//│ if (caseScrut === true) { -//│ return false -//│ } else if (caseScrut === false) { -//│ return true -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) +//│ switch (caseScrut) { +//│ case true: +//│ return false; +//│ break; +//│ case false: +//│ return true; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; //│ } //│ }); //│ return lambda1 @@ -208,14 +215,17 @@ fun fact = case //│ let lambda1; //│ lambda1 = (undefined, function (caseScrut) { //│ let n, tmp1, tmp2, tmp3; -//│ if (caseScrut === 0) { -//│ return 1 -//│ } else { -//│ n = caseScrut; -//│ tmp1 = fact(); -//│ tmp2 = n - 1; -//│ tmp3 = tmp1(tmp2); -//│ return n * tmp3 +//│ switch (caseScrut) { +//│ case 0: +//│ return 1; +//│ break; +//│ default: +//│ n = caseScrut; +//│ tmp1 = fact(); +//│ tmp2 = n - 1; +//│ tmp3 = tmp1(tmp2); +//│ return n * tmp3; +//│ break; //│ } //│ }); //│ return lambda1 diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls b/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls index d72caafeeb..b35945e5e2 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls @@ -93,10 +93,13 @@ fun test2() = //│ funny = function funny() { //│ let lambda, lambda1; //│ lambda = (undefined, function (caseScrut) { -//│ if (caseScrut === 0) { -//│ return 0 -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) +//│ switch (caseScrut) { +//│ case 0: +//│ return 0; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; //│ } //│ }); //│ lambda1 = (undefined, function (caseScrut) { @@ -130,7 +133,7 @@ fun test2() = fun test3 = print("Hi") //│ ╔══[ERROR] Function definition shape not yet supported for test3 -//│ ║ l.131: print("Hi") +//│ ║ l.134: print("Hi") //│ ╙── ^^^^^^^^^^^ //│ Type: ⊤ diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls b/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls index 4e5ffb710e..2c3962fe06 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls @@ -65,7 +65,6 @@ declare module wasm with declare module debug with fun printStack - fun getLocals declare module annotations with object compile diff --git a/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls b/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls index 510dbe3eeb..184e762c7a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls @@ -16,9 +16,14 @@ x => if x is //│ JS (unsanitized): //│ let lambda2; //│ lambda2 = (undefined, function (x) { -//│ if (x === 0) { -//│ return true -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (x) { +//│ case 0: +//│ return true; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ }); //│ lambda2 //│ = fun @@ -28,9 +33,14 @@ case 0 then true //│ JS (unsanitized): //│ let lambda3; //│ lambda3 = (undefined, function (caseScrut) { -//│ if (caseScrut === 0) { -//│ return true -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (caseScrut) { +//│ case 0: +//│ return true; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ }); //│ lambda3 //│ = fun @@ -53,10 +63,10 @@ case 0 then true :todo // TODO: support this braceless syntax? case [x] then x, [] then 0 //│ ╔══[ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.54: case [x] then x, [] then 0 +//│ ║ l.64: case [x] then x, [] then 0 //│ ╙── ^^^^^^^^^ //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.54: case [x] then x, [] then 0 +//│ ║ l.64: case [x] then x, [] then 0 //│ ╙── ^^^^^^^^^^^^^^^ :sjs @@ -66,7 +76,14 @@ case //│ JS (unsanitized): //│ let lambda11; //│ lambda11 = (undefined, function (caseScrut) { -//│ if (caseScrut === 0) { return true } else { return false } +//│ switch (caseScrut) { +//│ case 0: +//│ return true; +//│ break; +//│ default: +//│ return false; +//│ break; +//│ } //│ }); //│ lambda11 //│ = fun diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls index 55c80c83c3..81b747ab9e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls @@ -127,11 +127,14 @@ fun f(x) = if x is //│ argument0$4 = x3.value; //│ x4 = argument0$4; //│ scrut1 = x4 > 0; -//│ if (scrut1 === true) { -//│ tmp5 = 42; -//│ break split_root$1 -//│ } else { -//│ break split_1$ +//│ switch (scrut1) { +//│ case true: +//│ tmp5 = 42; +//│ break split_root$1; +//│ break; +//│ default: +//│ break split_1$; +//│ break; //│ } //│ } else if (x3 instanceof None1.class) { //│ tmp5 = "ok"; @@ -201,11 +204,14 @@ fun f(x) = print of if x is //│ split_1$: { //│ if (x3 instanceof Some1.class) { //│ argument0$4 = x3.value; -//│ if (argument0$4 === 0) { -//│ tmp9 = "0"; -//│ break split_root$1 -//│ } else { -//│ break split_1$ +//│ switch (argument0$4) { +//│ case 0: +//│ tmp9 = "0"; +//│ break split_root$1; +//│ break; +//│ default: +//│ break split_1$; +//│ break; //│ } //│ } else if (x3 instanceof None1.class) { //│ tmp9 = "ok"; diff --git a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls index a4fddd8d26..c16f815fee 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls @@ -83,9 +83,14 @@ else foo = 1 //│ let foo1, scrut; //│ foo1 = undefined; //│ scrut = true; -//│ if (scrut === true) { -//│ foo1 -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (scrut) { +//│ case true: +//│ foo1; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ foo = undefined let foo @@ -98,7 +103,16 @@ else //│ let foo2, scrut1; //│ foo2 = undefined; //│ scrut1 = true; -//│ if (scrut1 === true) { foo2 = 0; runtime.Unit } else { foo2 = 1; runtime.Unit } +//│ switch (scrut1) { +//│ case true: +//│ foo2 = 0; +//│ runtime.Unit; +//│ break; +//│ default: +//│ foo2 = 1; +//│ runtime.Unit; +//│ break; +//│ } //│ foo = 0 @@ -120,7 +134,7 @@ foo :fixme fun f() = foo = 0 //│ ╔══[PARSE ERROR] Expected end of input; found '=' keyword instead -//│ ║ l.121: fun f() = foo = 0 +//│ ║ l.135: fun f() = foo = 0 //│ ╙── ^ //│ JS (unsanitized): //│ let f4; f4 = function f() { return foo2 }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls b/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls index 797b5c0c47..27a3cb8511 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls @@ -22,10 +22,15 @@ fun f(x) = //│ f1 = function f(x) { //│ let scrut, tmp, tmp1; //│ scrut = x < 0; -//│ if (scrut === true) { -//│ tmp = Predef.print("whoops"); -//│ return 0 -//│ } else { tmp1 = runtime.Unit; } +//│ switch (scrut) { +//│ case true: +//│ tmp = Predef.print("whoops"); +//│ return 0; +//│ break; +//│ default: +//│ tmp1 = runtime.Unit; +//│ break; +//│ } //│ return x + 1 //│ }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls index 883fdd4d3e..3691ed5059 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls @@ -23,9 +23,14 @@ if false then 0 //│ JS (unsanitized): //│ let scrut; //│ scrut = false; -//│ if (scrut === true) { -//│ 0 -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (scrut) { +//│ case true: +//│ 0; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') @@ -41,9 +46,14 @@ foo() //│ foo = function foo() { //│ let scrut1; //│ scrut1 = false; -//│ if (scrut1 === true) { -//│ return 0 -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (scrut1) { +//│ case true: +//│ return 0; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ }; //│ foo() //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') diff --git a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls index a440378432..7124979169 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls @@ -11,7 +11,14 @@ let f = x => if x then print("ok") else print("ko") //│ JS (unsanitized): //│ let f, lambda; //│ lambda = (undefined, function (x) { -//│ if (x === true) { return Predef.print("ok") } else { return Predef.print("ko") } +//│ switch (x) { +//│ case true: +//│ return Predef.print("ok"); +//│ break; +//│ default: +//│ return Predef.print("ko"); +//│ break; +//│ } //│ }); //│ f = lambda; //│ f = fun @@ -29,9 +36,14 @@ let f = x => print((if x then "ok" else "ko") + "!") //│ let f1, lambda1; //│ lambda1 = (undefined, function (x) { //│ let tmp, tmp1; -//│ if (x === true) { -//│ tmp = "ok"; -//│ } else { tmp = "ko"; } +//│ switch (x) { +//│ case true: +//│ tmp = "ok"; +//│ break; +//│ default: +//│ tmp = "ko"; +//│ break; +//│ } //│ tmp1 = tmp + "!"; //│ return Predef.print(tmp1) //│ }); @@ -44,9 +56,14 @@ let f = x => print((if x and x then "ok" else "ko") + "!") //│ let f2, lambda2; //│ lambda2 = (undefined, function (x) { //│ let tmp, tmp1; -//│ if (x === true) { -//│ tmp = "ok"; -//│ } else { tmp = "ko"; } +//│ switch (x) { +//│ case true: +//│ tmp = "ok"; +//│ break; +//│ default: +//│ tmp = "ko"; +//│ break; +//│ } //│ tmp1 = tmp + "!"; //│ return Predef.print(tmp1) //│ }); diff --git a/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls b/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls index 0d43252465..9ff2422b53 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls @@ -15,8 +15,10 @@ Example.f(123) let s = Example.f //│ JS: //│ selRes = Example1.f; -//│ if (selRes === undefined) { -//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'f' yielded 'undefined'")) +//│ switch (selRes) { +//│ case undefined: +//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'f' yielded 'undefined'")); +//│ break; //│ } //│ s = selRes; //│ block$res3 = undefined; @@ -28,7 +30,7 @@ s(123) :e Example |>. s(123) //│ ╔══[ERROR] Unexpected moduleful reference of type Example. -//│ ║ l.29: Example |>. s(123) +//│ ║ l.31: Example |>. s(123) //│ ║ ^^^^^^^ //│ ╙── Module argument passed to a non-module parameter. //│ = [123, 456] diff --git a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls index ec8c349b28..9dda19f099 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls @@ -267,6 +267,13 @@ fun f(x) = //│ f16 = function f(x3) { //│ let scrut; //│ scrut = x3 == 0; -//│ if (scrut === true) { return 1 } else { return 2 } +//│ switch (scrut) { +//│ case true: +//│ return 1; +//│ break; +//│ default: +//│ return 2; +//│ break; +//│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/RandomStuff.mls b/hkmc2/shared/src/test/mlscript/codegen/RandomStuff.mls index 10940ea078..99eeb17cb0 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/RandomStuff.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/RandomStuff.mls @@ -8,7 +8,14 @@ fun foo() = if false do foo() //│ foo = function foo() { //│ let scrut; //│ scrut = false; -//│ if (scrut === true) { return foo() } else { return runtime.Unit } +//│ switch (scrut) { +//│ case true: +//│ return foo(); +//│ break; +//│ default: +//│ return runtime.Unit; +//│ break; +//│ } //│ }; :sjs @@ -71,10 +78,10 @@ do let foo = 1 fun foo(x) = foo //│ ╔══[ERROR] Name 'foo' is already used -//│ ║ l.71: let foo = 1 +//│ ║ l.78: let foo = 1 //│ ║ ^^^^^^^ //│ ╟── by a member declared in the same block -//│ ║ l.72: fun foo(x) = foo +//│ ║ l.79: fun foo(x) = foo //│ ╙── ^^^^^^^^^^^^^^^^ //│ JS (unsanitized): //│ let foo3, foo4; foo3 = function foo(x) { return foo4 }; foo4 = 1; diff --git a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls index d5ef1fbb25..9fb860ac49 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls @@ -210,8 +210,10 @@ console.log(1) //│ JS: //│ selRes = globalThis.console; //│ discarded = globalThis.console$__checkNotMethod; -//│ if (selRes === undefined) { -//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'console' yielded 'undefined'")) +//│ switch (selRes) { +//│ case undefined: +//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'console' yielded 'undefined'")); +//│ break; //│ } //│ block$res10 = runtime.safeCall(selRes.log(1)); //│ undefined @@ -224,8 +226,10 @@ globalThis.x //│ JS: //│ selRes1 = globalThis.x; //│ discarded1 = globalThis.x$__checkNotMethod; -//│ if (selRes1 === undefined) { -//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'x' yielded 'undefined'")) +//│ switch (selRes1) { +//│ case undefined: +//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'x' yielded 'undefined'")); +//│ break; //│ } //│ block$res11 = selRes1; //│ undefined @@ -284,13 +288,24 @@ if M.A(1).y is //│ tmp11 = runtime.checkCall(M1.A(1)); //│ selRes2 = tmp11.y; //│ discarded2 = tmp11.y$__checkNotMethod; -//│ if (selRes2 === undefined) { -//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'y' yielded 'undefined'")) +//│ switch (selRes2) { +//│ case undefined: +//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'y' yielded 'undefined'")); +//│ break; //│ } //│ scrut = selRes2; //│ x = scrut; //│ scrut1 = x == 1; -//│ if (scrut1 === true) { block$res13 = x; undefined } else { block$res13 = 0; undefined } +//│ switch (scrut1) { +//│ case true: +//│ block$res13 = x; +//│ undefined; +//│ break; +//│ default: +//│ block$res13 = 0; +//│ undefined; +//│ break; +//│ } //│ ═══[RUNTIME ERROR] Error: Access to required field 'y' yielded 'undefined' @@ -302,14 +317,18 @@ M.A(1).y //│ JS: //│ selRes3 = globalThis.console; //│ discarded3 = globalThis.console$__checkNotMethod; -//│ if (selRes3 === undefined) { -//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'console' yielded 'undefined'")) +//│ switch (selRes3) { +//│ case undefined: +//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'console' yielded 'undefined'")); +//│ break; //│ } //│ tmp12 = runtime.checkCall(M1.A(1)); //│ selRes4 = tmp12.y; //│ discarded4 = tmp12.y$__checkNotMethod; -//│ if (selRes4 === undefined) { -//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'y' yielded 'undefined'")) +//│ switch (selRes4) { +//│ case undefined: +//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'y' yielded 'undefined'")); +//│ break; //│ } //│ block$res14 = runtime.safeCall(selRes3.log(selRes4)); //│ undefined diff --git a/hkmc2/shared/src/test/mlscript/codegen/Throw.mls b/hkmc2/shared/src/test/mlscript/codegen/Throw.mls index 4ef0cd898a..8224c3d378 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Throw.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Throw.mls @@ -43,7 +43,14 @@ f(false) //│ JS (unsanitized): //│ let f3; //│ f3 = function f(x) { -//│ if (x === true) { throw globalThis.Error("x") } else { throw globalThis.Error("y") } +//│ switch (x) { +//│ case true: +//│ throw globalThis.Error("x"); +//│ break; +//│ default: +//│ throw globalThis.Error("y"); +//│ break; +//│ } //│ }; //│ f3(false) //│ ═══[RUNTIME ERROR] Error: y diff --git a/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls b/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls index 1b3593632c..5d50c07a6a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls @@ -11,9 +11,18 @@ fun fib(a) = if //│ fib = function fib(a) { //│ let scrut, tmp, tmp1, tmp2, tmp3; //│ scrut = a <= 1; -//│ if (scrut === true) { -//│ return a -//│ } else { tmp = a - 1; tmp1 = fib(tmp); tmp2 = a - 2; tmp3 = fib(tmp2); return tmp1 + tmp3 } +//│ switch (scrut) { +//│ case true: +//│ return a; +//│ break; +//│ default: +//│ tmp = a - 1; +//│ tmp1 = fib(tmp); +//│ tmp2 = a - 2; +//│ tmp3 = fib(tmp2); +//│ return tmp1 + tmp3; +//│ break; +//│ } //│ }; fun f(x) = g(x) diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index 407c7639f2..c7a87f8828 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -11,17 +11,27 @@ //│ while1 = (undefined, function () { //│ let scrut; //│ scrut = true; -//│ if (scrut === true) { -//│ tmp = 0; -//│ return while1() -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) +//│ switch (scrut) { +//│ case true: +//│ tmp = 0; +//│ return while1(); +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; //│ } //│ return runtime.LoopEnd //│ }); //│ tmp1 = while1(); //│ tmp2 = tmp1 !== runtime.LoopEnd; -//│ if (tmp2 === true) { return tmp1 } else { return tmp } +//│ switch (tmp2) { +//│ case true: +//│ return tmp1; +//│ break; +//│ default: +//│ return tmp; +//│ break; +//│ } //│ }); //│ lambda //│ = fun @@ -61,12 +71,17 @@ while x //│ tmp4 = undefined; //│ while3 = (undefined, function () { //│ let tmp6; -//│ if (x2 === true) { -//│ tmp6 = Predef.print("Hello World"); -//│ x2 = false; -//│ tmp4 = runtime.Unit; -//│ return while3() -//│ } else { tmp4 = 42; } +//│ switch (x2) { +//│ case true: +//│ tmp6 = Predef.print("Hello World"); +//│ x2 = false; +//│ tmp4 = runtime.Unit; +//│ return while3(); +//│ break; +//│ default: +//│ tmp4 = 42; +//│ break; +//│ } //│ return runtime.LoopEnd //│ }); //│ tmp5 = while3(); @@ -118,19 +133,29 @@ while //│ let i2, scrut, tmp15; //│ i2 = 0; //│ scrut = i2 < 10; -//│ if (scrut === true) { -//│ tmp15 = i2 + 1; -//│ i2 = tmp15; -//│ tmp12 = runtime.Unit; -//│ return while7() -//│ } else { -//│ tmp12 = runtime.Unit; +//│ switch (scrut) { +//│ case true: +//│ tmp15 = i2 + 1; +//│ i2 = tmp15; +//│ tmp12 = runtime.Unit; +//│ return while7(); +//│ break; +//│ default: +//│ tmp12 = runtime.Unit; +//│ break; //│ } //│ return runtime.LoopEnd //│ }); //│ tmp13 = while7(); //│ tmp14 = tmp13 !== runtime.LoopEnd; -//│ if (tmp14 === true) { return tmp13 } else { return tmp12 } +//│ switch (tmp14) { +//│ case true: +//│ return tmp13; +//│ break; +//│ default: +//│ return tmp12; +//│ break; +//│ } //│ }); //│ lambda2 //│ = fun @@ -229,7 +254,14 @@ fun f(ls) = //│ }); //│ tmp19 = while10(); //│ tmp20 = tmp19 !== runtime.LoopEnd; -//│ if (tmp20 === true) { return tmp19 } else { return tmp18 } +//│ switch (tmp20) { +//│ case true: +//│ return tmp19; +//│ break; +//│ default: +//│ return tmp18; +//│ break; +//│ } //│ }; f(0) @@ -297,10 +329,10 @@ while print("Hello World"); false then 0(0) else 1 //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.297: then 0(0) +//│ ║ l.329: then 0(0) //│ ╙── ^^^^ //│ ╔══[ERROR] Unrecognized term split (false literal) -//│ ║ l.296: while print("Hello World"); false +//│ ║ l.328: while print("Hello World"); false //│ ╙── ^^^^^ //│ > Hello World //│ ═══[RUNTIME ERROR] Error: match error @@ -310,12 +342,12 @@ while { print("Hello World"), false } then 0(0) else 1 //│ ╔══[ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.309: while { print("Hello World"), false } +//│ ║ l.341: while { print("Hello World"), false } //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.310: then 0(0) +//│ ║ l.342: then 0(0) //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.311: else 1 +//│ ║ l.343: else 1 //│ ╙── ^^^^ :fixme @@ -325,14 +357,14 @@ while then 0(0) else 1 //│ ╔══[ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.323: print("Hello World") +//│ ║ l.355: print("Hello World") //│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.324: false +//│ ║ l.356: false //│ ║ ^^^^^^^^^ -//│ ║ l.325: then 0(0) +//│ ║ l.357: then 0(0) //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.326: else 1 +//│ ║ l.358: else 1 //│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index b540e2c5ca..42fdffc90a 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -185,7 +185,6 @@ declare module wasm with declare module debug with fun printStack - fun getLocals declare module annotations with object compile diff --git a/hkmc2/shared/src/test/mlscript/handlers/BadHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/BadHandlers.mls index 5df46d2bf2..5da159ac74 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/BadHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/BadHandlers.mls @@ -1,11 +1,12 @@ :js :effectHandlers +:lift +:noSanityCheck +class Object -:re handle h = Object with {} h.oops -//│ ═══[RUNTIME ERROR] Error: Access to required field 'oops' yielded 'undefined' handle h = Object with {} val x = 1 @@ -18,16 +19,14 @@ x + 1 :e x //│ ╔══[ERROR] Name not found: x -//│ ║ l.19: x +//│ ║ l.20: x //│ ╙── ^ // Getter -:fixme handle h = Object with fun lol(k) = k(()) h.lol -//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$3 handle h = Object with fun lol()(k) = k(42) @@ -41,11 +40,9 @@ object Foo with x + 1 :e -:re Foo.x //│ ╔══[ERROR] Object 'Foo' does not contain member 'x' -//│ ║ l.45: Foo.x +//│ ║ l.43: Foo.x //│ ╙── ^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls index de274fb30f..4d2894ba0a 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls @@ -1,5 +1,7 @@ :js :effectHandlers debug +:lift +:noSanityCheck @@ -17,106 +19,91 @@ fun f() = print of raiseUnhandledEffect() j / i //│ JS (unsanitized): -//│ let f, getLocals2; -//│ getLocals2 = function getLocals() { -//│ let prev, thisInfo, arr, tmp; -//│ prev = []; -//│ arr = globalThis.Object.freeze([]); -//│ thisInfo = new runtime.FnLocalsInfo.class("\u2039top level\u203A", arr); -//│ tmp = runtime.safeCall(prev.push(thisInfo)); -//│ return prev -//│ }; +//│ let f, f$debugInfo; +//│ f$debugInfo = globalThis.Object.freeze([ +//│ "f", +//│ 0, +//│ "i", +//│ 1, +//│ "j", +//│ 2, +//│ "k" +//│ ]); //│ f = function f() { -//│ let i, j, k, scrut, tmp, tmp1, getLocals3, Cont$func$f$1, doUnwind; -//│ getLocals3 = function getLocals() { -//│ let i1, j1, k1, prev, thisInfo, arr, tmp2; -//│ prev = getLocals2(); -//│ i1 = new runtime.LocalVarInfo.class("i", i); -//│ j1 = new runtime.LocalVarInfo.class("j", j); -//│ k1 = new runtime.LocalVarInfo.class("k", k); -//│ arr = globalThis.Object.freeze([ -//│ i1, -//│ j1, -//│ k1 -//│ ]); -//│ thisInfo = new runtime.FnLocalsInfo.class("f", arr); -//│ tmp2 = runtime.safeCall(prev.push(thisInfo)); -//│ return prev -//│ }; -//│ globalThis.Object.freeze(class Cont$func$f$ extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$func$f$1 = this -//│ } -//│ constructor(pc) { -//│ let tmp2; -//│ tmp2 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 1) { -//│ tmp = value$; -//│ } else if (this.pc === 2) { -//│ tmp1 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 3) { -//│ return j / i -//│ } else if (this.pc === 4) { -//│ tmp1 = Predef.print(tmp); -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return this.doUnwind(tmp1, 2) -//│ } -//│ this.pc = 2; -//│ continue contLoop -//│ } else if (this.pc === 1) { -//│ this.pc = 4; -//│ continue contLoop -//│ } else if (this.pc === 2) { -//│ this.pc = 3; -//│ continue contLoop +//│ let i, j, k, scrut, tmp1, tmp2, pc, saveOffset; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ i = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ j = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ k = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 3; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 4; +//│ tmp1 = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 5; +//│ tmp2 = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ i = 0; +//│ j = 100; +//│ k = 2000; +//│ scrut = i == 0; +//│ switch (scrut) { +//│ case true: +//│ runtime.resumeValue = Predef.raiseUnhandledEffect(); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, f, f$debugInfo, "Debugging.mls:19:14", 4, null, 6, i, j, k, scrut, tmp1, tmp2) +//│ } +//│ pc = 4; +//│ continue main; +//│ break; +//│ default: +//│ tmp2 = runtime.Unit; +//│ break; //│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ return j / i; +//│ break; +//│ case 2: +//│ tmp2 = runtime.resumeValue; +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 3: +//│ runtime.resumeValue = Predef.print(tmp1); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, f, f$debugInfo, "Debugging.mls:19:5", 2, null, 6, i, j, k, scrut, tmp1, tmp2) +//│ } +//│ pc = 2; +//│ continue main; +//│ break; +//│ case 4: +//│ tmp1 = runtime.resumeValue; +//│ pc = 3; +//│ continue main; //│ break; -//│ } -//│ } -//│ get getLocals() { -//│ return getLocals3(); -//│ } -//│ get getLoc() { -//│ if (this.pc === 1) { -//│ return "Debugging.mls:17:14" -//│ } else if (this.pc === 2) { -//│ return "Debugging.mls:17:5" -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$func$f$"]; -//│ }); -//│ doUnwind = function doUnwind(res1, pc) { -//│ res1.contTrace.last.next = new Cont$func$f$1(pc); -//│ res1.contTrace.last = res1.contTrace.last.next; -//│ return res1 -//│ }; -//│ i = 0; -//│ j = 100; -//│ k = 2000; -//│ scrut = i == 0; -//│ if (scrut === true) { -//│ tmp = Predef.raiseUnhandledEffect(); -//│ if (tmp instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp, 1) -//│ } -//│ tmp1 = Predef.print(tmp); -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp1, 2) //│ } -//│ } else { tmp1 = runtime.Unit; } -//│ return j / i +//│ break; +//│ } //│ }; :re f() //│ ═══[RUNTIME ERROR] Error: Unhandled effect FatalEffect -//│ at f (Debugging.mls:17:14) +//│ at f (Debugging.mls:19:14) :re fun lambda_test(f) = @@ -126,8 +113,8 @@ lambda_test(() => raiseUnhandledEffect() 100) //│ ═══[RUNTIME ERROR] Error: Unhandled effect FatalEffect -//│ at lambda (Debugging.mls:126:3) -//│ at lambda_test (Debugging.mls:123:3) +//│ at lambda (Debugging.mls:113:3) +//│ at lambda_test (Debugging.mls:110:3) import "../../mlscript-compile/Runtime.mls" @@ -136,13 +123,6 @@ import "../../mlscript-compile/Runtime.mls" let res = Runtime.try(f) res.reified.contTrace.next.getLocals -//│ = [ -//│ FnLocalsInfo("‹top level›", []), -//│ FnLocalsInfo( -//│ "f", -//│ [LocalVarInfo("i", 0), LocalVarInfo("j", 100), LocalVarInfo("k", 2000)] -//│ ) -//│ ] Runtime.debugEff(res.reified) //│ > Debug EffectSig: @@ -150,7 +130,7 @@ Runtime.debugEff(res.reified) //│ > handlerFun: null //│ > resumed: false //│ > -//│ > Cont$func$f$(pc=1, last) -> (null) +//│ > FunctionContFrame(pc=undefined, last) -> (null) //│ > res.resumeWith(42) @@ -163,20 +143,19 @@ class Debugger() with fun break(payload) module Test with - fun test(j)(using dbg: Debugger) = + fun test(j, dbg) = let i = 0 let k = 2000 if i == 0 do set i = dbg.break("whoops") j / i - fun main()(using Debugger) = - test(12) + test(34) + fun main(dbg) = + test(12, dbg) + test(34, dbg) let res = handle h = Debugger with fun break(payload)(resume) = resume - using Debugger = h - Runtime.try(() => Test.main()) + Runtime.try(() => Test.main(h)) //│ res = EffectHandle(_) :re @@ -195,20 +174,16 @@ res.resumeWith(666) let i = 100 fun f() = let j = 200 - print(debug.getLocals()) debug.printStack(true) set j = 300 debug.printStack(false) debug.printStack(true) - print(debug.getLocals()) //│ i = 100 f() -//│ > [FnLocalsInfo("‹top level›", [LocalVarInfo("i", 100)]), FnLocalsInfo("f", [LocalVarInfo("j", 200)])] //│ > Stack Trace: -//│ > at f (Debugging.mls:199:3) with locals: j=200 +//│ > at f (Debugging.mls:177:3) with locals: j=200 //│ > Stack Trace: -//│ > at f (Debugging.mls:201:3) +//│ > at f (Debugging.mls:179:3) //│ > Stack Trace: -//│ > at f (Debugging.mls:202:3) with locals: j=300 -//│ > [FnLocalsInfo("‹top level›", [LocalVarInfo("i", 100)]), FnLocalsInfo("f", [LocalVarInfo("j", 300)])] +//│ > at tail position diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls index b2d56c6af5..6f29dee06d 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck class PrintEffect with fun p(s: String): () @@ -17,10 +19,10 @@ handle h = PrintEffect with h.p(x) h.aux(3) //│ ╔══[ERROR] Name not found: h -//│ ║ l.15: h.p(x) +//│ ║ l.17: h.p(x) //│ ╙── ^ //│ ╔══[ERROR] Name not found: h -//│ ║ l.17: h.p(x) +//│ ║ l.19: h.p(x) //│ ╙── ^ handle h1 = PrintEffect with diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 41f5579d45..7b42afd513 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with @@ -156,70 +158,55 @@ if true do fun f() = 3 f() //│ JS (unsanitized): -//│ let tmp11, handleBlock$11; -//│ handleBlock$11 = function handleBlock$() { -//│ let f, h, scrut, res, Cont$handleBlock$h$11, doUnwind, Handler$h$12; -//│ globalThis.Object.freeze(class Handler$h$11 extends Effect1 { -//│ static { -//│ Handler$h$12 = this -//│ } -//│ constructor() { -//│ let tmp12; -//│ tmp12 = super(); -//│ } -//│ perform(arg) { -//│ let hdlrFun; -//│ hdlrFun = function hdlrFun(k) { -//│ return arg -//│ }; -//│ return runtime.mkEffect(this, hdlrFun) -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Handler$h$"]; -//│ }); -//│ h = new Handler$h$12(); -//│ globalThis.Object.freeze(class Cont$handleBlock$h$10 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handleBlock$h$11 = this -//│ } -//│ constructor(pc) { -//│ let tmp12; -//│ tmp12 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 1) { -//│ res = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 1) { -//│ return res -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$handleBlock$h$"]; -//│ }); -//│ doUnwind = function doUnwind(res1, pc) { -//│ res1.contTrace.last.next = new Cont$handleBlock$h$11(pc); -//│ return runtime.handleBlockImpl(res1, h) -//│ }; -//│ f = function f() { -//│ return 3 -//│ }; -//│ scrut = true; -//│ if (scrut === true) { -//│ res = f(); -//│ if (res instanceof runtime.EffectSig.class) { -//│ return doUnwind(res, 1) +//│ let f, h11, tmp11, handleBlock$11, Handler$h$perform11, Handler$h$23, Handler$h$perform$11; +//│ Handler$h$perform$11 = function Handler$h$perform$(Handler$h$$instance, arg, k) { +//│ return arg +//│ }; +//│ Handler$h$perform11 = function Handler$h$perform(Handler$h$$instance, arg) { +//│ return (k) => { +//│ return Handler$h$perform$11(Handler$h$$instance, arg, k) +//│ } +//│ }; +//│ globalThis.Object.freeze(class Handler$h$22 extends Effect1 { +//│ static { +//│ Handler$h$23 = this +//│ } +//│ constructor() { +//│ let tmp12; +//│ tmp12 = super(); +//│ if (tmp12 instanceof runtime.EffectSig.class) { +//│ tmp12 = runtime.topLevelEffect(tmp12, false); //│ } -//│ return res -//│ } else { -//│ return runtime.Unit +//│ tmp12; +//│ } +//│ perform(arg) { +//│ let Handler$h$perform$here; +//│ Handler$h$perform$here = runtime.safeCall(Handler$h$perform11(this, arg)); +//│ return runtime.mkEffect(this, Handler$h$perform$here) //│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "Handler$h$"]; +//│ }); +//│ h11 = new Handler$h$23(); +//│ if (h11 instanceof runtime.EffectSig.class) { +//│ h11 = runtime.topLevelEffect(h11, false); +//│ } +//│ f = function f() { +//│ return 3 //│ }; -//│ tmp11 = handleBlock$11(); +//│ handleBlock$11 = (undefined, function () { +//│ let scrut; +//│ scrut = true; +//│ switch (scrut) { +//│ case true: +//│ return f(); +//│ break; +//│ default: +//│ return runtime.Unit; +//│ break; +//│ } +//│ }); +//│ tmp11 = runtime.enterHandleBlock(h11, handleBlock$11); //│ if (tmp11 instanceof runtime.EffectSig.class) { //│ tmp11 = runtime.topLevelEffect(tmp11, false); //│ } @@ -370,6 +357,7 @@ handle h = Eff with foo(h) //│ = 123 +:w :expect 123 fun foo(h) = h.perform() @@ -394,6 +382,22 @@ fun foo(h) = handle h = Eff with fun perform()(k) = k(()) foo(h) +//│ ═══[WARNING] Modules are not yet lifted. +//│ ═══[WARNING] Modules are not yet lifted. +//│ ═══[WARNING] Modules are not yet lifted. +//│ ═══[WARNING] Modules are not yet lifted. +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.366: module A with +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.370: module A with +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.374: module A with +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.378: module A with +//│ ╙── ^ //│ = 123 // Access superclass fields diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls index ee16b6ecbd..3da9bff992 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls @@ -1,10 +1,13 @@ :js :effectHandlers +:lift +:noSanityCheck module M // * Notice that the two module definitions are lifted to the top of the block. :sjs +:w fun foo(h): module M = if false then module A @@ -12,32 +15,55 @@ fun foo(h): module M = else module A A +//│ ═══[WARNING] Modules are not yet lifted. +//│ ═══[WARNING] Modules are not yet lifted. +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.13: module A +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.16: module A +//│ ╙── ^ //│ JS (unsanitized): //│ let foo; //│ foo = function foo(h) { //│ let A2, A3, scrut; -//│ globalThis.Object.freeze(class A { -//│ static { -//│ A2 = this -//│ } -//│ constructor() { -//│ runtime.Unit; -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "A"]; -//│ }); -//│ globalThis.Object.freeze(class A1 { -//│ static { -//│ A3 = this -//│ } -//│ constructor() { -//│ runtime.Unit; -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "A"]; -//│ }); //│ scrut = false; -//│ if (scrut === true) { return A2 } else { return A3 } +//│ switch (scrut) { +//│ case true: +//│ globalThis.Object.freeze(class A { +//│ static { +//│ A2 = this +//│ } +//│ constructor() { +//│ runtime.Unit; +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "A"]; +//│ }); +//│ return A2; +//│ break; +//│ default: +//│ globalThis.Object.freeze(class A1 { +//│ static { +//│ A3 = this +//│ } +//│ constructor() { +//│ runtime.Unit; +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "A"]; +//│ }); +//│ return A3; +//│ break; +//│ } //│ }; +//│ ═══[WARNING] Modules are not yet lifted. +//│ ═══[WARNING] Modules are not yet lifted. +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.13: module A +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.16: module A +//│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls index fbef803763..9c921c74e4 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with @@ -19,61 +21,17 @@ data class Lol(h) with //│ Lol1.class = this //│ } //│ constructor(h) { -//│ let tmp, res, Cont$ctor$Lol$1, doUnwind; -//│ const this$Lol = this; -//│ globalThis.Object.freeze(class Cont$ctor$Lol$ extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$ctor$Lol$1 = this -//│ } -//│ constructor(pc) { -//│ let tmp1; -//│ tmp1 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 1) { -//│ tmp = value$; -//│ } else if (this.pc === 2) { -//│ res = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 3) { -//│ return this$Lol -//│ } else if (this.pc === 4) { -//│ res = Predef.print(tmp); -//│ if (res instanceof runtime.EffectSig.class) { -//│ return this.doUnwind(res, 2) -//│ } -//│ this.pc = 2; -//│ continue contLoop -//│ } else if (this.pc === 1) { -//│ this.pc = 4; -//│ continue contLoop -//│ } else if (this.pc === 2) { -//│ this.pc = 3; -//│ continue contLoop -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$ctor$Lol$"]; -//│ }); -//│ doUnwind = function doUnwind(res1, pc) { -//│ res1.contTrace.last.next = new Cont$ctor$Lol$1(pc); -//│ res1.contTrace.last = res1.contTrace.last.next; -//│ return res1 -//│ }; +//│ let tmp, tmp1; //│ this.h = h; //│ tmp = runtime.safeCall(this.h.perform("k")); //│ if (tmp instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp, 1) +//│ tmp = runtime.topLevelEffect(tmp, false); //│ } -//│ res = Predef.print(tmp); -//│ if (res instanceof runtime.EffectSig.class) { -//│ return doUnwind(res, 2) +//│ tmp1 = Predef.print(tmp); +//│ if (tmp1 instanceof runtime.EffectSig.class) { +//│ tmp1 = runtime.topLevelEffect(tmp1, false); //│ } -//│ res; +//│ tmp1; //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Lol", ["h"]]; @@ -96,48 +54,52 @@ handle k = Eff with fun test()(k) = k() k.test() print("test") -//│ ═══[RUNTIME ERROR] TypeError: k.test is not a function - +//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$ +:fixme let oops = handle h = Effect with fun perform(arg)(k) = print(arg) "b" Lol(h) -//│ > k -//│ oops = "b" +//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$2 +//│ oops = undefined +:fixme :expect "b" oops -//│ = "b" +//│ ═══[RUNTIME ERROR] Expected: '"b"', got: 'undefined' +:fixme let oops = handle h = Effect with fun perform(arg)(k) = print(arg) "b" new Lol(h) -//│ > k -//│ oops = "b" +//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$4 +//│ oops = undefined +:fixme :expect "b" oops -//│ = "b" +//│ ═══[RUNTIME ERROR] Expected: '"b"', got: 'undefined' +:fixme let oops = handle h = Effect with fun perform(arg)(k) = print(arg) k("b") Lol(h) -//│ > k -//│ > b -//│ oops = Lol(Handler$h$) +//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$6 +//│ oops = undefined +:fixme oops.h -//│ = Handler$h$ +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'h') let obj = mut {s: undefined} //│ obj = {s: undefined} diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsInMethods.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsInMethods.mls index 8d9b6c4188..8721eead92 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsInMethods.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsInMethods.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with diff --git a/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls b/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls index 915a0e177f..0422e4ee14 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck import "../../mlscript-compile/Stack.mls" open Stack diff --git a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls index 6c4e5c7824..bf96f94259 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Generator with fun produce(result): () diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlerBlockBindings.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlerBlockBindings.mls index af49a30cab..8113352575 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlerBlockBindings.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlerBlockBindings.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with @@ -17,7 +19,7 @@ let x = 123 :e x //│ ╔══[ERROR] Name not found: x -//│ ║ l.18: x +//│ ║ l.20: x //│ ╙── ^ @@ -31,7 +33,7 @@ x + 1 :e x //│ ╔══[ERROR] Name not found: x -//│ ║ l.32: x +//│ ║ l.34: x //│ ╙── ^ @@ -43,7 +45,7 @@ val y = 123 :e y //│ ╔══[ERROR] Name not found: y -//│ ║ l.44: y +//│ ║ l.46: y //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlersInClasses.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlersInClasses.mls index 891f751b41..8d45b8edf6 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlersInClasses.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlersInClasses.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with @@ -20,12 +22,10 @@ object Lol with //│ > A :e -:re Lol.x //│ ╔══[ERROR] Object 'Lol' does not contain member 'x' -//│ ║ l.24: Lol.x +//│ ║ l.25: Lol.x //│ ╙── ^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' object Lol with @@ -39,12 +39,10 @@ object Lol with //│ > B :e -:re Lol.x //│ ╔══[ERROR] Object 'Lol' does not contain member 'x' -//│ ║ l.43: Lol.x +//│ ║ l.42: Lol.x //│ ╙── ^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' data class Lol() with @@ -60,8 +58,6 @@ let oops = Lol() //│ > A //│ oops = Lol() -:re oops.x -//│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlersInMethods.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlersInMethods.mls index ad9f06f16e..17514c4b66 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlersInMethods.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlersInMethods.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls index 8cc07bcdbe..254a3096fe 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect diff --git a/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls b/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls index 7ed02d55f3..f17fc6676a 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect[A] with diff --git a/hkmc2/shared/src/test/mlscript/handlers/ManualEffectBinding.mls b/hkmc2/shared/src/test/mlscript/handlers/ManualEffectBinding.mls index 22318f19bc..de7fabe8e7 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ManualEffectBinding.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ManualEffectBinding.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with fun perform(): () diff --git a/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls index fa19da5680..63b8d29568 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck // * This file demonstrates the handler-based mechanism of our stack safety implementation // * by manually applying the transformation of a recursive factorial function diff --git a/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls b/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls index 87f3e9a4f9..964caee310 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck class Logger with fun info(s: Str): () diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedFun.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedFun.mls index 12e132e6c6..14385ad6d7 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedFun.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedFun.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck fun foo(f) = diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls index fb86903fd8..53b4ae6d81 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -1,12 +1,17 @@ :js :effectHandlers +:lift +:noSanityCheck let id = 0 //│ id = 0 - +// FIXME: if the following 2 blocks are together, lifter causes class ordering issue class MaybeStop with fun f(x: Bool): () + +// FIXME: Scoped related, id is treated as local by mistake if there is no (useless) assignment +set id = id fun handleEffects(g) = handle h1 = MaybeStop with fun f(x)(k) = diff --git a/hkmc2/shared/src/test/mlscript/handlers/NoStackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/NoStackSafety.mls new file mode 100644 index 0000000000..ecfd787002 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/NoStackSafety.mls @@ -0,0 +1,45 @@ +:js + +// sanity check +:expect 5050 +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +sum(100) +//│ = 5050 + + +// stack-overflows without :stackSafe +:re +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +sum(10000) +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + + +:re +fun foo() = + let f = () + set f = n => + if n <= 0 then 0 + else n + f(n-1) + f(10000) +foo() +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + +:sjs +:stackSafe 42 +fun hi(n) = n +hi(0) +//│ /!!!\ Option ':stackSafe' requires ':effectHandlers' to be set +//│ JS (unsanitized): +//│ let hi; hi = function hi(n) { return n }; hi(0) +//│ = 0 + +:stackSafe 42 +hi(0) +//│ /!!!\ Option ':stackSafe' requires ':effectHandlers' to be set +//│ = 0 diff --git a/hkmc2/shared/src/test/mlscript/handlers/NonLocalReturns.mls b/hkmc2/shared/src/test/mlscript/handlers/NonLocalReturns.mls index 6fff74eff2..0118c0d8bb 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NonLocalReturns.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NonLocalReturns.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck fun f() = @@ -55,18 +57,18 @@ f() :re fun f() = () => return 3 f()() -//│ ═══[RUNTIME ERROR] Error: Unhandled effect $_non$_local$_return$_effect$_6 +//│ ═══[RUNTIME ERROR] Error: Unhandled effect $_non$_local$_return$_effect$_12 :re fun f(): () -> Int = () => return () => 3 f()() -//│ ═══[RUNTIME ERROR] Error: Unhandled effect $_non$_local$_return$_effect$_7 +//│ ═══[RUNTIME ERROR] Error: Unhandled effect $_non$_local$_return$_effect$_14 :e return 100 //│ ╔══[ERROR] Return statements are not allowed outside of functions. -//│ ║ l.67: return 100 +//│ ║ l.69: return 100 //│ ╙── ^^^^^^^^^^ :e @@ -74,14 +76,14 @@ if true do while false do let f = () => return 100 //│ ╔══[ERROR] Return statements are not allowed outside of functions. -//│ ║ l.75: let f = () => return 100 +//│ ║ l.77: let f = () => return 100 //│ ╙── ^^^^^^^^^^ :e fun f() = type A = return "lol" //│ ╔══[ERROR] Return statements are not allowed in this context. -//│ ║ l.82: type A = return "lol" +//│ ║ l.84: type A = return "lol" //│ ╙── ^^^^^^^^^^^^ @@ -107,16 +109,21 @@ fun f() = return 100 4 f() -//│ ═══[RUNTIME ERROR] Expected: '100', got: '4' -//│ = 4 +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'nonLocalRetHandler$f' +//│ ═══[RUNTIME ERROR] ReferenceError: nonLocalRetHandler$f is not defined +//│ ═══[RUNTIME ERROR] Expected: '100', got: 'undefined' +// ctor cannot raise effect, so error is expected. +// However, there should be a runtime error raised. +:fixme :expect 100 fun f() = class A with return 100 new A f() -//│ = 100 +//│ ═══[RUNTIME ERROR] Error: Unhandled effect $_non$_local$_return$_effect$_20 +//│ ═══[RUNTIME ERROR] Expected: '100', got: 'undefined' :fixme :expect 100 @@ -125,9 +132,11 @@ fun f() = (() => return 100)() 4 f() -//│ ═══[RUNTIME ERROR] Expected: '100', got: '4' -//│ = 4 +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'nonLocalRetHandler$f' +//│ ═══[RUNTIME ERROR] ReferenceError: nonLocalRetHandler$f is not defined +//│ ═══[RUNTIME ERROR] Expected: '100', got: 'undefined' +:fixme :expect 100 fun f() = class A with @@ -135,4 +144,5 @@ fun f() = new A 4 f() -//│ = 100 +//│ ═══[RUNTIME ERROR] Error: Unhandled effect $_non$_local$_return$_effect$_24 +//│ ═══[RUNTIME ERROR] Expected: '100', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 74770fcc40..9f65607122 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect[A] with @@ -19,13 +21,13 @@ h1.perform("hello") :e h1 //│ ╔══[ERROR] Name not found: h1 -//│ ║ l.20: h1 +//│ ║ l.22: h1 //│ ╙── ^^ :e h1.perform("oops") //│ ╔══[ERROR] Name not found: h1 -//│ ║ l.26: h1.perform("oops") +//│ ║ l.28: h1.perform("oops") //│ ╙── ^^ :re @@ -71,10 +73,10 @@ handle h2 = Effect with h2.perform(3) ] //│ ╔══[ERROR] Name not found: h2 -//│ ║ l.65: then h2.perform(arg - 1) + " " + arg +//│ ║ l.67: then h2.perform(arg - 1) + " " + arg //│ ╙── ^^ //│ ╔══[ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.66: else "0" +//│ ║ l.68: else "0" //│ ╙── ^^^^ //│ > ––– //│ > performing 2 @@ -155,218 +157,220 @@ if true do h1.perform(()) str //│ JS (unsanitized): -//│ let str, scrut, tmp9, tmp10, handleBlock$7; +//│ let str, scrut, h11, tmp11, tmp12, handleBlock$9, Handler$h2$perform1, Handler$h2$3, handleBlock$10, Handler$h1$perform1, Handler$h1$3, Handler$h1$perform$1, handleBlock$$2, Handler$h2$perform$1; //│ str = ""; //│ scrut = true; -//│ if (scrut === true) { -//│ handleBlock$7 = function handleBlock$() { -//│ let h1, tmp11, handleBlock$8, Cont$handleBlock$h1$2, doUnwind, Handler$h1$2; -//│ globalThis.Object.freeze(class Handler$h1$1 extends Effect1 { +//│ switch (scrut) { +//│ case true: +//│ Handler$h1$perform$1 = function Handler$h1$perform$(Handler$h1$$instance, arg, k) { +//│ let tmp13, tmp14, tmp15, pc; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ tmp13 = str + "A"; +//│ str = tmp13; +//│ runtime.resumeValue = runtime.safeCall(k(arg)); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, Handler$h1$perform$1, null, "RecursiveHandlers.mls:149:7", 1, null, 0) +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ tmp14 = runtime.resumeValue; +//│ tmp15 = str + "A"; +//│ str = tmp15; +//│ return runtime.Unit; +//│ break; +//│ } +//│ break; +//│ } +//│ }; +//│ Handler$h1$perform1 = function Handler$h1$perform(Handler$h1$$instance, arg) { +//│ return (k) => { +//│ return Handler$h1$perform$1(Handler$h1$$instance, arg, k) +//│ } +//│ }; +//│ globalThis.Object.freeze(class Handler$h1$2 extends Effect1 { //│ static { -//│ Handler$h1$2 = this +//│ Handler$h1$3 = this //│ } //│ constructor() { -//│ let tmp12; -//│ tmp12 = super(); +//│ let tmp13; +//│ tmp13 = super(); +//│ if (tmp13 instanceof runtime.EffectSig.class) { +//│ tmp13 = runtime.topLevelEffect(tmp13, false); +//│ } +//│ tmp13; //│ } //│ perform(arg) { -//│ let hdlrFun; -//│ hdlrFun = function hdlrFun(k) { -//│ let tmp12, tmp13, tmp14, Cont$handler$h1$perform$2, doUnwind1; -//│ globalThis.Object.freeze(class Cont$handler$h1$perform$1 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handler$h1$perform$2 = this -//│ } -//│ constructor(pc) { -//│ let tmp15; -//│ tmp15 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 7) { -//│ tmp13 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 7) { -//│ tmp14 = str + "A"; -//│ str = tmp14; -//│ return runtime.Unit -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$handler$h1$perform$"]; -//│ }); -//│ doUnwind1 = function doUnwind(res7, pc) { -//│ res7.contTrace.last.next = new Cont$handler$h1$perform$2(pc); -//│ res7.contTrace.last = res7.contTrace.last.next; -//│ return res7 -//│ }; -//│ tmp12 = str + "A"; -//│ str = tmp12; -//│ tmp13 = runtime.safeCall(k(arg)); -//│ if (tmp13 instanceof runtime.EffectSig.class) { -//│ return doUnwind1(tmp13, 7) -//│ } -//│ tmp14 = str + "A"; -//│ str = tmp14; -//│ return runtime.Unit -//│ }; -//│ return runtime.mkEffect(this, hdlrFun) +//│ let Handler$h1$perform$here; +//│ Handler$h1$perform$here = runtime.safeCall(Handler$h1$perform1(this, arg)); +//│ return runtime.mkEffect(this, Handler$h1$perform$here) //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Handler$h1$"]; //│ }); -//│ h1 = new Handler$h1$2(); -//│ globalThis.Object.freeze(class Cont$handleBlock$h1$1 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handleBlock$h1$2 = this -//│ } -//│ constructor(pc) { -//│ let tmp12; -//│ tmp12 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 6) { -//│ tmp11 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 6) { -//│ return tmp11 -//│ } +//│ h11 = new Handler$h1$3(); +//│ if (h11 instanceof runtime.EffectSig.class) { +//│ h11 = runtime.topLevelEffect(h11, false); +//│ } +//│ Handler$h2$perform$1 = function Handler$h2$perform$(Handler$h2$$instance, arg, k) { +//│ let tmp13, tmp14, tmp15, tmp16, tmp17, pc; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; //│ break; -//│ } //│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$handleBlock$h1$"]; -//│ }); -//│ doUnwind = function doUnwind(res7, pc) { -//│ res7.contTrace.last.next = new Cont$handleBlock$h1$2(pc); -//│ return runtime.handleBlockImpl(res7, h1) -//│ }; -//│ handleBlock$8 = function handleBlock$() { -//│ let h2, tmp12, res7, Cont$handleBlock$h2$2, doUnwind1, Handler$h2$2; -//│ globalThis.Object.freeze(class Handler$h2$1 extends Effect1 { -//│ static { -//│ Handler$h2$2 = this -//│ } -//│ constructor() { -//│ let tmp13; -//│ tmp13 = super(); -//│ } -//│ perform(arg) { -//│ let hdlrFun; -//│ hdlrFun = function hdlrFun(k) { -//│ let tmp13, tmp14, tmp15, tmp16, tmp17, Cont$handler$h2$perform$2, doUnwind2; -//│ globalThis.Object.freeze(class Cont$handler$h2$perform$1 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handler$h2$perform$2 = this -//│ } -//│ constructor(pc) { -//│ let tmp18; -//│ tmp18 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 3) { -//│ tmp15 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 3) { -//│ tmp16 = str + "B"; -//│ tmp17 = str + tmp16; -//│ str = tmp17; -//│ return runtime.Unit -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$handler$h2$perform$"]; -//│ }); -//│ doUnwind2 = function doUnwind(res8, pc) { -//│ res8.contTrace.last.next = new Cont$handler$h2$perform$2(pc); -//│ res8.contTrace.last = res8.contTrace.last.next; -//│ return res8 -//│ }; +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: //│ tmp13 = str + "B"; //│ tmp14 = str + tmp13; //│ str = tmp14; -//│ tmp15 = runtime.safeCall(k(arg)); -//│ if (tmp15 instanceof runtime.EffectSig.class) { -//│ return doUnwind2(tmp15, 3) +//│ runtime.resumeValue = runtime.safeCall(k(arg)); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, Handler$h2$perform$1, null, "RecursiveHandlers.mls:154:7", 1, null, 0) //│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ tmp15 = runtime.resumeValue; //│ tmp16 = str + "B"; //│ tmp17 = str + tmp16; //│ str = tmp17; -//│ return runtime.Unit -//│ }; -//│ return runtime.mkEffect(this, hdlrFun) -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Handler$h2$"]; -//│ }); -//│ h2 = new Handler$h2$2(); -//│ globalThis.Object.freeze(class Cont$handleBlock$h2$1 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handleBlock$h2$2 = this +//│ return runtime.Unit; +//│ break; //│ } -//│ constructor(pc) { -//│ let tmp13; -//│ tmp13 = super(null); -//│ this.pc = pc; +//│ break; +//│ } +//│ }; +//│ Handler$h2$perform1 = function Handler$h2$perform(Handler$h2$$instance, arg) { +//│ return (k) => { +//│ return Handler$h2$perform$1(Handler$h2$$instance, arg, k) +//│ } +//│ }; +//│ globalThis.Object.freeze(class Handler$h2$2 extends Effect1 { +//│ static { +//│ Handler$h2$3 = this +//│ } +//│ constructor() { +//│ let tmp13; +//│ tmp13 = super(); +//│ if (tmp13 instanceof runtime.EffectSig.class) { +//│ tmp13 = runtime.topLevelEffect(tmp13, false); //│ } -//│ resume(value$) { -//│ if (this.pc === 1) { -//│ tmp12 = value$; -//│ } else if (this.pc === 2) { -//│ res7 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 1) { -//│ res7 = runtime.safeCall(h1.perform(runtime.Unit)); -//│ if (res7 instanceof runtime.EffectSig.class) { -//│ return this.doUnwind(res7, 2) -//│ } -//│ this.pc = 2; -//│ continue contLoop -//│ } else if (this.pc === 2) { -//│ return res7 +//│ tmp13; +//│ } +//│ perform(arg) { +//│ let Handler$h2$perform$here; +//│ Handler$h2$perform$here = runtime.safeCall(Handler$h2$perform1(this, arg)); +//│ return runtime.mkEffect(this, Handler$h2$perform$here) +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "Handler$h2$"]; +//│ }); +//│ handleBlock$$2 = function handleBlock$$(h22) { +//│ let tmp13, pc; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ runtime.resumeValue = runtime.safeCall(h22.perform(runtime.Unit)); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, handleBlock$$2, null, "RecursiveHandlers.mls:156:5", 2, null, 0) //│ } +//│ pc = 2; +//│ continue main; +//│ break; +//│ case 1: +//│ return runtime.safeCall(h11.perform(runtime.Unit)); +//│ break; +//│ case 2: +//│ tmp13 = runtime.resumeValue; +//│ pc = 1; +//│ continue main; //│ break; -//│ } //│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$handleBlock$h2$"]; -//│ }); -//│ doUnwind1 = function doUnwind(res8, pc) { -//│ res8.contTrace.last.next = new Cont$handleBlock$h2$2(pc); -//│ return runtime.handleBlockImpl(res8, h2) -//│ }; -//│ tmp12 = runtime.safeCall(h2.perform(runtime.Unit)); -//│ if (tmp12 instanceof runtime.EffectSig.class) { -//│ return doUnwind1(tmp12, 1) -//│ } -//│ res7 = runtime.safeCall(h1.perform(runtime.Unit)); -//│ if (res7 instanceof runtime.EffectSig.class) { -//│ return doUnwind1(res7, 2) +//│ break; //│ } -//│ return res7 //│ }; -//│ tmp11 = handleBlock$8(); +//│ handleBlock$9 = (undefined, function (h22) { +//│ return () => { +//│ return handleBlock$$2(h22) +//│ } +//│ }); +//│ handleBlock$10 = (undefined, function () { +//│ let h22, tmp13, handleBlock$$here, pc; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ runtime.resumeValue = new Handler$h2$3(); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, handleBlock$10, null, null, 2, null, 0) +//│ } +//│ pc = 2; +//│ continue main; +//│ break; +//│ case 1: +//│ tmp13 = runtime.resumeValue; +//│ return tmp13; +//│ break; +//│ case 2: +//│ h22 = runtime.resumeValue; +//│ handleBlock$$here = runtime.safeCall(handleBlock$9(h22)); +//│ runtime.resumeValue = runtime.enterHandleBlock(h22, handleBlock$$here); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, handleBlock$10, null, null, 1, null, 0) +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ } +//│ break; +//│ } +//│ }); +//│ tmp11 = runtime.enterHandleBlock(h11, handleBlock$10); //│ if (tmp11 instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp11, 6) +//│ tmp11 = runtime.topLevelEffect(tmp11, false); //│ } -//│ return tmp11 -//│ }; -//│ tmp9 = handleBlock$7(); -//│ if (tmp9 instanceof runtime.EffectSig.class) { -//│ tmp9 = runtime.topLevelEffect(tmp9, false); -//│ } -//│ tmp10 = tmp9; -//│ } else { tmp10 = runtime.Unit; } +//│ tmp12 = tmp11; +//│ break; +//│ default: +//│ tmp12 = runtime.Unit; +//│ break; +//│ } //│ str //│ = "BABABA" //│ str = "BABABA" diff --git a/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls b/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls index 85b4e91c39..89b9cd52b4 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with @@ -39,13 +41,11 @@ let l = () => return 3 4 //│ ╔══[ERROR] Return statements are not allowed outside of functions. -//│ ║ l.39: return 3 +//│ ║ l.41: return 3 //│ ╙── ^^^^^^^^ //│ l = fun l -:re l() -//│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. :e handle h1 = Effect with diff --git a/hkmc2/shared/src/test/mlscript/handlers/SetInHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/SetInHandlers.mls index 722499592a..d2669c1fe3 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/SetInHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/SetInHandlers.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck // * This feature relies on `finally` diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 86684cf7ea..12342b937b 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -1,18 +1,12 @@ :js +:effectHandlers +:lift +:noSanityCheck -// sanity check -:expect 5050 -fun sum(n) = - if n == 0 then 0 - else - n + sum(n - 1) -sum(100) -//│ = 5050 // preserve tail calls // MUST see "return hi1(tmp)" in the output :stackSafe 5 -:effectHandlers :expect 0 :sjs fun hi(n) = @@ -20,52 +14,53 @@ fun hi(n) = else hi(n - 1) hi(0) //│ JS (unsanitized): -//│ let hi, res, $_stack$_safe$_body$_; +//│ let hi, tmp, $_stack$_safe$_body$_; //│ hi = function hi(n) { -//│ let scrut, tmp, Cont$func$hi$1, doUnwind, stackDelayRes; -//│ globalThis.Object.freeze(class Cont$func$hi$ extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$func$hi$1 = this -//│ } -//│ constructor(pc) { -//│ let tmp1; -//│ tmp1 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ return hi(n) -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$func$hi$"]; -//│ }); -//│ doUnwind = function doUnwind(res1, pc) { -//│ res1.contTrace.last.next = new Cont$func$hi$1(pc); -//│ res1.contTrace.last = res1.contTrace.last.next; -//│ return res1 -//│ }; +//│ let scrut, tmp1, pc, saveOffset, stackDelayRes; //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ return doUnwind(stackDelayRes, 0) +//│ return runtime.unwind(stackDelayRes, 1, hi, null, "StackSafety.mls:12:1", 0, null, 2, n, tmp1) +//│ } +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ tmp1 = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } -//│ scrut = n == 0; -//│ if (scrut === true) { -//│ return 0 -//│ } else { -//│ tmp = n - 1; -//│ return hi(tmp) +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ scrut = n == 0; +//│ switch (scrut) { +//│ case true: +//│ return 0; +//│ break; +//│ default: +//│ tmp1 = n - 1; +//│ return hi(tmp1); +//│ break; +//│ } +//│ break; +//│ } +//│ break; //│ } //│ }; //│ $_stack$_safe$_body$_ = (undefined, function () { //│ return hi(0) //│ }); -//│ res = runtime.runStackSafe(5, $_stack$_safe$_body$_); -//│ if (res instanceof runtime.EffectSig.class) { res = runtime.topLevelEffect(res, false); } -//│ res +//│ tmp = runtime.runStackSafe(5, $_stack$_safe$_body$_); +//│ if (tmp instanceof runtime.EffectSig.class) { tmp = runtime.topLevelEffect(tmp, false); } +//│ tmp //│ = 0 :stackSafe 1000 -:effectHandlers :expect 50005000 :sjs fun sum(n) = @@ -74,79 +69,65 @@ fun sum(n) = n + sum(n - 1) sum(10000) //│ JS (unsanitized): -//│ let sum1, res1, $_stack$_safe$_body$_1; -//│ sum1 = function sum(n) { -//│ let scrut, tmp, tmp1, Cont$func$sum$1, doUnwind, curDepth, stackDelayRes; -//│ globalThis.Object.freeze(class Cont$func$sum$ extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$func$sum$1 = this -//│ } -//│ constructor(pc) { -//│ let tmp2; -//│ tmp2 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ let curDepth1; -//│ curDepth1 = runtime.stackDepth; -//│ if (this.pc === 1) { -//│ tmp1 = value$; -//│ } -//│ contLoop: while (true) { -//│ runtime.stackDepth = curDepth1; -//│ if (this.pc === 0) { -//│ return sum1(n) -//│ } else if (this.pc === 1) { -//│ return n + tmp1 -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$func$sum$"]; -//│ }); -//│ doUnwind = function doUnwind(res2, pc) { -//│ res2.contTrace.last.next = new Cont$func$sum$1(pc); -//│ res2.contTrace.last = res2.contTrace.last.next; -//│ return res2 -//│ }; +//│ let sum, tmp1, $_stack$_safe$_body$_1; +//│ sum = function sum(n) { +//│ let scrut, tmp2, tmp3, pc, saveOffset, tmp4, curDepth, stackDelayRes; //│ curDepth = runtime.stackDepth; //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ return doUnwind(stackDelayRes, 0) +//│ return runtime.unwind(stackDelayRes, 1, sum, null, "StackSafety.mls:66:1", 0, null, 2, tmp2, n) +//│ } +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ tmp2 = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ n = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } -//│ scrut = n == 0; -//│ if (scrut === true) { -//│ return 0 -//│ } else { -//│ tmp = n - 1; -//│ tmp1 = sum1(tmp); -//│ runtime.stackDepth = curDepth; -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp1, 1) +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ scrut = n == 0; +//│ switch (scrut) { +//│ case true: +//│ return 0; +//│ break; +//│ default: +//│ tmp2 = n - 1; +//│ tmp4 = sum(tmp2); +//│ runtime.stackDepth = curDepth; +//│ runtime.resumeValue = tmp4; +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, sum, null, "StackSafety.mls:69:9", 1, null, 2, tmp2, n) +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp3 = runtime.resumeValue; +//│ return n + tmp3; +//│ break; //│ } -//│ return n + tmp1 +//│ break; //│ } //│ }; //│ $_stack$_safe$_body$_1 = (undefined, function () { -//│ return sum1(10000) +//│ return sum(10000) //│ }); -//│ res1 = runtime.runStackSafe(1000, $_stack$_safe$_body$_1); -//│ if (res1 instanceof runtime.EffectSig.class) { res1 = runtime.topLevelEffect(res1, false); } -//│ res1 +//│ tmp1 = runtime.runStackSafe(1000, $_stack$_safe$_body$_1); +//│ if (tmp1 instanceof runtime.EffectSig.class) { tmp1 = runtime.topLevelEffect(tmp1, false); } +//│ tmp1 //│ = 50005000 -// stack-overflows without :stackSafe -:re -fun sum(n) = - if n == 0 then 0 - else - n + sum(n - 1) -sum(10000) -//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded -:effectHandlers :stackSafe 100 mut val ctr = 0 fun dummy(x) = x @@ -160,7 +141,6 @@ foo(foo) //│ ctr = 10001 :stackSafe 1000 -:effectHandlers :expect 50005000 val foo = val f = n => @@ -172,7 +152,6 @@ foo //│ foo = 50005000 :stackSafe 10 -:effectHandlers :expect 50005000 val foo = val f = n => @@ -183,21 +162,10 @@ foo //│ = 50005000 //│ foo = 50005000 -:re -fun foo() = - let f = () - set f = n => - if n <= 0 then 0 - else n + f(n-1) - f(10000) -foo() -//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded - abstract class Eff with fun perform(a): () // functions and lambdas inside handlers -:effectHandlers :stackSafe 100 :expect 50005000 fun foo(h) = h.perform @@ -212,7 +180,6 @@ foo(h) //│ = 50005000 // function call and defn inside handler -:effectHandlers :stackSafe 100 :expect 50005000 handle h = Eff with @@ -227,7 +194,7 @@ in foo(h) //│ = 50005000 -:effectHandlers + :stackSafe 100 :expect 50005000 module A with @@ -239,7 +206,6 @@ A.x //│ = 50005000 :re -:effectHandlers fun foo(h) = h.perform(2) handle h = Eff with fun perform(a)(resume) = @@ -251,36 +217,45 @@ handle h = Eff with foo(h) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded -:effectHandlers :stackSafe :sjs fun max(a, b) = if a < b then b else a //│ JS (unsanitized): //│ let max; //│ max = function max(a, b) { -//│ let scrut; -//│ scrut = a < b; -//│ if (scrut === true) { return b } else { return a } +//│ let scrut, pc, saveOffset; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ a = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ b = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ scrut = a < b; +//│ switch (scrut) { +//│ case true: +//│ return b; +//│ break; +//│ default: +//│ return a; +//│ break; +//│ } +//│ break; +//│ } +//│ break; +//│ } //│ }; -:sjs -:stackSafe 42 -fun hi(n) = n -hi(0) -//│ /!!!\ Option ':stackSafe' requires ':effectHandlers' to be set -//│ JS (unsanitized): -//│ let hi1; hi1 = function hi(n) { return n }; hi1(0) -//│ = 0 - -:stackSafe 42 -hi(0) -//│ /!!!\ Option ':stackSafe' requires ':effectHandlers' to be set -//│ = 0 - - :stackSafe 1000 -:effectHandlers :expect 100010000 fun sum(n) = if n == 0 then 0 @@ -291,7 +266,6 @@ bad() //│ = 100010000 -:effectHandlers :stackSafe 100 :expect 0 let depth = 0 @@ -307,7 +281,6 @@ h.whoops() // TODO: the stack depth is not accurately tracked in the runtime functions // for now this works, but it's not very safe -:effectHandlers :stackSafe :expect 10000 fun dummy = () @@ -327,7 +300,6 @@ abstract class Eff with fun raise() :re -:effectHandlers fun shift_stack(n, f) = if n >= 0 then shift_stack(n - 1, f) @@ -345,7 +317,6 @@ handler_stack(100) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded :stackSafe -:effectHandlers fun shift_stack(n, f) = if n >= 0 then shift_stack(n - 1, f) diff --git a/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls b/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls index f2568ef3dd..5d35fde3db 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class StackDelay with fun perform(): () diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls index 0aef56f838..fa7e6b8eb9 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class ThreadEffect with @@ -8,7 +10,7 @@ abstract class ThreadEffect with fun fork(thread: () -> ()): () fun yld(): () -class Lock(locked) with +class Lock(val locked) with fun lock(th: ThreadEffect) = while locked do th.yld() diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls index 26059f2a32..07c119c643 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls @@ -1,5 +1,7 @@ :js :effectHandlers debug +:lift +:noSanityCheck class ThreadEffect with @@ -43,11 +45,11 @@ in //│ > main end //│ > f 2 //│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$ -//│ at f (UserThreadsSafe.mls:17:3) -//│ at while (UserThreadsSafe.mls:10:7) -//│ at drain (pc=6) -//│ at fork (UserThreadsSafe.mls:35:5) -//│ at fork (UserThreadsSafe.mls:35:5) +//│ at f (UserThreadsSafe.mls:19:3) +//│ at while$ (UserThreadsSafe.mls:12:7) +//│ at ThreadEffect#drain (pc=undefined) +//│ at Handler$h$fork$ (UserThreadsSafe.mls:37:5) +//│ at Handler$h$fork$ (UserThreadsSafe.mls:37:5) // FIFO @@ -65,11 +67,11 @@ in //│ > main start //│ > main end //│ > f 0 -//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$1 -//│ at f (UserThreadsSafe.mls:17:3) -//│ at while (UserThreadsSafe.mls:10:7) -//│ at drain (pc=6) -//│ at fork (UserThreadsSafe.mls:58:5) -//│ at fork (UserThreadsSafe.mls:58:5) +//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$2 +//│ at f (UserThreadsSafe.mls:19:3) +//│ at while$ (UserThreadsSafe.mls:12:7) +//│ at ThreadEffect#drain (pc=undefined) +//│ at Handler$h$fork$ (UserThreadsSafe.mls:60:5) +//│ at Handler$h$fork$ (UserThreadsSafe.mls:60:5) diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls index c22ef1eadf..1d50621b5f 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls @@ -1,5 +1,7 @@ :js :effectHandlers debug +:lift +:noSanityCheck class ThreadEffect with @@ -46,11 +48,11 @@ in //│ > main end //│ > f 2 //│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$ -//│ at f (UserThreadsUnsafe.mls:12:3) -//│ at while (UserThreadsUnsafe.mls:29:5) -//│ at drain (pc=6) -//│ at fork (UserThreadsUnsafe.mls:38:5) -//│ at fork (UserThreadsUnsafe.mls:38:5) +//│ at f (UserThreadsUnsafe.mls:14:3) +//│ at while$ (UserThreadsUnsafe.mls:31:5) +//│ at drain (pc=undefined) +//│ at Handler$h$fork$ (UserThreadsUnsafe.mls:40:5) +//│ at Handler$h$fork$ (UserThreadsUnsafe.mls:40:5) // FIFO @@ -69,10 +71,10 @@ in //│ > main end //│ > f 1 //│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$ -//│ at f (UserThreadsUnsafe.mls:12:3) -//│ at while (UserThreadsUnsafe.mls:29:5) -//│ at drain (pc=6) -//│ at fork (UserThreadsUnsafe.mls:61:5) -//│ at fork (UserThreadsUnsafe.mls:61:5) +//│ at f (UserThreadsUnsafe.mls:14:3) +//│ at while$ (UserThreadsUnsafe.mls:31:5) +//│ at drain (pc=undefined) +//│ at Handler$h$fork$ (UserThreadsUnsafe.mls:63:5) +//│ at Handler$h$fork$ (UserThreadsUnsafe.mls:63:5) diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls index a7c5588ba5..5022d97944 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck :stackSafe 1000 fun selfApp(f) = f(f) @@ -86,7 +88,7 @@ let fact = x * prev b mkrec(a) -//│ fact = fun b +//│ fact = fun :expect 3628800 :stackSafe 5 diff --git a/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls index ec726f6ed4..567de084e9 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls @@ -3,6 +3,7 @@ // make sure class fields are discriminated properly for immutable captures when supported :effectHandlers +:noSanityCheck abstract class Effect with fun perform() handle h = Effect with @@ -11,7 +12,7 @@ fun f() = h.perform() 1 f() + f() + f() -//│ = 2 +//│ = 3 :expect 1 fun f(x) = @@ -88,13 +89,16 @@ fun f() = Good() f().foo() //│ JS (unsanitized): -//│ let Bad1, Good1, f5, tmp5, Bad$, Good$, f$capture3; +//│ let Bad1, Good1, f6, tmp5, Bad$, Good$, f$capture3; //│ Good$ = function Good$(isMut, x, y, z, f$capture4) { //│ let tmp6, tmp7; -//│ if (isMut === true) { -//│ tmp6 = new Good1.class(); -//│ } else { -//│ tmp6 = globalThis.Object.freeze(new Good1.class()); +//│ switch (isMut) { +//│ case true: +//│ tmp6 = new Good1.class(); +//│ break; +//│ default: +//│ tmp6 = globalThis.Object.freeze(new Good1.class()); +//│ break; //│ } //│ tmp7 = tmp6(x, y, z, f$capture4); //│ return tmp7 @@ -141,10 +145,13 @@ f().foo() //│ }); //│ Bad$ = function Bad$(isMut, f$capture4) { //│ let tmp6, tmp7; -//│ if (isMut === true) { -//│ tmp6 = new Bad1.class(); -//│ } else { -//│ tmp6 = globalThis.Object.freeze(new Bad1.class()); +//│ switch (isMut) { +//│ case true: +//│ tmp6 = new Bad1.class(); +//│ break; +//│ default: +//│ tmp6 = globalThis.Object.freeze(new Bad1.class()); +//│ break; //│ } //│ tmp7 = tmp6(f$capture4); //│ return tmp7 @@ -184,7 +191,7 @@ f().foo() //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "f$capture"]; //│ }); -//│ f5 = function f() { +//│ f6 = function f() { //│ let x, y, z, tmp6, tmp7, capture; //│ capture = new f$capture3(null); //│ x = 1; @@ -195,7 +202,7 @@ f().foo() //│ tmp7 = tmp6.foo(); //│ return Good$(false, x, y, z, capture) //│ }; -//│ tmp5 = f5(); +//│ tmp5 = f6(); //│ runtime.safeCall(tmp5.foo()) //│ = 10111 @@ -221,6 +228,7 @@ a.getX() :stackSafe 10 :effectHandlers +:noSanityCheck fun sum(n) = if n == 0 then 0 else diff --git a/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls b/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls index d5b4fad2c4..a00acccf06 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls @@ -10,10 +10,13 @@ data class A(x) with //│ let B1, A1, B$; //│ B$ = function B$(isMut, A$instance, y) { //│ let tmp, tmp1; -//│ if (isMut === true) { -//│ tmp = new B1.class(y); -//│ } else { -//│ tmp = globalThis.Object.freeze(new B1.class(y)); +//│ switch (isMut) { +//│ case true: +//│ tmp = new B1.class(y); +//│ break; +//│ default: +//│ tmp = globalThis.Object.freeze(new B1.class(y)); +//│ break; //│ } //│ tmp1 = tmp(A$instance); //│ return tmp1 diff --git a/hkmc2/shared/src/test/mlscript/lifter/EffectHandlers.mls b/hkmc2/shared/src/test/mlscript/lifter/EffectHandlers.mls index ceac941c04..c9f1796b9c 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/EffectHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/EffectHandlers.mls @@ -4,12 +4,14 @@ fun f() = 3 :effectHandlers +:noSanityCheck module A with data class Test with f() val a = 1 :effectHandlers +:noSanityCheck class Test with f() val a = 1 diff --git a/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls index dfd2bc3676..3ca74f94f1 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls @@ -383,17 +383,20 @@ fun f(x) = //│ let y1, scrut, g$here; //│ y1 = undefined; //│ scrut = x1 < 0; -//│ if (scrut === true) { -//│ y1 = 1; -//│ g$here = runtime.safeCall(g12(y1)); -//│ return globalThis.Object.freeze([ -//│ g$here, -//│ g$here -//│ ]) -//│ } else { -//│ y1 = 2; -//│ g$here = runtime.safeCall(g12(y1)); -//│ return globalThis.Object.freeze([ g$here, g$here ]) +//│ switch (scrut) { +//│ case true: +//│ y1 = 1; +//│ g$here = runtime.safeCall(g12(y1)); +//│ return globalThis.Object.freeze([ +//│ g$here, +//│ g$here +//│ ]); +//│ break; +//│ default: +//│ y1 = 2; +//│ g$here = runtime.safeCall(g12(y1)); +//│ return globalThis.Object.freeze([ g$here, g$here ]); +//│ break; //│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/lifter/Loops.mls b/hkmc2/shared/src/test/mlscript/lifter/Loops.mls index f3202dd07c..f584bf2b24 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/Loops.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/Loops.mls @@ -57,13 +57,16 @@ fun foo() = //│ while$1 = function while$(foo$capture6) { //│ let scrut, tmp2, lambda$here; //│ scrut = true; -//│ if (scrut === true) { -//│ tmp2 = foo$capture6.x$capture$0 + 1; -//│ foo$capture6.x$capture$0 = tmp2; -//│ lambda$here = runtime.safeCall(lambda2(foo$capture6)); -//│ return lambda$here -//│ } else { -//│ foo$capture6.tmp$capture$1 = runtime.Unit; +//│ switch (scrut) { +//│ case true: +//│ tmp2 = foo$capture6.x$capture$0 + 1; +//│ foo$capture6.x$capture$0 = tmp2; +//│ lambda$here = runtime.safeCall(lambda2(foo$capture6)); +//│ return lambda$here; +//│ break; +//│ default: +//│ foo$capture6.tmp$capture$1 = runtime.Unit; +//│ break; //│ } //│ return runtime.LoopEnd //│ }; @@ -85,7 +88,14 @@ fun foo() = //│ capture.tmp$capture$1 = undefined; //│ tmp2 = while$1(capture); //│ tmp3 = tmp2 !== runtime.LoopEnd; -//│ if (tmp3 === true) { return tmp2 } else { return capture.tmp$capture$1 } +//│ switch (tmp3) { +//│ case true: +//│ return tmp2; +//│ break; +//│ default: +//│ return capture.tmp$capture$1; +//│ break; +//│ } //│ }; :expect 2 diff --git a/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls b/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls index 1ab37b7a93..e960371bf7 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls @@ -30,10 +30,13 @@ foo(10) //│ let M3, foo1, M$; //│ M$ = function M$(isMut, y) { //│ let tmp; -//│ if (isMut === true) { -//│ tmp = new M3(y); -//│ } else { -//│ tmp = globalThis.Object.freeze(new M3(y)); +//│ switch (isMut) { +//│ case true: +//│ tmp = new M3(y); +//│ break; +//│ default: +//│ tmp = globalThis.Object.freeze(new M3(y)); +//│ break; //│ } //│ return tmp //│ }; diff --git a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls index 8b772c4748..359f94441b 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls @@ -1,5 +1,6 @@ :js :lift +:noSanityCheck // sanity check :expect 5050 @@ -21,65 +22,50 @@ fun hi(n) = else hi(n - 1) hi(0) //│ JS (unsanitized): -//│ let hi, Cont$func$hi$1, res, $_stack$_safe$_body$_, doUnwind$, Cont$func$hi$$; -//│ Cont$func$hi$$ = function Cont$func$hi$$(isMut, n, pc) { -//│ let tmp, tmp1; -//│ if (isMut === true) { -//│ tmp = new Cont$func$hi$1(pc); -//│ } else { -//│ tmp = globalThis.Object.freeze(new Cont$func$hi$1(pc)); -//│ } -//│ tmp1 = tmp(n); -//│ return tmp1 -//│ }; -//│ globalThis.Object.freeze(class Cont$func$hi$ extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$func$hi$1 = this -//│ } -//│ constructor(pc) { -//│ return (n) => { -//│ let tmp; -//│ tmp = super(null); -//│ this.n = n; -//│ this.pc = pc; -//│ return this; -//│ } -//│ } -//│ #n; -//│ get n() { return this.#n; } -//│ set n(value) { this.#n = value; } -//│ resume(value$) { -//│ return hi(this.n) -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$func$hi$"]; -//│ }); -//│ doUnwind$ = function doUnwind$(n, res1, pc) { -//│ res1.contTrace.last.next = Cont$func$hi$$(true, n, pc); -//│ res1.contTrace.last = res1.contTrace.last.next; -//│ return res1 -//│ }; +//│ let hi, tmp, $_stack$_safe$_body$_; //│ hi = function hi(n) { -//│ let scrut, tmp, stackDelayRes; +//│ let scrut, tmp1, pc, saveOffset, stackDelayRes; //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ return doUnwind$(n, stackDelayRes, 0) +//│ return runtime.unwind(stackDelayRes, 1, hi, null, "StackSafetyLift.mls:20:1", 0, null, 2, tmp1, n) //│ } -//│ scrut = n == 0; -//│ if (scrut === true) { -//│ return 0 -//│ } else { -//│ tmp = n - 1; -//│ return hi(tmp) +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ tmp1 = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ n = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ scrut = n == 0; +//│ switch (scrut) { +//│ case true: +//│ return 0; +//│ break; +//│ default: +//│ tmp1 = n - 1; +//│ return hi(tmp1); +//│ break; +//│ } +//│ break; +//│ } +//│ break; //│ } //│ }; //│ $_stack$_safe$_body$_ = (undefined, function () { //│ return hi(0) //│ }); -//│ res = runtime.runStackSafe(5, $_stack$_safe$_body$_); -//│ if (res instanceof runtime.EffectSig.class) { res = runtime.topLevelEffect(res, false); } -//│ res +//│ tmp = runtime.runStackSafe(5, $_stack$_safe$_body$_); +//│ if (tmp instanceof runtime.EffectSig.class) { tmp = runtime.topLevelEffect(tmp, false); } +//│ tmp //│ = 0 :sjs @@ -92,88 +78,62 @@ fun sum(n) = n + sum(n - 1) sum(10000) //│ JS (unsanitized): -//│ let sum1, Cont$func$sum$1, res1, $_stack$_safe$_body$_1, doUnwind$1, Cont$func$sum$$; -//│ Cont$func$sum$$ = function Cont$func$sum$$(isMut, n, tmp, pc) { -//│ let tmp1, tmp2; -//│ if (isMut === true) { -//│ tmp1 = new Cont$func$sum$1(pc); -//│ } else { -//│ tmp1 = globalThis.Object.freeze(new Cont$func$sum$1(pc)); -//│ } -//│ tmp2 = tmp1(n, tmp); -//│ return tmp2 -//│ }; -//│ globalThis.Object.freeze(class Cont$func$sum$ extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$func$sum$1 = this -//│ } -//│ constructor(pc) { -//│ return (n, tmp) => { -//│ let tmp1; -//│ tmp1 = super(null); -//│ this.n = n; -//│ this.tmp = tmp; -//│ this.pc = pc; -//│ return this; -//│ } -//│ } -//│ #n; -//│ #tmp; -//│ get n() { return this.#n; } -//│ set n(value) { this.#n = value; } -//│ get tmp() { return this.#tmp; } -//│ set tmp(value) { this.#tmp = value; } -//│ resume(value$) { -//│ let curDepth; -//│ curDepth = runtime.stackDepth; -//│ if (this.pc === 1) { -//│ this.tmp = value$; -//│ } -//│ contLoop: while (true) { -//│ runtime.stackDepth = curDepth; -//│ if (this.pc === 0) { -//│ return sum1(this.n) -//│ } else if (this.pc === 1) { -//│ return this.n + this.tmp -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$func$sum$"]; -//│ }); -//│ doUnwind$1 = function doUnwind$(n, tmp, res2, pc) { -//│ res2.contTrace.last.next = Cont$func$sum$$(true, n, tmp, pc); -//│ res2.contTrace.last = res2.contTrace.last.next; -//│ return res2 -//│ }; +//│ let sum1, tmp1, $_stack$_safe$_body$_1; //│ sum1 = function sum(n) { -//│ let scrut, tmp, tmp1, curDepth, stackDelayRes; +//│ let scrut, tmp2, tmp3, pc, saveOffset, tmp4, curDepth, stackDelayRes; //│ curDepth = runtime.stackDepth; //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ return doUnwind$1(n, tmp1, stackDelayRes, 0) +//│ return runtime.unwind(stackDelayRes, 1, sum1, null, "StackSafetyLift.mls:75:1", 0, null, 2, n, tmp2) //│ } -//│ scrut = n == 0; -//│ if (scrut === true) { -//│ return 0 -//│ } else { -//│ tmp = n - 1; -//│ tmp1 = sum1(tmp); -//│ runtime.stackDepth = curDepth; -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return doUnwind$1(n, tmp1, tmp1, 1) +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ tmp2 = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ scrut = n == 0; +//│ switch (scrut) { +//│ case true: +//│ return 0; +//│ break; +//│ default: +//│ tmp2 = n - 1; +//│ tmp4 = sum1(tmp2); +//│ runtime.stackDepth = curDepth; +//│ runtime.resumeValue = tmp4; +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, sum1, null, "StackSafetyLift.mls:78:9", 1, null, 2, n, tmp2) +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp3 = runtime.resumeValue; +//│ return n + tmp3; +//│ break; //│ } -//│ return n + tmp1 +//│ break; //│ } //│ }; //│ $_stack$_safe$_body$_1 = (undefined, function () { //│ return sum1(10000) //│ }); -//│ res1 = runtime.runStackSafe(1000, $_stack$_safe$_body$_1); -//│ if (res1 instanceof runtime.EffectSig.class) { res1 = runtime.topLevelEffect(res1, false); } -//│ res1 +//│ tmp1 = runtime.runStackSafe(1000, $_stack$_safe$_body$_1); +//│ if (tmp1 instanceof runtime.EffectSig.class) { tmp1 = runtime.topLevelEffect(tmp1, false); } +//│ tmp1 //│ = 50005000 // stack-overflows without :stackSafe @@ -274,9 +234,35 @@ fun max(a, b) = if a < b then b else a //│ JS (unsanitized): //│ let max; //│ max = function max(a, b) { -//│ let scrut; -//│ scrut = a < b; -//│ if (scrut === true) { return b } else { return a } +//│ let scrut, pc, saveOffset; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ a = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ b = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ scrut = a < b; +//│ switch (scrut) { +//│ case true: +//│ return b; +//│ break; +//│ default: +//│ return a; +//│ break; +//│ } +//│ break; +//│ } +//│ break; +//│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls index 01db6e89df..a371a7fa56 100644 --- a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls @@ -137,16 +137,19 @@ fun popByIndex(start, end, acc, lft) = //│ popByIndex = function popByIndex(start, end, acc, lft) { //│ let scrut, tmp34, tmp35, tmp36; //│ scrut = start >= end; -//│ if (scrut === true) { -//│ return acc -//│ } else { -//│ tmp34 = start + 1; -//│ tmp35 = runtime.safeCall(lft.at(start)); -//│ tmp36 = globalThis.Object.freeze([ -//│ ...acc, -//│ tmp35 -//│ ]); -//│ return popByIndex(tmp34, end, tmp36, lft) +//│ switch (scrut) { +//│ case true: +//│ return acc; +//│ break; +//│ default: +//│ tmp34 = start + 1; +//│ tmp35 = runtime.safeCall(lft.at(start)); +//│ tmp36 = globalThis.Object.freeze([ +//│ ...acc, +//│ tmp35 +//│ ]); +//│ return popByIndex(tmp34, end, tmp36, lft); +//│ break; //│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/JoinPoints.mls b/hkmc2/shared/src/test/mlscript/ucs/general/JoinPoints.mls index 789353450b..7214d4358f 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/JoinPoints.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/JoinPoints.mls @@ -7,9 +7,14 @@ x => if x is 0 then 1 //│ JS (unsanitized): //│ let lambda; //│ lambda = (undefined, function (x) { -//│ if (x === 0) { -//│ return 1 -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (x) { +//│ case 0: +//│ return 1; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ }); //│ lambda //│ = fun @@ -27,11 +32,14 @@ x => if x is [[0]] then 1 //│ element0$ = runtime.Tuple.get(x, 0); //│ if (runtime.Tuple.isArrayLike(element0$) && element0$.length === 1) { //│ element0$1 = runtime.Tuple.get(element0$, 0); -//│ if (element0$1 === 0) { -//│ tmp = 1; -//│ break split_root$ -//│ } else { -//│ break split_default$ +//│ switch (element0$1) { +//│ case 0: +//│ tmp = 1; +//│ break split_root$; +//│ break; +//│ default: +//│ break split_default$; +//│ break; //│ } //│ } else { //│ break split_default$ @@ -74,11 +82,14 @@ fun crazy(v) = //│ argument0$4 = argument0$3.value; //│ if (argument0$4 instanceof S1.class) { //│ argument0$5 = argument0$4.value; -//│ if (argument0$5 === 0) { -//│ tmp = "bruh!"; -//│ break split_root$ -//│ } else { -//│ break split_1$ +//│ switch (argument0$5) { +//│ case 0: +//│ tmp = "bruh!"; +//│ break split_root$; +//│ break; +//│ default: +//│ break split_1$; +//│ break; //│ } //│ } else { //│ break split_1$ diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls b/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls index 1c74e8bfff..2095db7e9e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls @@ -29,13 +29,23 @@ true and test(42) //│ split_root$2: { //│ split_1$2: { //│ scrut4 = true; -//│ if (scrut4 === true) { -//│ scrut5 = test(42); -//│ if (scrut5 === true) { -//│ tmp4 = true; -//│ break split_root$2 -//│ } else { break split_1$2 } -//│ } else { break split_1$2 } +//│ switch (scrut4) { +//│ case true: +//│ scrut5 = test(42); +//│ switch (scrut5) { +//│ case true: +//│ tmp4 = true; +//│ break split_root$2; +//│ break; +//│ default: +//│ break split_1$2; +//│ break; +//│ } +//│ break; +//│ default: +//│ break split_1$2; +//│ break; +//│ } //│ } //│ tmp4 = false; //│ } @@ -46,7 +56,7 @@ true and test(42) :fixme true or test(42) //│ ╔══[ERROR] Logical `or` is not yet supported. -//│ ║ l.47: true or test(42) +//│ ║ l.57: true or test(42) //│ ╙── ^^^^^^^^^^^^^^^^ //│ > 42 //│ = false diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls index 4bba48a888..eb7e92da53 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls @@ -10,13 +10,23 @@ let z = 0 :sjs if x === 0 then 1 else 2 //│ JS (unsanitized): -//│ let scrut; scrut = x === 0; if (scrut === true) { 1 } else { 2 } +//│ let scrut; scrut = x === 0; switch (scrut) { case true: 1; break; default: 2; break; } //│ = 1 :sjs let a = if x === 0 then 1 else 2 //│ JS (unsanitized): -//│ let a, scrut1, tmp; scrut1 = x === 0; if (scrut1 === true) { tmp = 1; } else { tmp = 2; } a = tmp; +//│ let a, scrut1, tmp; +//│ scrut1 = x === 0; +//│ switch (scrut1) { +//│ case true: +//│ tmp = 1; +//│ break; +//│ default: +//│ tmp = 2; +//│ break; +//│ } +//│ a = tmp; //│ a = 1 :sjs @@ -24,7 +34,14 @@ print of if x === 0 then 1 else 2 //│ JS (unsanitized): //│ let scrut2, tmp1; //│ scrut2 = x === 0; -//│ if (scrut2 === true) { tmp1 = 1; } else { tmp1 = 2; } +//│ switch (scrut2) { +//│ case true: +//│ tmp1 = 1; +//│ break; +//│ default: +//│ tmp1 = 2; +//│ break; +//│ } //│ Predef.print(tmp1) //│ > 1 @@ -41,27 +58,41 @@ if x is //│ let tmp2; //│ split_root$: { //│ split_1$: { -//│ if (x === 0) { -//│ if (y === 0) { -//│ if (z === 0) { -//│ tmp2 = "000"; -//│ break split_root$ -//│ } else if (z === 1) { -//│ tmp2 = "001"; -//│ break split_root$ -//│ } else { -//│ break split_1$ +//│ switch (x) { +//│ case 0: +//│ switch (y) { +//│ case 0: +//│ switch (z) { +//│ case 0: +//│ tmp2 = "000"; +//│ break split_root$; +//│ break; +//│ case 1: +//│ tmp2 = "001"; +//│ break split_root$; +//│ break; +//│ default: +//│ break split_1$; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp2 = "01"; +//│ break split_root$; +//│ break; +//│ default: +//│ break split_1$; +//│ break; //│ } -//│ } else if (y === 1) { -//│ tmp2 = "01"; -//│ break split_root$ -//│ } else { -//│ break split_1$ -//│ } -//│ } else if (x === 1) { -//│ tmp2 = "1"; -//│ break split_root$ -//│ } else { break split_1$ } +//│ break; +//│ case 1: +//│ tmp2 = "1"; +//│ break split_root$; +//│ break; +//│ default: +//│ break split_1$; +//│ break; +//│ } //│ } //│ tmp2 = ""; //│ } @@ -81,27 +112,41 @@ let qqq = if x is //│ let qqq, tmp3; //│ split_root$1: { //│ split_1$1: { -//│ if (x === 0) { -//│ if (y === 0) { -//│ if (z === 0) { -//│ tmp3 = "000"; -//│ break split_root$1 -//│ } else if (z === 1) { -//│ tmp3 = "001"; -//│ break split_root$1 -//│ } else { -//│ break split_1$1 +//│ switch (x) { +//│ case 0: +//│ switch (y) { +//│ case 0: +//│ switch (z) { +//│ case 0: +//│ tmp3 = "000"; +//│ break split_root$1; +//│ break; +//│ case 1: +//│ tmp3 = "001"; +//│ break split_root$1; +//│ break; +//│ default: +//│ break split_1$1; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp3 = "01"; +//│ break split_root$1; +//│ break; +//│ default: +//│ break split_1$1; +//│ break; //│ } -//│ } else if (y === 1) { -//│ tmp3 = "01"; -//│ break split_root$1 -//│ } else { -//│ break split_1$1 -//│ } -//│ } else if (x === 1) { -//│ tmp3 = "1"; -//│ break split_root$1 -//│ } else { break split_1$1 } +//│ break; +//│ case 1: +//│ tmp3 = "1"; +//│ break split_root$1; +//│ break; +//│ default: +//│ break split_1$1; +//│ break; +//│ } //│ } //│ tmp3 = ""; //│ } @@ -121,27 +166,41 @@ print of if x is //│ let tmp4; //│ split_root$2: { //│ split_1$2: { -//│ if (x === 0) { -//│ if (y === 0) { -//│ if (z === 0) { -//│ tmp4 = "000"; -//│ break split_root$2 -//│ } else if (z === 1) { -//│ tmp4 = "001"; -//│ break split_root$2 -//│ } else { -//│ break split_1$2 +//│ switch (x) { +//│ case 0: +//│ switch (y) { +//│ case 0: +//│ switch (z) { +//│ case 0: +//│ tmp4 = "000"; +//│ break split_root$2; +//│ break; +//│ case 1: +//│ tmp4 = "001"; +//│ break split_root$2; +//│ break; +//│ default: +//│ break split_1$2; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp4 = "01"; +//│ break split_root$2; +//│ break; +//│ default: +//│ break split_1$2; +//│ break; //│ } -//│ } else if (y === 1) { -//│ tmp4 = "01"; -//│ break split_root$2 -//│ } else { -//│ break split_1$2 -//│ } -//│ } else if (x === 1) { -//│ tmp4 = "1"; -//│ break split_root$2 -//│ } else { break split_1$2 } +//│ break; +//│ case 1: +//│ tmp4 = "1"; +//│ break split_root$2; +//│ break; +//│ default: +//│ break split_1$2; +//│ break; +//│ } //│ } //│ tmp4 = ""; //│ } @@ -164,27 +223,41 @@ fun foo(x, y, z) = //│ let tmp5; //│ split_root$3: { //│ split_1$3: { -//│ if (x1 === 0) { -//│ if (y1 === 0) { -//│ if (z1 === 0) { -//│ tmp5 = "000"; -//│ break split_root$3 -//│ } else if (z1 === 1) { -//│ tmp5 = "001"; -//│ break split_root$3 -//│ } else { -//│ break split_1$3 +//│ switch (x1) { +//│ case 0: +//│ switch (y1) { +//│ case 0: +//│ switch (z1) { +//│ case 0: +//│ tmp5 = "000"; +//│ break split_root$3; +//│ break; +//│ case 1: +//│ tmp5 = "001"; +//│ break split_root$3; +//│ break; +//│ default: +//│ break split_1$3; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp5 = "01"; +//│ break split_root$3; +//│ break; +//│ default: +//│ break split_1$3; +//│ break; //│ } -//│ } else if (y1 === 1) { -//│ tmp5 = "01"; -//│ break split_root$3 -//│ } else { -//│ break split_1$3 -//│ } -//│ } else if (x1 === 1) { -//│ tmp5 = "1"; -//│ break split_root$3 -//│ } else { break split_1$3 } +//│ break; +//│ case 1: +//│ tmp5 = "1"; +//│ break split_root$3; +//│ break; +//│ default: +//│ break split_1$3; +//│ break; +//│ } //│ } //│ tmp5 = ""; //│ } @@ -210,39 +283,60 @@ print of if //│ split_1$3: { //│ split_2$: { //│ split_3$: { -//│ if (x === 0) { -//│ if (y === 0) { -//│ if (z === 0) { -//│ tmp5 = "000"; -//│ break split_root$3 -//│ } else if (z === 1) { -//│ break split_1$3 -//│ } else { -//│ break split_2$ +//│ switch (x) { +//│ case 0: +//│ switch (y) { +//│ case 0: +//│ switch (z) { +//│ case 0: +//│ tmp5 = "000"; +//│ break split_root$3; +//│ break; +//│ case 1: +//│ break split_1$3; +//│ break; +//│ default: +//│ break split_2$; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp5 = "01_"; +//│ break split_root$3; +//│ break; +//│ default: +//│ switch (z) { +//│ case 1: +//│ break split_1$3; +//│ break; +//│ default: +//│ switch (y) { +//│ case 2: +//│ break split_3$; +//│ break; +//│ default: +//│ break split_2$; +//│ break; +//│ } +//│ break; +//│ } +//│ break; //│ } -//│ } else if (y === 1) { -//│ tmp5 = "01_"; -//│ break split_root$3 -//│ } else { -//│ if (z === 1) { -//│ break split_1$3 -//│ } else { -//│ if (y === 2) { -//│ break split_3$ -//│ } else { -//│ break split_2$ -//│ } +//│ break; +//│ case 1: +//│ tmp5 = "1__"; +//│ break split_root$3; +//│ break; +//│ default: +//│ switch (y) { +//│ case 2: +//│ break split_3$; +//│ break; +//│ default: +//│ break split_2$; +//│ break; //│ } -//│ } -//│ } else if (x === 1) { -//│ tmp5 = "1__"; -//│ break split_root$3 -//│ } else { -//│ if (y === 2) { -//│ break split_3$ -//│ } else { -//│ break split_2$ -//│ } +//│ break; //│ } //│ } //│ tmp5 = "_2_"; @@ -274,39 +368,60 @@ fun foo(x, y, z) = if //│ split_1$4: { //│ split_2$1: { //│ split_3$1: { -//│ if (x1 === 0) { -//│ if (y1 === 0) { -//│ if (z1 === 0) { -//│ tmp6 = "000"; -//│ break split_root$4 -//│ } else if (z1 === 1) { -//│ break split_1$4 -//│ } else { -//│ break split_2$1 +//│ switch (x1) { +//│ case 0: +//│ switch (y1) { +//│ case 0: +//│ switch (z1) { +//│ case 0: +//│ tmp6 = "000"; +//│ break split_root$4; +//│ break; +//│ case 1: +//│ break split_1$4; +//│ break; +//│ default: +//│ break split_2$1; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp6 = "01_"; +//│ break split_root$4; +//│ break; +//│ default: +//│ switch (z1) { +//│ case 1: +//│ break split_1$4; +//│ break; +//│ default: +//│ switch (y1) { +//│ case 2: +//│ break split_3$1; +//│ break; +//│ default: +//│ break split_2$1; +//│ break; +//│ } +//│ break; +//│ } +//│ break; //│ } -//│ } else if (y1 === 1) { -//│ tmp6 = "01_"; -//│ break split_root$4 -//│ } else { -//│ if (z1 === 1) { -//│ break split_1$4 -//│ } else { -//│ if (y1 === 2) { -//│ break split_3$1 -//│ } else { -//│ break split_2$1 -//│ } +//│ break; +//│ case 1: +//│ tmp6 = "1__"; +//│ break split_root$4; +//│ break; +//│ default: +//│ switch (y1) { +//│ case 2: +//│ break split_3$1; +//│ break; +//│ default: +//│ break split_2$1; +//│ break; //│ } -//│ } -//│ } else if (x1 === 1) { -//│ tmp6 = "1__"; -//│ break split_root$4 -//│ } else { -//│ if (y1 === 2) { -//│ break split_3$1 -//│ } else { -//│ break split_2$1 -//│ } +//│ break; //│ } //│ } //│ tmp6 = "_2_"; @@ -339,27 +454,41 @@ fun foo(x, y, z) = if x is //│ let value, tmp6; //│ split_root$4: { //│ split_1$4: { -//│ if (x1 === 0) { -//│ if (y1 === 0) { -//│ if (z1 === 0) { -//│ tmp6 = "000"; -//│ break split_root$4 -//│ } else if (z1 === 1) { -//│ tmp6 = "001"; -//│ break split_root$4 -//│ } else { -//│ break split_1$4 +//│ switch (x1) { +//│ case 0: +//│ switch (y1) { +//│ case 0: +//│ switch (z1) { +//│ case 0: +//│ tmp6 = "000"; +//│ break split_root$4; +//│ break; +//│ case 1: +//│ tmp6 = "001"; +//│ break split_root$4; +//│ break; +//│ default: +//│ break split_1$4; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp6 = "01"; +//│ break split_root$4; +//│ break; +//│ default: +//│ break split_1$4; +//│ break; //│ } -//│ } else if (y1 === 1) { -//│ tmp6 = "01"; -//│ break split_root$4 -//│ } else { -//│ break split_1$4 -//│ } -//│ } else if (x1 === 1) { -//│ tmp6 = "1"; -//│ break split_root$4 -//│ } else { break split_1$4 } +//│ break; +//│ case 1: +//│ tmp6 = "1"; +//│ break split_root$4; +//│ break; +//│ default: +//│ break split_1$4; +//│ break; +//│ } //│ } //│ value = "hello"; //│ tmp6 = expensive_call(value); @@ -466,6 +595,13 @@ let y = if true then 1 else 2 //│ JS (unsanitized): //│ let y1, scrut3, tmp6; //│ scrut3 = true; -//│ if (scrut3 === true) { tmp6 = 1; } else { tmp6 = 2; } +//│ switch (scrut3) { +//│ case true: +//│ tmp6 = 1; +//│ break; +//│ default: +//│ tmp6 = 2; +//│ break; +//│ } //│ y1 = tmp6; //│ y = 1 diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index f527cb10d8..3dee3215a8 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -79,6 +79,9 @@ abstract class MLsDiffMaker extends DiffMaker: output(s"$errMarker Option ':stackSafe' requires ':effectHandlers' to be set") if !effectHandlers.get.forall(effectHandlersOptions.contains(_)) then output(s"$errMarker Option ':effectHandlers' only supports 'debug' as option") + if effectHandlers.isSet then + if liftDefns.isUnset || noSanityCheck.isUnset then + output(s"$errMarker Option ':effectHandlers' requires ':lift' and ':noSanityCheck'") Config( sanityChecks = Opt.when(noSanityCheck.isUnset)(SanityChecks(light = true)), effectHandlers = Opt.when(effectHandlers.isSet)(EffectHandlers(