diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index bd81564176..c4cb02b6d6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -767,6 +767,7 @@ extension (k: Block => Block) def transform(f: (Block => Block) => (Block => Block)) = f(k) def assign(l: Local, r: Result) = k.chain(Assign(l, r, _)) + def assignScoped(l: Local, r: Result) = k.chain(Scoped(Set(l), _)).assign(l, r) def assignFieldN(lhs: Path, nme: Tree.Ident, rhs: Result) = k.chain(AssignField(lhs, nme, rhs, _)(N)) def break(l: Local): Block = k.rest(Break(l)) def continue(l: Local): Block = k.rest(Continue(l)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 21f198dd65..7993f28490 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -14,6 +14,10 @@ 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 + /** - For function bodies, fuse all shallowly-nested scopes into one top-level one, * because handler lowering relies on knowing all local variables in the function. @@ -49,20 +53,21 @@ class PreHandlerLowering extends BlockTransformer(new SymbolSubst): case None => super.applyScopedBlock(b) case Some(scopedForCurrentFun) => scopedForCurrentFun.addAll(syms) - super.applySubBlock(body) - case _ => super.applySubBlock(b) + applySubBlock(body) + case _ => applySubBlock(b) - object HandlerLowering: - - private final val getLocalsNme = "getLocals" - private final val doUnwindNme = "doUnwind" - private val pcIdent: Tree.Ident = Tree.Ident("pc") 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) @@ -70,44 +75,51 @@ 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] + + private enum HandlerCtx: + // currentFun: path to the current function for resumption + // 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 + case FunctionLike(ctx: FunctionCtx) + case Ctor + case ModCtor + case TopLevel + + def isCtor = this === Ctor || this === ModCtor + def isTopLevel = this === TopLevel + def allowDefn = isTopLevel || this === ModCtor + + private case class FunctionCtx(currentFun: Path, thisPath: Option[Path], resumeInfo: ResumeInfo, debugInfo: DebugInfo): + def doUnwind(path: Path, loc: Value, stateId: BigInt, restoreList: List[Local])(using paths: HandlerPaths) = + Return(Call(paths.unwindPath, ( + path :: + intLit(resumeInfo.plCnt) :: + currentFun :: + debugInfo.debugInfoPath :: + loc :: + intLit(stateId) :: + thisPath.getOrElse(unit) :: + intLit(restoreList.length) :: + restoreList.map(_.asPath) + ).map(_.asArg))(true, true, false), false) + + private case class ResumeInfo( + plCnt: Int, + currentLocals: List[Local], + currentStackSafetySym: Opt[FnOrCls], + ) - // 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 - private case class HandlerCtx( - isTopLevel: Bool, - isHandlerBody: Bool, - contName: Str, - ctorThis: Option[Path], - debugInfo: DebugInfo, - linkAndHandle: LinkState => Block, - ): - def nestDebugScope(locals: Set[Local], localsFn: Path) = copy(debugInfo = debugInfo.copy(inScopeLocals = - debugInfo.inScopeLocals ++ locals, prevLocalsFn = S(localsFn))) - - // inScopeLocals: Local variables that are in scope. - // prevLocalsFn: The function that gets the outer function's locals. private case class DebugInfo( debugNme: Str, - inScopeLocals: Set[Local], - prevLocalsFn: Opt[Path], + debugInfoPath: Path, ) - private object DebugInfo: - def topLevel(debugNme: Str) = DebugInfo(debugNme, Set.empty, N) - type StateId = BigInt import HandlerLowering.* @@ -121,36 +133,19 @@ 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 ctorEffectPath: Path = runtimePath.selSN("ctorEffect") + 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, 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) @@ -170,41 +165,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) = @@ -213,190 +173,117 @@ 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 - - private class FreshId: - // IMPORTANT: this must be >= 1 otherwise we get state ID collions with the "entry" state 0. - var id: Int = 1 + 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 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: FunctionCtx): 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 - // 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 = + // * afterEnd: what state End should jump to, if at all + 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) => + 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 @@ -404,9 +291,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 @@ -414,292 +304,257 @@ 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 Scoped(_, body) => go(body) 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): FunDefn = - 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, false)) - .ret(startSym.asPath) - - FunDefn.withFreshSymbol(N, BlockMemberSymbol(getLocalsNme, Nil), PlainParamList(Nil) :: Nil, body)(false) - - 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 ctx: FunctionCtx): List[Local] = + val localSet = ctx.resumeInfo.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. rewrite handler blocks in terms of classes and functions + * 2. class lifter + * 3. state machine transformation of all functions */ - // 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.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, scopedVars: collection.Set[Local]): Block = + given HandlerCtx = h + + def translateFunLike(fun: FunDefn, funcPath: Path, thisPath: Option[Path], debugNme: Str) = + val scopedVars = fun.body match + case Scoped(syms, body) => syms + case _ => Set() + val varList = (scopedVars ++ fun.params.flatMap(_.params.map(_.sym))) + .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.FunctionLike(FunctionCtx(funcPath, thisPath, ResumeInfo(fun.params.length, varList, S(L(fun.sym))), + DebugInfo(debugNme, if opt.debug then debugInfoSym.asPath else unit))) + val bod2 = translateBlock(fun.body, newCtx, scopedVars) + val fun2 = if fun.body is bod2 then fun else + FunDefn(fun.owner, fun.sym, fun.dSym, fun.params, bod2)(fun.forceTailRec) + (debugInfoSym, debugInfo, fun2) + + val subblockTransform = new BlockTransformer(SymbolSubst()): + override def applyDefn(defn: Defn)(k: Defn => Block): Block = defn match + case fun: FunDefn => + if !h.allowDefn 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, S(fun.dSym)), 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.allowDefn 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 so it is possible to handle it + // However, JSBuilder inserts extra statements between preCtor and ctor and it's not possible to replicate the exact behavior + // without many special handling. + val newCtor = translateCtorLike(bod.ctor, bod.isym.asPath, true) + 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, isym.asPath, false), translateCtorLike(ctor, isym.asPath, false), 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.isCtor then + return translateTopLevelOrCtor(b, res => Call(paths.ctorEffectPath, res.asArg :: Nil)(true, true, false)) + if h.isTopLevel then + return translateTopLevelOrCtor(b, res => Call(paths.topLevelEffectPath, res.asArg :: Value.Lit(Tree.BoolLit(opt.debug)).asArg :: Nil)(true, false, false)) + val ctx = h.asInstanceOf[HandlerCtx.FunctionLike].ctx + given FunctionCtx = ctx + val parts = partitionBlock(b) + if parts.states.size <= 1 && opt.stackSafety.isEmpty then + return b + val vars = if opt.debug then ctx.resumeInfo.currentLocals else computeRestoreList(parts) + ctx.resumeInfo.currentStackSafetySym.foreach: fnOrCls => + doUnwindMap += + fnOrCls -> (res => ctx.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, c.explicitTailCall) - 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, c.explicitTailCall) - 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) => + ctx.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, c.explicitTailCall) - 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).asPath, N) - case Some(cls) => - // create the doUnwind function - val doUnwindSym = BlockMemberSymbol(doUnwindNme, Nil, true) - val pcSym = VarSymbol(Tree.Ident("pc")) - val resSym = VarSymbol(Tree.Ident("res")) - val doUnwindBlk = h.linkAndHandle( - LinkState(resSym, Value.Ref(cls.sym, S(cls.isym)), pcSym.asPath) - ) - val doUnwindDef = FunDefn.withFreshSymbol( - N, doUnwindSym, - PlainParamList(Param.simple(resSym) :: Param.simple(pcSym) :: Nil) :: Nil, - doUnwindBlk - )(false) - - val doUnwindPath: Path = doUnwindDef.asPath - doUnwindMap += fnOrCls -> doUnwindPath - - val doUnwindLazy = Lazy(doUnwindPath) - val rst = genNormalBody(b, Value.Ref(cls.sym, S(cls.isym)), 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.extractDefns() - 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.asPath, params)(true, true, false)) - case Some(owner) => - S(Call(Select(owner.asPath, Tree.Ident(f.sym.nme))(N), params)(true, true, false)) - case _ => None // TODO: more than one plist - - FunDefn(f.owner, f.sym, f.dSym, f.params, translateBlock(f.body, - f.params.flatMap(_.paramSyms).toSet, - callSelf, - L(f.sym), - functionHandlerCtx(s"Cont$$func$$${symToStr(f.sym)}$$", f.sym.nme)) - )(forceTailRec = f.forceTailRec) - - 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), - ) - - 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 - - // Handle block becomes a FunDefn and CallPlaceholder - private def translateHandleBlock(h: HandleBlock)(using HandlerCtx): Block = - val sym = BlockMemberSymbol(s"handleBlock$$", Nil) - val tSym = TermSymbol.fromFunBms(sym, N) - val lbl = freshTmp("handlerBody") - val lblLoop = freshTmp("handlerLoop") - - val handlerBody = translateBlock( - h.body, Set.empty, S(Call(Value.Ref(sym, S(tSym)), Nil)(true, false, 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)) - ) - ) + + 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, false), _) + (computeOff, DynSelect(paths.runtimePath.selSN("resumeArr"), getSavedTmp.asPath, true)) + + val restoreVars = vars.zipWithIndex.foldLeft(blockBuilder.assign(pcVar, paths.resumePc).chain(Scoped(Set(getSavedTmp), _))): + case (builder, (local, idx)) => + val (computeOff, savePath) = getSaved(idx) + builder.chain(computeOff).assign(local, savePath) + + Scoped( + scopedVars ++ Set(pcVar), + 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 translateCtorLike(b: Block, thisPath: Path, isModCtor: Bool)(using h: HandlerCtx): Block = + translateBlock(b, if isModCtor then HandlerCtx.ModCtor else HandlerCtx.Ctor, Set.empty) + + private def translateTopLevelOrCtor(b: Block, onEffect: Path => Call)(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, onEffect(l.asPath), End()), + N) + .rest(rst) + val topLevelTransform = new BlockTransformerShallow(SymbolSubst()): + override def applyBlock(b: Block) = b match + 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 assignments if it is assign-like + val l = freshTmp() + Scoped(Set(l), topLevelCheck(l, r, k(Value.Ref(l)))) + case _ => super.applyResult(r)(k) + topLevelTransform.applyBlock(b) + + // 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.withFreshSymbol(N, sym, PlainParamList(Nil) :: Nil, h.body)(false) 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.withFreshSymbol( - N, sym, - PlainParamList(Param(FldFlags.empty, handler.resumeSym, N, Modulefulness.none) :: Nil) :: Nil, - mtdBdy + N, sym, PlainParamList(Param(FldFlags.empty, handler.resumeSym, N, Modulefulness.none) :: Nil) :: Nil, + handler.body )(false) FunDefn.withFreshSymbol( S(h.cls), @@ -707,19 +562,8 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, handler.params, Define( fDef, - Return(PureCall(paths.mkEffectPath, h.cls.asPath :: fDef.asPath :: Nil), false)))(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, false)) - .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}›") - ) - + Return(PureCall(paths.mkEffectPath, h.cls.asPath :: Value.Ref(sym, S(fDef.dSym)) :: Nil), false)))(false) + val clsDefn = ClsLikeDefn( N, // no owner h.cls, @@ -727,274 +571,36 @@ 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, false), 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, false), 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, tSym, PlainParamList(Nil) :: Nil, body)(false) - - val result = blockBuilder - .define(defn) - .rest( - ResultPlaceholder(h.res, freshId(), Call(defn.asPath, Nil)(true, true, false), h.rest) - ) - result + .define(bodyDefn) + .assign(h.res, Call(paths.enterHandleBlockPath, List(h.lhs.asPath.asArg, Value.Ref(sym, S(bodyDefn.dSym)).asArg))(true, true, false)) + .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(doUnwindNme))( - 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(doUnwindNme))( - N /* this refers to the method defined in Runtime.FunctionContFrame */ - ), - res.asPath.asArg :: Value.Lit(Tree.IntLit(uid)).asArg :: Nil)(true, false, 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 - ) + def translateHandleBlocks(b: Block): Block = - // 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.withFreshSymbol( - S(clsSym), // owner - resumeSym, - List(PlainParamList(List(Param(FldFlags.empty, resumedVal, N, Modulefulness.none)))), - resumeBody - )(false) - - val debugMtds = if !opt.debug then Nil else - - val getLocalsSym = BlockMemberSymbol(getLocalsNme, List()) - - val localsRes = h.debugInfo.prevLocalsFn match - case Some(value) => PureCall(value, Nil) - case None => Tuple(mut = true, Nil) - - val getLocalsFnDef = FunDefn.withFreshSymbol( - S(clsSym), - getLocalsSym, - List(), - Return(localsRes, false) - )(false) - - val getLocSym = BlockMemberSymbol("getLoc", List()) - val getLocFnDef = FunDefn.withFreshSymbol( - 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()), - )(false) - - 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, clsPath: Path, 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, clsPath, 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 = applySubBlock(bod) + val rst2 = applySubBlock(rst) + translateHandleBlockShallow(new 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 + def translateTopLevel(b: Block): (Block, collection.Map[FnOrCls, Path => Return]) = + doUnwindMap.clear() val preTransformed = new PreHandlerLowering().applyBlock(b) - val transformed = translateBlock(preTransformed, Set.empty, N, L(BlockMemberSymbol("", Nil)), topLevelCtx(s"Cont$$topLevel$$BAD", "‹top level›")) + val ctx = HandlerCtx.TopLevel + val transformed = translateBlock(preTransformed, ctx, Set.empty) (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 99f5caec51..13d805c786 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. @@ -482,7 +483,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, _)) => @@ -647,14 +648,14 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): case Some(c: ClsLikeDefn) => Value.Lit(Tree.BoolLit(false)).asArg :: getCallArgs(FunSyms(l, d), ctx) case _ => getCallArgs(FunSyms(l, d), ctx) applyListOf(args, applyArg(_)(_)): newArgs => - k(Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(c.isMlsFun, false, c.explicitTailCall)) + k(Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(c.isMlsFun, c.mayRaiseEffects, c.explicitTailCall)) case _ => super.applyResult(r)(k) case c @ Instantiate(mut, InstSel(l, S(d)), args) => ctx.bmsReqdInfo.get(l) match case Some(info) if !ctx.isModOrObj(l) => val extraArgs = Value.Lit(Tree.BoolLit(mut)).asArg :: getCallArgs(FunSyms(l, d), ctx) applyListOf(args, applyArg(_)(_)): newArgs => - k(Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(true, false, false)) + k(Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(true, true, false)) 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 @@ -952,7 +953,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, false)) // TODO: restParams not considered + .ret(Call(singleCallBms.asPath, args1 ++ args2)(true, true, false)) // TODO: restParams not considered val mainDefn = FunDefn(f.owner, f.sym, f.dSym, PlainParamList(extraParamsCpy) :: headPlistCopy :: Nil, bdy)(false) val auxDefn = FunDefn(N, singleCallBms.b, singleCallBms.d, flatPlist, lifted.body)(forceTailRec = f.forceTailRec) @@ -1043,7 +1044,7 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): ) for ps <- newAuxSyms do - val call = Call(curSym.asPath, ps.map(_.asPath.asArg))(true, false, false) + val call = Call(curSym.asPath, ps.map(_.asPath.asArg))(true, true, false) curSym = TempSymbol(None, "tmp") val thisSym = curSym acc = acc.assign(thisSym, call) @@ -1261,7 +1262,7 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): (if paramsSet.contains(s) then s.asPath else Value.Lit(Tree.UnitLit(true))).asArg // moved when the capture is instantiated val bod = blockBuilder - .assign(captureSym, Instantiate(mut = true, // * Note: `mut` is needed for capture classes + .assignScoped(captureSym, Instantiate(mut = true, // * Note: `mut` is needed for capture classes captureCls.sym.asPath, paramsList)) .rest(transformed) Lifted(FunDefn(f.owner, f.sym, f.dSym, f.params, bod)(forceTailRec = f.forceTailRec), captureCls :: newDefns) @@ -1274,7 +1275,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 8d049148f2..0ef05aa70f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -588,14 +588,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)) case t if instantiatedResolvedBms.exists(_ is ctx.builtins.scope.locally) => arg match case Tup(Fld(FldFlags.benign(), body, N) :: Nil) => @@ -645,7 +637,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): subTerm(rhs): par => subTerms(as): asr => HandleBlock(lhs, resSym, par, asr, cls, handlers, - term_nonTail(bod)(Ret), + inScopedBlock(returnedTerm(bod)), k(Value.Ref(resSym))) case st.Blk(sts, res) => block(sts, R(res))(k) case Assgn(lhs, rhs) => @@ -1040,21 +1032,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) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 977d240fc7..7d2b24bc16 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,31 @@ 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, false)) .ifthen( resSym.asPath, Case.Cls(paths.effectSigSym, paths.effectSigPath), - Return( - Call(doUnwindPath.get, resSym.asPath.asArg :: intLit(0).asArg :: Nil)(true, false, 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.extractDefns() - 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.dSym, defn.params, rewriteBlk(defn.body, L(defn.sym), 1))(defn.forceTailRec) + FunDefn(defn.owner, defn.sym, defn.dSym, defn.params, rewriteBlk(defn.body, L(defn.sym), 1))(defn.forceTailRec) 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 026610a470..4b5c976354 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/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 6cd7793ae2..7850ca998a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -196,7 +196,6 @@ object Elaborator: val not_impl = assumeObject("not_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/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index df097c0901..0cd526d12e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -13,10 +13,25 @@ let Runtime1; 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; } @@ -250,6 +265,11 @@ let Runtime1; toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "TraceLogger"]; }); + this.isResuming = false; + this.resumeValue = null; + this.resumeArr = null; + this.resumeIdx = null; + this.resumePc = null; (class FatalEffect { static { new this @@ -278,13 +298,95 @@ let Runtime1; 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)); }; (class FunctionContFrame { static { Runtime.FunctionContFrame.class = this } + constructor(next, saved) { + this.next = next; + this.saved = saved; + } + resume(value) { + let scrut, f, tmp, tmp1, tmp2; + scrut = this.saved.at(0) == 0; + if (scrut === true) { + tmp = runtime.safeCall(globalThis.console.log("cannot resume getters")); + } else { + tmp = runtime.Unit; + } + f = this.saved.at(1); + tmp3: while (true) { + let scrut1, tmp4, tmp5; + scrut1 = this.saved.at(0) > 1; + if (scrut1 === true) { + tmp4 = runtime.safeCall(f()); + f = tmp4; + tmp5 = this.saved.at(0) - 1; + this.saved[0] = tmp5; + tmp1 = runtime.Unit; + continue tmp3 + } else { + tmp1 = runtime.Unit; + } + break; + } + Runtime.isResuming = true; + Runtime.resumeValue = value; + Runtime.resumeArr = this.saved; + Runtime.resumeIdx = 7; + Runtime.resumePc = this.saved.at(4); + tmp2 = globalThis.Object.freeze([]); + return f.apply(this.saved.at(5), tmp2) + } + get getLocal() { + let debugInfo, res, i, tmp; + debugInfo = this.saved.at(2); + res = []; + i = 1; + tmp1: while (true) { + let scrut, tmp2, tmp3, tmp4, tmp5, tmp6; + scrut = i < debugInfo.length; + if (scrut === true) { + tmp2 = i + 1; + tmp3 = 7 + debugInfo.at(i); + tmp4 = globalThis.Object.freeze(new Runtime.LocalVarInfo.class(debugInfo.at(tmp2), this.saved.at(tmp3))); + tmp5 = runtime.safeCall(res.push(tmp4)); + tmp6 = i + 2; + i = tmp6; + tmp = runtime.Unit; + continue tmp1 + } else { + tmp = runtime.Unit; + } + break; + } + return res; + } + get getNme() { + return this.saved.at(2).at(0); + } + get getLoc() { + let loc; + loc = this.saved.at(3); + if (loc === null) { + return "pc=" + this.saved.at(4) + } else { + return loc + } + } + toString() { return runtime.render(this); } + static [definitionMetadata] = ["class", "FunctionContFrame", ["next", "saved"]]; + }); + this.FunctionContFrameOld = function FunctionContFrameOld(next) { + return globalThis.Object.freeze(new FunctionContFrameOld.class(next)); + }; + (class FunctionContFrameOld { + static { + Runtime.FunctionContFrameOld.class = this + } constructor(next) { this.next = next; } @@ -295,7 +397,7 @@ let Runtime1; 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)); @@ -380,6 +482,22 @@ let Runtime1; toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "LocalVarInfo", ["localName", "value"]]; }); + this.FakeError = function FakeError(stack) { + return globalThis.Object.freeze(new FakeError.class(stack)); + }; + (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; @@ -523,18 +641,18 @@ let Runtime1; return Runtime.mkEffect(Runtime.PrintStackEffect, showLocals) } static topLevelEffect(tr, debug) { - let tmp, tmp1; - tmp2: while (true) { - let scrut, tmp3, tmp4, tmp5, tmp6; + let tmp, tmp1, tmp2; + tmp3: while (true) { + let scrut, tmp4, tmp5, tmp6, tmp7; scrut = tr.handler === Runtime.PrintStackEffect; if (scrut === true) { - tmp3 = Runtime.showStackTrace("Stack Trace:", tr, debug, tr.handlerFun); - tmp4 = runtime.safeCall(globalThis.console.log(tmp3)); - tmp5 = Runtime.resume(tr.contTrace); - tmp6 = runtime.safeCall(tmp5(runtime.Unit)); - tr = tmp6; + tmp4 = Runtime.showStackTrace("Stack Trace:", tr, debug, tr.handlerFun); + tmp5 = runtime.safeCall(globalThis.console.log(tmp4)); + tmp6 = Runtime.resume(tr.contTrace); + tmp7 = runtime.safeCall(tmp6(runtime.Unit)); + tr = tmp7; tmp = runtime.Unit; - continue tmp2 + continue tmp3 } else { tmp = runtime.Unit; } @@ -542,7 +660,19 @@ let Runtime1; } if (tr instanceof Runtime.EffectSig.class) { tmp1 = "Error: Unhandled effect " + tr.handler.constructor.name; - throw Runtime.showStackTrace(tmp1, tr, debug, false) + tmp2 = Runtime.showStackTrace(tmp1, tr, debug, false); + throw Runtime.FakeError(tmp2) + } else { + return tr + } + } + static ctorEffect(tr) { + let tmp, tmp1, tmp2; + if (tr instanceof Runtime.EffectSig.class) { + tmp = "Error: Effect " + tr.handler.constructor.name; + tmp1 = tmp + " is raised inside a constructor"; + tmp2 = Runtime.showStackTrace(tmp1, tr, false, false); + throw Runtime.FakeError(tmp2) } else { return tr } @@ -559,33 +689,25 @@ let Runtime1; if (scrut === true) { cur = curHandler.next; tmp9: while (true) { - let scrut2, locals, curLocals, loc, loc1, localsMsg, scrut3, tmp10, tmp11, lambda, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17, tmp18, tmp19, tmp20; + let scrut2, curLocals, loc, localsMsg, scrut3, lambda, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17, tmp18; scrut2 = cur !== null; if (scrut2 === true) { - locals = cur.getLocals; - tmp10 = locals.length - 1; - curLocals = runtime.safeCall(locals.at(tmp10)); + curLocals = cur.getLocal; loc = cur.getLoc; - if (loc === null) { - tmp11 = "pc=" + cur.pc; - } else { - tmp11 = loc; - } - loc1 = tmp11; split_root$: { split_1$: { if (showLocals === true) { - scrut3 = curLocals.locals.length > 0; + scrut3 = curLocals.length > 0; if (scrut3 === true) { lambda = (undefined, function (l) { - let tmp21, tmp22; - tmp21 = l.localName + "="; - tmp22 = Rendering.render(l.value); - return tmp21 + tmp22 + let tmp19, tmp20; + tmp19 = l.localName + "="; + tmp20 = Rendering.render(l.value); + return tmp19 + tmp20 }); - tmp12 = runtime.safeCall(curLocals.locals.map(lambda)); - tmp13 = runtime.safeCall(tmp12.join(", ")); - tmp14 = " with locals: " + tmp13; + tmp10 = runtime.safeCall(curLocals.map(lambda)); + tmp11 = runtime.safeCall(tmp10.join(", ")); + tmp12 = " with locals: " + tmp11; break split_root$ } else { break split_1$ @@ -594,17 +716,17 @@ let Runtime1; break split_1$ } } - tmp14 = ""; + tmp12 = ""; } - localsMsg = tmp14; - tmp15 = "\n\tat " + curLocals.fnName; - tmp16 = tmp15 + " ("; - tmp17 = tmp16 + loc1; - tmp18 = tmp17 + ")"; - tmp19 = msg + tmp18; - msg = tmp19; - tmp20 = msg + localsMsg; - msg = tmp20; + localsMsg = tmp12; + tmp13 = "\n\tat " + cur.getNme; + tmp14 = tmp13 + " ("; + tmp15 = tmp14 + loc; + tmp16 = tmp15 + ")"; + tmp17 = msg + tmp16; + msg = tmp17; + tmp18 = msg + localsMsg; + msg = tmp18; cur = cur.next; atTail = false; tmp5 = runtime.Unit; @@ -813,6 +935,13 @@ let Runtime1; 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); diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index f00d0d458f..62b58bb8f1 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -121,10 +121,50 @@ 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 = + let loc = saved.[3] + if + loc is null then "pc=" + saved.[4] + else loc + +data abstract class FunctionContFrameOld(next) with fun resume(value) fun doUnwind(res1, newPc) = set @@ -142,6 +182,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 +194,13 @@ 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 + +fun ctorEffect(tr) = + if tr is EffectSig then + throw FakeError(showStackTrace("Error: Effect " + tr.handler.constructor.name + " is raised inside a constructor", tr, false, false)) else tr @@ -165,16 +213,14 @@ 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 +302,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/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/bbml/bbPrelude.mls b/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls index c974756539..593cf6f21e 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls @@ -81,7 +81,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/ScopedBlocksAndHandlers.mls b/hkmc2/shared/src/test/mlscript/codegen/ScopedBlocksAndHandlers.mls index df4a27c651..b59bf41806 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ScopedBlocksAndHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ScopedBlocksAndHandlers.mls @@ -1,4 +1,6 @@ :js +:lift +:noSanityCheck :effectHandlers @@ -19,11 +21,12 @@ fun f(x) = fun g() = z //│ JS (unsanitized): //│ let f; +//│ let g$; +//│ g$ = function g$(z) { +//│ return z +//│ }; //│ f = function f(x) { //│ let g, z; -//│ g = function g() { -//│ return z -//│ }; //│ if (x === true) { //│ if (x === true) { //│ z = 3; @@ -110,11 +113,12 @@ fun f(x) = fun g() = z ) //│ JS (unsanitized): //│ let f4; +//│ let g$3; +//│ g$3 = function g$(z) { +//│ return z +//│ }; //│ f4 = function f(x) { -//│ let g, z; -//│ g = function g() { -//│ return z -//│ }; +//│ let g2, z; //│ if (x === true) { z = 3; return runtime.Unit } else { return runtime.Unit } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 10f857e48e..f1be2441eb 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -201,7 +201,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 bd38be2a12..510320fefc 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 @@ -18,142 +20,95 @@ fun f() = j / i //│ JS (unsanitized): //│ let f; -//│ let 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$debugInfo; +//│ f$debugInfo = globalThis.Object.freeze([ +//│ "f", +//│ 0, +//│ "i", +//│ 1, +//│ "j", +//│ 2, +//│ "k" +//│ ]); //│ f = function f() { -//│ let i, j, k, scrut, tmp, tmp1; -//│ let 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 -//│ }; -//│ (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$) { -//│ switch (this.pc) { -//│ case 1: -//│ scrut = value$; -//│ break; -//│ case 2: -//│ tmp = value$; -//│ break; -//│ case 3: -//│ tmp1 = value$; -//│ break; -//│ } -//│ contLoop: while (true) { -//│ switch (this.pc) { -//│ case 4: -//│ return j / i; -//│ break; -//│ case 1: -//│ if (scrut === true) { -//│ tmp = Predef.raiseUnhandledEffect(); -//│ if (tmp instanceof runtime.EffectSig.class) { -//│ return this.doUnwind(tmp, 2) -//│ } -//│ this.pc = 2; -//│ continue contLoop -//│ } else { -//│ tmp1 = runtime.Unit; -//│ this.pc = 4; -//│ continue contLoop -//│ } -//│ /* unreachable */ -//│ break; -//│ case 5: -//│ tmp1 = Predef.print(tmp); -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return this.doUnwind(tmp1, 3) -//│ } -//│ this.pc = 3; -//│ continue contLoop; -//│ break; -//│ case 2: -//│ this.pc = 5; -//│ continue contLoop; -//│ break; -//│ case 3: -//│ this.pc = 4; -//│ continue contLoop; -//│ break; +//│ let i, j, k, scrut, tmp1, tmp2, pc; +//│ if (runtime.isResuming === true) { +//│ let saveOffset; +//│ 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; +//│ } else { +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ i = 0; +//│ j = 100; +//│ k = 2000; +//│ runtime.resumeValue = Predef.equals(i, 0); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, f, f$debugInfo, "Debugging.mls:18:6", 5, null, 6, i, j, k, scrut, tmp1, tmp2) //│ } +//│ pc = 5; +//│ continue main; +//│ break; +//│ case 5: +//│ scrut = runtime.resumeValue; +//│ if (scrut === 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 +//│ } else { +//│ tmp2 = runtime.Unit; +//│ pc = 1; +//│ continue main +//│ } +//│ /* unreachable */ +//│ 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() { -//│ switch (this.pc) { -//│ case 1: -//│ return "Debugging.mls:16:6"; -//│ break; -//│ case 2: -//│ return "Debugging.mls:17:14"; -//│ break; -//│ case 3: -//│ return "Debugging.mls:17:5"; -//│ break; -//│ } //│ } -//│ 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 = Predef.equals(i, 0); -//│ if (scrut instanceof runtime.EffectSig.class) { -//│ return doUnwind(scrut, 1) +//│ break; //│ } -//│ if (scrut === true) { -//│ tmp = Predef.raiseUnhandledEffect(); -//│ if (tmp instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp, 2) -//│ } -//│ tmp1 = Predef.print(tmp); -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp1, 3) -//│ } -//│ } else { tmp1 = runtime.Unit; } -//│ return j / i //│ }; :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) = @@ -163,8 +118,8 @@ lambda_test(() => raiseUnhandledEffect() 100) //│ ═══[RUNTIME ERROR] Error: Unhandled effect FatalEffect -//│ at lambda (Debugging.mls:163:3) -//│ at lambda_test (Debugging.mls:160:3) +//│ at lambda (Debugging.mls:118:3) +//│ at lambda_test (Debugging.mls:115:3) import "../../mlscript-compile/Runtime.mls" @@ -173,13 +128,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: @@ -187,7 +135,7 @@ Runtime.debugEff(res.reified) //│ > handlerFun: null //│ > resumed: false //│ > -//│ > Cont$func$f$(pc=2, last) -> (null) +//│ > FunctionContFrame(pc=undefined, last) -> (null) //│ > res.resumeWith(42) @@ -200,20 +148,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 @@ -232,20 +179,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:236:3) with locals: j=200 +//│ > at f (Debugging.mls:182:3) with locals: j=200 //│ > Stack Trace: -//│ > at f (Debugging.mls:238:3) +//│ > at f (Debugging.mls:184:3) //│ > Stack Trace: -//│ > at f (Debugging.mls:239: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 bee0bd40eb..099e590c12 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,75 +158,57 @@ if true do fun f() = 3 f() //│ JS (unsanitized): -//│ let f, scrut, tmp20; -//│ let handleBlock$11; -//│ handleBlock$11 = function handleBlock$() { -//│ let h, res, Cont$handleBlock$h$11, doUnwind, Handler$h$12; -//│ (class Handler$h$11 extends Effect1 { -//│ static { -//│ Handler$h$12 = this -//│ } -//│ constructor() { -//│ let tmp21; -//│ tmp21 = 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(); -//│ (class Cont$handleBlock$h$10 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handleBlock$h$11 = this -//│ } -//│ constructor(pc) { -//│ let tmp21; -//│ tmp21 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 1) { -//│ res = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 1) { -//│ return res -//│ } -//│ break; -//│ } +//│ let tmp11; +//│ let f, h11, 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) +//│ } +//│ }; +//│ (class Handler$h$22 extends Effect1 { +//│ static { +//│ Handler$h$23 = this +//│ } +//│ constructor() { +//│ let tmp12; +//│ tmp12 = super(); +//│ if (tmp12 instanceof runtime.EffectSig.class) { +//│ tmp12 = runtime.ctorEffect(tmp12); //│ } -//│ 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 -//│ }; +//│ 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 +//│ }; +//│ handleBlock$11 = (undefined, function () { +//│ let scrut; //│ scrut = true; //│ if (scrut === true) { -//│ res = f(); -//│ if (res instanceof runtime.EffectSig.class) { -//│ return doUnwind(res, 1) -//│ } -//│ return res +//│ return f() //│ } else { //│ return runtime.Unit //│ } -//│ }; -//│ tmp20 = handleBlock$11(); -//│ if (tmp20 instanceof runtime.EffectSig.class) { -//│ tmp20 = runtime.topLevelEffect(tmp20, false); +//│ }); +//│ tmp11 = runtime.enterHandleBlock(h11, handleBlock$11); +//│ if (tmp11 instanceof runtime.EffectSig.class) { +//│ tmp11 = runtime.topLevelEffect(tmp11, false); //│ } -//│ tmp20 +//│ tmp11 //│ = 3 module A with @@ -371,6 +355,7 @@ handle h = Eff with foo(h) //│ = 123 +:w :expect 123 fun foo(h) = h.perform() @@ -395,6 +380,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.364: module A with +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.368: module A with +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.372: module A with +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.376: 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 d2c18c01e4..a1ed16192f 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,52 @@ 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; -//│ (class A { -//│ static { -//│ A2 = this -//│ } -//│ constructor() { -//│ runtime.Unit; -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "A"]; -//│ }); -//│ (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 } +//│ if (scrut === true) { +//│ (class A { +//│ static { +//│ A2 = this +//│ } +//│ constructor() { +//│ runtime.Unit; +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "A"]; +//│ }); +//│ return A2 +//│ } else { +//│ (class A1 { +//│ static { +//│ A3 = this +//│ } +//│ constructor() { +//│ runtime.Unit; +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "A"]; +//│ }); +//│ return A3 +//│ } //│ }; +//│ ═══[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 714e88b636..8526e4a65b 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,70 +21,17 @@ data class Lol(h) with //│ Lol1.class = this //│ } //│ constructor(h) { -//│ let tmp; -//│ let res, Cont$ctor$Lol$1, doUnwind; -//│ const this$Lol = this; -//│ (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$) { -//│ switch (this.pc) { -//│ case 1: -//│ tmp = value$; -//│ break; -//│ case 2: -//│ res = value$; -//│ break; -//│ } -//│ contLoop: while (true) { -//│ switch (this.pc) { -//│ case 3: -//│ return this$Lol; -//│ break; -//│ case 4: -//│ res = Predef.print(tmp); -//│ if (res instanceof runtime.EffectSig.class) { -//│ return this.doUnwind(res, 2) -//│ } -//│ this.pc = 2; -//│ continue contLoop; -//│ break; -//│ case 1: -//│ this.pc = 4; -//│ continue contLoop; -//│ break; -//│ case 2: -//│ this.pc = 3; -//│ continue contLoop; -//│ break; -//│ } -//│ 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.ctorEffect(tmp); //│ } -//│ 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.ctorEffect(tmp1); //│ } -//│ res; +//│ tmp1; //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Lol", ["h"]]; @@ -105,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: Effect Handler$h$ is raised inside a constructor +:fixme let oops = handle h = Effect with fun perform(arg)(k) = print(arg) "b" Lol(h) -//│ > k -//│ oops = "b" +//│ ═══[RUNTIME ERROR] Error: Effect Handler$h$2 is raised inside a constructor +//│ 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: Effect Handler$h$4 is raised inside a constructor +//│ 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: Effect Handler$h$6 is raised inside a constructor +//│ 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..a5d613c8da 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -1,12 +1,15 @@ :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): () + 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..86c5b46d08 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,20 @@ 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. +:fixme :expect 100 fun f() = class A with return 100 new A f() -//│ = 100 +//│ ═══[RUNTIME ERROR] Error: Effect $_non$_local$_return$_effect$_20 is raised inside a constructor +//│ ═══[RUNTIME ERROR] Expected: '100', got: 'undefined' :fixme :expect 100 @@ -125,9 +131,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 +143,5 @@ fun f() = new A 4 f() -//│ = 100 +//│ ═══[RUNTIME ERROR] Error: Effect $_non$_local$_return$_effect$_24 is raised inside a constructor +//│ ═══[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 9d46462f07..91f730ecee 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,227 +157,205 @@ if true do h1.perform(()) str //│ JS (unsanitized): -//│ let str, scrut, tmp24, tmp25, tmp26, tmp27; -//│ let handleBlock$7; +//│ let str, scrut, tmp11, tmp12; +//│ let h11, 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, handleBlock$8, Cont$handleBlock$h1$2, doUnwind, Handler$h1$2; -//│ (class Handler$h1$1 extends Effect1 { -//│ static { -//│ Handler$h1$2 = this +//│ Handler$h1$perform$1 = function Handler$h1$perform$(Handler$h1$$instance, arg, k) { +//│ let tmp13, tmp14, tmp15, pc; +//│ if (runtime.isResuming === true) { +//│ let saveOffset; +//│ pc = runtime.resumePc; +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ 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; //│ } -//│ constructor() { -//│ let tmp28; -//│ tmp28 = super(); +//│ break; +//│ } +//│ }; +//│ Handler$h1$perform1 = function Handler$h1$perform(Handler$h1$$instance, arg) { +//│ return (k) => { +//│ return Handler$h1$perform$1(Handler$h1$$instance, arg, k) +//│ } +//│ }; +//│ (class Handler$h1$2 extends Effect1 { +//│ static { +//│ Handler$h1$3 = this +//│ } +//│ constructor() { +//│ let tmp13; +//│ tmp13 = super(); +//│ if (tmp13 instanceof runtime.EffectSig.class) { +//│ tmp13 = runtime.ctorEffect(tmp13); //│ } -//│ perform(arg) { -//│ let hdlrFun; -//│ hdlrFun = function hdlrFun(k) { -//│ let tmp28, tmp29, tmp30; -//│ let Cont$handler$h1$perform$2, doUnwind1; -//│ (class Cont$handler$h1$perform$1 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handler$h1$perform$2 = this -//│ } -//│ constructor(pc) { -//│ let tmp31; -//│ tmp31 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 6) { -//│ tmp29 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 6) { -//│ tmp30 = str + "A"; -//│ str = tmp30; -//│ 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 -//│ }; -//│ tmp28 = str + "A"; -//│ str = tmp28; -//│ tmp29 = runtime.safeCall(k(arg)); -//│ if (tmp29 instanceof runtime.EffectSig.class) { -//│ return doUnwind1(tmp29, 6) +//│ tmp13; +//│ } +//│ perform(arg) { +//│ 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$"]; +//│ }); +//│ 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; +//│ if (runtime.isResuming === true) { +//│ let saveOffset; +//│ pc = runtime.resumePc; +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ tmp13 = str + "B"; +//│ tmp14 = str + tmp13; +//│ str = tmp14; +//│ 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) //│ } -//│ tmp30 = str + "A"; -//│ str = tmp30; -//│ return runtime.Unit -//│ }; -//│ return runtime.mkEffect(this, hdlrFun) -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Handler$h1$"]; -//│ }); -//│ h1 = new Handler$h1$2(); -//│ (class Cont$handleBlock$h1$1 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handleBlock$h1$2 = this +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ tmp15 = runtime.resumeValue; +//│ tmp16 = str + "B"; +//│ tmp17 = str + tmp16; +//│ str = tmp17; +//│ return runtime.Unit; +//│ break; //│ } -//│ constructor(pc) { -//│ let tmp28; -//│ tmp28 = 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) +//│ } +//│ }; +//│ (class Handler$h2$2 extends Effect1 { +//│ static { +//│ Handler$h2$3 = this +//│ } +//│ constructor() { +//│ let tmp13; +//│ tmp13 = super(); +//│ if (tmp13 instanceof runtime.EffectSig.class) { +//│ tmp13 = runtime.ctorEffect(tmp13); //│ } -//│ resume(value$) { -//│ if (this.pc === 5) { -//│ tmp25 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 5) { -//│ return tmp25 +//│ 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; +//│ if (runtime.isResuming === true) { +//│ let saveOffset; +//│ pc = runtime.resumePc; +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ 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", 1, null, 0) //│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ tmp13 = runtime.resumeValue; +//│ return runtime.safeCall(h11.perform(runtime.Unit)); //│ 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, res7, Cont$handleBlock$h2$2, doUnwind1, Handler$h2$2; -//│ (class Handler$h2$1 extends Effect1 { -//│ static { -//│ Handler$h2$2 = this -//│ } -//│ constructor() { -//│ let tmp28; -//│ tmp28 = super(); -//│ } -//│ perform(arg) { -//│ let hdlrFun; -//│ hdlrFun = function hdlrFun(k) { -//│ let tmp28, tmp29, tmp30, tmp31, tmp32; -//│ let Cont$handler$h2$perform$2, doUnwind2; -//│ (class Cont$handler$h2$perform$1 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handler$h2$perform$2 = this -//│ } -//│ constructor(pc) { -//│ let tmp33; -//│ tmp33 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 3) { -//│ tmp30 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 3) { -//│ tmp31 = str + "B"; -//│ tmp32 = str + tmp31; -//│ str = tmp32; -//│ 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 -//│ }; -//│ tmp28 = str + "B"; -//│ tmp29 = str + tmp28; -//│ str = tmp29; -//│ tmp30 = runtime.safeCall(k(arg)); -//│ if (tmp30 instanceof runtime.EffectSig.class) { -//│ return doUnwind2(tmp30, 3) -//│ } -//│ tmp31 = str + "B"; -//│ tmp32 = str + tmp31; -//│ str = tmp32; -//│ return runtime.Unit -//│ }; -//│ return runtime.mkEffect(this, hdlrFun) -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Handler$h2$"]; -//│ }); -//│ h2 = new Handler$h2$2(); -//│ (class Cont$handleBlock$h2$1 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handleBlock$h2$2 = this -//│ } -//│ constructor(pc) { -//│ let tmp28; -//│ tmp28 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ switch (this.pc) { -//│ case 1: -//│ tmp26 = value$; -//│ break; -//│ case 2: -//│ res7 = value$; -//│ break; +//│ break; +//│ } +//│ }; +//│ handleBlock$9 = (undefined, function (h22) { +//│ return () => { +//│ return handleBlock$$2(h22) +//│ } +//│ }); +//│ handleBlock$10 = (undefined, function () { +//│ let tmp13, pc; +//│ let h22, handleBlock$$here; +//│ if (runtime.isResuming === true) { +//│ let saveOffset; +//│ pc = runtime.resumePc; +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ 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) //│ } -//│ contLoop: while (true) { -//│ switch (this.pc) { -//│ case 1: -//│ res7 = runtime.safeCall(h1.perform(runtime.Unit)); -//│ if (res7 instanceof runtime.EffectSig.class) { -//│ return this.doUnwind(res7, 2) -//│ } -//│ this.pc = 2; -//│ continue contLoop; -//│ break; -//│ case 2: -//│ return res7; -//│ break; -//│ } -//│ break; +//│ 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) //│ } -//│ } -//│ 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) -//│ }; -//│ tmp26 = runtime.safeCall(h2.perform(runtime.Unit)); -//│ if (tmp26 instanceof runtime.EffectSig.class) { -//│ return doUnwind1(tmp26, 1) -//│ } -//│ res7 = runtime.safeCall(h1.perform(runtime.Unit)); -//│ if (res7 instanceof runtime.EffectSig.class) { -//│ return doUnwind1(res7, 2) +//│ pc = 1; +//│ continue main; +//│ break; //│ } -//│ return res7 -//│ }; -//│ tmp25 = handleBlock$8(); -//│ if (tmp25 instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp25, 5) +//│ break; //│ } -//│ return tmp25 -//│ }; -//│ tmp24 = handleBlock$7(); -//│ if (tmp24 instanceof runtime.EffectSig.class) { -//│ tmp24 = runtime.topLevelEffect(tmp24, false); +//│ }); +//│ tmp11 = runtime.enterHandleBlock(h11, handleBlock$10); +//│ if (tmp11 instanceof runtime.EffectSig.class) { +//│ tmp11 = runtime.topLevelEffect(tmp11, false); //│ } -//│ tmp27 = tmp24; -//│ } else { tmp27 = runtime.Unit; } +//│ tmp12 = tmp11; +//│ } else { tmp12 = runtime.Unit; } //│ 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 e95579615e..0976612c52 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -1,21 +1,15 @@ :js +:effectHandlers +:lift +:noSanityCheck :noTailRec // * FIXME: Why doesn't the following work when using Predef function `(==) equals`? -// 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 hi(tmp)" in the output :stackSafe 5 -:effectHandlers :expect 0 :sjs fun hi(n) = @@ -23,54 +17,48 @@ fun hi(n) = else hi(n - 1) hi(0) //│ JS (unsanitized): -//│ let hi; -//│ let res, $_stack$_safe$_body$_; +//│ let hi, tmp; +//│ let $_stack$_safe$_body$_; //│ hi = function hi(n) { -//│ let scrut, tmp; -//│ let Cont$func$hi$1, doUnwind, stackDelayRes; -//│ (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; +//│ let 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:15:1", 0, null, 2, tmp1, n) //│ } -//│ scrut = n === 0; -//│ if (scrut === true) { -//│ return 0 +//│ if (runtime.isResuming === true) { +//│ let saveOffset; +//│ pc = runtime.resumePc; +//│ tmp1 = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ n = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; //│ } else { -//│ tmp = n - 1; -//│ return hi(tmp) +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ if (pc === 0) { +//│ scrut = n === 0; +//│ if (scrut === true) { +//│ return 0 +//│ } else { +//│ tmp1 = n - 1; +//│ return hi(tmp1) +//│ } +//│ } +//│ 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) = @@ -79,86 +67,62 @@ fun sum(n) = n + sum(n - 1) sum(10000) //│ JS (unsanitized): -//│ let sum1; -//│ let res1, $_stack$_safe$_body$_1; -//│ sum1 = function sum(n) { -//│ let scrut, tmp, tmp1; -//│ let Cont$func$sum$1, doUnwind, curDepth, stackDelayRes; -//│ (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; -//│ switch (this.pc) { -//│ case 0: -//│ return sum1(n); -//│ break; -//│ case 1: -//│ return n + tmp1; -//│ break; -//│ } -//│ 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; +//│ let $_stack$_safe$_body$_1; +//│ sum = function sum(n) { +//│ let scrut, tmp2, tmp3, pc; +//│ let 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:64:1", 0, null, 2, n, tmp2) //│ } -//│ scrut = n === 0; -//│ if (scrut === true) { -//│ return 0 +//│ if (runtime.isResuming === true) { +//│ let saveOffset; +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ tmp2 = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; //│ } else { -//│ tmp = n - 1; -//│ tmp1 = sum1(tmp); -//│ runtime.stackDepth = curDepth; -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp1, 1) +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ scrut = n === 0; +//│ if (scrut === true) { +//│ return 0 +//│ } else { +//│ 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:67:9", 1, null, 2, n, tmp2) +//│ } +//│ pc = 1; +//│ continue main +//│ } +//│ 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 @@ -172,7 +136,6 @@ foo(foo) //│ ctr = 10001 :stackSafe 1000 -:effectHandlers :expect 50005000 val foo = val f = n => @@ -184,7 +147,6 @@ foo //│ foo = 50005000 :stackSafe 10 -:effectHandlers :expect 50005000 val foo = val f = n => @@ -195,21 +157,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 @@ -224,7 +175,6 @@ foo(h) //│ = 50005000 // function call and defn inside handler -:effectHandlers :stackSafe 100 :expect 50005000 handle h = Eff with @@ -239,7 +189,7 @@ in foo(h) //│ = 50005000 -:effectHandlers + :stackSafe 100 :expect 50005000 module A with @@ -251,7 +201,6 @@ A.x //│ = 50005000 :re -:effectHandlers fun foo(h) = h.perform(2) handle h = Eff with fun perform(a)(resume) = @@ -263,36 +212,31 @@ 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; +//│ if (runtime.isResuming === true) { +//│ let saveOffset; +//│ pc = runtime.resumePc; +//│ a = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ b = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ if (pc === 0) { scrut = a < b; if (scrut === true) { return b } else { return a } } +//│ 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 @@ -303,7 +247,6 @@ bad() //│ = 100010000 -:effectHandlers :stackSafe 100 :expect 0 let depth = 0 @@ -319,7 +262,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 = () @@ -339,7 +281,6 @@ abstract class Eff with fun raise() :re -:effectHandlers fun shift_stack(n, f) = if n >= 0 then shift_stack(n - 1, f) @@ -357,7 +298,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 3d9675010b..be3050532a 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=7) -//│ 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=1) +//│ 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=7) -//│ 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=1) +//│ 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 f48ffa74ea..309e0bf5c7 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=7) -//│ 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=1) +//│ 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=7) -//│ 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=1) +//│ 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 759c8fbbc3..a53754b941 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) @@ -87,7 +89,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 24f10b87ad..e313b8cfb7 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,17 +89,17 @@ fun f() = Good() f().foo() //│ JS (unsanitized): -//│ let f6, tmp9; +//│ let f6, tmp5; //│ let Bad1, Good1, Bad$, Good$, f$capture3; //│ Good$ = function Good$(isMut, x, y, z, f$capture4) { -//│ let tmp10, tmp11; +//│ let tmp6, tmp7; //│ if (isMut === true) { -//│ tmp10 = new Good1.class(); +//│ tmp6 = new Good1.class(); //│ } else { -//│ tmp10 = globalThis.Object.freeze(new Good1.class()); +//│ tmp6 = globalThis.Object.freeze(new Good1.class()); //│ } -//│ tmp11 = tmp10(x, y, z, f$capture4); -//│ return tmp11 +//│ tmp7 = tmp6(x, y, z, f$capture4); +//│ return tmp7 //│ }; //│ Good1 = function Good() { //│ return (x, y, z, f$capture4) => { @@ -131,24 +132,24 @@ f().foo() //│ get z() { return this.#z; } //│ set z(value) { this.#z = value; } //│ foo() { -//│ let tmp10, tmp11; +//│ let tmp6, tmp7; //│ this.z = 100; -//│ tmp10 = this.x + this.y; -//│ tmp11 = tmp10 + this.z; -//│ return tmp11 + this.f$capture.w$capture$0 +//│ tmp6 = this.x + this.y; +//│ tmp7 = tmp6 + this.z; +//│ return tmp7 + this.f$capture.w$capture$0 //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Good", []]; //│ }); //│ Bad$ = function Bad$(isMut, f$capture4) { -//│ let tmp10, tmp11; +//│ let tmp6, tmp7; //│ if (isMut === true) { -//│ tmp10 = new Bad1.class(); +//│ tmp6 = new Bad1.class(); //│ } else { -//│ tmp10 = globalThis.Object.freeze(new Bad1.class()); +//│ tmp6 = globalThis.Object.freeze(new Bad1.class()); //│ } -//│ tmp11 = tmp10(f$capture4); -//│ return tmp11 +//│ tmp7 = tmp6(f$capture4); +//│ return tmp7 //│ }; //│ Bad1 = function Bad() { //│ return (f$capture4) => { @@ -186,19 +187,18 @@ f().foo() //│ static [definitionMetadata] = ["class", "f$capture"]; //│ }); //│ f6 = function f() { -//│ let x, y, z, w, tmp10, tmp11; -//│ let capture; +//│ let x, y, z, w, tmp6, tmp7, capture; //│ capture = new f$capture3(null); //│ x = 1; //│ y = 10; //│ z = 10; //│ capture.w$capture$0 = 1000; -//│ tmp10 = Bad$(false, capture); -//│ tmp11 = tmp10.foo(); +//│ tmp6 = Bad$(false, capture); +//│ tmp7 = tmp6.foo(); //│ return Good$(false, x, y, z, capture) //│ }; -//│ tmp9 = f6(); -//│ runtime.safeCall(tmp9.foo()) +//│ tmp5 = f6(); +//│ runtime.safeCall(tmp5.foo()) //│ = 10111 :expect 2 @@ -223,6 +223,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/EffectHandlers.mls b/hkmc2/shared/src/test/mlscript/lifter/EffectHandlers.mls index e628fd9870..daec0cd631 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 @@ -17,6 +19,7 @@ class Test with class Eff :effectHandlers +:noSanityCheck :expect 7 handle h = Eff with fun perform(x)(k) = k(x) + 1 diff --git a/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls index f8cdbc0ccb..b6761ed46e 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls @@ -207,8 +207,7 @@ f(1, 2, 1000) //│ static [definitionMetadata] = ["class", "f$capture"]; //│ }); //│ f7 = function f(unused, immutable, mutated) { -//│ let g3, h2, a1, tmp8, tmp9; -//│ let capture; +//│ let g3, h2, a1, tmp8, tmp9, capture; //│ capture = new f$capture5(mutated); //│ a1 = g$6(immutable, capture); //│ tmp8 = h$2(capture); @@ -307,8 +306,8 @@ g()(1) //│ static [definitionMetadata] = ["class", "g$capture"]; //│ }); //│ g6 = function g() { -//│ let y1; -//│ let capture, f$here; +//│ let y1, capture; +//│ let f$here; //│ capture = new g$capture1(null); //│ capture.y$capture$0 = 0; //│ f$here = runtime.safeCall(f14(capture)); @@ -455,8 +454,8 @@ fun f(x) = //│ static [definitionMetadata] = ["class", "f$capture"]; //│ }); //│ f24 = function f(x1) { -//│ let a1, tmp11; -//│ let capture, g$here; +//│ let a1, tmp11, capture; +//│ let g$here; //│ capture = new f$capture17(x1); //│ g$here = runtime.safeCall(g13(capture)); //│ a1 = g$here; diff --git a/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls b/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls index 8b09a22289..550876890c 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls @@ -147,8 +147,8 @@ fun foo(x, y) = //│ static [definitionMetadata] = ["class", "foo$capture"]; //│ }); //│ foo4 = function foo(x, y) { -//│ let foo31, tmp; -//│ let M$1, capture; +//│ let foo31, tmp, capture; +//│ let M$1; //│ capture = new foo$capture3(y); //│ M$1 = globalThis.Object.freeze(new M5(x, capture)); //│ tmp = foo3$(M$1, x, capture); diff --git a/hkmc2/shared/src/test/mlscript/lifter/Mutation.mls b/hkmc2/shared/src/test/mlscript/lifter/Mutation.mls index bf21ce9b2e..3ec73328ae 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/Mutation.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/Mutation.mls @@ -33,8 +33,8 @@ fun foo() = //│ static [definitionMetadata] = ["class", "foo$capture"]; //│ }); //│ foo = function foo() { -//│ let x, tmp; -//│ let capture, bar$here; +//│ let x, tmp, capture; +//│ let bar$here; //│ capture = new foo$capture1(null); //│ capture.x$capture$0 = 1; //│ bar$here = runtime.safeCall(bar(capture)); diff --git a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls index f10ee93f59..5968f1b88d 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 :noTailRec // * FIXME: Why doesn't the following work when using Predef function `(==) equals`? @@ -24,67 +25,45 @@ fun hi(n) = else hi(n - 1) hi(0) //│ JS (unsanitized): -//│ let hi; -//│ let 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 -//│ }; -//│ (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; +//│ let $_stack$_safe$_body$_; //│ hi = function hi(n) { -//│ let scrut, tmp; +//│ let scrut, tmp1, pc; //│ let 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:23:1", 0, null, 2, tmp1, n) //│ } -//│ scrut = n === 0; -//│ if (scrut === true) { -//│ return 0 +//│ if (runtime.isResuming === true) { +//│ let saveOffset; +//│ pc = runtime.resumePc; +//│ tmp1 = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ n = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; //│ } else { -//│ tmp = n - 1; -//│ return hi(tmp) +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ if (pc === 0) { +//│ scrut = n === 0; +//│ if (scrut === true) { +//│ return 0 +//│ } else { +//│ tmp1 = n - 1; +//│ return hi(tmp1) +//│ } +//│ } +//│ 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 @@ -97,93 +76,59 @@ fun sum(n) = n + sum(n - 1) sum(10000) //│ JS (unsanitized): -//│ let sum1; -//│ let 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 -//│ }; -//│ (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; -//│ switch (this.pc) { -//│ case 0: -//│ return sum1(this.n); -//│ break; -//│ case 1: -//│ return this.n + this.tmp; -//│ break; -//│ } -//│ 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; +//│ let $_stack$_safe$_body$_1; //│ sum1 = function sum(n) { -//│ let scrut, tmp, tmp1; -//│ let curDepth, stackDelayRes; +//│ let scrut, tmp2, tmp3, pc; +//│ let 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:73:1", 0, null, 2, n, tmp2) //│ } -//│ scrut = n === 0; -//│ if (scrut === true) { -//│ return 0 +//│ if (runtime.isResuming === true) { +//│ let saveOffset; +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ tmp2 = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; //│ } else { -//│ tmp = n - 1; -//│ tmp1 = sum1(tmp); -//│ runtime.stackDepth = curDepth; -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return doUnwind$1(n, tmp1, tmp1, 1) +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ scrut = n === 0; +//│ if (scrut === true) { +//│ return 0 +//│ } else { +//│ 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:76:9", 1, null, 2, n, tmp2) +//│ } +//│ pc = 1; +//│ continue main +//│ } +//│ 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 @@ -284,9 +229,21 @@ 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; +//│ if (runtime.isResuming === true) { +//│ let saveOffset; +//│ pc = runtime.resumePc; +//│ a = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ b = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ if (pc === 0) { scrut = a < b; if (scrut === true) { return b } else { return a } } +//│ break; +//│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/tailrec/Annots.mls b/hkmc2/shared/src/test/mlscript/tailrec/Annots.mls index 698e2a7a56..1abcd7d935 100644 --- a/hkmc2/shared/src/test/mlscript/tailrec/Annots.mls +++ b/hkmc2/shared/src/test/mlscript/tailrec/Annots.mls @@ -73,6 +73,8 @@ module A with :w :effectHandlers +:lift +:noSanityCheck handle h = Object with @tailcall fun f(r) = 2 @@ -83,5 +85,5 @@ fun test = @tailcall 1 + 2 //│ ╔══[WARNING] This annotation has no effect. //│ ╟── The @tailcall annotation has no effect on calls to built-in symbols. -//│ ║ l.83: @tailcall 1 + 2 +//│ ║ l.85: @tailcall 1 + 2 //│ ╙── ^^^^^ diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 306d0183c9..6d69bd1b7f 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -80,6 +80,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(