From 0dddaa6f982b6aaf4adf4b2b394256aa4e7016af Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Sat, 15 Nov 2025 19:09:32 +0800 Subject: [PATCH 01/14] initial --- .../hkmc2/codegen/BlockTransformer.scala | 2 +- .../scala/hkmc2/codegen/BlockTraverser.scala | 1 + .../scala/hkmc2/codegen/HandlerLowering.scala | 67 +++--- .../main/scala/hkmc2/codegen/Lowering.scala | 5 +- .../src/test/mlscript-compile/Runtime.mls | 6 +- hkmc2/shared/src/test/mlscript/HkScratch.mls | 223 ++++++++++++++++++ 6 files changed, 271 insertions(+), 33 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index 3a4fb35ae2..807764b2cb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -75,7 +75,7 @@ class BlockTransformer(subst: SymbolSubst): applyDefn(defn): defn2 => val rst2 = applySubBlock(rst) if (defn2 is defn) && (rst2 is rst) then b else Define(defn2, rst2) - case HandleBlock(l, res, par, args, cls, hdr, bod, rst) => + case h @ HandleBlock(l, res, par, args, cls, hdr, bod, rst) => val l2 = applyLocal(l) val res2 = applyLocal(res) applyPath(par): par2 => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala index 98aada13fd..febede7677 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala @@ -126,6 +126,7 @@ class BlockTraverser: cls.traverse applyPath(path) case Case.Tup(len, inf) => () + case Case.Field(_, _) => () def applyHandler(hdr: Handler): Unit = hdr.sym.traverse diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 761abd1b04..49f437f165 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -145,21 +145,29 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, Some(res, uid) case _ => None - // placeholder for effectful Results, such as Call, Instantiate and anything else that could - // return a continuation + // Placeholder for effectful Results, such as Call, Instantiate and anything else that could + // return a continuation. + // Note that the order of assignments is important! The lifter could insert stuff before a result, + // not after. For example, let r = foo(nestedBar) could be rewritten as + // let nestedBar$ = nestedBar(captures); let r = foo(nestedBar$). + // By ensuring the callSymbol comes *after* the result, the structure of the ResultPlaceholder + // is preserved. object ResultPlaceholder: private val callSymbol = freshTmp("resultPlaceholder") def apply(res: Local, uid: StateId, r: Result, rest: Block) = - Assign( + Assign(res, r, Assign( res, PureCall(Value.Ref(callSymbol), List(Value.Lit(Tree.IntLit(uid)))), - Assign(res, r, rest)) + 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) + res1, c, Assign( + res2, + PureCall(Value.Ref(`callSymbol`, _), List(Value.Lit(Tree.IntLit(uid)))), + rest) + ) if res1 == res2 => + Some(res1, uid, c, rest) case _ => None object StateTransition: @@ -461,11 +469,11 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, /** * The actual translation: - * 1. add call markers, transform class, function, lambda and sub handler blocks - * 2. - * a) generate continuation class - * b) generate normal function body - * 3. float out definitions + * 1. add call markers. rewrite handler blocks in terms of classes and functions + * 2. add debug methods + * 3. class lifter + * 4. state machine transformation of all functions. add unwind and resume state + * generate normal function body */ // callSelf allows the continuation class of the current block (belonging to some function f or class C) @@ -475,7 +483,8 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, val getLocalsFn = createGetLocalsFn(b, extraLocals)(using h) given HandlerCtx = h.nestDebugScope(b.userDefinedVars ++ extraLocals, getLocalsFn.sym.asPath) val stage1 = firstPass(b) - val stage2 = secondPass(stage1, fnOrCls, callSelf, getLocalsFn) + val stage2 = if opt.debug then Define(getLocalsFn, stage1) else stage1 + // val stage2 = secondPass(stage1, fnOrCls, callSelf, getLocalsFn) if h.isTopLevel then stage2 else thirdPass(stage2) private def firstPass(b: Block)(using HandlerCtx): Block = @@ -536,18 +545,18 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, 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 cls = if handlerCtx.isTopLevel then N else genContClass(b, callSelf) - val ret = cls match - case None => genNormalBody(b, BlockMemberSymbol("", Nil), N) - case Some(cls) => + val ret = + if handlerCtx.isTopLevel then genNormalBody(b, N) + else // create the doUnwind function val doUnwindSym = BlockMemberSymbol("doUnwind", Nil, true) doUnwindMap += fnOrCls -> doUnwindSym.asPath val pcSym = VarSymbol(Tree.Ident("pc")) val resSym = VarSymbol(Tree.Ident("res")) val doUnwindBlk = h.linkAndHandle( - LinkState(resSym, cls.sym.asPath, pcSym.asPath) + LinkState(resSym, paths.contClsPath, pcSym.asPath) ) val doUnwindDef = FunDefn( N, doUnwindSym, @@ -555,15 +564,11 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, doUnwindBlk ) val doUnwindLazy = Lazy(doUnwindSym.asPath) - val rst = genNormalBody(b, cls.sym, S(doUnwindLazy)) + val rst = genNormalBody(b, S(doUnwindLazy)) - if doUnwindLazy.isEmpty && opt.stackSafety.isEmpty then - blockBuilder - .define(cls) - .rest(rst) + if doUnwindLazy.isEmpty && opt.stackSafety.isEmpty then rst else blockBuilder - .define(cls) .define(doUnwindDef) .rest(rst) if opt.debug then @@ -644,7 +649,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, ) val handlerMtds = h.handlers.map: handler => - val sym = BlockMemberSymbol("hdlrFun", Nil, true) + val sym = BlockMemberSymbol(h.cls.nme + handler.sym.nme, 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)) @@ -923,12 +928,13 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, N, // TODO: bufferable? )) - private def genNormalBody(b: Block, clsSym: BlockMemberSymbol, doUnwind: Opt[Lazy[Path]])(using HandlerCtx): Block = + // Rewriites ResultPlaceholder. Checks if the result in the placeholder is an effect. + private def genNormalBody(b: Block, doUnwind: Opt[Lazy[Path]])(using HandlerCtx): Block = val transform = new BlockTransformerShallow(SymbolSubst()): override def applyBlock(b: Block): Block = b match case ResultPlaceholder(res, uid, c, rest) => val doUnwindBlk = doUnwind match - case None => Assign(res, topLevelCall(LinkState(res, clsSym.asPath, Value.Lit(Tree.IntLit(uid)))), End()) + case None => Assign(res, topLevelCall(LinkState(res, paths.contClsPath, 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) @@ -941,9 +947,12 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, case _ => super.applyBlock(b) transform.applyBlock(b) + + def translateTopLevel(b: Block): (Block, Map[FnOrCls, Path]) = doUnwindMap = Map.empty val transformed = translateBlock(b, Set.empty, N, L(BlockMemberSymbol("", Nil)), topLevelCtx(s"Cont$$topLevel$$BAD", "‹top level›")) - (transformed, doUnwindMap) + val lifted = Lifter(S(paths)).transform(transformed) + (lifted, doUnwindMap) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 739e9765f4..9762652bb2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -960,8 +960,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx): val flattened = stackSafe.flattened - val lifted = - if lift then Lifter(S(handlerPaths)).transform(flattened) + // the lifter will already be called in the handler lowering + val lifted = + if config.effectHandlers.isEmpty && lift then Lifter(S(handlerPaths)).transform(flattened) else flattened val bufferable = BufferableTransform().transform(lifted) diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index f00d0d458f..85d12a06ed 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -124,7 +124,11 @@ module TraceLogger with object FatalEffect object PrintStackEffect -data abstract class FunctionContFrame(next) with +data class FunctionContFrame(fn, resumeIdx, paramListCnt, next) with + fun resume(value) = + 0 // TODO + +data abstract class FunctionContFrameOld(next) with fun resume(value) fun doUnwind(res1, newPc) = set diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index ef38e8b363..b8571e8884 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -8,5 +8,228 @@ // :d // :todo +class Effect +:effectHandlers +:sjs +fun test(x) = + handle h = Effect with + fun bruh()(k) = + let res = k(2) + print(res) + h.bruh() +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: lookup_! (Scope.scala:112) +//│ FAILURE INFO: Tuple2: +//│ _1 = Tuple2: +//│ _1 = $resultPlaceholder +//│ _2 = class hkmc2.semantics.TempSymbol +//│ _2 = Scope: +//│ parent = S of Scope: +//│ parent = S of Scope: +//│ parent = S of Scope: +//│ parent = N +//│ curThis = S of S of globalThis:globalThis +//│ bindings = HashMap($block$res -> block$res, member:Effect -> Effect1, $runtime -> runtime, class:Effect -> Effect, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, member:Predef -> Predef, $block$res -> block$res1, $Block -> Block, $Shape -> Shape) +//│ curThis = N +//│ bindings = HashMap(class:Handler$h$ -> Handler$h$, member:Handler$h$ -> Handler$h$1, member:Handler$h$bruh$ -> Handler$h$bruh$, member:Handler$h$bruh -> Handler$h$bruh, member:handleBlock$ -> handleBlock$, member:test -> test) +//│ curThis = S of N +//│ bindings = HashMap(Handler$h$$instance -> Handler$h$$instance, k -> k) +//│ curThis = N +//│ bindings = HashMap(res -> res) +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'resultPlaceholder' +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: lookup_! (Scope.scala:112) +//│ FAILURE INFO: Tuple2: +//│ _1 = Tuple2: +//│ _1 = $resultPlaceholder +//│ _2 = class hkmc2.semantics.TempSymbol +//│ _2 = Scope: +//│ parent = S of Scope: +//│ parent = S of Scope: +//│ parent = S of Scope: +//│ parent = N +//│ curThis = S of S of globalThis:globalThis +//│ bindings = HashMap($block$res -> block$res, member:Effect -> Effect1, $runtime -> runtime, class:Effect -> Effect, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, member:Predef -> Predef, $block$res -> block$res1, $Block -> Block, $Shape -> Shape) +//│ curThis = N +//│ bindings = HashMap(class:Handler$h$ -> Handler$h$, member:Handler$h$ -> Handler$h$1, member:Handler$h$bruh$ -> Handler$h$bruh$, member:Handler$h$bruh -> Handler$h$bruh, member:handleBlock$ -> handleBlock$, member:test -> test) +//│ curThis = S of N +//│ bindings = HashMap() +//│ curThis = N +//│ bindings = HashMap($res -> res, h -> h) +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'resultPlaceholder' +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: lookup_! (Scope.scala:112) +//│ FAILURE INFO: Tuple2: +//│ _1 = Tuple2: +//│ _1 = $resultPlaceholder +//│ _2 = class hkmc2.semantics.TempSymbol +//│ _2 = Scope: +//│ parent = S of Scope: +//│ parent = S of Scope: +//│ parent = S of Scope: +//│ parent = N +//│ curThis = S of S of globalThis:globalThis +//│ bindings = HashMap($block$res -> block$res, member:Effect -> Effect1, $runtime -> runtime, class:Effect -> Effect, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, member:Predef -> Predef, $block$res -> block$res1, $Block -> Block, $Shape -> Shape) +//│ curThis = N +//│ bindings = HashMap(class:Handler$h$ -> Handler$h$, member:Handler$h$ -> Handler$h$1, member:Handler$h$bruh$ -> Handler$h$bruh$, member:Handler$h$bruh -> Handler$h$bruh, member:handleBlock$ -> handleBlock$, member:test -> test) +//│ curThis = S of N +//│ bindings = HashMap(x -> x) +//│ curThis = N +//│ bindings = HashMap($tmp -> tmp) +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'resultPlaceholder' +//│ JS (unsanitized): +//│ let test, handleBlock$, Handler$h$bruh, Handler$h$1, Handler$h$bruh$; +//│ Handler$h$bruh$ = function Handler$h$bruh$(Handler$h$$instance, k) { +//│ let res; +//│ res = runtime.safeCall(k(2)); +//│ res = resultPlaceholder(2); +//│ return Predef.print(res) +//│ }; +//│ Handler$h$bruh = function Handler$h$bruh(Handler$h$$instance) { +//│ return (k) => { +//│ return Handler$h$bruh$(Handler$h$$instance, k) +//│ } +//│ }; +//│ globalThis.Object.freeze(class Handler$h$ extends Effect1 { +//│ static { +//│ Handler$h$1 = this +//│ } +//│ constructor() { +//│ let tmp; +//│ tmp = super(); +//│ } +//│ bruh() { +//│ let Handler$h$bruh$here; +//│ Handler$h$bruh$here = runtime.safeCall(Handler$h$bruh(this)); +//│ return runtime.mkEffect(this, Handler$h$bruh$here) +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "Handler$h$"]; +//│ }); +//│ handleBlock$ = function handleBlock$() { +//│ let h, res; +//│ h = new Handler$h$1(); +//│ res = runtime.safeCall(h.bruh()); +//│ res = resultPlaceholder(1); +//│ return res +//│ }; +//│ test = function test(x) { let tmp; tmp = handleBlock$(); tmp = resultPlaceholder(4); return tmp }; +:sjs +data class A(x) +//│ JS (unsanitized): +//│ let A1; +//│ A1 = function A(x) { +//│ return globalThis.Object.freeze(new A.class(x)); +//│ }; +//│ globalThis.Object.freeze(class A { +//│ static { +//│ A1.class = this +//│ } +//│ constructor(x) { +//│ this.x = x; +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "A", ["x"]]; +//│ }); + +:effectHandlers +:sjs +fun test2(x) = + fun bruh() = x + test(bruh) + print("a") +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: lookup_! (Scope.scala:112) +//│ FAILURE INFO: Tuple2: +//│ _1 = Tuple2: +//│ _1 = $resultPlaceholder +//│ _2 = class hkmc2.semantics.TempSymbol +//│ _2 = Scope: +//│ parent = S of Scope: +//│ parent = S of Scope: +//│ parent = S of Scope: +//│ parent = N +//│ curThis = S of S of globalThis:globalThis +//│ bindings = HashMap(class:Handler$h$ -> Handler$h$, member:Handler$h$bruh -> Handler$h$bruh, member:A -> A1, $runtime -> runtime, class:A -> A, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, $block$res -> block$res2, $Block -> Block, $Shape -> Shape, member:Handler$h$ -> Handler$h$1, $block$res -> block$res3, member:Handler$h$bruh$ -> Handler$h$bruh$, $block$res -> block$res, member:Effect -> Effect1, class:Effect -> Effect, member:handleBlock$ -> handleBlock$, member:Predef -> Predef, $block$res -> block$res1, member:test -> test) +//│ curThis = N +//│ bindings = HashMap(member:bruh -> bruh, member:test2 -> test2, member:bruh$ -> bruh$) +//│ curThis = S of N +//│ bindings = HashMap(x -> x) +//│ curThis = N +//│ bindings = HashMap($bruh$here -> bruh$here, $tmp -> tmp) +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'resultPlaceholder' +//│ JS (unsanitized): +//│ let bruh, test2, bruh$; +//│ bruh$ = function bruh$(x) { +//│ return x +//│ }; +//│ bruh = function bruh(x) { +//│ return () => { +//│ return bruh$(x) +//│ } +//│ }; +//│ test2 = function test2(x) { +//│ let tmp, bruh$here; +//│ bruh$here = runtime.safeCall(bruh(x)); +//│ tmp = test(bruh$here); +//│ tmp = resultPlaceholder(1); +//│ return Predef.print("a") +//│ }; + +fun a(x)(y) = x + y + +:sjs +:effectHandlers +fun bruh = a(2)(3) + 1 +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: lookup_! (Scope.scala:112) +//│ FAILURE INFO: Tuple2: +//│ _1 = Tuple2: +//│ _1 = $resultPlaceholder +//│ _2 = class hkmc2.semantics.TempSymbol +//│ _2 = Scope: +//│ parent = S of Scope: +//│ parent = S of Scope: +//│ parent = S of Scope: +//│ parent = N +//│ curThis = S of S of globalThis:globalThis +//│ bindings = HashMap(member:Handler$h$bruh -> Handler$h$bruh, $block$res -> block$res5, $runtime -> runtime, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, $Block -> Block, $Shape -> Shape, member:Handler$h$ -> Handler$h$1, member:Handler$h$bruh$ -> Handler$h$bruh$, $block$res -> block$res, member:Effect -> Effect1, class:Effect -> Effect, member:Predef -> Predef, $block$res -> block$res1, member:test -> test, $block$res -> block$res4, class:Handler$h$ -> Handler$h$, member:A -> A1, class:A -> A, $block$res -> block$res2, $block$res -> block$res3, member:bruh$ -> bruh$, member:bruh -> bruh, member:test2 -> test2, member:handleBlock$ -> handleBlock$, member:a -> a) +//│ curThis = N +//│ bindings = HashMap(member:bruh -> bruh1) +//│ curThis = S of N +//│ bindings = HashMap() +//│ curThis = N +//│ bindings = HashMap($tmp -> tmp, $tmp -> tmp1) +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'resultPlaceholder' +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: lookup_! (Scope.scala:112) +//│ FAILURE INFO: Tuple2: +//│ _1 = Tuple2: +//│ _1 = $resultPlaceholder +//│ _2 = class hkmc2.semantics.TempSymbol +//│ _2 = Scope: +//│ parent = S of Scope: +//│ parent = S of Scope: +//│ parent = S of Scope: +//│ parent = N +//│ curThis = S of S of globalThis:globalThis +//│ bindings = HashMap(member:Handler$h$bruh -> Handler$h$bruh, $block$res -> block$res5, $runtime -> runtime, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, $Block -> Block, $Shape -> Shape, member:Handler$h$ -> Handler$h$1, member:Handler$h$bruh$ -> Handler$h$bruh$, $block$res -> block$res, member:Effect -> Effect1, class:Effect -> Effect, member:Predef -> Predef, $block$res -> block$res1, member:test -> test, $block$res -> block$res4, class:Handler$h$ -> Handler$h$, member:A -> A1, class:A -> A, $block$res -> block$res2, $block$res -> block$res3, member:bruh$ -> bruh$, member:bruh -> bruh, member:test2 -> test2, member:handleBlock$ -> handleBlock$, member:a -> a) +//│ curThis = N +//│ bindings = HashMap(member:bruh -> bruh1) +//│ curThis = S of N +//│ bindings = HashMap() +//│ curThis = N +//│ bindings = HashMap($tmp -> tmp, $tmp -> tmp1) +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'resultPlaceholder' +//│ JS (unsanitized): +//│ let bruh1; +//│ bruh1 = function bruh() { +//│ let tmp, tmp1; +//│ tmp = a(2); +//│ tmp = resultPlaceholder(1); +//│ tmp1 = runtime.safeCall(tmp(3)); +//│ tmp1 = resultPlaceholder(2); +//│ return tmp1 + 1 +//│ }; From afa6cdaffb0960cf2a81ec0c6f858224f250427c Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Mon, 17 Nov 2025 01:15:44 +0800 Subject: [PATCH 02/14] Est. 50% done --- .../scala/hkmc2/codegen/HandlerLowering.scala | 583 ++++++++---------- .../main/scala/hkmc2/codegen/Lowering.scala | 22 +- .../src/test/mlscript-compile/Runtime.mls | 3 + 3 files changed, 287 insertions(+), 321 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 49f437f165..1b0908de1a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -13,6 +13,8 @@ import semantics.* import semantics.Elaborator.ctx import semantics.Elaborator.State import hkmc2.Config.EffectHandlers +import scala.collection.mutable +import scala.util.boundary object HandlerLowering: @@ -35,6 +37,7 @@ object HandlerLowering: type FnOrCls = Either[BlockMemberSymbol, DefinitionSymbol[? <: ClassLikeDef] & InnerSymbol] + // TODO: Fix these comments // 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) @@ -44,15 +47,13 @@ object HandlerLowering: // 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], + currentFun: Opt[Path], + thisPath: 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))) + def isTopLevel = currentFun.isEmpty // inScopeLocals: Local variables that are in scope. // prevLocalsFn: The function that gets the outer function's locals. @@ -78,36 +79,27 @@ class HandlerPaths(using Elaborator.State): val handleBlockImplPath: Path = runtimePath.selSN("handleBlockImpl") val stackDelayClsPath: Path = runtimePath.selSN("StackDelay") val topLevelEffectPath: Path = runtimePath.selSN("topLevelEffect") + 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") def isHandlerClsPath(p: Path) = (p eq contClsPath) || (p eq stackDelayClsPath) || (p eq effectSigPath) 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 funcLikeHandlerCtx(funcPath: Path, thisPath: Option[Path], debugNme: Str)(using h: HandlerCtx) = + HandlerCtx(S(funcPath), thisPath, h.debugInfo.copy(debugNme)) + + private def functionHandlerCtx(funcPath: Path, debugNme: Str)(using HandlerCtx) = funcLikeHandlerCtx(funcPath, N, debugNme) private def topLevelCall(state: LinkState) = Call( paths.topLevelEffectPath, state.res.asPath.asArg :: Value.Lit(Tree.BoolLit(opt.debug)).asArg :: Nil )(true, false) - private def topLevelCtx(nme: Str, debugNme: Str) = HandlerCtx( - true, false, nme, N, DebugInfo.topLevel(debugNme), - state => Assign( - state.res, - topLevelCall(state), - End()) - ) - private def ctorCtx(ctorThis: Path, nme: Str, debugNme: Str)(using HandlerCtx) = funcLikeHandlerCtx(S(ctorThis), false, nme, debugNme) - private def handlerMtdCtx(nme: Str, debugNme: Str)(using HandlerCtx) = funcLikeHandlerCtx(N, true, nme, debugNme) - private def handlerCtx(using HandlerCtx): HandlerCtx = summon + private def topLevelCtx(nme: Str, debugNme: Str) = HandlerCtx(N, N, DebugInfo.topLevel(debugNme)) + private def ctorCtx(bms: BlockMemberSymbol, cls: ClassSymbol, debugNme: Str)(using HandlerCtx) = funcLikeHandlerCtx(Value.Ref(bms, S(cls)), S(Value.Ref(cls)), debugNme) private def freshTmp(dbgNme: Str = "tmp") = new TempSymbol(N, dbgNme) @@ -127,15 +119,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) = @@ -145,31 +128,6 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, Some(res, uid) case _ => None - // Placeholder for effectful Results, such as Call, Instantiate and anything else that could - // return a continuation. - // Note that the order of assignments is important! The lifter could insert stuff before a result, - // not after. For example, let r = foo(nestedBar) could be rewritten as - // let nestedBar$ = nestedBar(captures); let r = foo(nestedBar$). - // By ensuring the callSymbol comes *after* the result, the structure of the ResultPlaceholder - // is preserved. - object ResultPlaceholder: - private val callSymbol = freshTmp("resultPlaceholder") - def apply(res: Local, uid: StateId, r: Result, rest: Block) = - Assign(res, r, Assign( - res, - PureCall(Value.Ref(callSymbol), List(Value.Lit(Tree.IntLit(uid)))), - rest) - ) - def unapply(blk: Block) = blk match - case Assign( - res1, c, Assign( - res2, - PureCall(Value.Ref(`callSymbol`, _), List(Value.Lit(Tree.IntLit(uid)))), - rest) - ) if res1 == res2 => - Some(res1, uid, c, rest) - case _ => None - object StateTransition: private val transitionSymbol = freshTmp("transition") def apply(uid: StateId) = @@ -179,13 +137,6 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, S(uid) case _ => N - object FnEnd: - private val fnEndSymbol = freshTmp("fnEnd") - def apply() = Return(PureCall(Value.Ref(fnEndSymbol), Nil), 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 @@ -195,10 +146,10 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, 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]) + case class BlockPartition(blk: Block, sym: Opt[Local]) + type PartitionedBlock = Map[StateId, BlockPartition] // 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. @@ -267,38 +218,42 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, (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 - - 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 + // removes states that are not reachable from any resumption point (no longer in use as we always include everything) + // def removeUselessStates(states: PartitionedBlock): PartitionedBlock = + // def findEdges(part: BlockPartition) = + // val edges: mutable.Set[StateId] = mutable.Set.empty + // new BlockTraverser: + // applyBlock(part.blk) + // override def applyBlock(b: Block): Unit = b match + // case StateTransition(id) => edges += id + // case _ => super.applyBlock(b) + // edges + // // build edges + // val edges = states.map((id, part) => id -> findEdges(part)) + + // val visited: mutable.Set[StateId] = mutable.Set.empty + // val remaining: mutable.Set[StateId] = mutable.Set.from(states.flatMap(part => part._2.sym.fold(N)(_ => S(part._1)))) + + // 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._1)) + + object EffectfulResult: + def unapply(r: Result) = r match + case c: Call if c.mayRaiseEffects => S(r) + case _: Instantiate => S(r) + case _ => N + + def partitionBlock(blk: Block): PartitionedBlock = + val result = mutable.HashMap.empty[StateId, BlockPartition] + val freshId = FreshId() // * returns (truncated input block, child block states) // * blk: The block to transform @@ -306,62 +261,65 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, // * afterEnd: what state End should jump to, if at all // TODO: don't split within Match, Begin and Labels when not needed, ideally keep it intact. // Need careful analysis for this. - def go(blk: Block)(implicit labelIds: Map[Symbol, (StateId, StateId)], afterEnd: Option[StateId]): PartRet = + def go(blk: Block)(using labelIds: Map[Symbol, (StateId, StateId)], afterEnd: Option[StateId]): Block = boundary: + // First check if the current block contain any non trivial call, if so we need a partition + + // sym: the local that stores the result + def doNewPartition(sym: Local, res: Result, rst: Block) = + val stateId = freshId() + result(stateId) = BlockPartition(go(rst), S(sym)) + val newBlock = blockBuilder + .assign(sym, res) + .ifthen( + sym.asPath, + Case.Cls(paths.effectSigSym, paths.effectSigPath), + // TODO: ReturnCont may be unnecessary if we pass down related variables information to here. + ReturnCont(sym, stateId) + ) + .rest(StateTransition(stateId)) + boundary.break(newBlock) + val nonTrivialBlockChecker = new BlockDataTraverser(): + override def applyBlock(b: Block) = b match + // Special handling for tail calls + case Return(c @ Call(fun, args), false) => () // Prevents the recursion into applyResult + case Assign(lhs, EffectfulResult(r), rest) => + // Optimization to reuse lhs instead of fresh local + doNewPartition(lhs, r, rest) + override def applyResult(r: Result)(k: Result => Block) = r match + case EffectfulResult(r) => + // Fallback case, this may lead to unnecessary vars if it is assign-like + // FIXME: This fall back case might be not needed at all. + val l = freshTmp() + doNewPartition(l, r, k(Value.Ref(l))) + + // 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 Match(scrut, arms, dflt, rest) => + val newRest = go(rest) + val restId: StateId = newRest match + case StateTransition(uid) => uid case _ => - PartRet( - Match(scrut, newArms, dfltParts.map(_.head), StateTransition(restId)), - BlockState(restId, restParts.head, N) :: states - ) - case l @ Label(label, loop, body, rest) => + val id = freshId() + result(id) = BlockPartition(newRest, N) + id + 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, StateTransition(restId)) + + case 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 - ) + val newRest = go(rest) + val endId: StateId = newRest match // start of rest + case StateTransition(uid) => uid case _ => - PartRet( - StateTransition(startId), - BlockState(startId, bodyNew, N) :: BlockState(endId, restNew, N) :: parts ::: restParts - ) + val id = freshId() + result(id) = BlockPartition(newRest, N) + id + val newBody = go(body)(using labelIds + (label -> (startId, endId)), S(endId)) + StateTransition(startId) case Break(label) => val (start, end) = labelIds.get(label) match @@ -369,9 +327,9 @@ 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) + StateTransition(end) case Continue(label) => val (start, end) = labelIds.get(label) match @@ -379,68 +337,57 @@ 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) + StateTransition(start) - // for some reason, blocks sometimes start with Begin(End, ...) + // An optimization to omit useless state 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 Begin(sub, rest) => + val newRest = go(rest) + newRest match + case StateTransition(uid) => go(sub)(using afterEnd = S(uid)) 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) + val id = freshId() + result(id) = BlockPartition(newRest, N) + go(sub)(using afterEnd = S(id)) + + case End(_) => afterEnd match + case None => blk + case Some(id) => StateTransition(id) + + // 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. + // If the constructor is non-trivial, we will append `return thisVar;` afterwards. + // Erasing the implicit return early here is sound since trivial constructor will + // do the trivial transformation instead of continuing the instrumentation. + case Return(_, true) => afterEnd match + case None => End() + case Some(id) => StateTransition(id) // identity cases - case Assign(lhs, rhs, rest) => - val PartRet(head, parts) = go(rest) - PartRet(Assign(lhs, rhs, head), parts) - case blk @ AssignField(lhs, nme, rhs, rest) => - val PartRet(head, parts) = go(rest) - PartRet(AssignField(lhs, nme, rhs, head)(blk.symbol), parts) - - case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => - val PartRet(head, parts) = go(rest) - PartRet(AssignDynField(lhs, fld, arrayIdx, rhs, head), parts) - - case Return(_, _) => PartRet(blk, Nil) + case Define(defn, rest) => Define(defn, go(rest)) + case Assign(lhs, rhs, rest) => Assign(lhs, rhs, go(rest)) + case blk @ AssignField(lhs, nme, rhs, rest) => AssignField(lhs, nme, rhs, go(rest))(blk.symbol) + case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => AssignDynField(lhs, fld, arrayIdx, rhs, go(rest)) + case _: Return => blk // ignored cases case TryBlock(sub, finallyDo, rest) => ??? // ignore - case Throw(_) => PartRet(blk, Nil) + case Throw(_) => blk case _: HandleBlock => lastWords("unexpected handleBlock") // already translated at this point - val PartRet(head, states) = go(blk)(using labelIds, N) - - if inclEntryPoint then BlockState(0, head, N) :: states - else removeUselessStates(states, resumptionPoints) + val initId = freshId() + result(initId) = BlockPartition(go(blk)(using Map(), N), N) + Map.from(result) - 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) = + // extraLocals is used for things like immutable parameters, they are not mutated but they should still be added as locals for debugging + private def createGetLocalsFn(b: Block, extraLocals: Set[Local] = Set.empty)(using h: HandlerCtx) = val locals = (b.userDefinedVars ++ extraLocals) -- h.debugInfo.inScopeLocals val localsInfo = locals.toList.sortBy(_.uid).map: s => - FlowSymbol(s.nme) -> Instantiate(mut = true, localVarInfoPath, + FlowSymbol(s.nme) -> Instantiate(mut = true, paths.localVarInfoPath, Value.Lit(Tree.StrLit(s.nme)).asArg :: s.asPath.asArg :: Nil ) val startSym = FlowSymbol("prev") @@ -455,7 +402,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, .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, + .assign(thisInfo, Instantiate(mut = true, paths.fnLocalsPath, Value.Lit(Tree.StrLit(h.debugInfo.debugNme)).asArg :: Value.Ref(arrSym).asArg :: Nil @@ -476,73 +423,103 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, * generate normal function body */ - // callSelf allows the continuation class of the current block (belonging to some function f or class C) - // to call itself f or C in state 0 in case a stack delay effect was raised, which saves us from duplicating - // all the code in the first state - private def translateBlock(b: Block, extraLocals: Set[Local], callSelf: Opt[Result], fnOrCls: FnOrCls, h: HandlerCtx): Block = - val getLocalsFn = createGetLocalsFn(b, extraLocals)(using h) - given HandlerCtx = h.nestDebugScope(b.userDefinedVars ++ extraLocals, getLocalsFn.sym.asPath) + + private def translateBlock(blk: Block, h: HandlerCtx): Block = + val subblockTransform = new BlockTransformer(SymbolSubst()): + override def applyFunDefn(fun: FunDefn): FunDefn = + val bod2 = translateBlock(fun.body, functionHandlerCtx(Value.Ref(fun.sym, N), fun.sym.nme)) + if fun.body is bod2 then fun else + FunDefn(fun.owner, fun.sym, fun.params, bod2) + override def applyDefn(defn: Defn)(k: Defn => Block): Block = defn match + case ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, methods, privateFields, publicFields, preCtor, ctor, companion, bufferable) => + val newMtds = methods.map: f => + val bod2 = translateBlock(f.body, funcLikeHandlerCtx(Value.Ref(isym).sel(new Tree.Ident(f.sym.nme), f.sym.asTrm.get), S(Value.Ref(isym)), s"${sym.nme}.${f.sym.nme}")) + if f.body is bod2 then f else + FunDefn(f.owner, f.sym, f.params, bod2) + val newCtor = translateBlock(Begin(preCtor, ctor), ctorCtx(sym, sym.asCls.get, s"‹constructor of ${sym.nme}›")) + k(ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, newMtds, privateFields, publicFields, End(), newCtor, companion, bufferable)) + case _ => super.applyDefn(defn)(k) + val b = subblockTransform.applyBlock(blk) + + val getLocalsFn = createGetLocalsFn(b)(using h) + // All the defined variables are masked away from the inner scope (TODO: Scoped) + given HandlerCtx = h.nestDebugScope(b.definedVars, getLocalsFn.sym.asPath) + if h.isTopLevel then + return translateTrivialOrTopLevel(b) + val parts = partitionBlock(b) + if parts.size <= 1 then + return translateTrivialOrTopLevel(b) + val stage1 = firstPass(b) + val stage1 = tl.trace("=>" + b.showAsTree): + val t = firstPass(b) + tl.log("=<" + t.showAsTree) + t val stage2 = if opt.debug then Define(getLocalsFn, stage1) else stage1 // 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 - override def applyBlock(b: Block) = b match - case b: HandleBlock => - val rest = applyBlock(b.rest) - translateHandleBlock(b.copy(rest = rest)) - // This block optimizes tail-calls in the handler transformation. We do not optimize implicit returns. - // Implicit returns are used in top level and constructor: - // For top level, this correspond to the last statement which should also be checked for effect. - // For constructor, we will append `return this;` after the implicit return so it is not a tail call. - case Return(c @ Call(fun, args), false) if !handlerCtx.isHandlerBody => - applyPath(fun): fun2 => - applyArgs(args): args2 => - val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun, c.mayRaiseEffects) - if c2 is c then b else Return(c2, false) - // Optimization to avoid generation of unnecessary variables - case Assign(lhs, c @ Call(fun, args), rest) if c.mayRaiseEffects => - applyPath(fun): fun2 => - applyArgs(args): args2 => - val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun, c.mayRaiseEffects) - ResultPlaceholder(lhs, freshId(), c2, applyBlock(rest)) - case Assign(lhs, c @ Instantiate(mut, cls, args), rest) => - applyPath(cls): cls2 => - applyArgs(args): args2 => - val c2 = if (cls2 is cls) && (args2 is args) then c else Instantiate(mut, cls2, args2) - ResultPlaceholder(lhs, freshId(), c2, applyBlock(rest)) - case _ => super.applyBlock(b) - override def applyResult(r: Result)(k: Result => Block): Block = r match - case c @ Call(fun, args) if c.mayRaiseEffects => - val res = freshTmp("res") - applyPath(fun): fun2 => - applyArgs(args): args2 => - val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun, c.mayRaiseEffects) - ResultPlaceholder(res, freshId(), c2, k(Value.Ref(res))) - case c @ Instantiate(mut, cls, args) => - val res = freshTmp("res") - applyPath(cls): cls2 => - applyArgs(args): args2 => - val c2 = if (cls2 is cls) && (args2 is args) then c else Instantiate(mut, cls2, args2) - ResultPlaceholder(res, freshId(), c2, k(Value.Ref(res))) - case r => super.applyResult(r)(k) - override def applyPath(p: Path)(k: Path => Block): Block = p match - case Value.Ref(`getLocalsSym`, _) => k(handlerCtx.debugInfo.prevLocalsFn.get) - case _ => super.applyPath(p)(k) - override def applyLam(lam: Lambda): Lambda = - // This should normally be unreachable due to prior desugaring of lambda - raise(InternalError(msg"Unexpected lambda during handler lowering" -> lam.toLoc :: Nil, - source = Diagnostic.Source.Compilation)) - Lambda(lam.params, translateBlock(lam.body, lam.params.paramSyms.toSet, N, L(BlockMemberSymbol("", Nil, false)), functionHandlerCtx(s"Cont$$lambda$$", "‹lambda›"))) - override def applyDefn(defn: Defn)(k: Defn => Block): Block = defn match - case f: FunDefn => k(translateFun(f)) - case c: ClsLikeDefn => k(translateCls(c)) - case _: ValDefn => super.applyDefn(defn)(k) - transformer.applyBlock(b) + private def translateTrivialOrTopLevel(b: Block)(using HandlerCtx): Block = + // We shall add back the top level effect checks here + // If said block is trivial, this function will still add the debug information, for in the case where the error + // is raised in a tail call. + b + + // 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 + // override def applyBlock(b: Block) = b match + // case b: HandleBlock => + // die + // // This block optimizes tail-calls in the handler transformation. We do not optimize implicit returns. + // // Implicit returns are used in top level and constructor: + // // For top level, this correspond to the last statement which should also be checked for effect. + // // For constructor, we will append `return this;` after the implicit return so it is not a tail call. + // case Return(c @ Call(fun, args), false) if !handlerCtx.isHandlerBody => + // applyPath(fun): fun2 => + // applyArgs(args): args2 => + // val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun, c.mayRaiseEffects) + // if c2 is c then b else Return(c2, false) + // // Optimization to avoid generation of unnecessary variables + // case Assign(lhs, c @ Call(fun, args), rest) if c.mayRaiseEffects => + // applyPath(fun): fun2 => + // applyArgs(args): args2 => + // val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun, c.mayRaiseEffects) + // ResultPlaceholder(lhs, freshId(), c2, applyBlock(rest)) + // case Assign(lhs, c @ Instantiate(mut, cls, args), rest) => + // applyPath(cls): cls2 => + // applyArgs(args): args2 => + // val c2 = if (cls2 is cls) && (args2 is args) then c else Instantiate(mut, cls2, args2) + // ResultPlaceholder(lhs, freshId(), c2, applyBlock(rest)) + // case _ => super.applyBlock(b) + // override def applyResult(r: Result)(k: Result => Block): Block = r match + // case c @ Call(fun, args) if c.mayRaiseEffects => + // val res = freshTmp("res") + // applyPath(fun): fun2 => + // applyArgs(args): args2 => + // val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun, c.mayRaiseEffects) + // ResultPlaceholder(res, freshId(), c2, k(Value.Ref(res))) + // case c @ Instantiate(mut, cls, args) => + // val res = freshTmp("res") + // applyPath(cls): cls2 => + // applyArgs(args): args2 => + // val c2 = if (cls2 is cls) && (args2 is args) then c else Instantiate(mut, cls2, args2) + // ResultPlaceholder(res, freshId(), c2, k(Value.Ref(res))) + // case r => super.applyResult(r)(k) + // override def applyPath(p: Path)(k: Path => Block): Block = p match + // case Value.Ref(`getLocalsSym`, _) => k(handlerCtx.debugInfo.prevLocalsFn.get) // TODO: Port debug to new transformation + // 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) @@ -630,32 +607,19 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, 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 lbl = freshTmp("handlerBody") - val lblLoop = freshTmp("handlerLoop") - - val handlerBody = translateBlock( - h.body, Set.empty, S(Call(sym.asPath, Nil)(true, false)), L(sym), - HandlerCtx( - false, true, - s"Cont$$handleBlock$$${symToStr(h.lhs)}$$", N, - handlerCtx.debugInfo.copy(debugNme = s"‹handler body of ${h.lhs.nme}›"), - state => blockBuilder - .assignFieldN(state.res.asPath.contTrace.last, nextIdent, Instantiate(true, state.cls, state.uid.asArg :: Nil)) - .ret(PureCall(paths.handleBlockImplPath, state.res.asPath :: h.lhs.asPath :: Nil)) - ) - ) + // Handle block is rewritten into: + // 1. Instantiation of the handler + // 2. An effectful call to enterHandleBlock + private def translateHandleBlockShallow(h: HandleBlock): Block = + val sym = new BlockMemberSymbol("handleBlock$", Nil, false) + + val bodyDefn = FunDefn(N, sym, PlainParamList(Nil) :: Nil, h.body) val handlerMtds = h.handlers.map: handler => val sym = BlockMemberSymbol(h.cls.nme + handler.sym.nme, 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 fDef = FunDefn( N, sym, PlainParamList(Param(FldFlags.empty, handler.resumeSym, N, Modulefulness.none) :: Nil) :: Nil, - mtdBdy + handler.body ) FunDefn( S(h.cls), @@ -663,7 +627,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, Define( fDef, Return(PureCall(paths.mkEffectPath, h.cls.asPath :: Value.Ref(sym, N) :: Nil), false))) - + // Some limited handling of effects extending classes and having access to their fields. // Currently does not support super() raising effects. val tmp = freshTmp() @@ -671,10 +635,6 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, .assign(tmp, Call(Value.Ref(State.builtinOpsMap("super")), h.args.map(_.asArg))(true, true)) .ret(h.cls.asPath) - val ctorT = translateBlock(ctor, Set.empty, N, R(h.cls), // TODO: callSelf - ctorCtx(h.cls.asPath, s"Cont$$ctor$$${symToStr(sym)}$$", s"‹constructor of ${sym.nme}›") - ) - val clsDefn = ClsLikeDefn( N, // no owner h.cls, @@ -682,29 +642,31 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, syntax.Cls, N, Nil, S(h.par), handlerMtds, Nil, Nil, - Assign(freshTmp(), Call(Value.Ref(State.builtinOpsMap("super")), h.args.map(_.asArg))(true, true), End()), End(), + Assign(freshTmp(), Call(Value.Ref(State.builtinOpsMap("super")), h.args.map(_.asArg))(true, true), End()), N, - N, // TODO: bufferable? - ) // TODO: handle effect in super call - // NOTE: the super call is inside the preCtor - // during resumption we need to resume both the this.x = x bindings done in JSBuilder and the ctor - - val body = blockBuilder + N, + ) + + blockBuilder .define(clsDefn) .assign(h.lhs, Instantiate(mut = true, Value.Ref(clsDefn.sym, S(h.cls)), Nil)) - .rest(handlerBody) - - val defn = FunDefn( - N, // no owner - sym, PlainParamList(Nil) :: Nil, body) - - val result = blockBuilder - .define(defn) - .rest( - ResultPlaceholder(h.res, freshId(), Call(sym.asPath, Nil)(true, true), h.rest) - ) - result + .define(bodyDefn) + .assign(h.res, Call(paths.enterHandleBlockPath, List(h.lhs.asPath.asArg, Value.Ref(sym, N).asArg))(true, true)) + .rest(h.rest) + + def translateHandleBlocks(b: Block): Block = + + val transform = new BlockTransformer(SymbolSubst()): + override def applyBlock(b: Block) = b match + case HandleBlock(lhs, res, par, args, cls, hdr, bod, rst) => + val hdr2 = hdr.map(applyHandler) + val bod2 = applyBlock(bod) + val rst2 = applyBlock(rst) + translateHandleBlockShallow(HandleBlock(lhs, res, par, args, cls, hdr2, bod2, rst2)) + case _ => super.applyBlock(b) + transform.applyBlock(b) + private def genContClass(b: Block, callSelf: Opt[Result])(using h: HandlerCtx): Opt[ClsLikeDefn] = val clsSym = ClassSymbol( @@ -788,7 +750,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, case None => partitionBlock(actualBlock, true) case Some(value) => val someParts = partitionBlock(actualBlock, false) - BlockState(0, Return(value, false), N) :: someParts + BlockPartition(0, Return(value, false), N) :: someParts else partitionBlock(actualBlock, false) @@ -952,7 +914,6 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, def translateTopLevel(b: Block): (Block, Map[FnOrCls, Path]) = doUnwindMap = Map.empty - val transformed = translateBlock(b, Set.empty, N, L(BlockMemberSymbol("", Nil)), topLevelCtx(s"Cont$$topLevel$$BAD", "‹top level›")) - val lifted = Lifter(S(paths)).transform(transformed) - (lifted, doUnwindMap) + val transformed = translateBlock(b, topLevelCtx(s"Cont$$topLevel$$BAD", "‹top level›")) + (transformed, doUnwindMap) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 9762652bb2..1edc6c7142 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -950,20 +950,22 @@ 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 lifted = + if config.effectHandlers.isEmpty && lift then Lifter(S(handlerPaths)).transform(withHandlers1) + else withHandlers1 - val (withHandlers, doUnwindPaths) = config.effectHandlers.fold((desug, Map.empty)): opt => - HandlerLowering(handlerPaths, opt).translateTopLevel(desug) + 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 - - // the lifter will already be called in the handler lowering - val lifted = - if config.effectHandlers.isEmpty && lift then Lifter(S(handlerPaths)).transform(flattened) - else flattened + // val flattened = lifted.flattened val bufferable = BufferableTransform().transform(lifted) diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index 85d12a06ed..873b32f4de 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -121,6 +121,9 @@ module TraceLogger with // Private definitions for algebraic effects +// layout: +// | pc | isCtor | this | paramCnt | params... | localCnt | locals... | + object FatalEffect object PrintStackEffect From 13615fef6fd60a2e570109c823fcf2759f4c1b53 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 18 Nov 2025 17:48:17 +0800 Subject: [PATCH 03/14] Compilable version --- .../scala/hkmc2/codegen/HandlerLowering.scala | 788 +++++++++--------- .../main/scala/hkmc2/codegen/Lowering.scala | 6 +- .../src/test/mlscript-compile/Runtime.mjs | 78 +- .../src/test/mlscript-compile/Runtime.mls | 31 +- hkmc2/shared/src/test/mlscript/HkScratch.mls | 226 ----- 5 files changed, 523 insertions(+), 606 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 1b0908de1a..6088f5dbcf 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -49,22 +49,23 @@ object HandlerLowering: private case class HandlerCtx( currentFun: Opt[Path], thisPath: Option[Path], + plCnt: Int, + currentLocals: List[Local], debugInfo: DebugInfo, ): - def nestDebugScope(locals: Set[Local], localsFn: Path) = copy(debugInfo = debugInfo.copy(inScopeLocals = - debugInfo.inScopeLocals ++ locals, prevLocalsFn = S(localsFn))) def isTopLevel = currentFun.isEmpty - // inScopeLocals: Local variables that are in scope. + // inScopeLocals: Local variables that are in scope, including those that come from outer. // prevLocalsFn: The function that gets the outer function's locals. private case class DebugInfo( debugNme: Str, inScopeLocals: Set[Local], - prevLocalsFn: Opt[Path], - ) + ): + def nest(debugNme: Str, locals: Set[Local]) = copy( + debugNme = debugNme, inScopeLocals = inScopeLocals ++ locals) private object DebugInfo: - def topLevel(debugNme: Str) = DebugInfo(debugNme, Set.empty, N) + def topLevel(debugNme: Str, locals: Set[Local]) = DebugInfo(debugNme, Set.empty) type StateId = BigInt @@ -84,24 +85,25 @@ class HandlerPaths(using Elaborator.State): 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") - def isHandlerClsPath(p: Path) = - (p eq contClsPath) || (p eq stackDelayClsPath) || (p eq effectSigPath) + def isHandlerClsPath(p: Path) = false class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, Elaborator.State, Elaborator.Ctx): - private def funcLikeHandlerCtx(funcPath: Path, thisPath: Option[Path], debugNme: Str)(using h: HandlerCtx) = - HandlerCtx(S(funcPath), thisPath, h.debugInfo.copy(debugNme)) + private def funcLikeHandlerCtx(funcPath: Path, thisPath: Option[Path], debugNme: Str, plCnt: Int, curLocals: Set[Local])(using h: HandlerCtx) = + HandlerCtx(S(funcPath), thisPath, plCnt, (curLocals -- h.debugInfo.inScopeLocals).toList, h.debugInfo.nest(debugNme, curLocals)) - private def functionHandlerCtx(funcPath: Path, debugNme: Str)(using HandlerCtx) = funcLikeHandlerCtx(funcPath, N, debugNme) - private def topLevelCall(state: LinkState) = Call( - paths.topLevelEffectPath, - state.res.asPath.asArg :: Value.Lit(Tree.BoolLit(opt.debug)).asArg :: Nil - )(true, false) - private def topLevelCtx(nme: Str, debugNme: Str) = HandlerCtx(N, N, DebugInfo.topLevel(debugNme)) - private def ctorCtx(bms: BlockMemberSymbol, cls: ClassSymbol, debugNme: Str)(using HandlerCtx) = funcLikeHandlerCtx(Value.Ref(bms, S(cls)), S(Value.Ref(cls)), debugNme) + private def functionHandlerCtx(funcPath: Path, debugNme: Str, plCnt: Int, curLocals: Set[Local])(using HandlerCtx) = funcLikeHandlerCtx(funcPath, N, debugNme, plCnt, curLocals) + private def topLevelCtx(nme: Str, debugNme: Str, curLocals: Set[Local]) = + HandlerCtx(N, N, 0, curLocals.toList, DebugInfo.topLevel(debugNme, curLocals)) + private def ctorCtx(bms: BlockMemberSymbol, cls: ClassSymbol, debugNme: Str, plCnt: Int, curLocals: Set[Local])(using HandlerCtx) = + funcLikeHandlerCtx(Value.Ref(bms, S(cls)), S(Value.Ref(cls)), debugNme, 0, curLocals) private def freshTmp(dbgNme: Str = "tmp") = new TempSymbol(N, dbgNme) + private def intLit(i: BigInt) = Value.Lit(Tree.IntLit(i)) + private def unit = Value.Lit(Tree.UnitLit(true)) private def rtThrowMsg(msg: Str) = Throw( Instantiate(mut = false, State.globalThisSymbol.asPath.selN(Tree.Ident("Error")), @@ -119,15 +121,6 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, .map((fun, _)) case _ => N - 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 - object StateTransition: private val transitionSymbol = freshTmp("transition") def apply(uid: StateId) = @@ -149,7 +142,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, // blk: the block of code within this state // sym: the variable to which the resumed value should set case class BlockPartition(blk: Block, sym: Opt[Local]) - type PartitionedBlock = Map[StateId, BlockPartition] + case class PartitionedBlock(entry: StateId, states: Map[StateId, BlockPartition]) // 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. @@ -251,7 +244,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, case _: Instantiate => S(r) case _ => N - def partitionBlock(blk: Block): PartitionedBlock = + private def partitionBlock(blk: Block)(using h: HandlerCtx): PartitionedBlock = val result = mutable.HashMap.empty[StateId, BlockPartition] val freshId = FreshId() @@ -273,24 +266,33 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, .ifthen( sym.asPath, Case.Cls(paths.effectSigSym, paths.effectSigPath), - // TODO: ReturnCont may be unnecessary if we pass down related variables information to here. - ReturnCont(sym, stateId) + Return(Call(paths.unwindPath, ( + sym.asPath :: + intLit(h.plCnt) :: + h.currentFun.get :: + intLit(stateId) :: + h.thisPath.getOrElse(unit) :: + intLit(h.currentLocals.length) :: + h.currentLocals.map(_.asPath) + ).map(_.asArg))(true, true), false) ) .rest(StateTransition(stateId)) boundary.break(newBlock) - val nonTrivialBlockChecker = new BlockDataTraverser(): + val nonTrivialBlockChecker = new BlockDataTransformer(SymbolSubst()): override def applyBlock(b: Block) = b match // Special handling for tail calls - case Return(c @ Call(fun, args), false) => () // Prevents the recursion into applyResult + case Return(c @ Call(fun, args), false) => b // Prevents the recursion into applyResult case Assign(lhs, EffectfulResult(r), rest) => // Optimization to reuse lhs instead of fresh local doNewPartition(lhs, r, rest) + case _ => super.applyBlock(b) override def applyResult(r: Result)(k: Result => Block) = r match case EffectfulResult(r) => // Fallback case, this may lead to unnecessary vars if it is assign-like // FIXME: This fall back case might be not needed at all. val l = freshTmp() doNewPartition(l, r, k(Value.Ref(l))) + case _ => super.applyResult(r)(k) // If current block contains direct effectful result the following call will early exit. nonTrivialBlockChecker.applyBlock(blk) @@ -381,36 +383,36 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, val initId = freshId() result(initId) = BlockPartition(go(blk)(using Map(), N), N) - Map.from(result) + PartitionedBlock(initId, Map.from(result)) // extraLocals is used for things like immutable parameters, they are not mutated but they should still be added as locals for debugging - private def createGetLocalsFn(b: Block, extraLocals: Set[Local] = Set.empty)(using h: HandlerCtx) = - val locals = (b.userDefinedVars ++ extraLocals) -- h.debugInfo.inScopeLocals - val localsInfo = locals.toList.sortBy(_.uid).map: s => - FlowSymbol(s.nme) -> Instantiate(mut = true, paths.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") + // private def createGetLocalsFn(b: Block, extraLocals: Set[Local] = Set.empty)(using h: HandlerCtx) = + // val locals = (b.userDefinedVars ++ extraLocals) -- h.debugInfo.inScopeLocals + // val localsInfo = locals.toList.sortBy(_.uid).map: s => + // FlowSymbol(s.nme) -> Instantiate(mut = true, paths.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, paths.fnLocalsPath, - Value.Lit(Tree.StrLit(h.debugInfo.debugNme)).asArg - :: Value.Ref(arrSym).asArg - :: Nil - )) - .assign(TempSymbol(N, ""), Call(startSym.asPath.selSN("push"), thisInfo.asPath.asArg :: Nil)(false, false)) - .ret(startSym.asPath) - - FunDefn(N, BlockMemberSymbol("getLocals", Nil), PlainParamList(Nil) :: Nil, body) + // 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, paths.fnLocalsPath, + // Value.Lit(Tree.StrLit(h.debugInfo.debugNme)).asArg + // :: Value.Ref(arrSym).asArg + // :: Nil + // )) + // .assign(TempSymbol(N, ""), Call(startSym.asPath.selSN("push"), thisInfo.asPath.asArg :: Nil)(false, false)) + // .ret(startSym.asPath) + + // FunDefn(N, BlockMemberSymbol("getLocals", Nil), PlainParamList(Nil) :: Nil, body) var doUnwindMap: Map[FnOrCls, Path] = Map.empty @@ -425,39 +427,82 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, private def translateBlock(blk: Block, h: HandlerCtx): Block = + // TODO: add getLocal, similar mechanism as determine resumption. + // All the defined variables are masked away from the inner scope (TODO: Scoped) + given HandlerCtx = h + val subblockTransform = new BlockTransformer(SymbolSubst()): override def applyFunDefn(fun: FunDefn): FunDefn = - val bod2 = translateBlock(fun.body, functionHandlerCtx(Value.Ref(fun.sym, N), fun.sym.nme)) + if !h.isTopLevel then + raise(WarningReport(msg"Unexpected nested function: lambdas may not function correctly." -> fun.sym.toLoc :: Nil, source = Diagnostic.Source.Compilation)) + val bod2 = translateBlock(fun.body, functionHandlerCtx(Value.Ref(fun.sym, N), fun.sym.nme, fun.params.length, fun.body.definedVars)) if fun.body is bod2 then fun else FunDefn(fun.owner, fun.sym, fun.params, bod2) override def applyDefn(defn: Defn)(k: Defn => Block): Block = defn match case ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, methods, privateFields, publicFields, preCtor, ctor, companion, bufferable) => + if !h.isTopLevel then + raise(WarningReport(msg"Unexpected nested class: lambdas may not function correctly." -> isym.toLoc :: Nil, source = Diagnostic.Source.Compilation)) val newMtds = methods.map: f => - val bod2 = translateBlock(f.body, funcLikeHandlerCtx(Value.Ref(isym).sel(new Tree.Ident(f.sym.nme), f.sym.asTrm.get), S(Value.Ref(isym)), s"${sym.nme}.${f.sym.nme}")) + val bod2 = translateBlock(f.body, + funcLikeHandlerCtx(Value.Ref(isym).sel(new Tree.Ident(f.sym.nme), f.sym.asTrm.get), S(Value.Ref(isym)), s"${sym.nme}.${f.sym.nme}", f.params.length, f.body.definedVars)) if f.body is bod2 then f else FunDefn(f.owner, f.sym, f.params, bod2) - val newCtor = translateBlock(Begin(preCtor, ctor), ctorCtx(sym, sym.asCls.get, s"‹constructor of ${sym.nme}›")) + val blk = Begin(preCtor, ctor) + // FIXME: ctor cannot yield effect with call cnt 0 + // Possible solution: use a dummy ctor instead, which is a call to another normal static function. The function should also handle the super call. + val newCtor = translateBlock(blk, ctorCtx(sym, isym.asInstanceOf[ClassSymbol], s"‹constructor of ${sym.nme}›", 0, blk.definedVars)) k(ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, newMtds, privateFields, publicFields, End(), newCtor, companion, bufferable)) case _ => super.applyDefn(defn)(k) val b = subblockTransform.applyBlock(blk) - - val getLocalsFn = createGetLocalsFn(b)(using h) - // All the defined variables are masked away from the inner scope (TODO: Scoped) - given HandlerCtx = h.nestDebugScope(b.definedVars, getLocalsFn.sym.asPath) if h.isTopLevel then return translateTrivialOrTopLevel(b) val parts = partitionBlock(b) - if parts.size <= 1 then + if parts.states.size <= 1 then return translateTrivialOrTopLevel(b) - val stage1 = firstPass(b) - val stage1 = tl.trace("=>" + b.showAsTree): - val t = firstPass(b) - tl.log("=<" + t.showAsTree) - t - val stage2 = if opt.debug then Define(getLocalsFn, stage1) else stage1 - // val stage2 = secondPass(stage1, fnOrCls, callSelf, getLocalsFn) - if h.isTopLevel then stage2 else thirdPass(stage2) + val pcVar = freshTmp("pc") + val mainLoopLbl = freshTmp("main") + + val postTransform = new BlockTransformerShallow(SymbolSubst()): + override def applyBlock(b: Block) = b match + case StateTransition(uid) => + Assign(pcVar, Value.Lit(Tree.IntLit(uid)), Continue(mainLoopLbl)) + case _ => super.applyBlock(b) + + val arms = parts.states.toList.map: (id, part) => + Case.Lit(Tree.IntLit(id)) -> + postTransform.applyBlock(part.blk) + + val mainLoop = Label(mainLoopLbl, true, Match(Value.Ref(pcVar), arms, N, End()), End()) + + val getSavedTmp = freshTmp("saveOffset") + def getSaved(off: BigInt): (Block => Block, Path) = + if off == 0 then + return (id, DynSelect(paths.runtimePath.selSN("resumeArr"), paths.runtimePath.selSN("resumeIdx"), true)) + val computeOff = Assign(getSavedTmp, Call(State.builtinOpsMap("+").asPath, paths.runtimePath.selSN("resumeIdx").asArg :: intLit(off).asArg :: Nil)(false, false), _) + (computeOff, DynSelect(paths.runtimePath.selSN("resumeArr"), getSavedTmp.asPath, true)) + + val (pcComputeOff, pcSavePath) = getSaved(-3) + val restoreMap = mutable.HashMap.empty[Local, mutable.ArrayBuffer[StateId]] + parts.states.foreach: (id, part) => + part.sym.foreach: l => + restoreMap.getOrElseUpdate(l, mutable.ArrayBuffer.empty) += id + val restoreVars = h.currentLocals.zipWithIndex.foldLeft(blockBuilder.chain(pcComputeOff).assign(pcVar, pcSavePath)): + case (builder, (local, idx)) => + val (computeOff, savePath) = getSaved(idx) + builder.chain(Match( + pcVar.asPath, + restoreMap.getOrElse(local, mutable.ArrayBuffer.empty).map(id => Case.Lit(Tree.IntLit(id)) -> Assign(local, paths.runtimePath.selSN("resumeValue"), End())).toList, + S(blockBuilder.chain(computeOff).assign(local, savePath).end), + _)) + + 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 translateTrivialOrTopLevel(b: Block)(using HandlerCtx): Block = // We shall add back the top level effect checks here @@ -521,44 +566,44 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, // 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 = - if handlerCtx.isTopLevel then genNormalBody(b, N) - else - // create the doUnwind function - val doUnwindSym = BlockMemberSymbol("doUnwind", Nil, true) - doUnwindMap += fnOrCls -> doUnwindSym.asPath - val pcSym = VarSymbol(Tree.Ident("pc")) - val resSym = VarSymbol(Tree.Ident("res")) - val doUnwindBlk = h.linkAndHandle( - LinkState(resSym, paths.contClsPath, pcSym.asPath) - ) - val doUnwindDef = FunDefn( - N, doUnwindSym, - PlainParamList(Param.simple(resSym) :: Param.simple(pcSym) :: Nil) :: Nil, - doUnwindBlk - ) - val doUnwindLazy = Lazy(doUnwindSym.asPath) - val rst = genNormalBody(b, S(doUnwindLazy)) + // 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 = + // if handlerCtx.isTopLevel then genNormalBody(b, N) + // else + // // create the doUnwind function + // val doUnwindSym = BlockMemberSymbol("doUnwind", Nil, true) + // doUnwindMap += fnOrCls -> doUnwindSym.asPath + // val pcSym = VarSymbol(Tree.Ident("pc")) + // val resSym = VarSymbol(Tree.Ident("res")) + // val doUnwindBlk = h.linkAndHandle( + // LinkState(resSym, paths.contClsPath, pcSym.asPath) + // ) + // val doUnwindDef = FunDefn( + // N, doUnwindSym, + // PlainParamList(Param.simple(resSym) :: Param.simple(pcSym) :: Nil) :: Nil, + // doUnwindBlk + // ) + // val doUnwindLazy = Lazy(doUnwindSym.asPath) + // val rst = genNormalBody(b, S(doUnwindLazy)) - if doUnwindLazy.isEmpty && opt.stackSafety.isEmpty then rst - else - blockBuilder - .define(doUnwindDef) - .rest(rst) - if opt.debug then - Define(getLocalsFn, ret) - else - ret - - // moves definitions to the top level of the block - private def thirdPass(b: Block): Block = - // to ensure the fun and class references in the continuation class are properly scoped, - // we move all function defns to the top level of the handler block - val (blk, defns) = b.floatOutDefns() - defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) + // if doUnwindLazy.isEmpty && opt.stackSafety.isEmpty then rst + // else + // blockBuilder + // .define(doUnwindDef) + // .rest(rst) + // if opt.debug then + // Define(getLocalsFn, ret) + // else + // ret + + // // moves definitions to the top level of the block + // private def thirdPass(b: Block): Block = + // // to ensure the fun and class references in the continuation class are properly scoped, + // // we move all function defns to the top level of the handler block + // val (blk, defns) = b.floatOutDefns() + // defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) private def locToStr(l: Loc): Str = Scope.replaceInvalidCharacters(l.origin.fileName.last + "_L" + l.origin.startLineNum + "_" + l.spanStart + "_" + l.spanEnd) @@ -566,46 +611,46 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, private def symToStr(s: Symbol): Str = s"${Scope.replaceInvalidCharacters(s.nme)}" - private def translateFun(f: FunDefn)(using HandlerCtx): FunDefn = - val callSelf = f.params match - case pList :: Nil => - val params = pList.params.map(p => p.sym.asPath.asArg) - f.owner match - case None => S(Call(f.sym.asPath, params)(true, true)) - case Some(owner) => - S(Call(Select(owner.asPath, Tree.Ident(f.sym.nme))(N), params)(true, true)) - case _ => None // TODO: more than one plist + // private def translateFun(f: FunDefn)(using HandlerCtx): FunDefn = + // val callSelf = f.params match + // case pList :: Nil => + // val params = pList.params.map(p => p.sym.asPath.asArg) + // f.owner match + // case None => S(Call(f.sym.asPath, params)(true, true)) + // case Some(owner) => + // S(Call(Select(owner.asPath, Tree.Ident(f.sym.nme))(N), params)(true, true)) + // case _ => None // TODO: more than one plist - FunDefn(f.owner, f.sym, f.params, translateBlock(f.body, - f.params.flatMap(_.paramSyms).toSet, - callSelf, - L(f.sym), - functionHandlerCtx(s"Cont$$func$$${symToStr(f.sym)}$$", f.sym.nme)) - ) - - private def translateBody(cls: ClsLikeBody, sym: BlockMemberSymbol)(using HandlerCtx): ClsLikeBody = - val curCtorCtx = - if handlerCtx.isTopLevel - then - topLevelCtx(s"Cont$$modCtor$$${symToStr(sym)}$$", s"‹constructor of ${sym.nme}›") - else ctorCtx( - cls.isym.asPath, - s"Cont$$ctor$$${symToStr(sym)}$$", s"‹constructor of ${sym.nme}›") - ClsLikeBody( - cls.isym, - cls.methods.map(translateFun), - cls.privateFields, - cls.publicFields, - translateBlock(cls.ctor, Set.empty, N, R(cls.isym), curCtorCtx), - ) - - 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 + // FunDefn(f.owner, f.sym, f.params, translateBlock(f.body, + // f.params.flatMap(_.paramSyms).toSet, + // callSelf, + // L(f.sym), + // functionHandlerCtx(s"Cont$$func$$${symToStr(f.sym)}$$", f.sym.nme)) + // ) + + // private def translateBody(cls: ClsLikeBody, sym: BlockMemberSymbol)(using HandlerCtx): ClsLikeBody = + // val curCtorCtx = + // if handlerCtx.isTopLevel + // then + // topLevelCtx(s"Cont$$modCtor$$${symToStr(sym)}$$", s"‹constructor of ${sym.nme}›") + // else ctorCtx( + // cls.isym.asPath, + // s"Cont$$ctor$$${symToStr(sym)}$$", s"‹constructor of ${sym.nme}›") + // ClsLikeBody( + // cls.isym, + // cls.methods.map(translateFun), + // cls.privateFields, + // cls.publicFields, + // translateBlock(cls.ctor, Set.empty, N, R(cls.isym), curCtorCtx), + // ) + + // 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 is rewritten into: // 1. Instantiation of the handler @@ -630,11 +675,12 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, // Some limited handling of effects extending classes and having access to their fields. // Currently does not support super() raising effects. + // TODO: prob fixed (?) val tmp = freshTmp() val ctor = blockBuilder .assign(tmp, Call(Value.Ref(State.builtinOpsMap("super")), h.args.map(_.asArg))(true, true)) .ret(h.cls.asPath) - + val clsDefn = ClsLikeDefn( N, // no owner h.cls, @@ -668,252 +714,252 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, transform.applyBlock(b) - private def genContClass(b: Block, callSelf: Opt[Result])(using h: HandlerCtx): Opt[ClsLikeDefn] = - val clsSym = ClassSymbol( - Tree.DummyTypeDef(syntax.Cls), - Tree.Ident(handlerCtx.contName) - ) + // 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 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 + // val loopLbl = freshTmp("contLoop") + // val pcSymbol = TermSymbol(ParamBind, S(clsSym), pcIdent) + + // // This maps each state id to an optional location + // // Note that the value is an Option, and None must be inserted even if the location is not known + // // so that we can use the same map to enumerate all possible state id and check if there is any state id + // val pcToLoc = collection.mutable.Map.empty[StateId, Option[Loc]] + // var containsCall = false - // Create the DoUnwind function - doUnwindMap += R(clsSym) -> Select(clsSym.asPath, Tree.Ident("doUnwind"))( - N /* this refers to the method defined in Runtime.FunctionContFrame */ - ) - val newPcSym = VarSymbol(Tree.Ident("newPc")) - val resSym = VarSymbol(Tree.Ident("res")) - val doUnwindBlk = blockBuilder - .assign(pcSymbol, newPcSym.asPath) - .assignFieldN(resSym.asPath.contTrace.last, nextIdent, clsSym.asPath) - .assignFieldN(resSym.asPath.contTrace, lastIdent, clsSym.asPath) - .ret(resSym.asPath) + // // Create the DoUnwind function + // doUnwindMap += R(clsSym) -> Select(clsSym.asPath, Tree.Ident("doUnwind"))( + // N /* this refers to the method defined in Runtime.FunctionContFrame */ + // ) + // val newPcSym = VarSymbol(Tree.Ident("newPc")) + // val resSym = VarSymbol(Tree.Ident("res")) + // val doUnwindBlk = blockBuilder + // .assign(pcSymbol, newPcSym.asPath) + // .assignFieldN(resSym.asPath.contTrace.last, nextIdent, clsSym.asPath) + // .assignFieldN(resSym.asPath.contTrace, lastIdent, clsSym.asPath) + // .ret(resSym.asPath) - // Replaces ResultPlaceholders to check for effects and link the effect trace - def prepareBlock(b: Block): Block = - val transform = new BlockTransformerShallow(SymbolSubst()): - override def applyResult(r: Result)(k: Result => Block): Block = - r match - case c @ Call(Value.Ref(s: BuiltinSymbol, _), _) => () - case c: Call if !c.mayRaiseEffects => () - case _: Call | _: Instantiate => containsCall = true - case _ => () - super.applyResult(r)(k) + // // 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 + // 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 + // // 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) + // 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$")) + // 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) - BlockPartition(0, Return(value, false), N) :: someParts - else - partitionBlock(actualBlock, false) + // def createResumeBod = + // val parts = + // if opt.stackSafety.isDefined then callSelf match + // case None => partitionBlock(actualBlock, true) + // case Some(value) => + // val someParts = partitionBlock(actualBlock, false) + // BlockPartition(0, Return(value, false), N) :: someParts + // else + // partitionBlock(actualBlock, false) - def transformPart(blk: Block): Block = - val transform = new BlockTransformerShallow(SymbolSubst()): - override def applyBlock(b: Block): Block = b match - case ReturnCont(res, uid) => Return(Call( - Select(clsSym.asPath, Tree.Ident("doUnwind"))( - N /* this refers to the method defined in Runtime.FunctionContFrame */ - ), - res.asPath.asArg :: Value.Lit(Tree.IntLit(uid)).asArg :: Nil)(true, false), - false - ) - case StateTransition(uid) => - blockBuilder - .assign(pcSymbol, Value.Lit(Tree.IntLit(uid))) - .continue(loopLbl) - case FnEnd() => - blockBuilder.break(loopLbl) - case _ => super.applyBlock(b) - transform.applyBlock(blk) - - // match block representing the function body - val mainMatchCases = parts.toList.map(b => (Case.Lit(Tree.IntLit(b.id)), transformPart(b.blk))) - val mainMatchBlk = Match( - pcSymbol.asPath, - mainMatchCases, - N, - End() - ) + // def transformPart(blk: Block): Block = + // val transform = new BlockTransformerShallow(SymbolSubst()): + // override def applyBlock(b: Block): Block = b match + // case ReturnCont(res, uid) => Return(Call( + // Select(clsSym.asPath, Tree.Ident("doUnwind"))( + // N /* this refers to the method defined in Runtime.FunctionContFrame */ + // ), + // res.asPath.asArg :: Value.Lit(Tree.IntLit(uid)).asArg :: Nil)(true, false), + // false + // ) + // case StateTransition(uid) => + // blockBuilder + // .assign(pcSymbol, Value.Lit(Tree.IntLit(uid))) + // .continue(loopLbl) + // case FnEnd() => + // blockBuilder.break(loopLbl) + // case _ => super.applyBlock(b) + // transform.applyBlock(blk) + + // // match block representing the function body + // val mainMatchCases = parts.toList.map(b => (Case.Lit(Tree.IntLit(b.id)), transformPart(b.blk))) + // val mainMatchBlk = Match( + // pcSymbol.asPath, + // mainMatchCases, + // N, + // End() + // ) - val tmp = freshTmp() - val withResetDepth = - if opt.stackSafety.isDefined && !trivial then - AssignField(runtimePath, stackDepthIdent, depthSym.asPath, mainMatchBlk)(N) - else mainMatchBlk + // val 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()) + // val lbl = blockBuilder.label(loopLbl, loop = true, withResetDepth).rest(End()) - def createAssignment(sym: Local) = Assign(sym, resumedVal.asPath, End()) + // def createAssignment(sym: Local) = Assign(sym, resumedVal.asPath, End()) - val assignedResumedCases = for - b <- parts - sym <- b.sym - yield Case.Lit(Tree.IntLit(b.id)) -> createAssignment(sym) // NOTE: assume sym is in localsMap - - // assigns the resumed value - val body = - if assignedResumedCases.isEmpty then - lbl - else - Match( - pcSymbol.asPath, - assignedResumedCases, - N, - lbl - ) - - // assign cur depth - if opt.stackSafety.isDefined && !trivial then - Assign(depthSym, stackDepthPath, body) - else - body + // val assignedResumedCases = for + // b <- parts + // sym <- b.sym + // yield Case.Lit(Tree.IntLit(b.id)) -> createAssignment(sym) // NOTE: assume sym is in localsMap + + // // assigns the resumed value + // val body = + // if assignedResumedCases.isEmpty then + // lbl + // else + // Match( + // pcSymbol.asPath, + // assignedResumedCases, + // N, + // lbl + // ) + + // // assign cur depth + // if opt.stackSafety.isDefined && !trivial then + // Assign(depthSym, stackDepthPath, body) + // else + // body - val resumeBody = - if trivial then callSelf match - case None => actualBlock - case Some(value) => Return(value, false) - else createResumeBod + // val resumeBody = + // if trivial then callSelf match + // case None => actualBlock + // case Some(value) => Return(value, false) + // else createResumeBod - val resumeSym = BlockMemberSymbol("resume", List()) - val resumeFnDef = FunDefn( - S(clsSym), // owner - resumeSym, - List(PlainParamList(List(Param(FldFlags.empty, resumedVal, N, Modulefulness.none)))), - resumeBody - ) - - val debugMtds = if !opt.debug then Nil else + // val resumeSym = BlockMemberSymbol("resume", List()) + // val resumeFnDef = FunDefn( + // S(clsSym), // owner + // resumeSym, + // List(PlainParamList(List(Param(FldFlags.empty, resumedVal, N, Modulefulness.none)))), + // resumeBody + // ) + + // val debugMtds = if !opt.debug then Nil else - val getLocalsSym = BlockMemberSymbol("getLocals", List()) + // val getLocalsSym = BlockMemberSymbol("getLocals", List()) - val localsRes = h.debugInfo.prevLocalsFn match - case Some(value) => PureCall(value, Nil) - case None => Tuple(mut = true, Nil) + // val localsRes = h.debugInfo.prevLocalsFn match + // case Some(value) => PureCall(value, Nil) + // case None => Tuple(mut = true, Nil) - val getLocalsFnDef = FunDefn( - S(clsSym), - getLocalsSym, - List(), - Return(localsRes, false) - ) - - val getLocSym = BlockMemberSymbol("getLoc", List()) - val getLocFnDef = FunDefn( - S(clsSym), - getLocSym, - List(), - Match(pcSymbol.asPath, pcToLoc.toSortedMap.iterator.map: (stateId, loc) => - Case.Lit(Tree.IntLit(stateId)) -> Return(Value.Lit(loc.fold(Tree.UnitLit(true)): loc => - val (line, _, col) = loc.origin.fph.getLineColAt(loc.spanStart) - Tree.StrLit(s"${loc.origin.fileName.last}:${line + loc.origin.startLineNum - 1}:$col") - ), false) - .toList, N, End()), - ) - - getLocalsFnDef :: getLocFnDef :: Nil + // val getLocalsFnDef = FunDefn( + // S(clsSym), + // getLocalsSym, + // List(), + // Return(localsRes, false) + // ) + + // val getLocSym = BlockMemberSymbol("getLoc", List()) + // val getLocFnDef = FunDefn( + // S(clsSym), + // getLocSym, + // List(), + // Match(pcSymbol.asPath, pcToLoc.toSortedMap.iterator.map: (stateId, loc) => + // Case.Lit(Tree.IntLit(stateId)) -> Return(Value.Lit(loc.fold(Tree.UnitLit(true)): loc => + // val (line, _, col) = loc.origin.fph.getLineColAt(loc.spanStart) + // Tree.StrLit(s"${loc.origin.fileName.last}:${line + loc.origin.startLineNum - 1}:$col") + // ), false) + // .toList, N, End()), + // ) + + // getLocalsFnDef :: getLocFnDef :: Nil - val mtds = resumeFnDef :: debugMtds + // 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? - )) - - // Rewriites ResultPlaceholder. Checks if the result in the placeholder is an effect. - private def genNormalBody(b: Block, 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, paths.contClsPath, 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)) - case _ => super.applyBlock(b) + // 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? + // )) + + // // Rewriites ResultPlaceholder. Checks if the result in the placeholder is an effect. + // private def genNormalBody(b: Block, 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, paths.contClsPath, 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)) + // case _ => super.applyBlock(b) - transform.applyBlock(b) + // transform.applyBlock(b) def translateTopLevel(b: Block): (Block, Map[FnOrCls, Path]) = doUnwindMap = Map.empty - val transformed = translateBlock(b, topLevelCtx(s"Cont$$topLevel$$BAD", "‹top level›")) + val transformed = translateBlock(b, topLevelCtx(s"Cont$$topLevel$$BAD", "‹top level›", b.definedVars)) (transformed, doUnwindMap) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 1edc6c7142..3f67c4a75f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -955,7 +955,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): HandlerLowering(handlerPaths, opt).translateHandleBlocks(desug) val lifted = - if config.effectHandlers.isEmpty && lift then Lifter(S(handlerPaths)).transform(withHandlers1) + if config.effectHandlers.nonEmpty || lift then Lifter(S(handlerPaths)).transform(withHandlers1) else withHandlers1 val (withHandlers2, doUnwindPaths) = config.effectHandlers.fold((lifted, Map.empty)): opt => @@ -965,9 +965,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx): case N => withHandlers2 case S(sts) => StackSafeTransform(sts.stackLimit, handlerPaths, doUnwindPaths).transformTopLevel(withHandlers2) - // val flattened = lifted.flattened + val flattened = stackSafe.flattened - val bufferable = BufferableTransform().transform(lifted) + val bufferable = BufferableTransform().transform(flattened) val merged = MergeMatchArmTransformer.applyBlock(bufferable) diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index d5cc8a4258..3f0a1a5830 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -14,10 +14,25 @@ globalThis.Object.freeze(class Runtime { constructor() { runtime.Unit; } + static #isResuming; + static #resumeValue; + static #resumeArr; + static #resumeIdx; + static #resumePc; static #stackLimit; static #stackDepth; static #stackHandler; static #stackResume; + static get isResuming() { return Runtime.#isResuming; } + static set isResuming(value) { Runtime.#isResuming = value; } + static get resumeValue() { return Runtime.#resumeValue; } + static set resumeValue(value) { Runtime.#resumeValue = value; } + static get resumeArr() { return Runtime.#resumeArr; } + static set resumeArr(value) { Runtime.#resumeArr = value; } + static get resumeIdx() { return Runtime.#resumeIdx; } + static set resumeIdx(value) { Runtime.#resumeIdx = value; } + static get resumePc() { return Runtime.#resumePc; } + static set resumePc(value) { Runtime.#resumePc = value; } static get stackLimit() { return Runtime.#stackLimit; } static set stackLimit(value) { Runtime.#stackLimit = value; } static get stackDepth() { return Runtime.#stackDepth; } @@ -247,6 +262,11 @@ globalThis.Object.freeze(class Runtime { toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "TraceLogger"]; }); + this.isResuming = false; + this.resumeValue = null; + this.resumeArr = null; + this.resumeIdx = null; + this.resumePc = null; globalThis.Object.freeze(class FatalEffect { static { Runtime.FatalEffect = globalThis.Object.freeze(new this) @@ -271,13 +291,58 @@ globalThis.Object.freeze(class Runtime { toString() { return runtime.render(this); } static [definitionMetadata] = ["object", "PrintStackEffect"]; }); - this.FunctionContFrame = function FunctionContFrame(next) { - return globalThis.Object.freeze(new FunctionContFrame.class(next)); + this.FunctionContFrame = function FunctionContFrame(next, saved) { + return globalThis.Object.freeze(new FunctionContFrame.class(next, saved)); }; globalThis.Object.freeze(class FunctionContFrame { static { Runtime.FunctionContFrame.class = this } + constructor(next, saved) { + this.next = next; + this.saved = saved; + } + resume(value) { + let scrut, f, scrut1, tmp, tmp1, tmp2, tmp3, tmp4; + scrut = this.saved.at(0) == 0; + if (scrut === true) { + tmp = runtime.safeCall(globalThis.console.log("cannot resume getters")); + } else { + tmp = runtime.Unit; + } + f = this.saved.at(1); + tmp5: while (true) { + scrut1 = this.saved.at(0) > 1; + if (scrut1 === true) { + tmp1 = runtime.safeCall(f()); + f = tmp1; + tmp2 = this.saved.at(0) - 1; + this.saved[0] = tmp2; + tmp3 = runtime.Unit; + continue tmp5 + } else { + tmp3 = runtime.Unit; + } + break; + } + Runtime.isResuming = true; + Runtime.resumeValue = value; + Runtime.resumeArr = this.saved; + Runtime.resumeIdx = 5; + Runtime.resumePc = this.saved.at(2); + tmp4 = globalThis.Object.freeze([]); + return f.apply(this.saved.at(3), tmp4) + } + toString() { return runtime.render(this); } + static [definitionMetadata] = ["class", "FunctionContFrame", ["next", "saved"]]; + }); + this.FunctionContFrameOld = function FunctionContFrameOld(next) { + return globalThis.Object.freeze(new FunctionContFrameOld.class(next)); + }; + globalThis.Object.freeze(class FunctionContFrameOld { + static { + Runtime.FunctionContFrameOld.class = this + } constructor(next) { this.next = next; } @@ -288,7 +353,7 @@ globalThis.Object.freeze(class Runtime { return res1 } toString() { return runtime.render(this); } - static [definitionMetadata] = ["class", "FunctionContFrame", ["next"]]; + static [definitionMetadata] = ["class", "FunctionContFrameOld", ["next"]]; }); this.HandlerContFrame = function HandlerContFrame(next, nextHandler, handler) { return globalThis.Object.freeze(new HandlerContFrame.class(next, nextHandler, handler)); @@ -800,6 +865,13 @@ globalThis.Object.freeze(class Runtime { return runtime.safeCall(globalThis.console.log(eff)) } } + static unwind(cur, ...saved) { + let tmp; + tmp = new Runtime.FunctionContFrame.class(null, saved); + cur.contTrace.last.next = tmp; + cur.contTrace.last = cur.contTrace.last.next; + return cur + } static mkEffect(handler, handlerFun) { let res, tmp; tmp = new Runtime.ContTrace.class(null, null, null, null, false); diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index 873b32f4de..d6199e11a4 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -122,14 +122,33 @@ module TraceLogger with // Private definitions for algebraic effects // layout: -// | pc | isCtor | this | paramCnt | params... | localCnt | locals... | +// | callCnt | f | 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 class FunctionContFrame(fn, resumeIdx, paramListCnt, next) with +data class FunctionContFrame(next, saved) with fun resume(value) = - 0 // TODO + 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 = 5 + resumePc = saved.[2] + f.apply(saved.[3], []) data abstract class FunctionContFrameOld(next) with fun resume(value) @@ -263,6 +282,12 @@ fun debugEff(eff) = console.log(eff) // runtime implementations +fun unwind(cur, ...saved) = + set + cur.contTrace.last.next = new mut FunctionContFrame(null, saved) + cur.contTrace.last = cur.contTrace.last.next + cur + fun mkEffect(handler, handlerFun) = let res = new mut EffectSig(new mut ContTrace(null, null, null, null, false), handler, handlerFun) set diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index b8571e8884..92bb309da8 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -7,229 +7,3 @@ :global // :d // :todo - -class Effect - -:effectHandlers -:sjs -fun test(x) = - handle h = Effect with - fun bruh()(k) = - let res = k(2) - print(res) - h.bruh() -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: lookup_! (Scope.scala:112) -//│ FAILURE INFO: Tuple2: -//│ _1 = Tuple2: -//│ _1 = $resultPlaceholder -//│ _2 = class hkmc2.semantics.TempSymbol -//│ _2 = Scope: -//│ parent = S of Scope: -//│ parent = S of Scope: -//│ parent = S of Scope: -//│ parent = N -//│ curThis = S of S of globalThis:globalThis -//│ bindings = HashMap($block$res -> block$res, member:Effect -> Effect1, $runtime -> runtime, class:Effect -> Effect, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, member:Predef -> Predef, $block$res -> block$res1, $Block -> Block, $Shape -> Shape) -//│ curThis = N -//│ bindings = HashMap(class:Handler$h$ -> Handler$h$, member:Handler$h$ -> Handler$h$1, member:Handler$h$bruh$ -> Handler$h$bruh$, member:Handler$h$bruh -> Handler$h$bruh, member:handleBlock$ -> handleBlock$, member:test -> test) -//│ curThis = S of N -//│ bindings = HashMap(Handler$h$$instance -> Handler$h$$instance, k -> k) -//│ curThis = N -//│ bindings = HashMap(res -> res) -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'resultPlaceholder' -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: lookup_! (Scope.scala:112) -//│ FAILURE INFO: Tuple2: -//│ _1 = Tuple2: -//│ _1 = $resultPlaceholder -//│ _2 = class hkmc2.semantics.TempSymbol -//│ _2 = Scope: -//│ parent = S of Scope: -//│ parent = S of Scope: -//│ parent = S of Scope: -//│ parent = N -//│ curThis = S of S of globalThis:globalThis -//│ bindings = HashMap($block$res -> block$res, member:Effect -> Effect1, $runtime -> runtime, class:Effect -> Effect, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, member:Predef -> Predef, $block$res -> block$res1, $Block -> Block, $Shape -> Shape) -//│ curThis = N -//│ bindings = HashMap(class:Handler$h$ -> Handler$h$, member:Handler$h$ -> Handler$h$1, member:Handler$h$bruh$ -> Handler$h$bruh$, member:Handler$h$bruh -> Handler$h$bruh, member:handleBlock$ -> handleBlock$, member:test -> test) -//│ curThis = S of N -//│ bindings = HashMap() -//│ curThis = N -//│ bindings = HashMap($res -> res, h -> h) -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'resultPlaceholder' -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: lookup_! (Scope.scala:112) -//│ FAILURE INFO: Tuple2: -//│ _1 = Tuple2: -//│ _1 = $resultPlaceholder -//│ _2 = class hkmc2.semantics.TempSymbol -//│ _2 = Scope: -//│ parent = S of Scope: -//│ parent = S of Scope: -//│ parent = S of Scope: -//│ parent = N -//│ curThis = S of S of globalThis:globalThis -//│ bindings = HashMap($block$res -> block$res, member:Effect -> Effect1, $runtime -> runtime, class:Effect -> Effect, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, member:Predef -> Predef, $block$res -> block$res1, $Block -> Block, $Shape -> Shape) -//│ curThis = N -//│ bindings = HashMap(class:Handler$h$ -> Handler$h$, member:Handler$h$ -> Handler$h$1, member:Handler$h$bruh$ -> Handler$h$bruh$, member:Handler$h$bruh -> Handler$h$bruh, member:handleBlock$ -> handleBlock$, member:test -> test) -//│ curThis = S of N -//│ bindings = HashMap(x -> x) -//│ curThis = N -//│ bindings = HashMap($tmp -> tmp) -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'resultPlaceholder' -//│ JS (unsanitized): -//│ let test, handleBlock$, Handler$h$bruh, Handler$h$1, Handler$h$bruh$; -//│ Handler$h$bruh$ = function Handler$h$bruh$(Handler$h$$instance, k) { -//│ let res; -//│ res = runtime.safeCall(k(2)); -//│ res = resultPlaceholder(2); -//│ return Predef.print(res) -//│ }; -//│ Handler$h$bruh = function Handler$h$bruh(Handler$h$$instance) { -//│ return (k) => { -//│ return Handler$h$bruh$(Handler$h$$instance, k) -//│ } -//│ }; -//│ globalThis.Object.freeze(class Handler$h$ extends Effect1 { -//│ static { -//│ Handler$h$1 = this -//│ } -//│ constructor() { -//│ let tmp; -//│ tmp = super(); -//│ } -//│ bruh() { -//│ let Handler$h$bruh$here; -//│ Handler$h$bruh$here = runtime.safeCall(Handler$h$bruh(this)); -//│ return runtime.mkEffect(this, Handler$h$bruh$here) -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Handler$h$"]; -//│ }); -//│ handleBlock$ = function handleBlock$() { -//│ let h, res; -//│ h = new Handler$h$1(); -//│ res = runtime.safeCall(h.bruh()); -//│ res = resultPlaceholder(1); -//│ return res -//│ }; -//│ test = function test(x) { let tmp; tmp = handleBlock$(); tmp = resultPlaceholder(4); return tmp }; - -:sjs -data class A(x) -//│ JS (unsanitized): -//│ let A1; -//│ A1 = function A(x) { -//│ return globalThis.Object.freeze(new A.class(x)); -//│ }; -//│ globalThis.Object.freeze(class A { -//│ static { -//│ A1.class = this -//│ } -//│ constructor(x) { -//│ this.x = x; -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "A", ["x"]]; -//│ }); - -:effectHandlers -:sjs -fun test2(x) = - fun bruh() = x - test(bruh) - print("a") -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: lookup_! (Scope.scala:112) -//│ FAILURE INFO: Tuple2: -//│ _1 = Tuple2: -//│ _1 = $resultPlaceholder -//│ _2 = class hkmc2.semantics.TempSymbol -//│ _2 = Scope: -//│ parent = S of Scope: -//│ parent = S of Scope: -//│ parent = S of Scope: -//│ parent = N -//│ curThis = S of S of globalThis:globalThis -//│ bindings = HashMap(class:Handler$h$ -> Handler$h$, member:Handler$h$bruh -> Handler$h$bruh, member:A -> A1, $runtime -> runtime, class:A -> A, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, $block$res -> block$res2, $Block -> Block, $Shape -> Shape, member:Handler$h$ -> Handler$h$1, $block$res -> block$res3, member:Handler$h$bruh$ -> Handler$h$bruh$, $block$res -> block$res, member:Effect -> Effect1, class:Effect -> Effect, member:handleBlock$ -> handleBlock$, member:Predef -> Predef, $block$res -> block$res1, member:test -> test) -//│ curThis = N -//│ bindings = HashMap(member:bruh -> bruh, member:test2 -> test2, member:bruh$ -> bruh$) -//│ curThis = S of N -//│ bindings = HashMap(x -> x) -//│ curThis = N -//│ bindings = HashMap($bruh$here -> bruh$here, $tmp -> tmp) -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'resultPlaceholder' -//│ JS (unsanitized): -//│ let bruh, test2, bruh$; -//│ bruh$ = function bruh$(x) { -//│ return x -//│ }; -//│ bruh = function bruh(x) { -//│ return () => { -//│ return bruh$(x) -//│ } -//│ }; -//│ test2 = function test2(x) { -//│ let tmp, bruh$here; -//│ bruh$here = runtime.safeCall(bruh(x)); -//│ tmp = test(bruh$here); -//│ tmp = resultPlaceholder(1); -//│ return Predef.print("a") -//│ }; - -fun a(x)(y) = x + y - -:sjs -:effectHandlers -fun bruh = a(2)(3) + 1 -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: lookup_! (Scope.scala:112) -//│ FAILURE INFO: Tuple2: -//│ _1 = Tuple2: -//│ _1 = $resultPlaceholder -//│ _2 = class hkmc2.semantics.TempSymbol -//│ _2 = Scope: -//│ parent = S of Scope: -//│ parent = S of Scope: -//│ parent = S of Scope: -//│ parent = N -//│ curThis = S of S of globalThis:globalThis -//│ bindings = HashMap(member:Handler$h$bruh -> Handler$h$bruh, $block$res -> block$res5, $runtime -> runtime, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, $Block -> Block, $Shape -> Shape, member:Handler$h$ -> Handler$h$1, member:Handler$h$bruh$ -> Handler$h$bruh$, $block$res -> block$res, member:Effect -> Effect1, class:Effect -> Effect, member:Predef -> Predef, $block$res -> block$res1, member:test -> test, $block$res -> block$res4, class:Handler$h$ -> Handler$h$, member:A -> A1, class:A -> A, $block$res -> block$res2, $block$res -> block$res3, member:bruh$ -> bruh$, member:bruh -> bruh, member:test2 -> test2, member:handleBlock$ -> handleBlock$, member:a -> a) -//│ curThis = N -//│ bindings = HashMap(member:bruh -> bruh1) -//│ curThis = S of N -//│ bindings = HashMap() -//│ curThis = N -//│ bindings = HashMap($tmp -> tmp, $tmp -> tmp1) -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'resultPlaceholder' -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: lookup_! (Scope.scala:112) -//│ FAILURE INFO: Tuple2: -//│ _1 = Tuple2: -//│ _1 = $resultPlaceholder -//│ _2 = class hkmc2.semantics.TempSymbol -//│ _2 = Scope: -//│ parent = S of Scope: -//│ parent = S of Scope: -//│ parent = S of Scope: -//│ parent = N -//│ curThis = S of S of globalThis:globalThis -//│ bindings = HashMap(member:Handler$h$bruh -> Handler$h$bruh, $block$res -> block$res5, $runtime -> runtime, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, $Block -> Block, $Shape -> Shape, member:Handler$h$ -> Handler$h$1, member:Handler$h$bruh$ -> Handler$h$bruh$, $block$res -> block$res, member:Effect -> Effect1, class:Effect -> Effect, member:Predef -> Predef, $block$res -> block$res1, member:test -> test, $block$res -> block$res4, class:Handler$h$ -> Handler$h$, member:A -> A1, class:A -> A, $block$res -> block$res2, $block$res -> block$res3, member:bruh$ -> bruh$, member:bruh -> bruh, member:test2 -> test2, member:handleBlock$ -> handleBlock$, member:a -> a) -//│ curThis = N -//│ bindings = HashMap(member:bruh -> bruh1) -//│ curThis = S of N -//│ bindings = HashMap() -//│ curThis = N -//│ bindings = HashMap($tmp -> tmp, $tmp -> tmp1) -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'resultPlaceholder' -//│ JS (unsanitized): -//│ let bruh1; -//│ bruh1 = function bruh() { -//│ let tmp, tmp1; -//│ tmp = a(2); -//│ tmp = resultPlaceholder(1); -//│ tmp1 = runtime.safeCall(tmp(3)); -//│ tmp1 = resultPlaceholder(2); -//│ return tmp1 + 1 -//│ }; From d8ed731bc68c0d1ed2fa6ca3f5b895e56e29feb9 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 18 Nov 2025 17:59:06 +0800 Subject: [PATCH 04/14] Fix restore of params --- .../src/main/scala/hkmc2/codegen/HandlerLowering.scala | 9 ++++++--- hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 6088f5dbcf..ea1a9103cd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -435,7 +435,8 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, override def applyFunDefn(fun: FunDefn): FunDefn = if !h.isTopLevel then raise(WarningReport(msg"Unexpected nested function: lambdas may not function correctly." -> fun.sym.toLoc :: Nil, source = Diagnostic.Source.Compilation)) - val bod2 = translateBlock(fun.body, functionHandlerCtx(Value.Ref(fun.sym, N), fun.sym.nme, fun.params.length, fun.body.definedVars)) + val bod2 = translateBlock(fun.body, functionHandlerCtx(Value.Ref(fun.sym, N), fun.sym.nme, fun.params.length, + fun.body.definedVars ++ fun.params.flatMap(_.paramSyms))) if fun.body is bod2 then fun else FunDefn(fun.owner, fun.sym, fun.params, bod2) override def applyDefn(defn: Defn)(k: Defn => Block): Block = defn match @@ -444,13 +445,15 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, raise(WarningReport(msg"Unexpected nested class: lambdas may not function correctly." -> isym.toLoc :: Nil, source = Diagnostic.Source.Compilation)) val newMtds = methods.map: f => val bod2 = translateBlock(f.body, - funcLikeHandlerCtx(Value.Ref(isym).sel(new Tree.Ident(f.sym.nme), f.sym.asTrm.get), S(Value.Ref(isym)), s"${sym.nme}.${f.sym.nme}", f.params.length, f.body.definedVars)) + funcLikeHandlerCtx(Value.Ref(isym).sel(new Tree.Ident(f.sym.nme), f.sym.asTrm.get), S(Value.Ref(isym)), s"${sym.nme}.${f.sym.nme}", f.params.length, + f.body.definedVars ++ f.params.flatMap(_.paramSyms))) if f.body is bod2 then f else FunDefn(f.owner, f.sym, f.params, bod2) val blk = Begin(preCtor, ctor) // FIXME: ctor cannot yield effect with call cnt 0 // Possible solution: use a dummy ctor instead, which is a call to another normal static function. The function should also handle the super call. - val newCtor = translateBlock(blk, ctorCtx(sym, isym.asInstanceOf[ClassSymbol], s"‹constructor of ${sym.nme}›", 0, blk.definedVars)) + // val newCtor = translateBlock(blk, ctorCtx(sym, isym.asInstanceOf[ClassSymbol], s"‹constructor of ${sym.nme}›", 0, blk.definedVars)) + val newCtor = blk k(ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, newMtds, privateFields, publicFields, End(), newCtor, companion, bufferable)) case _ => super.applyDefn(defn)(k) val b = subblockTransform.applyBlock(blk) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 3f67c4a75f..c295becb56 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1007,7 +1007,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): trait LoweringSelSanityChecks(using Config, TL, Raise, State) extends Lowering: - private val instrument: Bool = config.sanityChecks.isDefined + private val instrument: Bool = config.sanityChecks.isDefined && config.effectHandlers.isEmpty override def setupSelection(prefix: st, nme: Tree.Ident, disamb: Opt[DefinitionSymbol[?]])(k: Result => Block)(using LoweringCtx): Block = if !instrument then return super.setupSelection(prefix, nme, disamb)(k) From f08ae28d019a3fae0bb03e358879638f1718c0cd Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Sat, 6 Dec 2025 03:58:46 +0800 Subject: [PATCH 05/14] Implement debugging --- .../scala/hkmc2/codegen/HandlerLowering.scala | 184 +++++++++--------- .../src/main/scala/hkmc2/codegen/Lifter.scala | 17 +- .../main/scala/hkmc2/codegen/Lowering.scala | 10 +- .../scala/hkmc2/codegen/UsedVarAnalyzer.scala | 11 +- .../scala/hkmc2/semantics/Elaborator.scala | 1 - .../src/test/mlscript-compile/Runtime.mjs | 119 ++++++----- .../src/test/mlscript-compile/Runtime.mls | 27 ++- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 3 + 8 files changed, 199 insertions(+), 173 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index ea1a9103cd..1d366ac9cc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -15,6 +15,7 @@ import semantics.Elaborator.State import hkmc2.Config.EffectHandlers import scala.collection.mutable import scala.util.boundary +import hkmc2.codegen.js.JSBuilder object HandlerLowering: @@ -29,10 +30,7 @@ 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] @@ -59,13 +57,14 @@ object HandlerLowering: // prevLocalsFn: The function that gets the outer function's locals. private case class DebugInfo( debugNme: Str, - inScopeLocals: Set[Local], + debugInfoPath: Path, + inScopeLocals: Set[Local], // TODO: Remove this after scoped block is implemented. ): - def nest(debugNme: Str, locals: Set[Local]) = copy( - debugNme = debugNme, inScopeLocals = inScopeLocals ++ locals) + def nest(debugNme: Str, debugInfoPath: Path, locals: List[Local]) = copy( + debugNme = debugNme, debugInfoPath, inScopeLocals = inScopeLocals ++ locals) private object DebugInfo: - def topLevel(debugNme: Str, locals: Set[Local]) = DebugInfo(debugNme, Set.empty) + def topLevel(debugNme: Str, locals: Set[Local]) = DebugInfo(debugNme, Value.Lit(Tree.UnitLit(true)), locals) type StateId = BigInt @@ -87,19 +86,17 @@ class HandlerPaths(using Elaborator.State): val localVarInfoPath: Path = runtimePath.selSN("LocalVarInfo").selSN("class") val unwindPath: Path = runtimePath.selSN("unwind") val isResuming: Path = runtimePath.selSN("isResuming") - - def isHandlerClsPath(p: Path) = false + val resumePc: Path = runtimePath.selSN("resumePc") class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, Elaborator.State, Elaborator.Ctx): - private def funcLikeHandlerCtx(funcPath: Path, thisPath: Option[Path], debugNme: Str, plCnt: Int, curLocals: Set[Local])(using h: HandlerCtx) = - HandlerCtx(S(funcPath), thisPath, plCnt, (curLocals -- h.debugInfo.inScopeLocals).toList, h.debugInfo.nest(debugNme, curLocals)) - - private def functionHandlerCtx(funcPath: Path, debugNme: Str, plCnt: Int, curLocals: Set[Local])(using HandlerCtx) = funcLikeHandlerCtx(funcPath, N, debugNme, plCnt, curLocals) + private def funcLikeHandlerCtx(funcPath: Path, thisPath: Option[Path], debugNme: Str, plCnt: Int, debugInfoPath: Path, scopedLocals: List[Local])(using h: HandlerCtx) = + HandlerCtx(S(funcPath), thisPath, plCnt, scopedLocals, h.debugInfo.nest(debugNme, debugInfoPath, scopedLocals)) private def topLevelCtx(nme: Str, debugNme: Str, curLocals: Set[Local]) = HandlerCtx(N, N, 0, curLocals.toList, DebugInfo.topLevel(debugNme, curLocals)) - private def ctorCtx(bms: BlockMemberSymbol, cls: ClassSymbol, debugNme: Str, plCnt: Int, curLocals: Set[Local])(using HandlerCtx) = - funcLikeHandlerCtx(Value.Ref(bms, S(cls)), S(Value.Ref(cls)), debugNme, 0, curLocals) + private def ctorCtx(bms: BlockMemberSymbol, cls: ClassSymbol, debugNme: Str, plCnt: Int, debugInfoPath: Path, scopedLocals: List[Local])(using h: HandlerCtx) = + HandlerCtx(N, N, 0, scopedLocals, h.debugInfo.nest(debugNme, debugInfoPath, scopedLocals)) + // funcLikeHandlerCtx(Value.Ref(bms, S(cls)), S(Value.Ref(cls)), debugNme, 0, curLocals) private def freshTmp(dbgNme: Str = "tmp") = new TempSymbol(N, dbgNme) private def intLit(i: BigInt) = Value.Lit(Tree.IntLit(i)) @@ -248,17 +245,16 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, val result = mutable.HashMap.empty[StateId, BlockPartition] val freshId = FreshId() - // * returns (truncated input block, child block states) // * blk: The block to transform // * labelIds: maps label IDs to the state at the start of the label and the state after the label - // * afterEnd: what state End should jump to, if at all + // * afterEnd: what state End should jump to, if at all // TODO: don't split within Match, Begin and Labels when not needed, ideally keep it intact. // Need careful analysis for this. def go(blk: Block)(using labelIds: Map[Symbol, (StateId, StateId)], afterEnd: Option[StateId]): Block = boundary: // First check if the current block contain any non trivial call, if so we need a partition // sym: the local that stores the result - def doNewPartition(sym: Local, res: Result, rst: Block) = + def doNewEffectPartition(sym: Local, res: Result, rst: Block) = val stateId = freshId() result(stateId) = BlockPartition(go(rst), S(sym)) val newBlock = blockBuilder @@ -270,6 +266,8 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, sym.asPath :: intLit(h.plCnt) :: h.currentFun.get :: + h.debugInfo.debugInfoPath :: + res.toLoc.fold(unit)(locToStr(_)) :: intLit(stateId) :: h.thisPath.getOrElse(unit) :: intLit(h.currentLocals.length) :: @@ -284,14 +282,14 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, case Return(c @ Call(fun, args), false) => b // Prevents the recursion into applyResult case Assign(lhs, EffectfulResult(r), rest) => // Optimization to reuse lhs instead of fresh local - doNewPartition(lhs, r, rest) + doNewEffectPartition(lhs, r, rest) case _ => super.applyBlock(b) override def applyResult(r: Result)(k: Result => Block) = r match case EffectfulResult(r) => // Fallback case, this may lead to unnecessary vars if it is assign-like // FIXME: This fall back case might be not needed at all. val l = freshTmp() - doNewPartition(l, r, k(Value.Ref(l))) + doNewEffectPartition(l, r, k(Value.Ref(l))) case _ => super.applyResult(r)(k) // If current block contains direct effectful result the following call will early exit. @@ -321,6 +319,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, result(id) = BlockPartition(newRest, N) id val newBody = go(body)(using labelIds + (label -> (startId, endId)), S(endId)) + result(startId) = BlockPartition(newBody, N) StateTransition(startId) case Break(label) => @@ -382,7 +381,8 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, case _: HandleBlock => lastWords("unexpected handleBlock") // already translated at this point val initId = freshId() - result(initId) = BlockPartition(go(blk)(using Map(), N), N) + val initPart = BlockPartition(go(blk)(using Map(), N), N) + result(initId) = initPart PartitionedBlock(initId, Map.from(result)) // extraLocals is used for things like immutable parameters, they are not mutated but they should still be added as locals for debugging @@ -431,30 +431,50 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, // All the defined variables are masked away from the inner scope (TODO: Scoped) given HandlerCtx = h - val subblockTransform = new BlockTransformer(SymbolSubst()): - override def applyFunDefn(fun: FunDefn): FunDefn = - if !h.isTopLevel then - raise(WarningReport(msg"Unexpected nested function: lambdas may not function correctly." -> fun.sym.toLoc :: Nil, source = Diagnostic.Source.Compilation)) - val bod2 = translateBlock(fun.body, functionHandlerCtx(Value.Ref(fun.sym, N), fun.sym.nme, fun.params.length, - fun.body.definedVars ++ fun.params.flatMap(_.paramSyms))) - if fun.body is bod2 then fun else + def translateFunLike(fun: FunDefn, funcPath: Path, thisPath: Option[Path], debugNme: Str) = + val varList = (fun.body.definedVars ++ 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 bod2 = translateBlock(fun.body, funcLikeHandlerCtx(funcPath, thisPath, debugNme, fun.params.length, + if opt.debug then debugInfoSym.asPath else Value.Lit(Tree.UnitLit(true)), varList)) + val fun2 = if fun.body is bod2 then fun else FunDefn(fun.owner, fun.sym, fun.params, bod2) + (debugInfoSym, debugInfo, fun2) + + val subblockTransform = new BlockTransformer(SymbolSubst()): override def applyDefn(defn: Defn)(k: Defn => Block): Block = defn match + case fun: FunDefn => + if !h.isTopLevel then + raise(WarningReport(msg"Unexpected nested function: lambdas may not function correctly." -> fun.sym.toLoc :: Nil, source = Diagnostic.Source.Compilation)) + val (debugInfoSym, debugInfo, fun2) = translateFunLike(fun, Value.Ref(fun.sym, N), N, fun.sym.nme) + if opt.debug then Assign(debugInfoSym, Tuple(false, debugInfo), k(fun2)) else k(fun2) case ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, methods, privateFields, publicFields, preCtor, ctor, companion, bufferable) => if !h.isTopLevel then raise(WarningReport(msg"Unexpected nested class: lambdas may not function correctly." -> isym.toLoc :: Nil, source = Diagnostic.Source.Compilation)) + val debugInfos = mutable.ArrayBuffer.empty[(Local, List[Arg])] val newMtds = methods.map: f => - val bod2 = translateBlock(f.body, - funcLikeHandlerCtx(Value.Ref(isym).sel(new Tree.Ident(f.sym.nme), f.sym.asTrm.get), S(Value.Ref(isym)), s"${sym.nme}.${f.sym.nme}", f.params.length, - f.body.definedVars ++ f.params.flatMap(_.paramSyms))) - if f.body is bod2 then f else - FunDefn(f.owner, f.sym, f.params, bod2) + 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 blk = Begin(preCtor, ctor) - // FIXME: ctor cannot yield effect with call cnt 0 - // Possible solution: use a dummy ctor instead, which is a call to another normal static function. The function should also handle the super call. - // val newCtor = translateBlock(blk, ctorCtx(sym, isym.asInstanceOf[ClassSymbol], s"‹constructor of ${sym.nme}›", 0, blk.definedVars)) - val newCtor = blk - k(ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, newMtds, privateFields, publicFields, End(), newCtor, companion, bufferable)) + val newCtor = translateTrivialOrTopLevel(blk) + 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 + val newCtor = translateTrivialOrTopLevel(bod.ctor) + ClsLikeBody(bod.isym, newMtds, bod.privateFields, bod.publicFields, newCtor) + val c2 = ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, newMtds, privateFields, publicFields, End(), newCtor, companion2, bufferable) + if opt.debug then + debugInfos.foldRight(k(c2)): (elem, blk) => + Assign(elem._1, Tuple(false, elem._2), blk) + else k(c2) case _ => super.applyDefn(defn)(k) val b = subblockTransform.applyBlock(blk) if h.isTopLevel then @@ -485,12 +505,11 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, val computeOff = Assign(getSavedTmp, Call(State.builtinOpsMap("+").asPath, paths.runtimePath.selSN("resumeIdx").asArg :: intLit(off).asArg :: Nil)(false, false), _) (computeOff, DynSelect(paths.runtimePath.selSN("resumeArr"), getSavedTmp.asPath, true)) - val (pcComputeOff, pcSavePath) = getSaved(-3) val restoreMap = mutable.HashMap.empty[Local, mutable.ArrayBuffer[StateId]] parts.states.foreach: (id, part) => part.sym.foreach: l => restoreMap.getOrElseUpdate(l, mutable.ArrayBuffer.empty) += id - val restoreVars = h.currentLocals.zipWithIndex.foldLeft(blockBuilder.chain(pcComputeOff).assign(pcVar, pcSavePath)): + val restoreVars = h.currentLocals.zipWithIndex.foldLeft(blockBuilder.assign(pcVar, paths.resumePc)): case (builder, (local, idx)) => val (computeOff, savePath) = getSaved(idx) builder.chain(Match( @@ -511,7 +530,32 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, // We shall add back the top level effect checks here // If said block is trivial, this function will still add the debug information, for in the case where the error // is raised in a tail call. - b + def topLevelCheck(l: Local, r: Result, rst: Block): Block = + blockBuilder + .assign(l, r) + .ifthen( + l.asPath, + Case.Cls(paths.effectSigSym, paths.effectSigPath), + Assign(l, Call(paths.topLevelEffectPath, l.asPath.asArg :: Value.Lit(Tree.BoolLit(opt.debug)).asArg :: Nil)(true, false), End()), + N) + .rest(rst) + val trivialTransform = new BlockTransformerShallow(SymbolSubst()): + override def applyBlock(b: Block) = b match + // Important: trivial block that contain tail call also pass through this, important to not treat those call as top level + // TODO: separate trivial logic (debugging) and top level (sanity checks and print stack effect) + case Return(EffectfulResult(r), false) => b + case Assign(lhs, EffectfulResult(r), rest) => + // Optimization to reuse lhs instead of fresh local + topLevelCheck(lhs, r, applyBlock(rest)) + case _ => super.applyBlock(b) + override def applyResult(r: Result)(k: Result => Block) = r match + case EffectfulResult(r) => + // Fallback case, this may lead to unnecessary vars if it is assign-like + // FIXME: This fall back case might be not needed at all. + val l = freshTmp() + topLevelCheck(l, r, k(Value.Ref(l))) + case _ => super.applyResult(r)(k) + trivialTransform.applyBlock(b) // private def firstPass(b: Block)(using HandlerCtx): Block = // val getLocalsSym = ctx.builtins.debug.getLocals @@ -607,54 +651,17 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, // // we move all function defns to the top level of the handler block // val (blk, defns) = b.floatOutDefns() // defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) + + private def locToStr(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")) - private def locToStr(l: Loc): Str = + private def locToVarStr(l: Loc): Str = Scope.replaceInvalidCharacters(l.origin.fileName.last + "_L" + l.origin.startLineNum + "_" + l.spanStart + "_" + l.spanEnd) private def symToStr(s: Symbol): Str = s"${Scope.replaceInvalidCharacters(s.nme)}" - // private def translateFun(f: FunDefn)(using HandlerCtx): FunDefn = - // val callSelf = f.params match - // case pList :: Nil => - // val params = pList.params.map(p => p.sym.asPath.asArg) - // f.owner match - // case None => S(Call(f.sym.asPath, params)(true, true)) - // case Some(owner) => - // S(Call(Select(owner.asPath, Tree.Ident(f.sym.nme))(N), params)(true, true)) - // case _ => None // TODO: more than one plist - - // FunDefn(f.owner, f.sym, f.params, translateBlock(f.body, - // f.params.flatMap(_.paramSyms).toSet, - // callSelf, - // L(f.sym), - // functionHandlerCtx(s"Cont$$func$$${symToStr(f.sym)}$$", f.sym.nme)) - // ) - - // private def translateBody(cls: ClsLikeBody, sym: BlockMemberSymbol)(using HandlerCtx): ClsLikeBody = - // val curCtorCtx = - // if handlerCtx.isTopLevel - // then - // topLevelCtx(s"Cont$$modCtor$$${symToStr(sym)}$$", s"‹constructor of ${sym.nme}›") - // else ctorCtx( - // cls.isym.asPath, - // s"Cont$$ctor$$${symToStr(sym)}$$", s"‹constructor of ${sym.nme}›") - // ClsLikeBody( - // cls.isym, - // cls.methods.map(translateFun), - // cls.privateFields, - // cls.publicFields, - // translateBlock(cls.ctor, Set.empty, N, R(cls.isym), curCtorCtx), - // ) - - // 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 is rewritten into: // 1. Instantiation of the handler // 2. An effectful call to enterHandleBlock @@ -678,11 +685,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, // Some limited handling of effects extending classes and having access to their fields. // Currently does not support super() raising effects. - // TODO: prob fixed (?) - val tmp = freshTmp() - val ctor = blockBuilder - .assign(tmp, Call(Value.Ref(State.builtinOpsMap("super")), h.args.map(_.asArg))(true, true)) - .ret(h.cls.asPath) + // TODO: prob fixed (?) ^^^ val clsDefn = ClsLikeDefn( N, // no owner @@ -691,8 +694,9 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, syntax.Cls, N, Nil, S(h.par), handlerMtds, Nil, Nil, + // Apparently, the lifter is not happy with any assignment in the preCtor... + Return(Call(Value.Ref(State.builtinOpsMap("super")), h.args.map(_.asArg))(true, true), true), End(), - Assign(freshTmp(), Call(Value.Ref(State.builtinOpsMap("super")), h.args.map(_.asArg))(true, true), End()), N, N, ) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index ce08029229..fe616cc71e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -247,9 +247,10 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): case PubField(isym, sym) => Select(isym.asPath, Tree.Ident(sym.nme))(d) - def isHandlerClsPath(p: Path) = handlerPaths match - case None => false - case Some(paths) => paths.isHandlerClsPath(p) + + val ignoredSet = Set(State.globalThisSymbol.asPath.selSN("Object"), State.runtimeSymbol.asPath.selSN("NonLocalReturn")) + + def isIgnoredPath(p: Path) = ignoredSet.contains(p) /** * Creates a capture class for a function consisting of its mutable (and possibly immutable) local variables. @@ -476,7 +477,7 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): // If B extends A, then A -> B is an edge parentPath match case None => () - case Some(path) if isHandlerClsPath(path) => () + case Some(path) if isIgnoredPath(path) => () case Some(Select(RefOfBms(s, _), Tree.Ident("class"))) => if clsSyms.contains(s) then extendsGraph += (s -> defn.sym) case Some(RefOfBms(s, _)) => @@ -639,14 +640,14 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): case Some(c: ClsLikeDefn) => Value.Lit(Tree.BoolLit(false)).asArg :: getCallArgs(l, ctx) case _ => getCallArgs(l, ctx) applyListOf(args, applyArg(_)(_)): newArgs => - k(Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(c.isMlsFun, false)) + k(Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(c.isMlsFun, c.mayRaiseEffects)) case _ => super.applyResult(r)(k) case c @ Instantiate(mut, InstSel(l), args) => ctx.bmsReqdInfo.get(l) match case Some(info) if !ctx.isModOrObj(l) => val extraArgs = Value.Lit(Tree.BoolLit(mut)).asArg :: getCallArgs(l, ctx) applyListOf(args, applyArg(_)(_)): newArgs => - k(Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(true, false)) + k(Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(true, true)) case _ => super.applyResult(r)(k) // LEGACY CODE: We previously directly created the closure and assigned it to the // variable here. But, since this closure may be re-used later, this doesn't work @@ -944,7 +945,7 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): val args2 = headPlistCopy.params.map(p => p.sym.asPath.asArg) val bdy = blockBuilder - .ret(Call(singleCallBms.asPath, args1 ++ args2)(true, false)) // TODO: restParams not considered + .ret(Call(singleCallBms.asPath, args1 ++ args2)(true, true)) // TODO: restParams not considered val mainDefn = FunDefn(f.owner, f.sym, PlainParamList(extraParamsCpy) :: headPlistCopy :: Nil, bdy) val auxDefn = FunDefn(N, singleCallBms, flatPlist, lifted.body) @@ -1035,7 +1036,7 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): ) for ps <- newAuxSyms do - val call = Call(curSym.asPath, ps.map(_.asPath.asArg))(true, false) + val call = Call(curSym.asPath, ps.map(_.asPath.asArg))(true, true) curSym = TempSymbol(None, "tmp") val thisSym = curSym acc = acc.assign(thisSym, call) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index c295becb56..c891981c32 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -512,14 +512,6 @@ class Lowering()(using Config, TL, Raise, State, Ctx): t.toLoc :: Nil, source = Diagnostic.Source.Compilation) conclude(Value.Ref(State.runtimeSymbol).selSN("raisePrintStackEffect").withLocOf(f)) - case t if instantiatedResolvedBms.exists(_ is ctx.builtins.debug.getLocals) => - if !config.effectHandlers.exists(_.debug) then - return fail: - ErrorReport( - msg"Debugging functions are not enabled" -> - t.toLoc :: Nil, - source = Diagnostic.Source.Compilation) - conclude(Value.Ref(ctx.builtins.debug.getLocals, N).withLocOf(f)) // * Due to whacky JS semantics, we need to make sure that selections leading to a call // * are preserved in the call and not moved to a temporary variable. case sel @ Sel(prefix, nme) => @@ -955,7 +947,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): HandlerLowering(handlerPaths, opt).translateHandleBlocks(desug) val lifted = - if config.effectHandlers.nonEmpty || lift then Lifter(S(handlerPaths)).transform(withHandlers1) + if lift then Lifter(S(handlerPaths)).transform(withHandlers1) else withHandlers1 val (withHandlers2, doUnwindPaths) = config.effectHandlers.fold((lifted, Map.empty)): opt => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala index 31432b48e3..a6c6ca56e3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala @@ -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 cdafc57c6b..c68de4a378 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -181,7 +181,6 @@ object Elaborator: val plus_impl = assumeObject("plus_impl") object debug extends VirtualModule(assumeBuiltinMod("debug")): val printStack = assumeObject("printStack") - val getLocals = assumeObject("getLocals") object annotations extends VirtualModule(assumeBuiltinMod("annotations")): val compile = assumeObject("compile") val buffered = assumeObject("buffered") diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index 3f0a1a5830..e1971232be 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -328,10 +328,39 @@ globalThis.Object.freeze(class Runtime { Runtime.isResuming = true; Runtime.resumeValue = value; Runtime.resumeArr = this.saved; - Runtime.resumeIdx = 5; - Runtime.resumePc = this.saved.at(2); + Runtime.resumeIdx = 7; + Runtime.resumePc = this.saved.at(4); tmp4 = globalThis.Object.freeze([]); - return f.apply(this.saved.at(3), tmp4) + return f.apply(this.saved.at(5), tmp4) + } + get getLocal() { + let debugInfo, res, i, scrut, tmp, tmp1, tmp2, tmp3, tmp4, tmp5; + debugInfo = this.saved.at(2); + res = []; + i = 1; + tmp6: while (true) { + scrut = i < debugInfo.length; + if (scrut === true) { + tmp = i + 1; + tmp1 = 7 + debugInfo.at(i); + tmp2 = globalThis.Object.freeze(new Runtime.LocalVarInfo.class(debugInfo.at(tmp), this.saved.at(tmp1))); + tmp3 = runtime.safeCall(res.push(tmp2)); + tmp4 = i + 2; + i = tmp4; + tmp5 = runtime.Unit; + continue tmp6 + } else { + tmp5 = runtime.Unit; + } + break; + } + return res; + } + get getNme() { + return this.saved.at(2).at(0); + } + get getLoc() { + return this.saved.at(3); } toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "FunctionContFrame", ["next", "saved"]]; @@ -603,42 +632,40 @@ globalThis.Object.freeze(class Runtime { } } static showStackTrace(header, tr, debug, showLocals) { - let msg, curHandler, atTail, scrut, cur, scrut1, locals, curLocals, loc, loc1, localsMsg, scrut2, scrut3, tmp, tmp1, lambda, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17, tmp18; + let msg, curHandler, atTail, scrut, cur, scrut1, curLocals, loc, loc1, localsMsg, scrut2, scrut3, tmp, lambda, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17; msg = header; curHandler = tr.contTrace; atTail = true; if (debug === true) { - tmp19: while (true) { + tmp18: while (true) { scrut = curHandler !== null; if (scrut === true) { cur = curHandler.next; - tmp20: while (true) { + tmp19: while (true) { scrut1 = cur !== null; if (scrut1 === true) { - locals = cur.getLocals; - tmp = locals.length - 1; - curLocals = runtime.safeCall(locals.at(tmp)); + curLocals = cur.getLocal; loc = cur.getLoc; if (loc === null) { - tmp1 = "pc=" + cur.pc; + tmp = "pc=" + cur.pc; } else { - tmp1 = loc; + tmp = loc; } - loc1 = tmp1; + loc1 = tmp; split_root$: { split_1$: { if (showLocals === true) { - scrut2 = curLocals.locals.length > 0; + scrut2 = curLocals.length > 0; if (scrut2 === true) { lambda = (undefined, function (l) { - let tmp21, tmp22; - tmp21 = l.localName + "="; - tmp22 = Rendering.render(l.value); - return tmp21 + tmp22 + let tmp20, tmp21; + tmp20 = l.localName + "="; + tmp21 = Rendering.render(l.value); + return tmp20 + tmp21 }); - tmp2 = runtime.safeCall(curLocals.locals.map(lambda)); - tmp3 = runtime.safeCall(tmp2.join(", ")); - tmp4 = " with locals: " + tmp3; + tmp1 = runtime.safeCall(curLocals.map(lambda)); + tmp2 = runtime.safeCall(tmp1.join(", ")); + tmp3 = " with locals: " + tmp2; break split_root$ } else { break split_1$ @@ -647,54 +674,54 @@ globalThis.Object.freeze(class Runtime { break split_1$ } } - tmp4 = ""; + tmp3 = ""; } - localsMsg = tmp4; - tmp5 = "\n\tat " + curLocals.fnName; - tmp6 = tmp5 + " ("; - tmp7 = tmp6 + loc1; - tmp8 = tmp7 + ")"; - tmp9 = msg + tmp8; + localsMsg = tmp3; + tmp4 = "\n\tat " + cur.getNme; + tmp5 = tmp4 + " ("; + tmp6 = tmp5 + loc1; + tmp7 = tmp6 + ")"; + tmp8 = msg + tmp7; + msg = tmp8; + tmp9 = msg + localsMsg; msg = tmp9; - tmp10 = msg + localsMsg; - msg = tmp10; cur = cur.next; atTail = false; - tmp11 = runtime.Unit; - continue tmp20 + tmp10 = runtime.Unit; + continue tmp19 } else { - tmp11 = runtime.Unit; + tmp10 = runtime.Unit; } break; } curHandler = curHandler.nextHandler; scrut3 = curHandler !== null; if (scrut3 === true) { - tmp12 = "\n\twith handler " + curHandler.handler.constructor.name; - tmp13 = msg + tmp12; - msg = tmp13; + tmp11 = "\n\twith handler " + curHandler.handler.constructor.name; + tmp12 = msg + tmp11; + msg = tmp12; atTail = false; - tmp14 = runtime.Unit; + tmp13 = runtime.Unit; } else { - tmp14 = runtime.Unit; + tmp13 = runtime.Unit; } - tmp15 = tmp14; - continue tmp19 + tmp14 = tmp13; + continue tmp18 } else { - tmp15 = runtime.Unit; + tmp14 = runtime.Unit; } break; } if (atTail === true) { - tmp16 = msg + "\n\tat tail position"; - msg = tmp16; - tmp17 = runtime.Unit; + tmp15 = msg + "\n\tat tail position"; + msg = tmp15; + tmp16 = runtime.Unit; } else { - tmp17 = runtime.Unit; + tmp16 = runtime.Unit; } - tmp18 = tmp17; + tmp17 = tmp16; } else { - tmp18 = runtime.Unit; + tmp17 = runtime.Unit; } return msg } diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index d6199e11a4..4a2fb53641 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -122,7 +122,7 @@ module TraceLogger with // Private definitions for algebraic effects // layout: -// | callCnt | f | pc | this | localCnt | locals... | +// | callCnt | f | debugInfo | pcStr | pc | this | localCnt | locals... | mut val isResuming = false mut val resumeValue = null @@ -146,9 +146,19 @@ data class FunctionContFrame(next, saved) with isResuming = true resumeValue = value resumeArr = saved - resumeIdx = 5 - resumePc = saved.[2] - f.apply(saved.[3], []) + resumeIdx = 7 + resumePc = saved.[4] + f.apply(saved.[5], []) + fun getLocal = + let debugInfo = saved.[2] + let res = mut [] + let i = 1 + while i < debugInfo.length do + res.push(new LocalVarInfo(debugInfo.[i + 1], saved.[7 + debugInfo.[i]])) + set i += 2 + res + fun getNme = saved.[2].[0] + fun getLoc = saved.[3] data abstract class FunctionContFrameOld(next) with fun resume(value) @@ -191,16 +201,15 @@ fun showStackTrace(header, tr, debug, showLocals) = while curHandler !== null do let cur = curHandler.next while cur !== null do - let locals = cur.getLocals - let curLocals = locals.at(locals.length - 1) + let curLocals = cur.getLocal let loc = cur.getLoc let loc = if loc is null then "pc=" + cur.pc else loc - let localsMsg = if showLocals and curLocals.locals.length > 0 then - " with locals: " + curLocals.locals.map(l => l.localName + "=" + Rendering.render(l.value)).join(", ") + let localsMsg = if showLocals and curLocals.length > 0 then + " with locals: " + curLocals.map(l => l.localName + "=" + Rendering.render(l.value)).join(", ") else "" set - msg += "\n\tat " + curLocals.fnName + " (" + loc + ")" + msg += "\n\tat " + cur.getNme + " (" + loc + ")" msg += localsMsg cur = cur.next atTail = false diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index f527cb10d8..3dee3215a8 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -79,6 +79,9 @@ abstract class MLsDiffMaker extends DiffMaker: output(s"$errMarker Option ':stackSafe' requires ':effectHandlers' to be set") if !effectHandlers.get.forall(effectHandlersOptions.contains(_)) then output(s"$errMarker Option ':effectHandlers' only supports 'debug' as option") + if effectHandlers.isSet then + if liftDefns.isUnset || noSanityCheck.isUnset then + output(s"$errMarker Option ':effectHandlers' requires ':lift' and ':noSanityCheck'") Config( sanityChecks = Opt.when(noSanityCheck.isUnset)(SanityChecks(light = true)), effectHandlers = Opt.when(effectHandlers.isSet)(EffectHandlers( From 0a9aa5f8b63e9ea3708f08df628365c5c133fadb Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Sat, 6 Dec 2025 21:13:12 +0800 Subject: [PATCH 06/14] Fix preCtor Return weirdness --- .../shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala | 4 +--- hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 1d366ac9cc..dd481db72a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -460,8 +460,6 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, S(Value.Ref(isym)), s"${sym.nme}#${f.sym.nme}") debugInfos += debugInfoSym -> debugInfo fun2 - val blk = Begin(preCtor, ctor) - val newCtor = translateTrivialOrTopLevel(blk) 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), @@ -470,7 +468,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, fun2 val newCtor = translateTrivialOrTopLevel(bod.ctor) ClsLikeBody(bod.isym, newMtds, bod.privateFields, bod.publicFields, newCtor) - val c2 = ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, newMtds, privateFields, publicFields, End(), newCtor, companion2, bufferable) + val c2 = ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, newMtds, privateFields, publicFields, translateTrivialOrTopLevel(preCtor), translateTrivialOrTopLevel(ctor), companion2, bufferable) if opt.debug then debugInfos.foldRight(k(c2)): (elem, blk) => Assign(elem._1, Tuple(false, elem._2), blk) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala index f934d160d2..0bd47a2885 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -303,8 +303,7 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: val privs = mkPrivs(pubFlds, privFlds, mtdPrefix, isym) - val preCtorCode = body(preCtor, true) - val ctorCode = doc"$preCtorCode${body(ctor, endSemi = true)}${ + val ctorCode = doc"${body(Begin(preCtor, ctor), endSemi = true)}${ kind match case syntax.Obj => doc" # ${defineProperty(doc"this", "class", doc"${scope.lookup_!(isym, isym.toLoc)}")}" From ee75aeb3b0eec0052a32baa58bdf0d7af1078ab9 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Sat, 6 Dec 2025 22:28:58 +0800 Subject: [PATCH 07/14] Implement stack safety --- .../scala/hkmc2/codegen/HandlerLowering.scala | 84 +++++++++++-------- .../hkmc2/codegen/StackSafeTransform.scala | 32 ++----- 2 files changed, 56 insertions(+), 60 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index dd481db72a..47f02933c7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -23,6 +23,12 @@ object HandlerLowering: private val nextIdent: Tree.Ident = Tree.Ident("next") private val lastIdent: Tree.Ident = Tree.Ident("last") private val contTraceIdent: Tree.Ident = Tree.Ident("contTrace") + private def unit = Value.Lit(Tree.UnitLit(true)) + private def intLit(i: BigInt) = Value.Lit(Tree.IntLit(i)) + + private def locToStr(loc: Loc) = + val (line, _, col) = loc.origin.fph.getLineColAt(loc.spanStart) + Value.Lit(Tree.StrLit(s"${loc.origin.fileName.last}:${line + loc.origin.startLineNum - 1}:$col")) extension (p: Path) def pc = p.selN(pcIdent) @@ -49,9 +55,22 @@ object HandlerLowering: thisPath: Option[Path], plCnt: Int, currentLocals: List[Local], + currentStackSafetySym: Option[FnOrCls], debugInfo: DebugInfo, ): def isTopLevel = currentFun.isEmpty + def doUnwind(res: Path, stateId: BigInt)(using paths: HandlerPaths) = + Return(Call(paths.unwindPath, ( + res :: + intLit(plCnt) :: + currentFun.get :: + debugInfo.debugInfoPath :: + res.toLoc.fold(unit)(locToStr(_)) :: + intLit(stateId) :: + thisPath.getOrElse(unit) :: + intLit(currentLocals.length) :: + currentLocals.map(_.asPath) + ).map(_.asArg))(true, true), false) // inScopeLocals: Local variables that are in scope, including those that come from outer. // prevLocalsFn: The function that gets the outer function's locals. @@ -89,18 +108,8 @@ class HandlerPaths(using Elaborator.State): val resumePc: Path = runtimePath.selSN("resumePc") class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, Elaborator.State, Elaborator.Ctx): - - private def funcLikeHandlerCtx(funcPath: Path, thisPath: Option[Path], debugNme: Str, plCnt: Int, debugInfoPath: Path, scopedLocals: List[Local])(using h: HandlerCtx) = - HandlerCtx(S(funcPath), thisPath, plCnt, scopedLocals, h.debugInfo.nest(debugNme, debugInfoPath, scopedLocals)) - private def topLevelCtx(nme: Str, debugNme: Str, curLocals: Set[Local]) = - HandlerCtx(N, N, 0, curLocals.toList, DebugInfo.topLevel(debugNme, curLocals)) - private def ctorCtx(bms: BlockMemberSymbol, cls: ClassSymbol, debugNme: Str, plCnt: Int, debugInfoPath: Path, scopedLocals: List[Local])(using h: HandlerCtx) = - HandlerCtx(N, N, 0, scopedLocals, h.debugInfo.nest(debugNme, debugInfoPath, scopedLocals)) - // funcLikeHandlerCtx(Value.Ref(bms, S(cls)), S(Value.Ref(cls)), debugNme, 0, curLocals) private def freshTmp(dbgNme: Str = "tmp") = new TempSymbol(N, dbgNme) - private def intLit(i: BigInt) = Value.Lit(Tree.IntLit(i)) - private def unit = Value.Lit(Tree.UnitLit(true)) private def rtThrowMsg(msg: Str) = Throw( Instantiate(mut = false, State.globalThisSymbol.asPath.selN(Tree.Ident("Error")), @@ -128,8 +137,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, case _ => N private class FreshId: - // IMPORTANT: this must be >= 1 otherwise we get state ID collions with the "entry" state 0. - var id: Int = 1 + var id: Int = 0 def apply() = val tmp = id id += 1 @@ -262,17 +270,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, .ifthen( sym.asPath, Case.Cls(paths.effectSigSym, paths.effectSigPath), - Return(Call(paths.unwindPath, ( - sym.asPath :: - intLit(h.plCnt) :: - h.currentFun.get :: - h.debugInfo.debugInfoPath :: - res.toLoc.fold(unit)(locToStr(_)) :: - intLit(stateId) :: - h.thisPath.getOrElse(unit) :: - intLit(h.currentLocals.length) :: - h.currentLocals.map(_.asPath) - ).map(_.asArg))(true, true), false) + h.doUnwind(sym.asPath, stateId)(using paths) ) .rest(StateTransition(stateId)) boundary.break(newBlock) @@ -414,7 +412,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, // FunDefn(N, BlockMemberSymbol("getLocals", Nil), PlainParamList(Nil) :: Nil, body) - var doUnwindMap: Map[FnOrCls, Path] = Map.empty + val doUnwindMap: mutable.Map[FnOrCls, Path => Return] = mutable.HashMap.empty /** * The actual translation: @@ -432,14 +430,16 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, given HandlerCtx = h def translateFunLike(fun: FunDefn, funcPath: Path, thisPath: Option[Path], debugNme: Str) = - val varList = (fun.body.definedVars ++ fun.params.flatMap(_.params.map(_.sym))).toList.sortBy(_.uid) + val varList = (fun.body.definedVars ++ fun.params.flatMap(_.params.map(_.sym))) + .filterNot(h.debugInfo.inScopeLocals(_)).toList.sortBy(_.uid) val debugInfo = Value.Lit(Tree.StrLit(debugNme)).asArg :: varList.zipWithIndex.filter(_._1.isInstanceOf[VarSymbol]) .flatMap: (sym, idx) => List(intLit(idx), Value.Lit(Tree.StrLit(sym.nme))) .map(_.asArg) val debugInfoSym = freshTmp(s"$debugNme$$debugInfo") - val bod2 = translateBlock(fun.body, funcLikeHandlerCtx(funcPath, thisPath, debugNme, fun.params.length, - if opt.debug then debugInfoSym.asPath else Value.Lit(Tree.UnitLit(true)), varList)) + val newCtx = HandlerCtx(S(funcPath), thisPath, fun.params.length, varList, S(L(fun.sym)), + h.debugInfo.nest(debugNme, if opt.debug then debugInfoSym.asPath else unit, varList)) + val bod2 = translateBlock(fun.body, newCtx) val fun2 = if fun.body is bod2 then fun else FunDefn(fun.owner, fun.sym, fun.params, bod2) (debugInfoSym, debugInfo, fun2) @@ -466,9 +466,13 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, S(Value.Ref(bod.isym)), s"${sym.nme}.${f.sym.nme}") debugInfos += debugInfoSym -> debugInfo fun2 - val newCtor = translateTrivialOrTopLevel(bod.ctor) + // We cannot use this bc there is no subblock transform... + // val newCtor = translateTrivialOrTopLevel(bod.ctor) + // TODO: Companion's ctor is more well behaved, handle this properly. + val newCtor = translateCtorLike(bod.ctor) + tl.log(s"companion name: ${bod.isym.nme}") ClsLikeBody(bod.isym, newMtds, bod.privateFields, bod.publicFields, newCtor) - val c2 = ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, newMtds, privateFields, publicFields, translateTrivialOrTopLevel(preCtor), translateTrivialOrTopLevel(ctor), companion2, bufferable) + val c2 = ClsLikeDefn(owner, isym, sym, kind, paramsOpt, auxParams, parentPath, newMtds, privateFields, publicFields, translateCtorLike(preCtor), translateCtorLike(ctor), companion2, bufferable) if opt.debug then debugInfos.foldRight(k(c2)): (elem, blk) => Assign(elem._1, Tuple(false, elem._2), blk) @@ -478,6 +482,10 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, if h.isTopLevel then return translateTrivialOrTopLevel(b) val parts = partitionBlock(b) + h.currentStackSafetySym.foreach: fnOrCls => + doUnwindMap += + fnOrCls -> + (res => h.doUnwind(res, parts.entry)(using paths)) if parts.states.size <= 1 then return translateTrivialOrTopLevel(b) @@ -524,6 +532,10 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, S(Assign(pcVar, intLit(parts.entry), End())), mainLoop) + private def translateCtorLike(b: Block)(using h: HandlerCtx): Block = + translateBlock(b, HandlerCtx(N, N, 0, b.definedVars.filterNot(h.debugInfo.inScopeLocals(_)).toList, N, + h.debugInfo.nest("ctor-like block", unit, b.definedVars.toList))) + private def translateTrivialOrTopLevel(b: Block)(using HandlerCtx): Block = // We shall add back the top level effect checks here // If said block is trivial, this function will still add the debug information, for in the case where the error @@ -649,10 +661,6 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, // // we move all function defns to the top level of the handler block // val (blk, defns) = b.floatOutDefns() // defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) - - private def locToStr(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")) private def locToVarStr(l: Loc): Str = Scope.replaceInvalidCharacters(l.origin.fileName.last + "_L" + l.origin.startLineNum + "_" + l.spanStart + "_" + l.spanEnd) @@ -963,8 +971,12 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, - def translateTopLevel(b: Block): (Block, Map[FnOrCls, Path]) = - doUnwindMap = Map.empty - val transformed = translateBlock(b, topLevelCtx(s"Cont$$topLevel$$BAD", "‹top level›", b.definedVars)) + def translateTopLevel(b: Block): (Block, collection.Map[FnOrCls, Path => Return]) = + doUnwindMap.clear() + val ctx = HandlerCtx(N, N, 0, b.definedVars.toList, N, DebugInfo.topLevel( + "‹top level›", + b.definedVars + )) + val transformed = translateBlock(b, ctx) (transformed, doUnwindMap) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index df9f4130eb..c3dc7f151b 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,33 @@ 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 + 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? val resSym = TempSymbol(None, "stackDelayRes") blockBuilder .staticif(usedDepth, _.assign(curDepth, stackDepthPath)) .rest(newBody) else val resSym = TempSymbol(None, "stackDelayRes") - val rewritten = blockBuilder + blockBuilder .staticif(usedDepth, _.assign(curDepth, stackDepthPath)) .assignFieldN(runtimePath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(increment))) .assign(resSym, Call(checkDepthPath, Nil)(true, true)) .ifthen( resSym.asPath, Case.Cls(paths.effectSigSym, paths.effectSigPath), - Return( - Call(doUnwindPath.get, resSym.asPath.asArg :: intLit(0).asArg :: Nil)(true, false), - false - ) + doUnwind.get(resSym.asPath) ) .rest(newBody) - // Float out defns, including the doUnwind function, so that they appear at the top of the block - // This is because the doUnwind function must appear before the checks inserted by the stack - // safety pass. - // However, due to how tightly coupled the stack safety and handler lowering are, it might be - // better to simply merge the two passes in the future. - val (blk, defns) = doUnwindPath.get match - case Value.Ref(sym, _) => rewritten.floatOutDefns() - case _ => (rewritten, Nil) - defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) def rewriteFn(defn: FunDefn) = - if doUnwindFns.contains(defn.sym) then defn - else FunDefn(defn.owner, defn.sym, defn.params, rewriteBlk(defn.body, L(defn.sym), 1)) + FunDefn(defn.owner, defn.sym, defn.params, rewriteBlk(defn.body, L(defn.sym), 1)) def transformTopLevel(b: Block) = transform(b, TempSymbol(N), true) From cb950b9f3d2090582ff73415d6a08f180da68cdf Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Sun, 7 Dec 2025 19:49:02 +0800 Subject: [PATCH 08/14] Stack safety fix and test changes --- .../scala/hkmc2/codegen/HandlerLowering.scala | 4 +- .../src/test/mlscript-compile/Runtime.mjs | 25 +- .../src/test/mlscript-compile/Runtime.mls | 4 +- .../test/mlscript/basics/ModuleMethods.mls | 12 +- .../src/test/mlscript/bbml/bbPrelude.mls | 1 - .../src/test/mlscript/decls/Prelude.mls | 1 - .../test/mlscript/handlers/BadHandlers.mls | 13 +- .../src/test/mlscript/handlers/Debugging.mls | 190 ++++---- .../mlscript/handlers/EffectInHandler.mls | 6 +- .../src/test/mlscript/handlers/Effects.mls | 137 +++--- .../test/mlscript/handlers/EffectsHygiene.mls | 89 +++- .../mlscript/handlers/EffectsInClasses.mls | 86 +--- .../mlscript/handlers/EffectsInMethods.mls | 2 + .../test/mlscript/handlers/GeneratorStack.mls | 2 + .../src/test/mlscript/handlers/Generators.mls | 2 + .../handlers/HandlerBlockBindings.mls | 8 +- .../mlscript/handlers/HandlersInClasses.mls | 12 +- .../mlscript/handlers/HandlersInMethods.mls | 2 + .../mlscript/handlers/HandlersScratch.mls | 2 + .../test/mlscript/handlers/LeakingEffects.mls | 2 + .../mlscript/handlers/ManualEffectBinding.mls | 2 + .../mlscript/handlers/ManualStackSafety.mls | 2 + .../mlscript/handlers/MultiResumption.mls | 2 + .../src/test/mlscript/handlers/NestedFun.mls | 2 + .../test/mlscript/handlers/NestedHandlers.mls | 7 +- .../test/mlscript/handlers/NoStackSafety.mls | 45 ++ .../mlscript/handlers/NonLocalReturns.mls | 32 +- .../mlscript/handlers/RecursiveHandlers.mls | 421 +++++++++--------- .../mlscript/handlers/ReturnInHandler.mls | 6 +- .../test/mlscript/handlers/SetInHandlers.mls | 2 + .../test/mlscript/handlers/StackSafety.mls | 246 +++++----- .../handlers/TailCallOptimization.mls | 2 + .../test/mlscript/handlers/UserThreads.mls | 4 +- .../mlscript/handlers/UserThreadsSafe.mls | 24 +- .../mlscript/handlers/UserThreadsUnsafe.mls | 22 +- .../test/mlscript/handlers/ZCombinator.mls | 4 +- .../src/test/mlscript/lifter/ClassInFun.mls | 10 +- .../test/mlscript/lifter/EffectHandlers.mls | 2 + .../test/mlscript/lifter/StackSafetyLift.mls | 221 +++++---- 39 files changed, 861 insertions(+), 795 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/handlers/NoStackSafety.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 47f02933c7..9db4d8207b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -482,12 +482,12 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, if h.isTopLevel then return translateTrivialOrTopLevel(b) val parts = partitionBlock(b) + if parts.states.size <= 1 && opt.stackSafety.isEmpty then + return translateTrivialOrTopLevel(b) h.currentStackSafetySym.foreach: fnOrCls => doUnwindMap += fnOrCls -> (res => h.doUnwind(res, parts.entry)(using paths)) - if parts.states.size <= 1 then - return translateTrivialOrTopLevel(b) val pcVar = freshTmp("pc") val mainLoopLbl = freshTmp("main") diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index e1971232be..9a1e9128ec 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -467,6 +467,22 @@ globalThis.Object.freeze(class Runtime { toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "LocalVarInfo", ["localName", "value"]]; }); + this.FakeError = function FakeError(stack) { + return globalThis.Object.freeze(new FakeError.class(stack)); + }; + globalThis.Object.freeze(class FakeError { + static { + Runtime.FakeError.class = this + } + constructor(stack) { + this.stack = stack; + } + toString() { + return this.stack + } + [prettyPrint]() { return this.toString(); } + static [definitionMetadata] = ["class", "FakeError", ["stack"]]; + }); this.stackLimit = 0; this.stackDepth = 0; this.stackHandler = null; @@ -608,8 +624,8 @@ globalThis.Object.freeze(class Runtime { return Runtime.mkEffect(Runtime.PrintStackEffect, showLocals) } static topLevelEffect(tr, debug) { - let scrut, tmp, tmp1, tmp2, tmp3, tmp4, tmp5; - tmp6: while (true) { + let scrut, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; + tmp7: while (true) { scrut = tr.handler === Runtime.PrintStackEffect; if (scrut === true) { tmp = Runtime.showStackTrace("Stack Trace:", tr, debug, tr.handlerFun); @@ -618,7 +634,7 @@ globalThis.Object.freeze(class Runtime { tmp3 = runtime.safeCall(tmp2(runtime.Unit)); tr = tmp3; tmp4 = runtime.Unit; - continue tmp6 + continue tmp7 } else { tmp4 = runtime.Unit; } @@ -626,7 +642,8 @@ globalThis.Object.freeze(class Runtime { } if (tr instanceof Runtime.EffectSig.class) { tmp5 = "Error: Unhandled effect " + tr.handler.constructor.name; - throw Runtime.showStackTrace(tmp5, tr, debug, false) + tmp6 = Runtime.showStackTrace(tmp5, tr, debug, false); + throw Runtime.FakeError(tmp6) } else { return tr } diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index 4a2fb53641..92f882256d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -178,6 +178,8 @@ class NonLocalReturn with data class FnLocalsInfo(fnName, locals) data class LocalVarInfo(localName, value) +data class FakeError(stack) with + fun toString() = stack fun raisePrintStackEffect(showLocals) = @@ -188,7 +190,7 @@ fun topLevelEffect(tr, debug) = console.log(showStackTrace("Stack Trace:", tr, debug, tr.handlerFun)) set tr = resume(tr.contTrace)(()) if tr is EffectSig then - throw showStackTrace("Error: Unhandled effect " + tr.handler.constructor.name, tr, debug, false) + throw FakeError(showStackTrace("Error: Unhandled effect " + tr.handler.constructor.name, tr, debug, false)) else tr 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 4e5ffb710e..2c3962fe06 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls @@ -65,7 +65,6 @@ declare module wasm with declare module debug with fun printStack - fun getLocals declare module annotations with object compile diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index b540e2c5ca..42fdffc90a 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -185,7 +185,6 @@ declare module wasm with declare module debug with fun printStack - fun getLocals declare module annotations with object compile diff --git a/hkmc2/shared/src/test/mlscript/handlers/BadHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/BadHandlers.mls index 5df46d2bf2..5da159ac74 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/BadHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/BadHandlers.mls @@ -1,11 +1,12 @@ :js :effectHandlers +:lift +:noSanityCheck +class Object -:re handle h = Object with {} h.oops -//│ ═══[RUNTIME ERROR] Error: Access to required field 'oops' yielded 'undefined' handle h = Object with {} val x = 1 @@ -18,16 +19,14 @@ x + 1 :e x //│ ╔══[ERROR] Name not found: x -//│ ║ l.19: x +//│ ║ l.20: x //│ ╙── ^ // Getter -:fixme handle h = Object with fun lol(k) = k(()) h.lol -//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$3 handle h = Object with fun lol()(k) = k(42) @@ -41,11 +40,9 @@ object Foo with x + 1 :e -:re Foo.x //│ ╔══[ERROR] Object 'Foo' does not contain member 'x' -//│ ║ l.45: Foo.x +//│ ║ l.43: Foo.x //│ ╙── ^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls index de274fb30f..ba0c58ad44 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls @@ -1,5 +1,7 @@ :js :effectHandlers debug +:lift +:noSanityCheck @@ -17,106 +19,84 @@ fun f() = print of raiseUnhandledEffect() j / i //│ JS (unsanitized): -//│ let f, getLocals2; -//│ getLocals2 = function getLocals() { -//│ let prev, thisInfo, arr, tmp; -//│ prev = []; -//│ arr = globalThis.Object.freeze([]); -//│ thisInfo = new runtime.FnLocalsInfo.class("\u2039top level\u203A", arr); -//│ tmp = runtime.safeCall(prev.push(thisInfo)); -//│ return prev -//│ }; +//│ let f, f$debugInfo; +//│ f$debugInfo = globalThis.Object.freeze([ +//│ "f", +//│ 0, +//│ "i", +//│ 1, +//│ "j", +//│ 2, +//│ "k" +//│ ]); //│ f = function f() { -//│ let i, j, k, scrut, tmp, tmp1, getLocals3, Cont$func$f$1, doUnwind; -//│ getLocals3 = function getLocals() { -//│ let i1, j1, k1, prev, thisInfo, arr, tmp2; -//│ prev = getLocals2(); -//│ i1 = new runtime.LocalVarInfo.class("i", i); -//│ j1 = new runtime.LocalVarInfo.class("j", j); -//│ k1 = new runtime.LocalVarInfo.class("k", k); -//│ arr = globalThis.Object.freeze([ -//│ i1, -//│ j1, -//│ k1 -//│ ]); -//│ thisInfo = new runtime.FnLocalsInfo.class("f", arr); -//│ tmp2 = runtime.safeCall(prev.push(thisInfo)); -//│ return prev -//│ }; -//│ globalThis.Object.freeze(class Cont$func$f$ extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$func$f$1 = this +//│ let i, j, k, scrut, tmp1, tmp2, pc, saveOffset; +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ i = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ j = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ k = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 3; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ if (pc === 4) { +//│ tmp1 = runtime.resumeValue; +//│ } else { +//│ saveOffset = runtime.resumeIdx + 4; +//│ tmp1 = runtime.resumeArr.at(saveOffset); //│ } -//│ constructor(pc) { -//│ let tmp2; -//│ tmp2 = super(null); -//│ this.pc = pc; +//│ if (pc === 2) { +//│ tmp2 = runtime.resumeValue; +//│ } else { +//│ saveOffset = runtime.resumeIdx + 5; +//│ tmp2 = runtime.resumeArr.at(saveOffset); //│ } -//│ resume(value$) { -//│ if (this.pc === 1) { -//│ tmp = value$; -//│ } else if (this.pc === 2) { -//│ tmp1 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 3) { -//│ return j / i -//│ } else if (this.pc === 4) { -//│ tmp1 = Predef.print(tmp); -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return this.doUnwind(tmp1, 2) -//│ } -//│ this.pc = 2; -//│ continue contLoop -//│ } else if (this.pc === 1) { -//│ this.pc = 4; -//│ continue contLoop -//│ } else if (this.pc === 2) { -//│ this.pc = 3; -//│ continue contLoop +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ if (pc === 0) { +//│ i = 0; +//│ j = 100; +//│ k = 2000; +//│ scrut = i == 0; +//│ if (scrut === true) { +//│ tmp1 = Predef.raiseUnhandledEffect(); +//│ if (tmp1 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp1, 1, f, f$debugInfo, null, 4, null, 6, i, j, k, scrut, tmp1, tmp2) //│ } -//│ break; +//│ pc = 4; +//│ continue main +//│ } else { +//│ tmp2 = runtime.Unit; +//│ pc = 1; +//│ continue main //│ } -//│ } -//│ get getLocals() { -//│ return getLocals3(); -//│ } -//│ get getLoc() { -//│ if (this.pc === 1) { -//│ return "Debugging.mls:17:14" -//│ } else if (this.pc === 2) { -//│ return "Debugging.mls:17:5" +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) { +//│ return j / i +//│ } else if (pc === 2) { +//│ pc = 1; +//│ continue main +//│ } else if (pc === 3) { +//│ tmp2 = Predef.print(tmp1); +//│ if (tmp2 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp2, 1, f, f$debugInfo, null, 2, null, 6, i, j, k, scrut, tmp1, tmp2) //│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$func$f$"]; -//│ }); -//│ doUnwind = function doUnwind(res1, pc) { -//│ res1.contTrace.last.next = new Cont$func$f$1(pc); -//│ res1.contTrace.last = res1.contTrace.last.next; -//│ return res1 -//│ }; -//│ i = 0; -//│ j = 100; -//│ k = 2000; -//│ scrut = i == 0; -//│ if (scrut === true) { -//│ tmp = Predef.raiseUnhandledEffect(); -//│ if (tmp instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp, 1) -//│ } -//│ tmp1 = Predef.print(tmp); -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp1, 2) -//│ } -//│ } else { tmp1 = runtime.Unit; } -//│ return j / i +//│ pc = 2; +//│ continue main +//│ } else if (pc === 4) { pc = 3; continue main } +//│ break; +//│ } //│ }; :re f() //│ ═══[RUNTIME ERROR] Error: Unhandled effect FatalEffect -//│ at f (Debugging.mls:17:14) +//│ at f (pc=undefined) :re fun lambda_test(f) = @@ -126,8 +106,8 @@ lambda_test(() => raiseUnhandledEffect() 100) //│ ═══[RUNTIME ERROR] Error: Unhandled effect FatalEffect -//│ at lambda (Debugging.mls:126:3) -//│ at lambda_test (Debugging.mls:123:3) +//│ at lambda (pc=undefined) +//│ at lambda_test (pc=undefined) import "../../mlscript-compile/Runtime.mls" @@ -136,13 +116,6 @@ import "../../mlscript-compile/Runtime.mls" let res = Runtime.try(f) res.reified.contTrace.next.getLocals -//│ = [ -//│ FnLocalsInfo("‹top level›", []), -//│ FnLocalsInfo( -//│ "f", -//│ [LocalVarInfo("i", 0), LocalVarInfo("j", 100), LocalVarInfo("k", 2000)] -//│ ) -//│ ] Runtime.debugEff(res.reified) //│ > Debug EffectSig: @@ -150,7 +123,7 @@ Runtime.debugEff(res.reified) //│ > handlerFun: null //│ > resumed: false //│ > -//│ > Cont$func$f$(pc=1, last) -> (null) +//│ > FunctionContFrame(pc=undefined, last) -> (null) //│ > res.resumeWith(42) @@ -163,20 +136,19 @@ class Debugger() with fun break(payload) module Test with - fun test(j)(using dbg: Debugger) = + fun test(j, dbg) = let i = 0 let k = 2000 if i == 0 do set i = dbg.break("whoops") j / i - fun main()(using Debugger) = - test(12) + test(34) + fun main(dbg) = + test(12, dbg) + test(34, dbg) let res = handle h = Debugger with fun break(payload)(resume) = resume - using Debugger = h - Runtime.try(() => Test.main()) + Runtime.try(() => Test.main(h)) //│ res = EffectHandle(_) :re @@ -195,20 +167,16 @@ res.resumeWith(666) let i = 100 fun f() = let j = 200 - print(debug.getLocals()) debug.printStack(true) set j = 300 debug.printStack(false) debug.printStack(true) - print(debug.getLocals()) //│ i = 100 f() -//│ > [FnLocalsInfo("‹top level›", [LocalVarInfo("i", 100)]), FnLocalsInfo("f", [LocalVarInfo("j", 200)])] //│ > Stack Trace: -//│ > at f (Debugging.mls:199:3) with locals: j=200 +//│ > at f (pc=undefined) with locals: j=200 //│ > Stack Trace: -//│ > at f (Debugging.mls:201:3) +//│ > at f (pc=undefined) //│ > Stack Trace: -//│ > at f (Debugging.mls:202:3) with locals: j=300 -//│ > [FnLocalsInfo("‹top level›", [LocalVarInfo("i", 100)]), FnLocalsInfo("f", [LocalVarInfo("j", 300)])] +//│ > at tail position diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls index b2d56c6af5..6f29dee06d 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck class PrintEffect with fun p(s: String): () @@ -17,10 +19,10 @@ handle h = PrintEffect with h.p(x) h.aux(3) //│ ╔══[ERROR] Name not found: h -//│ ║ l.15: h.p(x) +//│ ║ l.17: h.p(x) //│ ╙── ^ //│ ╔══[ERROR] Name not found: h -//│ ║ l.17: h.p(x) +//│ ║ l.19: h.p(x) //│ ╙── ^ handle h1 = PrintEffect with diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 41f5579d45..aa9a2c6730 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with @@ -156,70 +158,66 @@ if true do fun f() = 3 f() //│ JS (unsanitized): -//│ let tmp11, handleBlock$11; -//│ handleBlock$11 = function handleBlock$() { -//│ let f, h, scrut, res, Cont$handleBlock$h$11, doUnwind, Handler$h$12; -//│ globalThis.Object.freeze(class Handler$h$11 extends Effect1 { -//│ static { -//│ Handler$h$12 = this -//│ } -//│ constructor() { -//│ let tmp12; -//│ tmp12 = super(); -//│ } -//│ perform(arg) { -//│ let hdlrFun; -//│ hdlrFun = function hdlrFun(k) { -//│ return arg -//│ }; -//│ return runtime.mkEffect(this, hdlrFun) -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Handler$h$"]; -//│ }); -//│ h = new Handler$h$12(); -//│ globalThis.Object.freeze(class Cont$handleBlock$h$10 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handleBlock$h$11 = this -//│ } -//│ constructor(pc) { -//│ let tmp12; -//│ tmp12 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 1) { -//│ res = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 1) { -//│ return res -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$handleBlock$h$"]; -//│ }); -//│ doUnwind = function doUnwind(res1, pc) { -//│ res1.contTrace.last.next = new Cont$handleBlock$h$11(pc); -//│ return runtime.handleBlockImpl(res1, h) -//│ }; -//│ f = function f() { -//│ return 3 -//│ }; -//│ scrut = true; -//│ if (scrut === true) { -//│ res = f(); -//│ if (res instanceof runtime.EffectSig.class) { -//│ return doUnwind(res, 1) +//│ let f, h11, tmp11, handleBlock$11, Handler$h$perform11, Handler$h$23, Handler$h$perform$11; +//│ Handler$h$perform$11 = function Handler$h$perform$(Handler$h$$instance, arg, k) { +//│ return arg +//│ }; +//│ Handler$h$perform11 = function Handler$h$perform(Handler$h$$instance, arg) { +//│ return (k) => { +//│ return Handler$h$perform$11(Handler$h$$instance, arg, k) +//│ } +//│ }; +//│ globalThis.Object.freeze(class Handler$h$22 extends Effect1 { +//│ static { +//│ Handler$h$23 = this +//│ } +//│ constructor() { +//│ let tmp12; +//│ tmp12 = super(); +//│ if (tmp12 instanceof runtime.EffectSig.class) { +//│ tmp12 = runtime.topLevelEffect(tmp12, false); //│ } -//│ return res -//│ } else { -//│ return runtime.Unit +//│ tmp12; //│ } +//│ perform(arg) { +//│ let Handler$h$perform$here; +//│ Handler$h$perform$here = runtime.safeCall(Handler$h$perform11(this, arg)); +//│ return runtime.mkEffect(this, Handler$h$perform$here) +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "Handler$h$"]; +//│ }); +//│ h11 = new Handler$h$23(); +//│ if (h11 instanceof runtime.EffectSig.class) { +//│ h11 = runtime.topLevelEffect(h11, false); +//│ } +//│ f = function f() { +//│ return 3 //│ }; -//│ tmp11 = handleBlock$11(); +//│ handleBlock$11 = (undefined, function () { +//│ let scrut, pc; +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ scrut = runtime.resumeArr.at(runtime.resumeIdx); +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ if (pc === 0) { +//│ scrut = true; +//│ if (scrut === true) { +//│ return f() +//│ } else { +//│ return runtime.Unit +//│ } +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) {} +//│ break; +//│ } +//│ }); +//│ tmp11 = runtime.enterHandleBlock(h11, handleBlock$11); //│ if (tmp11 instanceof runtime.EffectSig.class) { //│ tmp11 = runtime.topLevelEffect(tmp11, false); //│ } @@ -370,6 +368,7 @@ handle h = Eff with foo(h) //│ = 123 +:w :expect 123 fun foo(h) = h.perform() @@ -394,6 +393,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.377: module A with +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.381: module A with +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.385: module A with +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.389: module A with +//│ ╙── ^ //│ = 123 // Access superclass fields diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls index ee16b6ecbd..6cc9c5cdd8 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,72 @@ fun foo(h): module M = else module A A +//│ ═══[WARNING] Modules are not yet lifted. +//│ ═══[WARNING] Modules are not yet lifted. +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.13: module A +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.16: module A +//│ ╙── ^ //│ JS (unsanitized): //│ let foo; //│ foo = function foo(h) { -//│ let A2, A3, scrut; -//│ globalThis.Object.freeze(class A { -//│ static { -//│ A2 = this -//│ } -//│ constructor() { -//│ runtime.Unit; -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "A"]; -//│ }); -//│ globalThis.Object.freeze(class A1 { -//│ static { -//│ A3 = this -//│ } -//│ constructor() { -//│ runtime.Unit; -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "A"]; -//│ }); -//│ scrut = false; -//│ if (scrut === true) { return A2 } else { return A3 } +//│ let A2, A3, scrut, pc, saveOffset; +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ A2 = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ A3 = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ h = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 3; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ if (pc === 0) { +//│ scrut = false; +//│ if (scrut === true) { +//│ globalThis.Object.freeze(class A { +//│ static { +//│ A2 = this +//│ } +//│ constructor() { +//│ runtime.Unit; +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "A"]; +//│ }); +//│ return A2 +//│ } else { +//│ globalThis.Object.freeze(class A1 { +//│ static { +//│ A3 = this +//│ } +//│ constructor() { +//│ runtime.Unit; +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "A"]; +//│ }); +//│ return A3 +//│ } +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) {} +//│ break; +//│ } //│ }; +//│ ═══[WARNING] Modules are not yet lifted. +//│ ═══[WARNING] Modules are not yet lifted. +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.13: module A +//│ ╙── ^ +//│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. +//│ ║ l.16: module A +//│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls index fbef803763..9c921c74e4 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with @@ -19,61 +21,17 @@ data class Lol(h) with //│ Lol1.class = this //│ } //│ constructor(h) { -//│ let tmp, res, Cont$ctor$Lol$1, doUnwind; -//│ const this$Lol = this; -//│ globalThis.Object.freeze(class Cont$ctor$Lol$ extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$ctor$Lol$1 = this -//│ } -//│ constructor(pc) { -//│ let tmp1; -//│ tmp1 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 1) { -//│ tmp = value$; -//│ } else if (this.pc === 2) { -//│ res = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 3) { -//│ return this$Lol -//│ } else if (this.pc === 4) { -//│ res = Predef.print(tmp); -//│ if (res instanceof runtime.EffectSig.class) { -//│ return this.doUnwind(res, 2) -//│ } -//│ this.pc = 2; -//│ continue contLoop -//│ } else if (this.pc === 1) { -//│ this.pc = 4; -//│ continue contLoop -//│ } else if (this.pc === 2) { -//│ this.pc = 3; -//│ continue contLoop -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$ctor$Lol$"]; -//│ }); -//│ doUnwind = function doUnwind(res1, pc) { -//│ res1.contTrace.last.next = new Cont$ctor$Lol$1(pc); -//│ res1.contTrace.last = res1.contTrace.last.next; -//│ return res1 -//│ }; +//│ let tmp, tmp1; //│ this.h = h; //│ tmp = runtime.safeCall(this.h.perform("k")); //│ if (tmp instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp, 1) +//│ tmp = runtime.topLevelEffect(tmp, false); //│ } -//│ res = Predef.print(tmp); -//│ if (res instanceof runtime.EffectSig.class) { -//│ return doUnwind(res, 2) +//│ tmp1 = Predef.print(tmp); +//│ if (tmp1 instanceof runtime.EffectSig.class) { +//│ tmp1 = runtime.topLevelEffect(tmp1, false); //│ } -//│ res; +//│ tmp1; //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Lol", ["h"]]; @@ -96,48 +54,52 @@ handle k = Eff with fun test()(k) = k() k.test() print("test") -//│ ═══[RUNTIME ERROR] TypeError: k.test is not a function - +//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$ +:fixme let oops = handle h = Effect with fun perform(arg)(k) = print(arg) "b" Lol(h) -//│ > k -//│ oops = "b" +//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$2 +//│ oops = undefined +:fixme :expect "b" oops -//│ = "b" +//│ ═══[RUNTIME ERROR] Expected: '"b"', got: 'undefined' +:fixme let oops = handle h = Effect with fun perform(arg)(k) = print(arg) "b" new Lol(h) -//│ > k -//│ oops = "b" +//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$4 +//│ oops = undefined +:fixme :expect "b" oops -//│ = "b" +//│ ═══[RUNTIME ERROR] Expected: '"b"', got: 'undefined' +:fixme let oops = handle h = Effect with fun perform(arg)(k) = print(arg) k("b") Lol(h) -//│ > k -//│ > b -//│ oops = Lol(Handler$h$) +//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$6 +//│ oops = undefined +:fixme oops.h -//│ = Handler$h$ +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'h') let obj = mut {s: undefined} //│ obj = {s: undefined} diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsInMethods.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsInMethods.mls index 8d9b6c4188..8721eead92 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsInMethods.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsInMethods.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with diff --git a/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls b/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls index 915a0e177f..0422e4ee14 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck import "../../mlscript-compile/Stack.mls" open Stack diff --git a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls index 6c4e5c7824..bf96f94259 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Generator with fun produce(result): () diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlerBlockBindings.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlerBlockBindings.mls index af49a30cab..8113352575 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlerBlockBindings.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlerBlockBindings.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with @@ -17,7 +19,7 @@ let x = 123 :e x //│ ╔══[ERROR] Name not found: x -//│ ║ l.18: x +//│ ║ l.20: x //│ ╙── ^ @@ -31,7 +33,7 @@ x + 1 :e x //│ ╔══[ERROR] Name not found: x -//│ ║ l.32: x +//│ ║ l.34: x //│ ╙── ^ @@ -43,7 +45,7 @@ val y = 123 :e y //│ ╔══[ERROR] Name not found: y -//│ ║ l.44: y +//│ ║ l.46: y //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlersInClasses.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlersInClasses.mls index 891f751b41..8d45b8edf6 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlersInClasses.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlersInClasses.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with @@ -20,12 +22,10 @@ object Lol with //│ > A :e -:re Lol.x //│ ╔══[ERROR] Object 'Lol' does not contain member 'x' -//│ ║ l.24: Lol.x +//│ ║ l.25: Lol.x //│ ╙── ^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' object Lol with @@ -39,12 +39,10 @@ object Lol with //│ > B :e -:re Lol.x //│ ╔══[ERROR] Object 'Lol' does not contain member 'x' -//│ ║ l.43: Lol.x +//│ ║ l.42: Lol.x //│ ╙── ^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' data class Lol() with @@ -60,8 +58,6 @@ let oops = Lol() //│ > A //│ oops = Lol() -:re oops.x -//│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlersInMethods.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlersInMethods.mls index ad9f06f16e..17514c4b66 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlersInMethods.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlersInMethods.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls index 8cc07bcdbe..254a3096fe 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect diff --git a/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls b/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls index 7ed02d55f3..f17fc6676a 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect[A] with diff --git a/hkmc2/shared/src/test/mlscript/handlers/ManualEffectBinding.mls b/hkmc2/shared/src/test/mlscript/handlers/ManualEffectBinding.mls index 22318f19bc..de7fabe8e7 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ManualEffectBinding.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ManualEffectBinding.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect with fun perform(): () diff --git a/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls index fa19da5680..63b8d29568 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck // * This file demonstrates the handler-based mechanism of our stack safety implementation // * by manually applying the transformation of a recursive factorial function diff --git a/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls b/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls index 87f3e9a4f9..964caee310 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck class Logger with fun info(s: Str): () diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedFun.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedFun.mls index 12e132e6c6..14385ad6d7 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedFun.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedFun.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck fun foo(f) = diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls index fb86903fd8..53b4ae6d81 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -1,12 +1,17 @@ :js :effectHandlers +:lift +:noSanityCheck let id = 0 //│ id = 0 - +// FIXME: if the following 2 blocks are together, lifter causes class ordering issue class MaybeStop with fun f(x: Bool): () + +// FIXME: Scoped related, id is treated as local by mistake if there is no (useless) assignment +set id = id fun handleEffects(g) = handle h1 = MaybeStop with fun f(x)(k) = diff --git a/hkmc2/shared/src/test/mlscript/handlers/NoStackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/NoStackSafety.mls new file mode 100644 index 0000000000..ecfd787002 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/NoStackSafety.mls @@ -0,0 +1,45 @@ +:js + +// sanity check +:expect 5050 +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +sum(100) +//│ = 5050 + + +// stack-overflows without :stackSafe +:re +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +sum(10000) +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + + +:re +fun foo() = + let f = () + set f = n => + if n <= 0 then 0 + else n + f(n-1) + f(10000) +foo() +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + +:sjs +:stackSafe 42 +fun hi(n) = n +hi(0) +//│ /!!!\ Option ':stackSafe' requires ':effectHandlers' to be set +//│ JS (unsanitized): +//│ let hi; hi = function hi(n) { return n }; hi(0) +//│ = 0 + +:stackSafe 42 +hi(0) +//│ /!!!\ Option ':stackSafe' requires ':effectHandlers' to be set +//│ = 0 diff --git a/hkmc2/shared/src/test/mlscript/handlers/NonLocalReturns.mls b/hkmc2/shared/src/test/mlscript/handlers/NonLocalReturns.mls index 6fff74eff2..0118c0d8bb 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NonLocalReturns.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NonLocalReturns.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck fun f() = @@ -55,18 +57,18 @@ f() :re fun f() = () => return 3 f()() -//│ ═══[RUNTIME ERROR] Error: Unhandled effect $_non$_local$_return$_effect$_6 +//│ ═══[RUNTIME ERROR] Error: Unhandled effect $_non$_local$_return$_effect$_12 :re fun f(): () -> Int = () => return () => 3 f()() -//│ ═══[RUNTIME ERROR] Error: Unhandled effect $_non$_local$_return$_effect$_7 +//│ ═══[RUNTIME ERROR] Error: Unhandled effect $_non$_local$_return$_effect$_14 :e return 100 //│ ╔══[ERROR] Return statements are not allowed outside of functions. -//│ ║ l.67: return 100 +//│ ║ l.69: return 100 //│ ╙── ^^^^^^^^^^ :e @@ -74,14 +76,14 @@ if true do while false do let f = () => return 100 //│ ╔══[ERROR] Return statements are not allowed outside of functions. -//│ ║ l.75: let f = () => return 100 +//│ ║ l.77: let f = () => return 100 //│ ╙── ^^^^^^^^^^ :e fun f() = type A = return "lol" //│ ╔══[ERROR] Return statements are not allowed in this context. -//│ ║ l.82: type A = return "lol" +//│ ║ l.84: type A = return "lol" //│ ╙── ^^^^^^^^^^^^ @@ -107,16 +109,21 @@ fun f() = return 100 4 f() -//│ ═══[RUNTIME ERROR] Expected: '100', got: '4' -//│ = 4 +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'nonLocalRetHandler$f' +//│ ═══[RUNTIME ERROR] ReferenceError: nonLocalRetHandler$f is not defined +//│ ═══[RUNTIME ERROR] Expected: '100', got: 'undefined' +// ctor cannot raise effect, so error is expected. +// However, there should be a runtime error raised. +:fixme :expect 100 fun f() = class A with return 100 new A f() -//│ = 100 +//│ ═══[RUNTIME ERROR] Error: Unhandled effect $_non$_local$_return$_effect$_20 +//│ ═══[RUNTIME ERROR] Expected: '100', got: 'undefined' :fixme :expect 100 @@ -125,9 +132,11 @@ fun f() = (() => return 100)() 4 f() -//│ ═══[RUNTIME ERROR] Expected: '100', got: '4' -//│ = 4 +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'nonLocalRetHandler$f' +//│ ═══[RUNTIME ERROR] ReferenceError: nonLocalRetHandler$f is not defined +//│ ═══[RUNTIME ERROR] Expected: '100', got: 'undefined' +:fixme :expect 100 fun f() = class A with @@ -135,4 +144,5 @@ fun f() = new A 4 f() -//│ = 100 +//│ ═══[RUNTIME ERROR] Error: Unhandled effect $_non$_local$_return$_effect$_24 +//│ ═══[RUNTIME ERROR] Expected: '100', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 74770fcc40..33a02a0f41 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class Effect[A] with @@ -19,13 +21,13 @@ h1.perform("hello") :e h1 //│ ╔══[ERROR] Name not found: h1 -//│ ║ l.20: h1 +//│ ║ l.22: h1 //│ ╙── ^^ :e h1.perform("oops") //│ ╔══[ERROR] Name not found: h1 -//│ ║ l.26: h1.perform("oops") +//│ ║ l.28: h1.perform("oops") //│ ╙── ^^ :re @@ -71,10 +73,10 @@ handle h2 = Effect with h2.perform(3) ] //│ ╔══[ERROR] Name not found: h2 -//│ ║ l.65: then h2.perform(arg - 1) + " " + arg +//│ ║ l.67: then h2.perform(arg - 1) + " " + arg //│ ╙── ^^ //│ ╔══[ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.66: else "0" +//│ ║ l.68: else "0" //│ ╙── ^^^^ //│ > ––– //│ > performing 2 @@ -155,218 +157,235 @@ if true do h1.perform(()) str //│ JS (unsanitized): -//│ let str, scrut, tmp9, tmp10, handleBlock$7; +//│ let str, scrut, h11, tmp11, tmp12, handleBlock$9, Handler$h2$perform1, Handler$h2$3, handleBlock$10, Handler$h1$perform1, Handler$h1$3, Handler$h1$perform$1, handleBlock$$2, Handler$h2$perform$1; //│ str = ""; //│ scrut = true; //│ if (scrut === true) { -//│ handleBlock$7 = function handleBlock$() { -//│ let h1, tmp11, handleBlock$8, Cont$handleBlock$h1$2, doUnwind, Handler$h1$2; -//│ globalThis.Object.freeze(class Handler$h1$1 extends Effect1 { -//│ static { -//│ Handler$h1$2 = this +//│ Handler$h1$perform$1 = function Handler$h1$perform$(Handler$h1$$instance, arg, k) { +//│ let tmp13, tmp14, tmp15, pc, saveOffset; +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ k = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ tmp13 = runtime.resumeArr.at(saveOffset); +//│ if (pc === 1) { +//│ tmp14 = runtime.resumeValue; +//│ } else { +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp14 = runtime.resumeArr.at(saveOffset); //│ } -//│ constructor() { -//│ let tmp12; -//│ tmp12 = super(); -//│ } -//│ perform(arg) { -//│ let hdlrFun; -//│ hdlrFun = function hdlrFun(k) { -//│ let tmp12, tmp13, tmp14, Cont$handler$h1$perform$2, doUnwind1; -//│ globalThis.Object.freeze(class Cont$handler$h1$perform$1 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handler$h1$perform$2 = this -//│ } -//│ constructor(pc) { -//│ let tmp15; -//│ tmp15 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 7) { -//│ tmp13 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 7) { -//│ tmp14 = str + "A"; -//│ str = tmp14; -//│ return runtime.Unit -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$handler$h1$perform$"]; -//│ }); -//│ doUnwind1 = function doUnwind(res7, pc) { -//│ res7.contTrace.last.next = new Cont$handler$h1$perform$2(pc); -//│ res7.contTrace.last = res7.contTrace.last.next; -//│ return res7 -//│ }; -//│ tmp12 = str + "A"; -//│ str = tmp12; -//│ tmp13 = runtime.safeCall(k(arg)); -//│ if (tmp13 instanceof runtime.EffectSig.class) { -//│ return doUnwind1(tmp13, 7) -//│ } -//│ tmp14 = str + "A"; -//│ str = tmp14; -//│ return runtime.Unit -//│ }; -//│ return runtime.mkEffect(this, hdlrFun) +//│ saveOffset = runtime.resumeIdx + 3; +//│ tmp15 = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 4; +//│ arg = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 5; +//│ Handler$h1$$instance = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ if (pc === 0) { +//│ tmp13 = str + "A"; +//│ str = tmp13; +//│ tmp14 = runtime.safeCall(k(arg)); +//│ if (tmp14 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp14, 1, Handler$h1$perform$1, null, null, 1, null, 6, k, tmp13, tmp14, tmp15, arg, Handler$h1$$instance) +//│ } +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) { +//│ tmp15 = str + "A"; +//│ str = tmp15; +//│ return runtime.Unit //│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Handler$h1$"]; -//│ }); -//│ h1 = new Handler$h1$2(); -//│ globalThis.Object.freeze(class Cont$handleBlock$h1$1 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handleBlock$h1$2 = this +//│ break; +//│ } +//│ }; +//│ Handler$h1$perform1 = function Handler$h1$perform(Handler$h1$$instance, arg) { +//│ return (k) => { +//│ return Handler$h1$perform$1(Handler$h1$$instance, arg, k) +//│ } +//│ }; +//│ globalThis.Object.freeze(class Handler$h1$2 extends Effect1 { +//│ static { +//│ Handler$h1$3 = this +//│ } +//│ constructor() { +//│ let tmp13; +//│ tmp13 = super(); +//│ if (tmp13 instanceof runtime.EffectSig.class) { +//│ tmp13 = runtime.topLevelEffect(tmp13, false); //│ } -//│ constructor(pc) { -//│ let tmp12; -//│ tmp12 = super(null); -//│ this.pc = pc; +//│ 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, saveOffset; +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ k = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ tmp13 = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp14 = runtime.resumeArr.at(saveOffset); +//│ if (pc === 1) { +//│ tmp15 = runtime.resumeValue; +//│ } else { +//│ saveOffset = runtime.resumeIdx + 3; +//│ tmp15 = runtime.resumeArr.at(saveOffset); //│ } -//│ resume(value$) { -//│ if (this.pc === 6) { -//│ tmp11 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 6) { -//│ return tmp11 -//│ } -//│ break; +//│ saveOffset = runtime.resumeIdx + 4; +//│ tmp16 = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 5; +//│ tmp17 = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 6; +//│ arg = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 7; +//│ Handler$h2$$instance = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ if (pc === 0) { +//│ tmp13 = str + "B"; +//│ tmp14 = str + tmp13; +//│ str = tmp14; +//│ tmp15 = runtime.safeCall(k(arg)); +//│ if (tmp15 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp15, 1, Handler$h2$perform$1, null, null, 1, null, 8, k, tmp13, tmp14, tmp15, tmp16, tmp17, arg, Handler$h2$$instance) //│ } +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) { +//│ tmp16 = str + "B"; +//│ tmp17 = str + tmp16; +//│ str = tmp17; +//│ return runtime.Unit //│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$handleBlock$h1$"]; -//│ }); -//│ doUnwind = function doUnwind(res7, pc) { -//│ res7.contTrace.last.next = new Cont$handleBlock$h1$2(pc); -//│ return runtime.handleBlockImpl(res7, h1) -//│ }; -//│ handleBlock$8 = function handleBlock$() { -//│ let h2, tmp12, res7, Cont$handleBlock$h2$2, doUnwind1, Handler$h2$2; -//│ globalThis.Object.freeze(class Handler$h2$1 extends Effect1 { -//│ static { -//│ Handler$h2$2 = this -//│ } -//│ constructor() { -//│ let tmp13; -//│ tmp13 = super(); -//│ } -//│ perform(arg) { -//│ let hdlrFun; -//│ hdlrFun = function hdlrFun(k) { -//│ let tmp13, tmp14, tmp15, tmp16, tmp17, Cont$handler$h2$perform$2, doUnwind2; -//│ globalThis.Object.freeze(class Cont$handler$h2$perform$1 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handler$h2$perform$2 = this -//│ } -//│ constructor(pc) { -//│ let tmp18; -//│ tmp18 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 3) { -//│ tmp15 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 3) { -//│ tmp16 = str + "B"; -//│ tmp17 = str + tmp16; -//│ str = tmp17; -//│ return runtime.Unit -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$handler$h2$perform$"]; -//│ }); -//│ doUnwind2 = function doUnwind(res8, pc) { -//│ res8.contTrace.last.next = new Cont$handler$h2$perform$2(pc); -//│ res8.contTrace.last = res8.contTrace.last.next; -//│ return res8 -//│ }; -//│ tmp13 = str + "B"; -//│ tmp14 = str + tmp13; -//│ str = tmp14; -//│ tmp15 = runtime.safeCall(k(arg)); -//│ if (tmp15 instanceof runtime.EffectSig.class) { -//│ return doUnwind2(tmp15, 3) -//│ } -//│ tmp16 = str + "B"; -//│ tmp17 = str + tmp16; -//│ str = tmp17; -//│ return runtime.Unit -//│ }; -//│ return runtime.mkEffect(this, hdlrFun) -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Handler$h2$"]; -//│ }); -//│ h2 = new Handler$h2$2(); -//│ globalThis.Object.freeze(class Cont$handleBlock$h2$1 extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$handleBlock$h2$2 = this +//│ break; +//│ } +//│ }; +//│ Handler$h2$perform1 = function Handler$h2$perform(Handler$h2$$instance, arg) { +//│ return (k) => { +//│ return Handler$h2$perform$1(Handler$h2$$instance, arg, k) +//│ } +//│ }; +//│ globalThis.Object.freeze(class Handler$h2$2 extends Effect1 { +//│ static { +//│ Handler$h2$3 = this +//│ } +//│ constructor() { +//│ let tmp13; +//│ tmp13 = super(); +//│ if (tmp13 instanceof runtime.EffectSig.class) { +//│ tmp13 = runtime.topLevelEffect(tmp13, false); +//│ } +//│ 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, saveOffset; +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ if (pc === 1) { +//│ tmp13 = runtime.resumeValue; +//│ } else { +//│ tmp13 = runtime.resumeArr.at(runtime.resumeIdx); +//│ } +//│ saveOffset = runtime.resumeIdx + 1; +//│ h22 = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ if (pc === 0) { +//│ tmp13 = runtime.safeCall(h22.perform(runtime.Unit)); +//│ if (tmp13 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp13, 1, handleBlock$$2, null, null, 1, null, 2, tmp13, h22) //│ } -//│ constructor(pc) { -//│ let tmp13; -//│ tmp13 = super(null); -//│ this.pc = pc; +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) { +//│ return runtime.safeCall(h11.perform(runtime.Unit)) +//│ } +//│ break; +//│ } +//│ }; +//│ handleBlock$9 = (undefined, function (h22) { +//│ return () => { +//│ return handleBlock$$2(h22) +//│ } +//│ }); +//│ handleBlock$10 = (undefined, function () { +//│ let h22, tmp13, handleBlock$$here, pc, saveOffset; +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ if (pc === 1) { +//│ h22 = runtime.resumeValue; +//│ } else { +//│ h22 = runtime.resumeArr.at(runtime.resumeIdx); +//│ } +//│ if (pc === 2) { +//│ tmp13 = runtime.resumeValue; +//│ } else { +//│ saveOffset = runtime.resumeIdx + 1; +//│ tmp13 = runtime.resumeArr.at(saveOffset); +//│ } +//│ saveOffset = runtime.resumeIdx + 2; +//│ handleBlock$$here = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ if (pc === 0) { +//│ h22 = new Handler$h2$3(); +//│ if (h22 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(h22, 1, handleBlock$10, null, null, 1, null, 3, h22, tmp13, handleBlock$$here) //│ } -//│ resume(value$) { -//│ if (this.pc === 1) { -//│ tmp12 = value$; -//│ } else if (this.pc === 2) { -//│ res7 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 1) { -//│ res7 = runtime.safeCall(h1.perform(runtime.Unit)); -//│ if (res7 instanceof runtime.EffectSig.class) { -//│ return this.doUnwind(res7, 2) -//│ } -//│ this.pc = 2; -//│ continue contLoop -//│ } else if (this.pc === 2) { -//│ return res7 -//│ } -//│ break; -//│ } +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) { +//│ handleBlock$$here = runtime.safeCall(handleBlock$9(h22)); +//│ tmp13 = runtime.enterHandleBlock(h22, handleBlock$$here); +//│ if (tmp13 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp13, 1, handleBlock$10, null, null, 2, null, 3, h22, tmp13, handleBlock$$here) //│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$handleBlock$h2$"]; -//│ }); -//│ doUnwind1 = function doUnwind(res8, pc) { -//│ res8.contTrace.last.next = new Cont$handleBlock$h2$2(pc); -//│ return runtime.handleBlockImpl(res8, h2) -//│ }; -//│ tmp12 = runtime.safeCall(h2.perform(runtime.Unit)); -//│ if (tmp12 instanceof runtime.EffectSig.class) { -//│ return doUnwind1(tmp12, 1) +//│ pc = 2; +//│ continue main +//│ } else if (pc === 2) { +//│ return tmp13 //│ } -//│ res7 = runtime.safeCall(h1.perform(runtime.Unit)); -//│ if (res7 instanceof runtime.EffectSig.class) { -//│ return doUnwind1(res7, 2) -//│ } -//│ return res7 -//│ }; -//│ tmp11 = handleBlock$8(); -//│ if (tmp11 instanceof runtime.EffectSig.class) { -//│ return doUnwind(tmp11, 6) +//│ break; //│ } -//│ return tmp11 -//│ }; -//│ tmp9 = handleBlock$7(); -//│ if (tmp9 instanceof runtime.EffectSig.class) { -//│ tmp9 = runtime.topLevelEffect(tmp9, false); +//│ }); +//│ tmp11 = runtime.enterHandleBlock(h11, handleBlock$10); +//│ if (tmp11 instanceof runtime.EffectSig.class) { +//│ tmp11 = runtime.topLevelEffect(tmp11, false); //│ } -//│ tmp10 = tmp9; -//│ } else { tmp10 = 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 86684cf7ea..b559b5a231 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -1,18 +1,12 @@ :js +:effectHandlers +:lift +:noSanityCheck -// sanity check -:expect 5050 -fun sum(n) = - if n == 0 then 0 - else - n + sum(n - 1) -sum(100) -//│ = 5050 // preserve tail calls // MUST see "return hi1(tmp)" in the output :stackSafe 5 -:effectHandlers :expect 0 :sjs fun hi(n) = @@ -20,52 +14,52 @@ fun hi(n) = else hi(n - 1) hi(0) //│ JS (unsanitized): -//│ let hi, res, $_stack$_safe$_body$_; +//│ let hi, tmp, $_stack$_safe$_body$_; //│ hi = function hi(n) { -//│ let scrut, tmp, Cont$func$hi$1, doUnwind, stackDelayRes; -//│ globalThis.Object.freeze(class Cont$func$hi$ extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$func$hi$1 = this -//│ } -//│ constructor(pc) { -//│ let tmp1; -//│ tmp1 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ return hi(n) -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$func$hi$"]; -//│ }); -//│ doUnwind = function doUnwind(res1, pc) { -//│ res1.contTrace.last.next = new Cont$func$hi$1(pc); -//│ res1.contTrace.last = res1.contTrace.last.next; -//│ return res1 -//│ }; +//│ let scrut, tmp1, pc, saveOffset, stackDelayRes; //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ return doUnwind(stackDelayRes, 0) +//│ return runtime.unwind(stackDelayRes, 1, hi, null, null, 0, null, 3, n, scrut, tmp1) //│ } -//│ scrut = n == 0; -//│ if (scrut === true) { -//│ return 0 +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp1 = 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; +//│ pc = 2; +//│ continue main +//│ } +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) {} else if (pc === 2) { +//│ 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) = @@ -74,79 +68,67 @@ fun sum(n) = n + sum(n - 1) sum(10000) //│ JS (unsanitized): -//│ let sum1, res1, $_stack$_safe$_body$_1; -//│ sum1 = function sum(n) { -//│ let scrut, tmp, tmp1, Cont$func$sum$1, doUnwind, curDepth, stackDelayRes; -//│ globalThis.Object.freeze(class Cont$func$sum$ extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$func$sum$1 = this -//│ } -//│ constructor(pc) { -//│ let tmp2; -//│ tmp2 = super(null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ let curDepth1; -//│ curDepth1 = runtime.stackDepth; -//│ if (this.pc === 1) { -//│ tmp1 = value$; -//│ } -//│ contLoop: while (true) { -//│ runtime.stackDepth = curDepth1; -//│ if (this.pc === 0) { -//│ return sum1(n) -//│ } else if (this.pc === 1) { -//│ return n + tmp1 -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$func$sum$"]; -//│ }); -//│ doUnwind = function doUnwind(res2, pc) { -//│ res2.contTrace.last.next = new Cont$func$sum$1(pc); -//│ res2.contTrace.last = res2.contTrace.last.next; -//│ return res2 -//│ }; +//│ let sum, tmp1, $_stack$_safe$_body$_1; +//│ sum = function sum(n) { +//│ let scrut, tmp2, tmp3, pc, saveOffset, 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, null, 0, null, 4, n, scrut, tmp2, tmp3) //│ } -//│ scrut = n == 0; -//│ if (scrut === true) { -//│ return 0 +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp2 = runtime.resumeArr.at(saveOffset); +//│ if (pc === 2) { +//│ tmp3 = runtime.resumeValue; +//│ } else { +//│ saveOffset = runtime.resumeIdx + 3; +//│ tmp3 = 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) { +//│ if (pc === 0) { +//│ scrut = n == 0; +//│ if (scrut === true) { +//│ return 0 +//│ } else { +//│ tmp2 = n - 1; +//│ pc = 3; +//│ continue main +//│ } +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) {} else if (pc === 2) { +//│ return n + tmp3 +//│ } else if (pc === 3) { +//│ tmp3 = sum(tmp2); +//│ runtime.stackDepth = curDepth; +//│ if (tmp3 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp3, 1, sum, null, null, 2, null, 4, n, scrut, tmp2, tmp3) +//│ } +//│ pc = 2; +//│ continue main //│ } -//│ return n + tmp1 +//│ break; //│ } //│ }; //│ $_stack$_safe$_body$_1 = (undefined, function () { -//│ return sum1(10000) +//│ return sum(10000) //│ }); -//│ res1 = runtime.runStackSafe(1000, $_stack$_safe$_body$_1); -//│ if (res1 instanceof runtime.EffectSig.class) { res1 = runtime.topLevelEffect(res1, false); } -//│ res1 +//│ tmp1 = runtime.runStackSafe(1000, $_stack$_safe$_body$_1); +//│ if (tmp1 instanceof runtime.EffectSig.class) { tmp1 = runtime.topLevelEffect(tmp1, false); } +//│ tmp1 //│ = 50005000 -// stack-overflows without :stackSafe -:re -fun sum(n) = - if n == 0 then 0 - else - n + sum(n - 1) -sum(10000) -//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded -:effectHandlers :stackSafe 100 mut val ctr = 0 fun dummy(x) = x @@ -160,7 +142,6 @@ foo(foo) //│ ctr = 10001 :stackSafe 1000 -:effectHandlers :expect 50005000 val foo = val f = n => @@ -172,7 +153,6 @@ foo //│ foo = 50005000 :stackSafe 10 -:effectHandlers :expect 50005000 val foo = val f = n => @@ -183,21 +163,10 @@ foo //│ = 50005000 //│ foo = 50005000 -:re -fun foo() = - let f = () - set f = n => - if n <= 0 then 0 - else n + f(n-1) - f(10000) -foo() -//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded - abstract class Eff with fun perform(a): () // functions and lambdas inside handlers -:effectHandlers :stackSafe 100 :expect 50005000 fun foo(h) = h.perform @@ -212,7 +181,6 @@ foo(h) //│ = 50005000 // function call and defn inside handler -:effectHandlers :stackSafe 100 :expect 50005000 handle h = Eff with @@ -227,7 +195,7 @@ in foo(h) //│ = 50005000 -:effectHandlers + :stackSafe 100 :expect 50005000 module A with @@ -239,7 +207,6 @@ A.x //│ = 50005000 :re -:effectHandlers fun foo(h) = h.perform(2) handle h = Eff with fun perform(a)(resume) = @@ -251,36 +218,39 @@ handle h = Eff with foo(h) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded -:effectHandlers :stackSafe :sjs fun max(a, b) = if a < b then b else a //│ JS (unsanitized): //│ let max; //│ max = function max(a, b) { -//│ let scrut; -//│ scrut = a < b; -//│ if (scrut === true) { return b } else { return a } +//│ let scrut, pc, saveOffset; +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ a = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ b = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ scrut = 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 } +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) {} +//│ break; +//│ } //│ }; -:sjs -:stackSafe 42 -fun hi(n) = n -hi(0) -//│ /!!!\ Option ':stackSafe' requires ':effectHandlers' to be set -//│ JS (unsanitized): -//│ let hi1; hi1 = function hi(n) { return n }; hi1(0) -//│ = 0 - -:stackSafe 42 -hi(0) -//│ /!!!\ Option ':stackSafe' requires ':effectHandlers' to be set -//│ = 0 - - :stackSafe 1000 -:effectHandlers :expect 100010000 fun sum(n) = if n == 0 then 0 @@ -291,7 +261,6 @@ bad() //│ = 100010000 -:effectHandlers :stackSafe 100 :expect 0 let depth = 0 @@ -307,7 +276,6 @@ h.whoops() // TODO: the stack depth is not accurately tracked in the runtime functions // for now this works, but it's not very safe -:effectHandlers :stackSafe :expect 10000 fun dummy = () @@ -327,7 +295,6 @@ abstract class Eff with fun raise() :re -:effectHandlers fun shift_stack(n, f) = if n >= 0 then shift_stack(n - 1, f) @@ -345,7 +312,6 @@ handler_stack(100) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded :stackSafe -:effectHandlers fun shift_stack(n, f) = if n >= 0 then shift_stack(n - 1, f) diff --git a/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls b/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls index f2568ef3dd..5d35fde3db 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class StackDelay with fun perform(): () diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls index 0aef56f838..fa7e6b8eb9 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck abstract class ThreadEffect with @@ -8,7 +10,7 @@ abstract class ThreadEffect with fun fork(thread: () -> ()): () fun yld(): () -class Lock(locked) with +class Lock(val locked) with fun lock(th: ThreadEffect) = while locked do th.yld() diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls index 26059f2a32..4b88cf6870 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls @@ -1,5 +1,7 @@ :js :effectHandlers debug +:lift +:noSanityCheck class ThreadEffect with @@ -43,11 +45,11 @@ in //│ > main end //│ > f 2 //│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$ -//│ at f (UserThreadsSafe.mls:17:3) -//│ at while (UserThreadsSafe.mls:10:7) -//│ at drain (pc=6) -//│ at fork (UserThreadsSafe.mls:35:5) -//│ at fork (UserThreadsSafe.mls:35:5) +//│ at f (pc=undefined) +//│ at while$ (pc=undefined) +//│ at ThreadEffect#drain (pc=undefined) +//│ at Handler$h$fork$ (pc=undefined) +//│ at Handler$h$fork$ (pc=undefined) // FIFO @@ -65,11 +67,11 @@ in //│ > main start //│ > main end //│ > f 0 -//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$1 -//│ at f (UserThreadsSafe.mls:17:3) -//│ at while (UserThreadsSafe.mls:10:7) -//│ at drain (pc=6) -//│ at fork (UserThreadsSafe.mls:58:5) -//│ at fork (UserThreadsSafe.mls:58:5) +//│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$2 +//│ at f (pc=undefined) +//│ at while$ (pc=undefined) +//│ at ThreadEffect#drain (pc=undefined) +//│ at Handler$h$fork$ (pc=undefined) +//│ at Handler$h$fork$ (pc=undefined) diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls index c22ef1eadf..0b3d5a4a29 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls @@ -1,5 +1,7 @@ :js :effectHandlers debug +:lift +:noSanityCheck class ThreadEffect with @@ -46,11 +48,11 @@ in //│ > main end //│ > f 2 //│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$ -//│ at f (UserThreadsUnsafe.mls:12:3) -//│ at while (UserThreadsUnsafe.mls:29:5) -//│ at drain (pc=6) -//│ at fork (UserThreadsUnsafe.mls:38:5) -//│ at fork (UserThreadsUnsafe.mls:38:5) +//│ at f (pc=undefined) +//│ at while$ (pc=undefined) +//│ at drain (pc=undefined) +//│ at Handler$h$fork$ (pc=undefined) +//│ at Handler$h$fork$ (pc=undefined) // FIFO @@ -69,10 +71,10 @@ in //│ > main end //│ > f 1 //│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$ -//│ at f (UserThreadsUnsafe.mls:12:3) -//│ at while (UserThreadsUnsafe.mls:29:5) -//│ at drain (pc=6) -//│ at fork (UserThreadsUnsafe.mls:61:5) -//│ at fork (UserThreadsUnsafe.mls:61:5) +//│ at f (pc=undefined) +//│ at while$ (pc=undefined) +//│ at drain (pc=undefined) +//│ at Handler$h$fork$ (pc=undefined) +//│ at Handler$h$fork$ (pc=undefined) diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls index a7c5588ba5..5022d97944 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -1,5 +1,7 @@ :js :effectHandlers +:lift +:noSanityCheck :stackSafe 1000 fun selfApp(f) = f(f) @@ -86,7 +88,7 @@ let fact = x * prev b mkrec(a) -//│ fact = fun b +//│ fact = fun :expect 3628800 :stackSafe 5 diff --git a/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls index ec726f6ed4..1dfb8d3e47 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,7 +89,7 @@ fun f() = Good() f().foo() //│ JS (unsanitized): -//│ let Bad1, Good1, f5, tmp5, Bad$, Good$, f$capture3; +//│ let Bad1, Good1, f6, tmp5, Bad$, Good$, f$capture3; //│ Good$ = function Good$(isMut, x, y, z, f$capture4) { //│ let tmp6, tmp7; //│ if (isMut === true) { @@ -184,7 +185,7 @@ f().foo() //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "f$capture"]; //│ }); -//│ f5 = function f() { +//│ f6 = function f() { //│ let x, y, z, tmp6, tmp7, capture; //│ capture = new f$capture3(null); //│ x = 1; @@ -195,7 +196,7 @@ f().foo() //│ tmp7 = tmp6.foo(); //│ return Good$(false, x, y, z, capture) //│ }; -//│ tmp5 = f5(); +//│ tmp5 = f6(); //│ runtime.safeCall(tmp5.foo()) //│ = 10111 @@ -221,6 +222,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 ceac941c04..c9f1796b9c 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/EffectHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/EffectHandlers.mls @@ -4,12 +4,14 @@ fun f() = 3 :effectHandlers +:noSanityCheck module A with data class Test with f() val a = 1 :effectHandlers +:noSanityCheck class Test with f() val a = 1 diff --git a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls index 8b772c4748..44708ae13a 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls @@ -1,5 +1,6 @@ :js :lift +:noSanityCheck // sanity check :expect 5050 @@ -21,65 +22,49 @@ fun hi(n) = else hi(n - 1) hi(0) //│ JS (unsanitized): -//│ let hi, Cont$func$hi$1, res, $_stack$_safe$_body$_, doUnwind$, Cont$func$hi$$; -//│ Cont$func$hi$$ = function Cont$func$hi$$(isMut, n, pc) { -//│ let tmp, tmp1; -//│ if (isMut === true) { -//│ tmp = new Cont$func$hi$1(pc); -//│ } else { -//│ tmp = globalThis.Object.freeze(new Cont$func$hi$1(pc)); -//│ } -//│ tmp1 = tmp(n); -//│ return tmp1 -//│ }; -//│ globalThis.Object.freeze(class Cont$func$hi$ extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$func$hi$1 = this -//│ } -//│ constructor(pc) { -//│ return (n) => { -//│ let tmp; -//│ tmp = super(null); -//│ this.n = n; -//│ this.pc = pc; -//│ return this; -//│ } -//│ } -//│ #n; -//│ get n() { return this.#n; } -//│ set n(value) { this.#n = value; } -//│ resume(value$) { -//│ return hi(this.n) -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$func$hi$"]; -//│ }); -//│ doUnwind$ = function doUnwind$(n, res1, pc) { -//│ res1.contTrace.last.next = Cont$func$hi$$(true, n, pc); -//│ res1.contTrace.last = res1.contTrace.last.next; -//│ return res1 -//│ }; +//│ let hi, tmp, $_stack$_safe$_body$_; //│ hi = function hi(n) { -//│ let scrut, tmp, stackDelayRes; +//│ let scrut, tmp1, pc, saveOffset, stackDelayRes; //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ return doUnwind$(n, stackDelayRes, 0) +//│ return runtime.unwind(stackDelayRes, 1, hi, null, null, 0, null, 3, n, scrut, tmp1) //│ } -//│ scrut = n == 0; -//│ if (scrut === true) { -//│ return 0 +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp1 = 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; +//│ pc = 2; +//│ continue main +//│ } +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) {} else if (pc === 2) { +//│ 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 @@ -92,88 +77,64 @@ fun sum(n) = n + sum(n - 1) sum(10000) //│ JS (unsanitized): -//│ let sum1, Cont$func$sum$1, res1, $_stack$_safe$_body$_1, doUnwind$1, Cont$func$sum$$; -//│ Cont$func$sum$$ = function Cont$func$sum$$(isMut, n, tmp, pc) { -//│ let tmp1, tmp2; -//│ if (isMut === true) { -//│ tmp1 = new Cont$func$sum$1(pc); -//│ } else { -//│ tmp1 = globalThis.Object.freeze(new Cont$func$sum$1(pc)); -//│ } -//│ tmp2 = tmp1(n, tmp); -//│ return tmp2 -//│ }; -//│ globalThis.Object.freeze(class Cont$func$sum$ extends runtime.FunctionContFrame.class { -//│ static { -//│ Cont$func$sum$1 = this -//│ } -//│ constructor(pc) { -//│ return (n, tmp) => { -//│ let tmp1; -//│ tmp1 = super(null); -//│ this.n = n; -//│ this.tmp = tmp; -//│ this.pc = pc; -//│ return this; -//│ } -//│ } -//│ #n; -//│ #tmp; -//│ get n() { return this.#n; } -//│ set n(value) { this.#n = value; } -//│ get tmp() { return this.#tmp; } -//│ set tmp(value) { this.#tmp = value; } -//│ resume(value$) { -//│ let curDepth; -//│ curDepth = runtime.stackDepth; -//│ if (this.pc === 1) { -//│ this.tmp = value$; -//│ } -//│ contLoop: while (true) { -//│ runtime.stackDepth = curDepth; -//│ if (this.pc === 0) { -//│ return sum1(this.n) -//│ } else if (this.pc === 1) { -//│ return this.n + this.tmp -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Cont$func$sum$"]; -//│ }); -//│ doUnwind$1 = function doUnwind$(n, tmp, res2, pc) { -//│ res2.contTrace.last.next = Cont$func$sum$$(true, n, tmp, pc); -//│ res2.contTrace.last = res2.contTrace.last.next; -//│ return res2 -//│ }; +//│ let sum1, tmp1, $_stack$_safe$_body$_1; //│ sum1 = function sum(n) { -//│ let scrut, tmp, tmp1, curDepth, stackDelayRes; +//│ let scrut, tmp2, tmp3, pc, saveOffset, 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, null, 0, null, 4, n, scrut, tmp2, tmp3) //│ } -//│ scrut = n == 0; -//│ if (scrut === true) { -//│ return 0 +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp2 = runtime.resumeArr.at(saveOffset); +//│ if (pc === 2) { +//│ tmp3 = runtime.resumeValue; +//│ } else { +//│ saveOffset = runtime.resumeIdx + 3; +//│ tmp3 = 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) { +//│ if (pc === 0) { +//│ scrut = n == 0; +//│ if (scrut === true) { +//│ return 0 +//│ } else { +//│ tmp2 = n - 1; +//│ pc = 3; +//│ continue main +//│ } +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) {} else if (pc === 2) { +//│ return n + tmp3 +//│ } else if (pc === 3) { +//│ tmp3 = sum1(tmp2); +//│ runtime.stackDepth = curDepth; +//│ if (tmp3 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp3, 1, sum1, null, null, 2, null, 4, n, scrut, tmp2, tmp3) +//│ } +//│ pc = 2; +//│ continue main //│ } -//│ return n + tmp1 +//│ break; //│ } //│ }; //│ $_stack$_safe$_body$_1 = (undefined, function () { //│ return sum1(10000) //│ }); -//│ res1 = runtime.runStackSafe(1000, $_stack$_safe$_body$_1); -//│ if (res1 instanceof runtime.EffectSig.class) { res1 = runtime.topLevelEffect(res1, false); } -//│ res1 +//│ tmp1 = runtime.runStackSafe(1000, $_stack$_safe$_body$_1); +//│ if (tmp1 instanceof runtime.EffectSig.class) { tmp1 = runtime.topLevelEffect(tmp1, false); } +//│ tmp1 //│ = 50005000 // stack-overflows without :stackSafe @@ -274,9 +235,29 @@ fun max(a, b) = if a < b then b else a //│ JS (unsanitized): //│ let max; //│ max = function max(a, b) { -//│ let scrut; -//│ scrut = a < b; -//│ if (scrut === true) { return b } else { return a } +//│ let scrut, pc, saveOffset; +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ a = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ b = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ scrut = 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 } +//│ pc = 1; +//│ continue main +//│ } else if (pc === 1) {} +//│ break; +//│ } //│ }; From c86df4317becf0c0bc79080de754691980e4bcb0 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 9 Dec 2025 18:36:58 +0800 Subject: [PATCH 09/14] Use switch whenver possible --- .../scala/hkmc2/codegen/HandlerLowering.scala | 19 +- .../scala/hkmc2/codegen/js/JSBuilder.scala | 10 + .../src/test/mlscript-compile/Predef.mjs | 44 +- .../src/test/mlscript-compile/Runtime.mjs | 762 +++++++++++------- .../src/test/mlscript/basics/LazySpreads.mls | 23 +- .../mlscript/basics/MultilineExpressions.mls | 25 +- .../src/test/mlscript/bbml/bbCodeGen.mls | 56 +- .../src/test/mlscript/bbml/bbGetters.mls | 13 +- .../test/mlscript/codegen/CaseShorthand.mls | 35 +- .../test/mlscript/codegen/ClassMatching.mls | 26 +- .../test/mlscript/codegen/DelayedLetInit.mls | 24 +- .../src/test/mlscript/codegen/EarlyReturn.mls | 13 +- .../src/test/mlscript/codegen/GlobalThis.mls | 22 +- .../src/test/mlscript/codegen/IfThenElse.mls | 31 +- .../test/mlscript/codegen/ModuleMethods.mls | 8 +- .../src/test/mlscript/codegen/PartialApps.mls | 9 +- .../src/test/mlscript/codegen/RandomStuff.mls | 13 +- .../test/mlscript/codegen/SanityChecks.mls | 41 +- .../src/test/mlscript/codegen/Throw.mls | 9 +- .../src/test/mlscript/codegen/TraceLog.mls | 15 +- .../src/test/mlscript/codegen/While.mls | 92 ++- .../src/test/mlscript/handlers/Debugging.mls | 129 +-- .../src/test/mlscript/handlers/Effects.mls | 50 +- .../test/mlscript/handlers/EffectsHygiene.mls | 94 ++- .../mlscript/handlers/RecursiveHandlers.mls | 459 ++++++----- .../test/mlscript/handlers/StackSafety.mls | 194 +++-- .../src/test/mlscript/lifter/ClassInFun.mls | 22 +- .../src/test/mlscript/lifter/DefnsInClass.mls | 11 +- .../src/test/mlscript/lifter/FunInFun.mls | 25 +- .../shared/src/test/mlscript/lifter/Loops.mls | 26 +- .../test/mlscript/lifter/ModulesObjects.mls | 11 +- .../test/mlscript/lifter/StackSafetyLift.mls | 194 +++-- .../test/mlscript/std/FingerTreeListTest.mls | 23 +- .../test/mlscript/ucs/general/JoinPoints.mls | 37 +- .../ucs/general/LogicalConnectives.mls | 26 +- .../ucs/normalization/Deduplication.mls | 468 +++++++---- 36 files changed, 1864 insertions(+), 1195 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 9db4d8207b..6cab031c99 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -41,17 +41,13 @@ object HandlerLowering: type FnOrCls = Either[BlockMemberSymbol, DefinitionSymbol[? <: ClassLikeDef] & InnerSymbol] - // TODO: Fix these comments - // isTopLevel: - // whether the current block is the top level block, as we do not emit code for continuation class on the top level - // since we cannot return an effect signature on the top level (we are not in a function so return statement are invalid) - // contName: the name of the continuation class - // ctorThis: the path to `this` in the constructor, this is used to insert `return this;` at the end of constructor. - // linkAndHandle: - // a function that takes a LinkState and returns a block that links the continuation class and handles the effect - // this is a convenience function which initializes the continuation class in function context or throw an error in top level + // currentFun: path to the current function for resumption, none if not instrumented like top level or constructor + // thisPath: path to `this` binding if the function is a method, `this` will be rebinded on resumption + // plCnt: how many times to call this function for resumption, as we have arbitrary number of parameter lists + // currentLocals: All locals to be saved and reloaded, this cannot include any variables in outer scopes + // currentStackSafetySym: The symbol to be used for stack safety private case class HandlerCtx( - currentFun: Opt[Path], + currentFun: Option[Path], thisPath: Option[Path], plCnt: Int, currentLocals: List[Local], @@ -72,8 +68,7 @@ object HandlerLowering: currentLocals.map(_.asPath) ).map(_.asArg))(true, true), false) - // inScopeLocals: Local variables that are in scope, including those that come from outer. - // prevLocalsFn: The function that gets the outer function's locals. + // inScopeLocals: All variables that are in scope, including those that come from outer scope. private case class DebugInfo( debugNme: Str, debugInfoPath: Path, diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala index 0bd47a2885..432d3b429d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -446,6 +446,16 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: case S(el) => returningTerm(el, endSemi = true) case N => doc"" e :: returningTerm(rest, endSemi) + case Match(scrut, arms, els, rest) if arms.forall(_._1.isInstanceOf[Case.Lit]) => + val l = arms.foldLeft(doc""): (acc, arm) => + acc :: doc" # case ${arm._1.asInstanceOf[Case.Lit].lit.idStr}: #{ ${ + returningTerm(arm._2, endSemi = true) + } # break; #} " + val e = els match + case S(el) => + doc" # default: #{ ${ returningTerm(el, endSemi = true) } # break; #} " + case N => doc"" + doc" # switch (${result(scrut)}) { #{ ${l :: e} #} # }" :: returningTerm(rest, endSemi) case Match(scrut, hd :: tl, els, rest) => val sd = result(scrut) def cond(cse: Case) = cse match diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index a4ce900a78..a70d824bed 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -127,27 +127,33 @@ globalThis.Object.freeze(class Predef { let len, scrut, i, init, scrut1, tmp, tmp1, tmp2, tmp3; len = rest.length; scrut = len == 0; - if (scrut === true) { - return first - } else { - i = len - 1; - init = runtime.safeCall(rest.at(i)); - tmp4: while (true) { - scrut1 = i > 0; - if (scrut1 === true) { - tmp = i - 1; - i = tmp; - tmp1 = runtime.safeCall(rest.at(i)); - tmp2 = runtime.safeCall(f(tmp1, init)); - init = tmp2; - tmp3 = runtime.Unit; - continue tmp4 - } else { - tmp3 = runtime.Unit; + switch (scrut) { + case true: + return first; + break; + default: + i = len - 1; + init = runtime.safeCall(rest.at(i)); + tmp4: while (true) { + scrut1 = i > 0; + switch (scrut1) { + case true: + tmp = i - 1; + i = tmp; + tmp1 = runtime.safeCall(rest.at(i)); + tmp2 = runtime.safeCall(f(tmp1, init)); + init = tmp2; + tmp3 = runtime.Unit; + continue tmp4; + break; + default: + tmp3 = runtime.Unit; + break; + } + break; } + return runtime.safeCall(f(first, init)); break; - } - return runtime.safeCall(f(first, init)) } } } diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index 9a1e9128ec..d1a73ebcb1 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -157,17 +157,23 @@ globalThis.Object.freeze(class Runtime { static get(xs, i) { let scrut, scrut1, tmp, tmp1, tmp2; scrut = i >= xs.length; - if (scrut === true) { - throw globalThis.RangeError("Tuple.get: index out of bounds") - } else { - tmp = runtime.Unit; + switch (scrut) { + case true: + throw globalThis.RangeError("Tuple.get: index out of bounds"); + break; + default: + tmp = runtime.Unit; + break; } tmp1 = - xs.length; scrut1 = i < tmp1; - if (scrut1 === true) { - throw globalThis.RangeError("Tuple.get: negative index out of bounds") - } else { - tmp2 = runtime.Unit; + switch (scrut1) { + case true: + throw globalThis.RangeError("Tuple.get: negative index out of bounds"); + break; + default: + tmp2 = runtime.Unit; + break; } return xs.at(i) } @@ -190,10 +196,13 @@ globalThis.Object.freeze(class Runtime { static get(string, i) { let scrut; scrut = i >= string.length; - if (scrut === true) { - throw globalThis.RangeError("Str.get: index out of bounds") - } else { - return runtime.safeCall(string.at(i)) + switch (scrut) { + case true: + throw globalThis.RangeError("Str.get: index out of bounds"); + break; + default: + return runtime.safeCall(string.at(i)); + break; } } static take(string, n) { @@ -226,37 +235,46 @@ globalThis.Object.freeze(class Runtime { static indent() { let scrut, prev, tmp; scrut = TraceLogger.enabled; - if (scrut === true) { - prev = TraceLogger.indentLvl; - tmp = prev + 1; - TraceLogger.indentLvl = tmp; - return prev - } else { - return runtime.Unit + switch (scrut) { + case true: + prev = TraceLogger.indentLvl; + tmp = prev + 1; + TraceLogger.indentLvl = tmp; + return prev; + break; + default: + return runtime.Unit; + break; } } static resetIndent(n) { let scrut; scrut = TraceLogger.enabled; - if (scrut === true) { - TraceLogger.indentLvl = n; - return runtime.Unit - } else { - return runtime.Unit + switch (scrut) { + case true: + TraceLogger.indentLvl = n; + return runtime.Unit; + break; + default: + return runtime.Unit; + break; } } static log(msg) { let scrut, tmp, tmp1, tmp2, tmp3, tmp4; scrut = TraceLogger.enabled; - if (scrut === true) { - tmp = runtime.safeCall("| ".repeat(TraceLogger.indentLvl)); - tmp1 = runtime.safeCall(" ".repeat(TraceLogger.indentLvl)); - tmp2 = "\n" + tmp1; - tmp3 = msg.replaceAll("\n", tmp2); - tmp4 = tmp + tmp3; - return runtime.safeCall(globalThis.console.log(tmp4)) - } else { - return runtime.Unit + switch (scrut) { + case true: + tmp = runtime.safeCall("| ".repeat(TraceLogger.indentLvl)); + tmp1 = runtime.safeCall(" ".repeat(TraceLogger.indentLvl)); + tmp2 = "\n" + tmp1; + tmp3 = msg.replaceAll("\n", tmp2); + tmp4 = tmp + tmp3; + return runtime.safeCall(globalThis.console.log(tmp4)); + break; + default: + return runtime.Unit; + break; } } toString() { return runtime.render(this); } @@ -305,23 +323,29 @@ globalThis.Object.freeze(class Runtime { resume(value) { let scrut, f, scrut1, tmp, tmp1, tmp2, tmp3, tmp4; scrut = this.saved.at(0) == 0; - if (scrut === true) { - tmp = runtime.safeCall(globalThis.console.log("cannot resume getters")); - } else { - tmp = runtime.Unit; + switch (scrut) { + case true: + tmp = runtime.safeCall(globalThis.console.log("cannot resume getters")); + break; + default: + tmp = runtime.Unit; + break; } f = this.saved.at(1); tmp5: while (true) { scrut1 = this.saved.at(0) > 1; - if (scrut1 === true) { - tmp1 = runtime.safeCall(f()); - f = tmp1; - tmp2 = this.saved.at(0) - 1; - this.saved[0] = tmp2; - tmp3 = runtime.Unit; - continue tmp5 - } else { - tmp3 = runtime.Unit; + switch (scrut1) { + case true: + tmp1 = runtime.safeCall(f()); + f = tmp1; + tmp2 = this.saved.at(0) - 1; + this.saved[0] = tmp2; + tmp3 = runtime.Unit; + continue tmp5; + break; + default: + tmp3 = runtime.Unit; + break; } break; } @@ -340,17 +364,20 @@ globalThis.Object.freeze(class Runtime { i = 1; tmp6: while (true) { scrut = i < debugInfo.length; - if (scrut === true) { - tmp = i + 1; - tmp1 = 7 + debugInfo.at(i); - tmp2 = globalThis.Object.freeze(new Runtime.LocalVarInfo.class(debugInfo.at(tmp), this.saved.at(tmp1))); - tmp3 = runtime.safeCall(res.push(tmp2)); - tmp4 = i + 2; - i = tmp4; - tmp5 = runtime.Unit; - continue tmp6 - } else { - tmp5 = runtime.Unit; + switch (scrut) { + case true: + tmp = i + 1; + tmp1 = 7 + debugInfo.at(i); + tmp2 = globalThis.Object.freeze(new Runtime.LocalVarInfo.class(debugInfo.at(tmp), this.saved.at(tmp1))); + tmp3 = runtime.safeCall(res.push(tmp2)); + tmp4 = i + 2; + i = tmp4; + tmp5 = runtime.Unit; + continue tmp6; + break; + default: + tmp5 = runtime.Unit; + break; } break; } @@ -547,51 +574,69 @@ globalThis.Object.freeze(class Runtime { return runtime.short_and(isUB, lambda1) }); scrut = runtime.short_or(tmp, lambda); - if (scrut === true) { - scrut1 = functionName.length > 0; - if (scrut1 === true) { - tmp1 = " '" + functionName; - tmp2 = tmp1 + "'"; - } else { - tmp2 = ""; - } - name = tmp2; - tmp3 = "Function" + name; - tmp4 = tmp3 + " expected "; - if (isUB === true) { - tmp5 = ""; - } else { - tmp5 = "at least "; - } - tmp6 = tmp4 + tmp5; - tmp7 = tmp6 + expected; - tmp8 = tmp7 + " argument"; - scrut2 = expected === 1; - if (scrut2 === true) { - tmp9 = ""; - } else { - tmp9 = "s"; - } - tmp10 = tmp8 + tmp9; - tmp11 = tmp10 + " but got "; - tmp12 = tmp11 + got; - throw globalThis.Error(tmp12) - } else { - return runtime.Unit + switch (scrut) { + case true: + scrut1 = functionName.length > 0; + switch (scrut1) { + case true: + tmp1 = " '" + functionName; + tmp2 = tmp1 + "'"; + break; + default: + tmp2 = ""; + break; + } + name = tmp2; + tmp3 = "Function" + name; + tmp4 = tmp3 + " expected "; + switch (isUB) { + case true: + tmp5 = ""; + break; + default: + tmp5 = "at least "; + break; + } + tmp6 = tmp4 + tmp5; + tmp7 = tmp6 + expected; + tmp8 = tmp7 + " argument"; + scrut2 = expected === 1; + switch (scrut2) { + case true: + tmp9 = ""; + break; + default: + tmp9 = "s"; + break; + } + tmp10 = tmp8 + tmp9; + tmp11 = tmp10 + " but got "; + tmp12 = tmp11 + got; + throw globalThis.Error(tmp12); + break; + default: + return runtime.Unit; + break; } } static safeCall(x) { - if (x === undefined) { - return runtime.Unit - } else { - return x + switch (x) { + case undefined: + return runtime.Unit; + break; + default: + return x; + break; } } static checkCall(x) { - if (x === undefined) { - throw globalThis.Error("MLscript call unexpectedly returned `undefined`, the forbidden value.") - } else { - return x + switch (x) { + case undefined: + throw globalThis.Error("MLscript call unexpectedly returned `undefined`, the forbidden value."); + break; + default: + return x; + break; } } static deboundMethod(mtdName, clsName) { @@ -627,16 +672,19 @@ globalThis.Object.freeze(class Runtime { let scrut, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; tmp7: while (true) { scrut = tr.handler === Runtime.PrintStackEffect; - if (scrut === true) { - tmp = Runtime.showStackTrace("Stack Trace:", tr, debug, tr.handlerFun); - tmp1 = runtime.safeCall(globalThis.console.log(tmp)); - tmp2 = Runtime.resume(tr.contTrace); - tmp3 = runtime.safeCall(tmp2(runtime.Unit)); - tr = tmp3; - tmp4 = runtime.Unit; - continue tmp7 - } else { - tmp4 = runtime.Unit; + switch (scrut) { + case true: + tmp = Runtime.showStackTrace("Stack Trace:", tr, debug, tr.handlerFun); + tmp1 = runtime.safeCall(globalThis.console.log(tmp)); + tmp2 = Runtime.resume(tr.contTrace); + tmp3 = runtime.safeCall(tmp2(runtime.Unit)); + tr = tmp3; + tmp4 = runtime.Unit; + continue tmp7; + break; + default: + tmp4 = runtime.Unit; + break; } break; } @@ -653,92 +701,116 @@ globalThis.Object.freeze(class Runtime { msg = header; curHandler = tr.contTrace; atTail = true; - if (debug === true) { - tmp18: while (true) { - scrut = curHandler !== null; - if (scrut === true) { - cur = curHandler.next; - tmp19: while (true) { - scrut1 = cur !== null; - if (scrut1 === true) { - curLocals = cur.getLocal; - loc = cur.getLoc; - if (loc === null) { - tmp = "pc=" + cur.pc; - } else { - tmp = loc; - } - loc1 = tmp; - split_root$: { - split_1$: { - if (showLocals === true) { - scrut2 = curLocals.length > 0; - if (scrut2 === true) { - lambda = (undefined, function (l) { - let tmp20, tmp21; - tmp20 = l.localName + "="; - tmp21 = Rendering.render(l.value); - return tmp20 + tmp21 - }); - tmp1 = runtime.safeCall(curLocals.map(lambda)); - tmp2 = runtime.safeCall(tmp1.join(", ")); - tmp3 = " with locals: " + tmp2; - break split_root$ - } else { - break split_1$ + switch (debug) { + case true: + tmp18: while (true) { + scrut = curHandler !== null; + switch (scrut) { + case true: + cur = curHandler.next; + tmp19: while (true) { + scrut1 = cur !== null; + switch (scrut1) { + case true: + curLocals = cur.getLocal; + loc = cur.getLoc; + switch (loc) { + case null: + tmp = "pc=" + cur.pc; + break; + default: + tmp = loc; + break; } - } else { - break split_1$ - } + loc1 = tmp; + split_root$: { + split_1$: { + switch (showLocals) { + case true: + scrut2 = curLocals.length > 0; + switch (scrut2) { + case true: + lambda = (undefined, function (l) { + let tmp20, tmp21; + tmp20 = l.localName + "="; + tmp21 = Rendering.render(l.value); + return tmp20 + tmp21 + }); + tmp1 = runtime.safeCall(curLocals.map(lambda)); + tmp2 = runtime.safeCall(tmp1.join(", ")); + tmp3 = " with locals: " + tmp2; + break split_root$; + break; + default: + break split_1$; + break; + } + break; + default: + break split_1$; + break; + } + } + tmp3 = ""; + } + localsMsg = tmp3; + tmp4 = "\n\tat " + cur.getNme; + tmp5 = tmp4 + " ("; + tmp6 = tmp5 + loc1; + tmp7 = tmp6 + ")"; + tmp8 = msg + tmp7; + msg = tmp8; + tmp9 = msg + localsMsg; + msg = tmp9; + cur = cur.next; + atTail = false; + tmp10 = runtime.Unit; + continue tmp19; + break; + default: + tmp10 = runtime.Unit; + break; } - tmp3 = ""; + break; } - localsMsg = tmp3; - tmp4 = "\n\tat " + cur.getNme; - tmp5 = tmp4 + " ("; - tmp6 = tmp5 + loc1; - tmp7 = tmp6 + ")"; - tmp8 = msg + tmp7; - msg = tmp8; - tmp9 = msg + localsMsg; - msg = tmp9; - cur = cur.next; - atTail = false; - tmp10 = runtime.Unit; - continue tmp19 - } else { - tmp10 = runtime.Unit; - } - break; - } - curHandler = curHandler.nextHandler; - scrut3 = curHandler !== null; - if (scrut3 === true) { - tmp11 = "\n\twith handler " + curHandler.handler.constructor.name; - tmp12 = msg + tmp11; - msg = tmp12; - atTail = false; - tmp13 = runtime.Unit; - } else { - tmp13 = runtime.Unit; + curHandler = curHandler.nextHandler; + scrut3 = curHandler !== null; + switch (scrut3) { + case true: + tmp11 = "\n\twith handler " + curHandler.handler.constructor.name; + tmp12 = msg + tmp11; + msg = tmp12; + atTail = false; + tmp13 = runtime.Unit; + break; + default: + tmp13 = runtime.Unit; + break; + } + tmp14 = tmp13; + continue tmp18; + break; + default: + tmp14 = runtime.Unit; + break; } - tmp14 = tmp13; - continue tmp18 - } else { - tmp14 = runtime.Unit; + break; + } + switch (atTail) { + case true: + tmp15 = msg + "\n\tat tail position"; + msg = tmp15; + tmp16 = runtime.Unit; + break; + default: + tmp16 = runtime.Unit; + break; } + tmp17 = tmp16; + break; + default: + tmp17 = runtime.Unit; break; - } - if (atTail === true) { - tmp15 = msg + "\n\tat tail position"; - msg = tmp15; - tmp16 = runtime.Unit; - } else { - tmp16 = runtime.Unit; - } - tmp17 = tmp16; - } else { - tmp17 = runtime.Unit; } return msg } @@ -750,41 +822,53 @@ globalThis.Object.freeze(class Runtime { lambda = (undefined, function (m, marker) { let scrut3, tmp8, tmp9; scrut3 = runtime.safeCall(m.has(cont)); - if (scrut3 === true) { - tmp8 = ", " + marker; - tmp9 = result + tmp8; - result = tmp9; - return runtime.Unit - } else { - return runtime.Unit + switch (scrut3) { + case true: + tmp8 = ", " + marker; + tmp9 = result + tmp8; + result = tmp9; + return runtime.Unit; + break; + default: + return runtime.Unit; + break; } }); tmp1 = runtime.safeCall(hl.forEach(lambda)); scrut = runtime.safeCall(vis.has(cont)); - if (scrut === true) { - tmp2 = reps + 1; - reps = tmp2; - scrut1 = reps > 10; - if (scrut1 === true) { - throw globalThis.Error("10 repeated continuation frame (loop?)") - } else { - tmp3 = runtime.Unit; - } - tmp4 = result + ", REPEAT"; - result = tmp4; - tmp5 = runtime.Unit; - } else { - tmp5 = runtime.safeCall(vis.add(cont)); + switch (scrut) { + case true: + tmp2 = reps + 1; + reps = tmp2; + scrut1 = reps > 10; + switch (scrut1) { + case true: + throw globalThis.Error("10 repeated continuation frame (loop?)"); + break; + default: + tmp3 = runtime.Unit; + break; + } + tmp4 = result + ", REPEAT"; + result = tmp4; + tmp5 = runtime.Unit; + break; + default: + tmp5 = runtime.safeCall(vis.add(cont)); + break; } tmp6 = result + ") -> "; tmp7 = Runtime.showFunctionContChain(cont.next, hl, vis, reps); return tmp6 + tmp7 } else { scrut2 = cont === null; - if (scrut2 === true) { - return "(null)" - } else { - return "(NOT CONT)" + switch (scrut2) { + case true: + return "(null)"; + break; + default: + return "(NOT CONT)"; + break; } } } @@ -795,41 +879,53 @@ globalThis.Object.freeze(class Runtime { lambda = (undefined, function (m, marker) { let scrut3, tmp7, tmp8; scrut3 = runtime.safeCall(m.has(cont)); - if (scrut3 === true) { - tmp7 = ", " + marker; - tmp8 = result + tmp7; - result = tmp8; - return runtime.Unit - } else { - return runtime.Unit + switch (scrut3) { + case true: + tmp7 = ", " + marker; + tmp8 = result + tmp7; + result = tmp8; + return runtime.Unit; + break; + default: + return runtime.Unit; + break; } }); tmp = runtime.safeCall(hl.forEach(lambda)); scrut = runtime.safeCall(vis.has(cont)); - if (scrut === true) { - tmp1 = reps + 1; - reps = tmp1; - scrut1 = reps > 10; - if (scrut1 === true) { - throw globalThis.Error("10 repeated continuation frame (loop?)") - } else { - tmp2 = runtime.Unit; - } - tmp3 = result + ", REPEAT"; - result = tmp3; - tmp4 = runtime.Unit; - } else { - tmp4 = runtime.safeCall(vis.add(cont)); + switch (scrut) { + case true: + tmp1 = reps + 1; + reps = tmp1; + scrut1 = reps > 10; + switch (scrut1) { + case true: + throw globalThis.Error("10 repeated continuation frame (loop?)"); + break; + default: + tmp2 = runtime.Unit; + break; + } + tmp3 = result + ", REPEAT"; + result = tmp3; + tmp4 = runtime.Unit; + break; + default: + tmp4 = runtime.safeCall(vis.add(cont)); + break; } tmp5 = result + " -> "; tmp6 = Runtime.showFunctionContChain(cont.next, hl, vis, reps); return tmp5 + tmp6 } else { scrut2 = cont === null; - if (scrut2 === true) { - return "(null)" - } else { - return "(NOT HANDLER CONT)" + switch (scrut2) { + case true: + return "(null)"; + break; + default: + return "(NOT HANDLER CONT)"; + break; } } } @@ -852,16 +948,22 @@ globalThis.Object.freeze(class Runtime { if (contTrace instanceof Runtime.ContTrace.class) { tmp = globalThis.console.log("resumed: ", contTrace.resumed); scrut = contTrace.last === contTrace; - if (scrut === true) { - tmp1 = runtime.safeCall(globalThis.console.log("")); - } else { - tmp1 = runtime.Unit; + switch (scrut) { + case true: + tmp1 = runtime.safeCall(globalThis.console.log("")); + break; + default: + tmp1 = runtime.Unit; + break; } scrut1 = contTrace.lastHandler === contTrace; - if (scrut1 === true) { - tmp2 = runtime.safeCall(globalThis.console.log("")); - } else { - tmp2 = runtime.Unit; + switch (scrut1) { + case true: + tmp2 = runtime.safeCall(globalThis.console.log("")); + break; + default: + tmp2 = runtime.Unit; + break; } vis = globalThis.Object.freeze(new globalThis.Set()); hl = globalThis.Object.freeze(new globalThis.Map()); @@ -880,14 +982,17 @@ globalThis.Object.freeze(class Runtime { cur = contTrace.nextHandler; tmp15: while (true) { scrut2 = cur !== null; - if (scrut2 === true) { - tmp11 = Runtime.showHandlerContChain(cur, hl, vis, 0); - tmp12 = runtime.safeCall(globalThis.console.log(tmp11)); - cur = cur.nextHandler; - tmp13 = runtime.Unit; - continue tmp15 - } else { - tmp13 = runtime.Unit; + switch (scrut2) { + case true: + tmp11 = Runtime.showHandlerContChain(cur, hl, vis, 0); + tmp12 = runtime.safeCall(globalThis.console.log(tmp11)); + cur = cur.nextHandler; + tmp13 = runtime.Unit; + continue tmp15; + break; + default: + tmp13 = runtime.Unit; + break; } break; } @@ -947,11 +1052,14 @@ globalThis.Object.freeze(class Runtime { if (cur instanceof Runtime.EffectSig.class) { nxt = Runtime.handleEffect(cur); scrut = cur === nxt; - if (scrut === true) { - return cur - } else { - cur = nxt; - tmp = runtime.Unit; + switch (scrut) { + case true: + return cur; + break; + default: + cur = nxt; + tmp = runtime.Unit; + break; } tmp1 = tmp; continue tmp2 @@ -969,17 +1077,23 @@ globalThis.Object.freeze(class Runtime { split_root$: { split_1$: { scrut = prevHandlerFrame.nextHandler !== null; - if (scrut === true) { - scrut1 = prevHandlerFrame.nextHandler.handler !== cur.handler; - if (scrut1 === true) { - prevHandlerFrame = prevHandlerFrame.nextHandler; - tmp = runtime.Unit; - continue tmp6 - } else { - break split_1$ - } - } else { - break split_1$ + switch (scrut) { + case true: + scrut1 = prevHandlerFrame.nextHandler.handler !== cur.handler; + switch (scrut1) { + case true: + prevHandlerFrame = prevHandlerFrame.nextHandler; + tmp = runtime.Unit; + continue tmp6; + break; + default: + break split_1$; + break; + } + break; + default: + break split_1$; + break; } } tmp = runtime.Unit; @@ -987,10 +1101,13 @@ globalThis.Object.freeze(class Runtime { break; } scrut2 = prevHandlerFrame.nextHandler === null; - if (scrut2 === true) { - return cur - } else { - tmp1 = runtime.Unit; + switch (scrut2) { + case true: + return cur; + break; + default: + tmp1 = runtime.Unit; + break; } handlerFrame = prevHandlerFrame.nextHandler; saved = new Runtime.ContTrace.class(handlerFrame.next, cur.contTrace.last, handlerFrame.nextHandler, cur.contTrace.lastHandler, false); @@ -1003,20 +1120,26 @@ globalThis.Object.freeze(class Runtime { cur = tmp3; if (cur instanceof Runtime.EffectSig.class) { scrut3 = saved.next !== null; - if (scrut3 === true) { - cur.contTrace.last.next = saved.next; - cur.contTrace.last = saved.last; - tmp4 = runtime.Unit; - } else { - tmp4 = runtime.Unit; + switch (scrut3) { + case true: + cur.contTrace.last.next = saved.next; + cur.contTrace.last = saved.last; + tmp4 = runtime.Unit; + break; + default: + tmp4 = runtime.Unit; + break; } scrut4 = saved.nextHandler !== null; - if (scrut4 === true) { - cur.contTrace.lastHandler.nextHandler = saved.nextHandler; - cur.contTrace.lastHandler = saved.lastHandler; - tmp5 = runtime.Unit; - } else { - tmp5 = runtime.Unit; + switch (scrut4) { + case true: + cur.contTrace.lastHandler.nextHandler = saved.nextHandler; + cur.contTrace.lastHandler = saved.lastHandler; + tmp5 = runtime.Unit; + break; + default: + tmp5 = runtime.Unit; + break; } return cur } else { @@ -1027,10 +1150,13 @@ globalThis.Object.freeze(class Runtime { return (value) => { let scrut, tmp, tmp1; scrut = contTrace.resumed; - if (scrut === true) { - throw globalThis.Error("Multiple resumption") - } else { - tmp = runtime.Unit; + switch (scrut) { + case true: + throw globalThis.Error("Multiple resumption"); + break; + default: + tmp = runtime.Unit; + break; } contTrace.resumed = true; tmp1 = Runtime.resumeContTrace(contTrace, value); @@ -1051,18 +1177,24 @@ globalThis.Object.freeze(class Runtime { value.contTrace.last.next = cont.next; value.contTrace.lastHandler.nextHandler = handlerCont; scrut = contTrace.last !== cont; - if (scrut === true) { - value.contTrace.last = contTrace.last; - tmp1 = runtime.Unit; - } else { - tmp1 = runtime.Unit; + switch (scrut) { + case true: + value.contTrace.last = contTrace.last; + tmp1 = runtime.Unit; + break; + default: + tmp1 = runtime.Unit; + break; } scrut1 = handlerCont !== null; - if (scrut1 === true) { - value.contTrace.lastHandler = contTrace.lastHandler; - tmp2 = runtime.Unit; - } else { - tmp2 = runtime.Unit; + switch (scrut1) { + case true: + value.contTrace.lastHandler = contTrace.lastHandler; + tmp2 = runtime.Unit; + break; + default: + tmp2 = runtime.Unit; + break; } return value } else { @@ -1092,10 +1224,13 @@ globalThis.Object.freeze(class Runtime { return Runtime.stackHandler !== null }); scrut = runtime.short_and(tmp, lambda); - if (scrut === true) { - return runtime.safeCall(Runtime.stackHandler.delay()) - } else { - return runtime.Unit + switch (scrut) { + case true: + return runtime.safeCall(Runtime.stackHandler.delay()); + break; + default: + return runtime.Unit; + break; } } static runStackSafe(limit, f) { @@ -1107,16 +1242,19 @@ globalThis.Object.freeze(class Runtime { Runtime.stackDepth = 1; tmp2: while (true) { scrut = Runtime.stackResume !== null; - if (scrut === true) { - saved = Runtime.stackResume; - Runtime.stackResume = null; - tmp = runtime.safeCall(saved()); - result = tmp; - Runtime.stackDepth = 1; - tmp1 = runtime.Unit; - continue tmp2 - } else { - tmp1 = runtime.Unit; + switch (scrut) { + case true: + saved = Runtime.stackResume; + Runtime.stackResume = null; + tmp = runtime.safeCall(saved()); + result = tmp; + Runtime.stackDepth = 1; + tmp1 = runtime.Unit; + continue tmp2; + break; + default: + tmp1 = runtime.Unit; + break; } break; } diff --git a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls index 26fb4b1450..b7e9505255 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls @@ -22,14 +22,17 @@ fun buildPalindrome = case //│ let lambda; //│ lambda = (undefined, function (caseScrut) { //│ let n, tmp3, tmp4, tmp5; -//│ if (caseScrut === 0) { -//│ return globalThis.Object.freeze([]) -//│ } else { -//│ n = caseScrut; -//│ tmp3 = buildPalindrome(); -//│ tmp4 = n - 1; -//│ tmp5 = tmp3(tmp4); -//│ return globalThis.Object.freeze(runtime.Tuple.lazyConcat(n, runtime.Tuple.split, tmp5, n)) +//│ switch (caseScrut) { +//│ case 0: +//│ return globalThis.Object.freeze([]); +//│ break; +//│ default: +//│ n = caseScrut; +//│ tmp3 = buildPalindrome(); +//│ tmp4 = n - 1; +//│ tmp5 = tmp3(tmp4); +//│ return globalThis.Object.freeze(runtime.Tuple.lazyConcat(n, runtime.Tuple.split, tmp5, n)); +//│ break; //│ } //│ }); //│ return lambda @@ -80,13 +83,13 @@ sum2(arr) :e fun f(..xs) = xs //│ ╔══[ERROR] Lazy spread parameters not allowed. -//│ ║ l.81: fun f(..xs) = xs +//│ ║ l.84: fun f(..xs) = xs //│ ╙── ^^^^ :ge sum2(..arr) //│ ╔══[COMPILATION ERROR] Lazy spreads are not supported in call arguments -//│ ║ l.87: sum2(..arr) +//│ ║ l.90: sum2(..arr) //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 2 diff --git a/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls b/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls index 5025320ce4..530565d1d6 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls @@ -83,9 +83,14 @@ if 1 + 2 //│ let scrut, tmp17; //│ tmp17 = 2 * 3; //│ scrut = 1 + tmp17; -//│ if (scrut === true) { -//│ 0 -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (scrut) { +//│ case true: +//│ 0; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ ═══[RUNTIME ERROR] Error: match error :pt @@ -116,23 +121,23 @@ if 1 + 2 * 3 then 0 + 4 then 1 //│ ╔══[PARSE ERROR] Operator cannot be used inside this operator split -//│ ║ l.117: + 4 then 1 +//│ ║ l.122: + 4 then 1 //│ ║ ^ //│ ╟── as it has lower precedence than the splitting operator here -//│ ║ l.116: * 3 then 0 +//│ ║ l.121: * 3 then 0 //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected 'then' keyword in this operator split inner position -//│ ║ l.117: + 4 then 1 +//│ ║ l.122: + 4 then 1 //│ ║ ^^^^ //│ ╟── Note: the operator split starts here -//│ ║ l.116: * 3 then 0 +//│ ║ l.121: * 3 then 0 //│ ╙── ^ //│ ╔══[ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.115: if 1 + 2 +//│ ║ l.120: if 1 + 2 //│ ║ ^ -//│ ║ l.116: * 3 then 0 +//│ ║ l.121: * 3 then 0 //│ ║ ^^^^^^^^^^^^ -//│ ║ l.117: + 4 then 1 +//│ ║ l.122: + 4 then 1 //│ ╙── ^^^^^ :pt diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls b/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls index 19e96e6493..4b2d8bbebb 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls @@ -112,7 +112,7 @@ inc(41) :sjs if 1 == 2 then 0 else 42 //│ JS (unsanitized): -//│ let scrut; scrut = 1 == 2; if (scrut === true) { 0 } else { 42 } +//│ let scrut; scrut = 1 == 2; switch (scrut) { case true: 0; break; default: 42; break; } //│ = 42 //│ Type: Int @@ -152,14 +152,17 @@ fun pow(x) = case //│ let lambda1; //│ lambda1 = (undefined, function (caseScrut) { //│ let n, tmp, tmp1, tmp2; -//│ if (caseScrut === 0) { -//│ return 1 -//│ } else { -//│ n = caseScrut; -//│ tmp = pow(x1); -//│ tmp1 = n - 1; -//│ tmp2 = runtime.safeCall(tmp(tmp1)); -//│ return x1 * tmp2 +//│ switch (caseScrut) { +//│ case 0: +//│ return 1; +//│ break; +//│ default: +//│ n = caseScrut; +//│ tmp = pow(x1); +//│ tmp1 = n - 1; +//│ tmp2 = runtime.safeCall(tmp(tmp1)); +//│ return x1 * tmp2; +//│ break; //│ } //│ }); //│ return lambda1 @@ -176,12 +179,16 @@ fun nott = case //│ nott = function nott() { //│ let lambda1; //│ lambda1 = (undefined, function (caseScrut) { -//│ if (caseScrut === true) { -//│ return false -//│ } else if (caseScrut === false) { -//│ return true -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) +//│ switch (caseScrut) { +//│ case true: +//│ return false; +//│ break; +//│ case false: +//│ return true; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; //│ } //│ }); //│ return lambda1 @@ -208,14 +215,17 @@ fun fact = case //│ let lambda1; //│ lambda1 = (undefined, function (caseScrut) { //│ let n, tmp1, tmp2, tmp3; -//│ if (caseScrut === 0) { -//│ return 1 -//│ } else { -//│ n = caseScrut; -//│ tmp1 = fact(); -//│ tmp2 = n - 1; -//│ tmp3 = tmp1(tmp2); -//│ return n * tmp3 +//│ switch (caseScrut) { +//│ case 0: +//│ return 1; +//│ break; +//│ default: +//│ n = caseScrut; +//│ tmp1 = fact(); +//│ tmp2 = n - 1; +//│ tmp3 = tmp1(tmp2); +//│ return n * tmp3; +//│ break; //│ } //│ }); //│ return lambda1 diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls b/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls index d72caafeeb..b35945e5e2 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls @@ -93,10 +93,13 @@ fun test2() = //│ funny = function funny() { //│ let lambda, lambda1; //│ lambda = (undefined, function (caseScrut) { -//│ if (caseScrut === 0) { -//│ return 0 -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) +//│ switch (caseScrut) { +//│ case 0: +//│ return 0; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; //│ } //│ }); //│ lambda1 = (undefined, function (caseScrut) { @@ -130,7 +133,7 @@ fun test2() = fun test3 = print("Hi") //│ ╔══[ERROR] Function definition shape not yet supported for test3 -//│ ║ l.131: print("Hi") +//│ ║ l.134: print("Hi") //│ ╙── ^^^^^^^^^^^ //│ Type: ⊤ diff --git a/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls b/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls index 510dbe3eeb..184e762c7a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls @@ -16,9 +16,14 @@ x => if x is //│ JS (unsanitized): //│ let lambda2; //│ lambda2 = (undefined, function (x) { -//│ if (x === 0) { -//│ return true -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (x) { +//│ case 0: +//│ return true; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ }); //│ lambda2 //│ = fun @@ -28,9 +33,14 @@ case 0 then true //│ JS (unsanitized): //│ let lambda3; //│ lambda3 = (undefined, function (caseScrut) { -//│ if (caseScrut === 0) { -//│ return true -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (caseScrut) { +//│ case 0: +//│ return true; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ }); //│ lambda3 //│ = fun @@ -53,10 +63,10 @@ case 0 then true :todo // TODO: support this braceless syntax? case [x] then x, [] then 0 //│ ╔══[ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.54: case [x] then x, [] then 0 +//│ ║ l.64: case [x] then x, [] then 0 //│ ╙── ^^^^^^^^^ //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.54: case [x] then x, [] then 0 +//│ ║ l.64: case [x] then x, [] then 0 //│ ╙── ^^^^^^^^^^^^^^^ :sjs @@ -66,7 +76,14 @@ case //│ JS (unsanitized): //│ let lambda11; //│ lambda11 = (undefined, function (caseScrut) { -//│ if (caseScrut === 0) { return true } else { return false } +//│ switch (caseScrut) { +//│ case 0: +//│ return true; +//│ break; +//│ default: +//│ return false; +//│ break; +//│ } //│ }); //│ lambda11 //│ = fun diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls index 55c80c83c3..81b747ab9e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls @@ -127,11 +127,14 @@ fun f(x) = if x is //│ argument0$4 = x3.value; //│ x4 = argument0$4; //│ scrut1 = x4 > 0; -//│ if (scrut1 === true) { -//│ tmp5 = 42; -//│ break split_root$1 -//│ } else { -//│ break split_1$ +//│ switch (scrut1) { +//│ case true: +//│ tmp5 = 42; +//│ break split_root$1; +//│ break; +//│ default: +//│ break split_1$; +//│ break; //│ } //│ } else if (x3 instanceof None1.class) { //│ tmp5 = "ok"; @@ -201,11 +204,14 @@ fun f(x) = print of if x is //│ split_1$: { //│ if (x3 instanceof Some1.class) { //│ argument0$4 = x3.value; -//│ if (argument0$4 === 0) { -//│ tmp9 = "0"; -//│ break split_root$1 -//│ } else { -//│ break split_1$ +//│ switch (argument0$4) { +//│ case 0: +//│ tmp9 = "0"; +//│ break split_root$1; +//│ break; +//│ default: +//│ break split_1$; +//│ break; //│ } //│ } else if (x3 instanceof None1.class) { //│ tmp9 = "ok"; diff --git a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls index a4fddd8d26..c16f815fee 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls @@ -83,9 +83,14 @@ else foo = 1 //│ let foo1, scrut; //│ foo1 = undefined; //│ scrut = true; -//│ if (scrut === true) { -//│ foo1 -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (scrut) { +//│ case true: +//│ foo1; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ foo = undefined let foo @@ -98,7 +103,16 @@ else //│ let foo2, scrut1; //│ foo2 = undefined; //│ scrut1 = true; -//│ if (scrut1 === true) { foo2 = 0; runtime.Unit } else { foo2 = 1; runtime.Unit } +//│ switch (scrut1) { +//│ case true: +//│ foo2 = 0; +//│ runtime.Unit; +//│ break; +//│ default: +//│ foo2 = 1; +//│ runtime.Unit; +//│ break; +//│ } //│ foo = 0 @@ -120,7 +134,7 @@ foo :fixme fun f() = foo = 0 //│ ╔══[PARSE ERROR] Expected end of input; found '=' keyword instead -//│ ║ l.121: fun f() = foo = 0 +//│ ║ l.135: fun f() = foo = 0 //│ ╙── ^ //│ JS (unsanitized): //│ let f4; f4 = function f() { return foo2 }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls b/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls index 797b5c0c47..27a3cb8511 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls @@ -22,10 +22,15 @@ fun f(x) = //│ f1 = function f(x) { //│ let scrut, tmp, tmp1; //│ scrut = x < 0; -//│ if (scrut === true) { -//│ tmp = Predef.print("whoops"); -//│ return 0 -//│ } else { tmp1 = runtime.Unit; } +//│ switch (scrut) { +//│ case true: +//│ tmp = Predef.print("whoops"); +//│ return 0; +//│ break; +//│ default: +//│ tmp1 = runtime.Unit; +//│ break; +//│ } //│ return x + 1 //│ }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls index 883fdd4d3e..3691ed5059 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls @@ -23,9 +23,14 @@ if false then 0 //│ JS (unsanitized): //│ let scrut; //│ scrut = false; -//│ if (scrut === true) { -//│ 0 -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (scrut) { +//│ case true: +//│ 0; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') @@ -41,9 +46,14 @@ foo() //│ foo = function foo() { //│ let scrut1; //│ scrut1 = false; -//│ if (scrut1 === true) { -//│ return 0 -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (scrut1) { +//│ case true: +//│ return 0; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ }; //│ foo() //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') diff --git a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls index a440378432..7124979169 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls @@ -11,7 +11,14 @@ let f = x => if x then print("ok") else print("ko") //│ JS (unsanitized): //│ let f, lambda; //│ lambda = (undefined, function (x) { -//│ if (x === true) { return Predef.print("ok") } else { return Predef.print("ko") } +//│ switch (x) { +//│ case true: +//│ return Predef.print("ok"); +//│ break; +//│ default: +//│ return Predef.print("ko"); +//│ break; +//│ } //│ }); //│ f = lambda; //│ f = fun @@ -29,9 +36,14 @@ let f = x => print((if x then "ok" else "ko") + "!") //│ let f1, lambda1; //│ lambda1 = (undefined, function (x) { //│ let tmp, tmp1; -//│ if (x === true) { -//│ tmp = "ok"; -//│ } else { tmp = "ko"; } +//│ switch (x) { +//│ case true: +//│ tmp = "ok"; +//│ break; +//│ default: +//│ tmp = "ko"; +//│ break; +//│ } //│ tmp1 = tmp + "!"; //│ return Predef.print(tmp1) //│ }); @@ -44,9 +56,14 @@ let f = x => print((if x and x then "ok" else "ko") + "!") //│ let f2, lambda2; //│ lambda2 = (undefined, function (x) { //│ let tmp, tmp1; -//│ if (x === true) { -//│ tmp = "ok"; -//│ } else { tmp = "ko"; } +//│ switch (x) { +//│ case true: +//│ tmp = "ok"; +//│ break; +//│ default: +//│ tmp = "ko"; +//│ break; +//│ } //│ tmp1 = tmp + "!"; //│ return Predef.print(tmp1) //│ }); diff --git a/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls b/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls index 0d43252465..9ff2422b53 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls @@ -15,8 +15,10 @@ Example.f(123) let s = Example.f //│ JS: //│ selRes = Example1.f; -//│ if (selRes === undefined) { -//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'f' yielded 'undefined'")) +//│ switch (selRes) { +//│ case undefined: +//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'f' yielded 'undefined'")); +//│ break; //│ } //│ s = selRes; //│ block$res3 = undefined; @@ -28,7 +30,7 @@ s(123) :e Example |>. s(123) //│ ╔══[ERROR] Unexpected moduleful reference of type Example. -//│ ║ l.29: Example |>. s(123) +//│ ║ l.31: Example |>. s(123) //│ ║ ^^^^^^^ //│ ╙── Module argument passed to a non-module parameter. //│ = [123, 456] diff --git a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls index ec8c349b28..9dda19f099 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls @@ -267,6 +267,13 @@ fun f(x) = //│ f16 = function f(x3) { //│ let scrut; //│ scrut = x3 == 0; -//│ if (scrut === true) { return 1 } else { return 2 } +//│ switch (scrut) { +//│ case true: +//│ return 1; +//│ break; +//│ default: +//│ return 2; +//│ break; +//│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/RandomStuff.mls b/hkmc2/shared/src/test/mlscript/codegen/RandomStuff.mls index 10940ea078..99eeb17cb0 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/RandomStuff.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/RandomStuff.mls @@ -8,7 +8,14 @@ fun foo() = if false do foo() //│ foo = function foo() { //│ let scrut; //│ scrut = false; -//│ if (scrut === true) { return foo() } else { return runtime.Unit } +//│ switch (scrut) { +//│ case true: +//│ return foo(); +//│ break; +//│ default: +//│ return runtime.Unit; +//│ break; +//│ } //│ }; :sjs @@ -71,10 +78,10 @@ do let foo = 1 fun foo(x) = foo //│ ╔══[ERROR] Name 'foo' is already used -//│ ║ l.71: let foo = 1 +//│ ║ l.78: let foo = 1 //│ ║ ^^^^^^^ //│ ╟── by a member declared in the same block -//│ ║ l.72: fun foo(x) = foo +//│ ║ l.79: fun foo(x) = foo //│ ╙── ^^^^^^^^^^^^^^^^ //│ JS (unsanitized): //│ let foo3, foo4; foo3 = function foo(x) { return foo4 }; foo4 = 1; diff --git a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls index d5ef1fbb25..9fb860ac49 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls @@ -210,8 +210,10 @@ console.log(1) //│ JS: //│ selRes = globalThis.console; //│ discarded = globalThis.console$__checkNotMethod; -//│ if (selRes === undefined) { -//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'console' yielded 'undefined'")) +//│ switch (selRes) { +//│ case undefined: +//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'console' yielded 'undefined'")); +//│ break; //│ } //│ block$res10 = runtime.safeCall(selRes.log(1)); //│ undefined @@ -224,8 +226,10 @@ globalThis.x //│ JS: //│ selRes1 = globalThis.x; //│ discarded1 = globalThis.x$__checkNotMethod; -//│ if (selRes1 === undefined) { -//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'x' yielded 'undefined'")) +//│ switch (selRes1) { +//│ case undefined: +//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'x' yielded 'undefined'")); +//│ break; //│ } //│ block$res11 = selRes1; //│ undefined @@ -284,13 +288,24 @@ if M.A(1).y is //│ tmp11 = runtime.checkCall(M1.A(1)); //│ selRes2 = tmp11.y; //│ discarded2 = tmp11.y$__checkNotMethod; -//│ if (selRes2 === undefined) { -//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'y' yielded 'undefined'")) +//│ switch (selRes2) { +//│ case undefined: +//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'y' yielded 'undefined'")); +//│ break; //│ } //│ scrut = selRes2; //│ x = scrut; //│ scrut1 = x == 1; -//│ if (scrut1 === true) { block$res13 = x; undefined } else { block$res13 = 0; undefined } +//│ switch (scrut1) { +//│ case true: +//│ block$res13 = x; +//│ undefined; +//│ break; +//│ default: +//│ block$res13 = 0; +//│ undefined; +//│ break; +//│ } //│ ═══[RUNTIME ERROR] Error: Access to required field 'y' yielded 'undefined' @@ -302,14 +317,18 @@ M.A(1).y //│ JS: //│ selRes3 = globalThis.console; //│ discarded3 = globalThis.console$__checkNotMethod; -//│ if (selRes3 === undefined) { -//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'console' yielded 'undefined'")) +//│ switch (selRes3) { +//│ case undefined: +//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'console' yielded 'undefined'")); +//│ break; //│ } //│ tmp12 = runtime.checkCall(M1.A(1)); //│ selRes4 = tmp12.y; //│ discarded4 = tmp12.y$__checkNotMethod; -//│ if (selRes4 === undefined) { -//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'y' yielded 'undefined'")) +//│ switch (selRes4) { +//│ case undefined: +//│ throw globalThis.Object.freeze(new globalThis.Error("Access to required field 'y' yielded 'undefined'")); +//│ break; //│ } //│ block$res14 = runtime.safeCall(selRes3.log(selRes4)); //│ undefined diff --git a/hkmc2/shared/src/test/mlscript/codegen/Throw.mls b/hkmc2/shared/src/test/mlscript/codegen/Throw.mls index 4ef0cd898a..8224c3d378 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Throw.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Throw.mls @@ -43,7 +43,14 @@ f(false) //│ JS (unsanitized): //│ let f3; //│ f3 = function f(x) { -//│ if (x === true) { throw globalThis.Error("x") } else { throw globalThis.Error("y") } +//│ switch (x) { +//│ case true: +//│ throw globalThis.Error("x"); +//│ break; +//│ default: +//│ throw globalThis.Error("y"); +//│ break; +//│ } //│ }; //│ f3(false) //│ ═══[RUNTIME ERROR] Error: y diff --git a/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls b/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls index 1b3593632c..5d50c07a6a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls @@ -11,9 +11,18 @@ fun fib(a) = if //│ fib = function fib(a) { //│ let scrut, tmp, tmp1, tmp2, tmp3; //│ scrut = a <= 1; -//│ if (scrut === true) { -//│ return a -//│ } else { tmp = a - 1; tmp1 = fib(tmp); tmp2 = a - 2; tmp3 = fib(tmp2); return tmp1 + tmp3 } +//│ switch (scrut) { +//│ case true: +//│ return a; +//│ break; +//│ default: +//│ tmp = a - 1; +//│ tmp1 = fib(tmp); +//│ tmp2 = a - 2; +//│ tmp3 = fib(tmp2); +//│ return tmp1 + tmp3; +//│ break; +//│ } //│ }; fun f(x) = g(x) diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index 407c7639f2..c7a87f8828 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -11,17 +11,27 @@ //│ while1 = (undefined, function () { //│ let scrut; //│ scrut = true; -//│ if (scrut === true) { -//│ tmp = 0; -//│ return while1() -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) +//│ switch (scrut) { +//│ case true: +//│ tmp = 0; +//│ return while1(); +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; //│ } //│ return runtime.LoopEnd //│ }); //│ tmp1 = while1(); //│ tmp2 = tmp1 !== runtime.LoopEnd; -//│ if (tmp2 === true) { return tmp1 } else { return tmp } +//│ switch (tmp2) { +//│ case true: +//│ return tmp1; +//│ break; +//│ default: +//│ return tmp; +//│ break; +//│ } //│ }); //│ lambda //│ = fun @@ -61,12 +71,17 @@ while x //│ tmp4 = undefined; //│ while3 = (undefined, function () { //│ let tmp6; -//│ if (x2 === true) { -//│ tmp6 = Predef.print("Hello World"); -//│ x2 = false; -//│ tmp4 = runtime.Unit; -//│ return while3() -//│ } else { tmp4 = 42; } +//│ switch (x2) { +//│ case true: +//│ tmp6 = Predef.print("Hello World"); +//│ x2 = false; +//│ tmp4 = runtime.Unit; +//│ return while3(); +//│ break; +//│ default: +//│ tmp4 = 42; +//│ break; +//│ } //│ return runtime.LoopEnd //│ }); //│ tmp5 = while3(); @@ -118,19 +133,29 @@ while //│ let i2, scrut, tmp15; //│ i2 = 0; //│ scrut = i2 < 10; -//│ if (scrut === true) { -//│ tmp15 = i2 + 1; -//│ i2 = tmp15; -//│ tmp12 = runtime.Unit; -//│ return while7() -//│ } else { -//│ tmp12 = runtime.Unit; +//│ switch (scrut) { +//│ case true: +//│ tmp15 = i2 + 1; +//│ i2 = tmp15; +//│ tmp12 = runtime.Unit; +//│ return while7(); +//│ break; +//│ default: +//│ tmp12 = runtime.Unit; +//│ break; //│ } //│ return runtime.LoopEnd //│ }); //│ tmp13 = while7(); //│ tmp14 = tmp13 !== runtime.LoopEnd; -//│ if (tmp14 === true) { return tmp13 } else { return tmp12 } +//│ switch (tmp14) { +//│ case true: +//│ return tmp13; +//│ break; +//│ default: +//│ return tmp12; +//│ break; +//│ } //│ }); //│ lambda2 //│ = fun @@ -229,7 +254,14 @@ fun f(ls) = //│ }); //│ tmp19 = while10(); //│ tmp20 = tmp19 !== runtime.LoopEnd; -//│ if (tmp20 === true) { return tmp19 } else { return tmp18 } +//│ switch (tmp20) { +//│ case true: +//│ return tmp19; +//│ break; +//│ default: +//│ return tmp18; +//│ break; +//│ } //│ }; f(0) @@ -297,10 +329,10 @@ while print("Hello World"); false then 0(0) else 1 //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.297: then 0(0) +//│ ║ l.329: then 0(0) //│ ╙── ^^^^ //│ ╔══[ERROR] Unrecognized term split (false literal) -//│ ║ l.296: while print("Hello World"); false +//│ ║ l.328: while print("Hello World"); false //│ ╙── ^^^^^ //│ > Hello World //│ ═══[RUNTIME ERROR] Error: match error @@ -310,12 +342,12 @@ while { print("Hello World"), false } then 0(0) else 1 //│ ╔══[ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.309: while { print("Hello World"), false } +//│ ║ l.341: while { print("Hello World"), false } //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.310: then 0(0) +//│ ║ l.342: then 0(0) //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.311: else 1 +//│ ║ l.343: else 1 //│ ╙── ^^^^ :fixme @@ -325,14 +357,14 @@ while then 0(0) else 1 //│ ╔══[ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.323: print("Hello World") +//│ ║ l.355: print("Hello World") //│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.324: false +//│ ║ l.356: false //│ ║ ^^^^^^^^^ -//│ ║ l.325: then 0(0) +//│ ║ l.357: then 0(0) //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.326: else 1 +//│ ║ l.358: else 1 //│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls index ba0c58ad44..91251ad53d 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls @@ -31,64 +31,85 @@ fun f() = //│ ]); //│ f = function f() { //│ let i, j, k, scrut, tmp1, tmp2, pc, saveOffset; -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ i = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ j = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ k = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 3; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ if (pc === 4) { -//│ tmp1 = runtime.resumeValue; -//│ } else { -//│ saveOffset = runtime.resumeIdx + 4; -//│ tmp1 = runtime.resumeArr.at(saveOffset); -//│ } -//│ if (pc === 2) { -//│ tmp2 = runtime.resumeValue; -//│ } else { -//│ saveOffset = runtime.resumeIdx + 5; -//│ tmp2 = runtime.resumeArr.at(saveOffset); -//│ } -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ i = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ j = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ k = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 3; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ switch (pc) { +//│ case 4: +//│ tmp1 = runtime.resumeValue; +//│ break; +//│ default: +//│ saveOffset = runtime.resumeIdx + 4; +//│ tmp1 = runtime.resumeArr.at(saveOffset); +//│ break; +//│ } +//│ switch (pc) { +//│ case 2: +//│ tmp2 = runtime.resumeValue; +//│ break; +//│ default: +//│ saveOffset = runtime.resumeIdx + 5; +//│ tmp2 = runtime.resumeArr.at(saveOffset); +//│ break; +//│ } +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } //│ main: while (true) { -//│ if (pc === 0) { -//│ i = 0; -//│ j = 100; -//│ k = 2000; -//│ scrut = i == 0; -//│ if (scrut === true) { -//│ tmp1 = Predef.raiseUnhandledEffect(); -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp1, 1, f, f$debugInfo, null, 4, null, 6, i, j, k, scrut, tmp1, tmp2) +//│ switch (pc) { +//│ case 0: +//│ i = 0; +//│ j = 100; +//│ k = 2000; +//│ scrut = i == 0; +//│ switch (scrut) { +//│ case true: +//│ tmp1 = Predef.raiseUnhandledEffect(); +//│ if (tmp1 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp1, 1, f, f$debugInfo, null, 4, null, 6, i, j, k, scrut, tmp1, tmp2) +//│ } +//│ pc = 4; +//│ continue main; +//│ break; +//│ default: +//│ tmp2 = runtime.Unit; +//│ pc = 1; +//│ continue main; +//│ break; //│ } -//│ pc = 4; -//│ continue main -//│ } else { -//│ tmp2 = runtime.Unit; //│ pc = 1; -//│ continue main -//│ } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) { -//│ return j / i -//│ } else if (pc === 2) { -//│ pc = 1; -//│ continue main -//│ } else if (pc === 3) { -//│ tmp2 = Predef.print(tmp1); -//│ if (tmp2 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp2, 1, f, f$debugInfo, null, 2, null, 6, i, j, k, scrut, tmp1, tmp2) -//│ } -//│ pc = 2; -//│ continue main -//│ } else if (pc === 4) { pc = 3; continue main } +//│ continue main; +//│ break; +//│ case 1: +//│ return j / i; +//│ break; +//│ case 2: +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 3: +//│ tmp2 = Predef.print(tmp1); +//│ if (tmp2 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp2, 1, f, f$debugInfo, null, 2, null, 6, i, j, k, scrut, tmp1, tmp2) +//│ } +//│ pc = 2; +//│ continue main; +//│ break; +//│ case 4: +//│ pc = 3; +//│ continue main; +//│ break; +//│ } //│ break; //│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index aa9a2c6730..1cebd096eb 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -196,24 +196,34 @@ if true do //│ }; //│ handleBlock$11 = (undefined, function () { //│ let scrut, pc; -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ scrut = runtime.resumeArr.at(runtime.resumeIdx); -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ scrut = runtime.resumeArr.at(runtime.resumeIdx); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } //│ main: while (true) { -//│ if (pc === 0) { -//│ scrut = true; -//│ if (scrut === true) { -//│ return f() -//│ } else { -//│ return runtime.Unit -//│ } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) {} +//│ switch (pc) { +//│ case 0: +//│ scrut = true; +//│ switch (scrut) { +//│ case true: +//│ return f(); +//│ break; +//│ default: +//│ return runtime.Unit; +//│ break; +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ break; +//│ } //│ break; //│ } //│ }); @@ -398,16 +408,16 @@ foo(h) //│ ═══[WARNING] Modules are not yet lifted. //│ ═══[WARNING] Modules are not yet lifted. //│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. -//│ ║ l.377: module A with +//│ ║ l.387: module A with //│ ╙── ^ //│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. -//│ ║ l.381: module A with +//│ ║ l.391: module A with //│ ╙── ^ //│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. -//│ ║ l.385: module A with +//│ ║ l.395: module A with //│ ╙── ^ //│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. -//│ ║ l.389: module A with +//│ ║ l.399: module A with //│ ╙── ^ //│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls index 6cc9c5cdd8..1917e72f85 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls @@ -27,50 +27,60 @@ fun foo(h): module M = //│ let foo; //│ foo = function foo(h) { //│ let A2, A3, scrut, pc, saveOffset; -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ A2 = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ A3 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ h = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 3; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ A2 = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ A3 = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ h = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 3; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } //│ main: while (true) { -//│ if (pc === 0) { -//│ scrut = false; -//│ if (scrut === true) { -//│ globalThis.Object.freeze(class A { -//│ static { -//│ A2 = this -//│ } -//│ constructor() { -//│ runtime.Unit; -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "A"]; -//│ }); -//│ return A2 -//│ } else { -//│ globalThis.Object.freeze(class A1 { -//│ static { -//│ A3 = this -//│ } -//│ constructor() { -//│ runtime.Unit; -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "A"]; -//│ }); -//│ return A3 -//│ } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) {} +//│ switch (pc) { +//│ case 0: +//│ scrut = false; +//│ switch (scrut) { +//│ case true: +//│ globalThis.Object.freeze(class A { +//│ static { +//│ A2 = this +//│ } +//│ constructor() { +//│ runtime.Unit; +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "A"]; +//│ }); +//│ return A2; +//│ break; +//│ default: +//│ globalThis.Object.freeze(class A1 { +//│ static { +//│ A3 = this +//│ } +//│ constructor() { +//│ runtime.Unit; +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "A"]; +//│ }); +//│ return A3; +//│ break; +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ break; +//│ } //│ break; //│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 33a02a0f41..aaa6f94723 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -160,232 +160,277 @@ str //│ let str, scrut, h11, tmp11, tmp12, handleBlock$9, Handler$h2$perform1, Handler$h2$3, handleBlock$10, Handler$h1$perform1, Handler$h1$3, Handler$h1$perform$1, handleBlock$$2, Handler$h2$perform$1; //│ str = ""; //│ scrut = true; -//│ if (scrut === true) { -//│ Handler$h1$perform$1 = function Handler$h1$perform$(Handler$h1$$instance, arg, k) { -//│ let tmp13, tmp14, tmp15, pc, saveOffset; -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ k = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ tmp13 = runtime.resumeArr.at(saveOffset); -//│ if (pc === 1) { -//│ tmp14 = runtime.resumeValue; -//│ } else { -//│ saveOffset = runtime.resumeIdx + 2; -//│ tmp14 = runtime.resumeArr.at(saveOffset); +//│ switch (scrut) { +//│ case true: +//│ Handler$h1$perform$1 = function Handler$h1$perform$(Handler$h1$$instance, arg, k) { +//│ let tmp13, tmp14, tmp15, pc, saveOffset; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ k = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ tmp13 = runtime.resumeArr.at(saveOffset); +//│ switch (pc) { +//│ case 1: +//│ tmp14 = runtime.resumeValue; +//│ break; +//│ default: +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp14 = runtime.resumeArr.at(saveOffset); +//│ break; +//│ } +//│ saveOffset = runtime.resumeIdx + 3; +//│ tmp15 = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 4; +//│ arg = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 5; +//│ Handler$h1$$instance = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } -//│ saveOffset = runtime.resumeIdx + 3; -//│ tmp15 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 4; -//│ arg = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 5; -//│ Handler$h1$$instance = runtime.resumeArr.at(saveOffset); -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; -//│ } -//│ main: while (true) { -//│ if (pc === 0) { -//│ tmp13 = str + "A"; -//│ str = tmp13; -//│ tmp14 = runtime.safeCall(k(arg)); -//│ if (tmp14 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp14, 1, Handler$h1$perform$1, null, null, 1, null, 6, k, tmp13, tmp14, tmp15, arg, Handler$h1$$instance) +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ tmp13 = str + "A"; +//│ str = tmp13; +//│ tmp14 = runtime.safeCall(k(arg)); +//│ if (tmp14 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp14, 1, Handler$h1$perform$1, null, null, 1, null, 6, k, tmp13, tmp14, tmp15, arg, Handler$h1$$instance) +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ tmp15 = str + "A"; +//│ str = tmp15; +//│ return runtime.Unit; +//│ break; //│ } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) { -//│ tmp15 = str + "A"; -//│ str = tmp15; -//│ return runtime.Unit +//│ break; //│ } -//│ break; -//│ } -//│ }; -//│ Handler$h1$perform1 = function Handler$h1$perform(Handler$h1$$instance, arg) { -//│ return (k) => { -//│ return Handler$h1$perform$1(Handler$h1$$instance, arg, k) -//│ } -//│ }; -//│ globalThis.Object.freeze(class Handler$h1$2 extends Effect1 { -//│ static { -//│ Handler$h1$3 = this -//│ } -//│ constructor() { -//│ let tmp13; -//│ tmp13 = super(); -//│ if (tmp13 instanceof runtime.EffectSig.class) { -//│ tmp13 = runtime.topLevelEffect(tmp13, false); +//│ }; +//│ Handler$h1$perform1 = function Handler$h1$perform(Handler$h1$$instance, arg) { +//│ return (k) => { +//│ return Handler$h1$perform$1(Handler$h1$$instance, arg, k) //│ } -//│ 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, saveOffset; -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ k = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ tmp13 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ tmp14 = runtime.resumeArr.at(saveOffset); -//│ if (pc === 1) { -//│ tmp15 = runtime.resumeValue; -//│ } else { -//│ saveOffset = runtime.resumeIdx + 3; -//│ tmp15 = runtime.resumeArr.at(saveOffset); +//│ }; +//│ globalThis.Object.freeze(class Handler$h1$2 extends Effect1 { +//│ static { +//│ Handler$h1$3 = this //│ } -//│ saveOffset = runtime.resumeIdx + 4; -//│ tmp16 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 5; -//│ tmp17 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 6; -//│ arg = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 7; -//│ Handler$h2$$instance = runtime.resumeArr.at(saveOffset); -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; -//│ } -//│ main: while (true) { -//│ if (pc === 0) { -//│ tmp13 = str + "B"; -//│ tmp14 = str + tmp13; -//│ str = tmp14; -//│ tmp15 = runtime.safeCall(k(arg)); -//│ if (tmp15 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp15, 1, Handler$h2$perform$1, null, null, 1, null, 8, k, tmp13, tmp14, tmp15, tmp16, tmp17, arg, Handler$h2$$instance) +//│ constructor() { +//│ let tmp13; +//│ tmp13 = super(); +//│ if (tmp13 instanceof runtime.EffectSig.class) { +//│ tmp13 = runtime.topLevelEffect(tmp13, false); //│ } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) { -//│ tmp16 = str + "B"; -//│ tmp17 = str + tmp16; -//│ str = tmp17; -//│ return runtime.Unit +//│ tmp13; //│ } -//│ break; -//│ } -//│ }; -//│ Handler$h2$perform1 = function Handler$h2$perform(Handler$h2$$instance, arg) { -//│ return (k) => { -//│ return Handler$h2$perform$1(Handler$h2$$instance, arg, k) -//│ } -//│ }; -//│ globalThis.Object.freeze(class Handler$h2$2 extends Effect1 { -//│ static { -//│ Handler$h2$3 = this -//│ } -//│ constructor() { -//│ let tmp13; -//│ tmp13 = super(); -//│ if (tmp13 instanceof runtime.EffectSig.class) { -//│ tmp13 = runtime.topLevelEffect(tmp13, false); +//│ 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) //│ } -//│ 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$h1$"]; +//│ }); +//│ h11 = new Handler$h1$3(); +//│ if (h11 instanceof runtime.EffectSig.class) { +//│ h11 = runtime.topLevelEffect(h11, false); //│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Handler$h2$"]; -//│ }); -//│ handleBlock$$2 = function handleBlock$$(h22) { -//│ let tmp13, pc, saveOffset; -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ if (pc === 1) { -//│ tmp13 = runtime.resumeValue; -//│ } else { -//│ tmp13 = runtime.resumeArr.at(runtime.resumeIdx); +//│ Handler$h2$perform$1 = function Handler$h2$perform$(Handler$h2$$instance, arg, k) { +//│ let tmp13, tmp14, tmp15, tmp16, tmp17, pc, saveOffset; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ k = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ tmp13 = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp14 = runtime.resumeArr.at(saveOffset); +//│ switch (pc) { +//│ case 1: +//│ tmp15 = runtime.resumeValue; +//│ break; +//│ default: +//│ saveOffset = runtime.resumeIdx + 3; +//│ tmp15 = runtime.resumeArr.at(saveOffset); +//│ break; +//│ } +//│ saveOffset = runtime.resumeIdx + 4; +//│ tmp16 = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 5; +//│ tmp17 = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 6; +//│ arg = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 7; +//│ Handler$h2$$instance = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } -//│ saveOffset = runtime.resumeIdx + 1; -//│ h22 = runtime.resumeArr.at(saveOffset); -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; -//│ } -//│ main: while (true) { -//│ if (pc === 0) { -//│ tmp13 = runtime.safeCall(h22.perform(runtime.Unit)); +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ tmp13 = str + "B"; +//│ tmp14 = str + tmp13; +//│ str = tmp14; +//│ tmp15 = runtime.safeCall(k(arg)); +//│ if (tmp15 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp15, 1, Handler$h2$perform$1, null, null, 1, null, 8, k, tmp13, tmp14, tmp15, tmp16, tmp17, arg, Handler$h2$$instance) +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ tmp16 = str + "B"; +//│ tmp17 = str + tmp16; +//│ str = tmp17; +//│ return runtime.Unit; +//│ break; +//│ } +//│ break; +//│ } +//│ }; +//│ Handler$h2$perform1 = function Handler$h2$perform(Handler$h2$$instance, arg) { +//│ return (k) => { +//│ return Handler$h2$perform$1(Handler$h2$$instance, arg, k) +//│ } +//│ }; +//│ globalThis.Object.freeze(class Handler$h2$2 extends Effect1 { +//│ static { +//│ Handler$h2$3 = this +//│ } +//│ constructor() { +//│ let tmp13; +//│ tmp13 = super(); //│ if (tmp13 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp13, 1, handleBlock$$2, null, null, 1, null, 2, tmp13, h22) +//│ tmp13 = runtime.topLevelEffect(tmp13, false); //│ } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) { -//│ return runtime.safeCall(h11.perform(runtime.Unit)) +//│ tmp13; //│ } -//│ break; -//│ } -//│ }; -//│ handleBlock$9 = (undefined, function (h22) { -//│ return () => { -//│ return handleBlock$$2(h22) -//│ } -//│ }); -//│ handleBlock$10 = (undefined, function () { -//│ let h22, tmp13, handleBlock$$here, pc, saveOffset; -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ if (pc === 1) { -//│ h22 = runtime.resumeValue; -//│ } else { -//│ h22 = runtime.resumeArr.at(runtime.resumeIdx); +//│ 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) //│ } -//│ if (pc === 2) { -//│ tmp13 = runtime.resumeValue; -//│ } else { -//│ saveOffset = runtime.resumeIdx + 1; -//│ tmp13 = runtime.resumeArr.at(saveOffset); +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "Handler$h2$"]; +//│ }); +//│ handleBlock$$2 = function handleBlock$$(h22) { +//│ let tmp13, pc, saveOffset; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ switch (pc) { +//│ case 1: +//│ tmp13 = runtime.resumeValue; +//│ break; +//│ default: +//│ tmp13 = runtime.resumeArr.at(runtime.resumeIdx); +//│ break; +//│ } +//│ saveOffset = runtime.resumeIdx + 1; +//│ h22 = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } -//│ saveOffset = runtime.resumeIdx + 2; -//│ handleBlock$$here = runtime.resumeArr.at(saveOffset); -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; -//│ } -//│ main: while (true) { -//│ if (pc === 0) { -//│ h22 = new Handler$h2$3(); -//│ if (h22 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(h22, 1, handleBlock$10, null, null, 1, null, 3, h22, tmp13, handleBlock$$here) +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ tmp13 = runtime.safeCall(h22.perform(runtime.Unit)); +//│ if (tmp13 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp13, 1, handleBlock$$2, null, null, 1, null, 2, tmp13, h22) +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ return runtime.safeCall(h11.perform(runtime.Unit)); +//│ break; //│ } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) { -//│ handleBlock$$here = runtime.safeCall(handleBlock$9(h22)); -//│ tmp13 = runtime.enterHandleBlock(h22, handleBlock$$here); -//│ if (tmp13 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp13, 1, handleBlock$10, null, null, 2, null, 3, h22, tmp13, handleBlock$$here) +//│ break; +//│ } +//│ }; +//│ handleBlock$9 = (undefined, function (h22) { +//│ return () => { +//│ return handleBlock$$2(h22) +//│ } +//│ }); +//│ handleBlock$10 = (undefined, function () { +//│ let h22, tmp13, handleBlock$$here, pc, saveOffset; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ switch (pc) { +//│ case 1: +//│ h22 = runtime.resumeValue; +//│ break; +//│ default: +//│ h22 = runtime.resumeArr.at(runtime.resumeIdx); +//│ break; +//│ } +//│ switch (pc) { +//│ case 2: +//│ tmp13 = runtime.resumeValue; +//│ break; +//│ default: +//│ saveOffset = runtime.resumeIdx + 1; +//│ tmp13 = runtime.resumeArr.at(saveOffset); +//│ break; +//│ } +//│ saveOffset = runtime.resumeIdx + 2; +//│ handleBlock$$here = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; +//│ } +//│ main: while (true) { +//│ switch (pc) { +//│ case 0: +//│ h22 = new Handler$h2$3(); +//│ if (h22 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(h22, 1, handleBlock$10, null, null, 1, null, 3, h22, tmp13, handleBlock$$here) +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ handleBlock$$here = runtime.safeCall(handleBlock$9(h22)); +//│ tmp13 = runtime.enterHandleBlock(h22, handleBlock$$here); +//│ if (tmp13 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp13, 1, handleBlock$10, null, null, 2, null, 3, h22, tmp13, handleBlock$$here) +//│ } +//│ pc = 2; +//│ continue main; +//│ break; +//│ case 2: +//│ return tmp13; +//│ break; //│ } -//│ pc = 2; -//│ continue main -//│ } else if (pc === 2) { -//│ return tmp13 +//│ break; //│ } -//│ break; +//│ }); +//│ tmp11 = runtime.enterHandleBlock(h11, handleBlock$10); +//│ if (tmp11 instanceof runtime.EffectSig.class) { +//│ tmp11 = runtime.topLevelEffect(tmp11, false); //│ } -//│ }); -//│ tmp11 = runtime.enterHandleBlock(h11, handleBlock$10); -//│ if (tmp11 instanceof runtime.EffectSig.class) { -//│ tmp11 = runtime.topLevelEffect(tmp11, false); -//│ } -//│ tmp12 = tmp11; -//│ } else { tmp12 = runtime.Unit; } +//│ tmp12 = tmp11; +//│ break; +//│ default: +//│ tmp12 = runtime.Unit; +//│ break; +//│ } //│ str //│ = "BABABA" //│ str = "BABABA" diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index b559b5a231..cf20654374 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -22,31 +22,42 @@ hi(0) //│ if (stackDelayRes instanceof runtime.EffectSig.class) { //│ return runtime.unwind(stackDelayRes, 1, hi, null, null, 0, null, 3, n, scrut, tmp1) //│ } -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ n = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ tmp1 = runtime.resumeArr.at(saveOffset); -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp1 = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } //│ main: while (true) { -//│ if (pc === 0) { -//│ scrut = n == 0; -//│ if (scrut === true) { -//│ return 0 -//│ } else { -//│ tmp1 = n - 1; -//│ pc = 2; -//│ continue main -//│ } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) {} else if (pc === 2) { -//│ return hi(tmp1) +//│ switch (pc) { +//│ case 0: +//│ scrut = n == 0; +//│ switch (scrut) { +//│ case true: +//│ return 0; +//│ break; +//│ default: +//│ tmp1 = n - 1; +//│ pc = 2; +//│ continue main; +//│ break; +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ break; +//│ case 2: +//│ return hi(tmp1); +//│ break; //│ } //│ break; //│ } @@ -77,45 +88,60 @@ sum(10000) //│ if (stackDelayRes instanceof runtime.EffectSig.class) { //│ return runtime.unwind(stackDelayRes, 1, sum, null, null, 0, null, 4, n, scrut, tmp2, tmp3) //│ } -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ n = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ tmp2 = runtime.resumeArr.at(saveOffset); -//│ if (pc === 2) { -//│ tmp3 = runtime.resumeValue; -//│ } else { -//│ saveOffset = runtime.resumeIdx + 3; -//│ tmp3 = runtime.resumeArr.at(saveOffset); -//│ } -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp2 = runtime.resumeArr.at(saveOffset); +//│ switch (pc) { +//│ case 2: +//│ tmp3 = runtime.resumeValue; +//│ break; +//│ default: +//│ saveOffset = runtime.resumeIdx + 3; +//│ tmp3 = runtime.resumeArr.at(saveOffset); +//│ break; +//│ } +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } //│ main: while (true) { -//│ if (pc === 0) { -//│ scrut = n == 0; -//│ if (scrut === true) { -//│ return 0 -//│ } else { -//│ tmp2 = n - 1; -//│ pc = 3; -//│ continue main -//│ } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) {} else if (pc === 2) { -//│ return n + tmp3 -//│ } else if (pc === 3) { -//│ tmp3 = sum(tmp2); -//│ runtime.stackDepth = curDepth; -//│ if (tmp3 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp3, 1, sum, null, null, 2, null, 4, n, scrut, tmp2, tmp3) -//│ } -//│ pc = 2; -//│ continue main +//│ switch (pc) { +//│ case 0: +//│ scrut = n == 0; +//│ switch (scrut) { +//│ case true: +//│ return 0; +//│ break; +//│ default: +//│ tmp2 = n - 1; +//│ pc = 3; +//│ continue main; +//│ break; +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ break; +//│ case 2: +//│ return n + tmp3; +//│ break; +//│ case 3: +//│ tmp3 = sum(tmp2); +//│ runtime.stackDepth = curDepth; +//│ if (tmp3 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp3, 1, sum, null, null, 2, null, 4, n, scrut, tmp2, tmp3) +//│ } +//│ pc = 2; +//│ continue main; +//│ break; //│ } //│ break; //│ } @@ -225,26 +251,38 @@ fun max(a, b) = if a < b then b else a //│ let max; //│ max = function max(a, b) { //│ let scrut, pc, saveOffset; -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ a = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ b = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ a = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ b = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } //│ main: while (true) { -//│ if (pc === 0) { -//│ scrut = a < b; -//│ if (scrut === true) { -//│ return b -//│ } else { return a } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) {} +//│ switch (pc) { +//│ case 0: +//│ scrut = a < b; +//│ switch (scrut) { +//│ case true: +//│ return b; +//│ break; +//│ default: +//│ return a; +//│ break; +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ break; +//│ } //│ break; //│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls index 1dfb8d3e47..567de084e9 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls @@ -92,10 +92,13 @@ f().foo() //│ let Bad1, Good1, f6, tmp5, Bad$, Good$, f$capture3; //│ Good$ = function Good$(isMut, x, y, z, f$capture4) { //│ let tmp6, tmp7; -//│ if (isMut === true) { -//│ tmp6 = new Good1.class(); -//│ } else { -//│ tmp6 = globalThis.Object.freeze(new Good1.class()); +//│ switch (isMut) { +//│ case true: +//│ tmp6 = new Good1.class(); +//│ break; +//│ default: +//│ tmp6 = globalThis.Object.freeze(new Good1.class()); +//│ break; //│ } //│ tmp7 = tmp6(x, y, z, f$capture4); //│ return tmp7 @@ -142,10 +145,13 @@ f().foo() //│ }); //│ Bad$ = function Bad$(isMut, f$capture4) { //│ let tmp6, tmp7; -//│ if (isMut === true) { -//│ tmp6 = new Bad1.class(); -//│ } else { -//│ tmp6 = globalThis.Object.freeze(new Bad1.class()); +//│ switch (isMut) { +//│ case true: +//│ tmp6 = new Bad1.class(); +//│ break; +//│ default: +//│ tmp6 = globalThis.Object.freeze(new Bad1.class()); +//│ break; //│ } //│ tmp7 = tmp6(f$capture4); //│ return tmp7 diff --git a/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls b/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls index d5b4fad2c4..a00acccf06 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls @@ -10,10 +10,13 @@ data class A(x) with //│ let B1, A1, B$; //│ B$ = function B$(isMut, A$instance, y) { //│ let tmp, tmp1; -//│ if (isMut === true) { -//│ tmp = new B1.class(y); -//│ } else { -//│ tmp = globalThis.Object.freeze(new B1.class(y)); +//│ switch (isMut) { +//│ case true: +//│ tmp = new B1.class(y); +//│ break; +//│ default: +//│ tmp = globalThis.Object.freeze(new B1.class(y)); +//│ break; //│ } //│ tmp1 = tmp(A$instance); //│ return tmp1 diff --git a/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls index dfd2bc3676..3ca74f94f1 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls @@ -383,17 +383,20 @@ fun f(x) = //│ let y1, scrut, g$here; //│ y1 = undefined; //│ scrut = x1 < 0; -//│ if (scrut === true) { -//│ y1 = 1; -//│ g$here = runtime.safeCall(g12(y1)); -//│ return globalThis.Object.freeze([ -//│ g$here, -//│ g$here -//│ ]) -//│ } else { -//│ y1 = 2; -//│ g$here = runtime.safeCall(g12(y1)); -//│ return globalThis.Object.freeze([ g$here, g$here ]) +//│ switch (scrut) { +//│ case true: +//│ y1 = 1; +//│ g$here = runtime.safeCall(g12(y1)); +//│ return globalThis.Object.freeze([ +//│ g$here, +//│ g$here +//│ ]); +//│ break; +//│ default: +//│ y1 = 2; +//│ g$here = runtime.safeCall(g12(y1)); +//│ return globalThis.Object.freeze([ g$here, g$here ]); +//│ break; //│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/lifter/Loops.mls b/hkmc2/shared/src/test/mlscript/lifter/Loops.mls index f3202dd07c..f584bf2b24 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/Loops.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/Loops.mls @@ -57,13 +57,16 @@ fun foo() = //│ while$1 = function while$(foo$capture6) { //│ let scrut, tmp2, lambda$here; //│ scrut = true; -//│ if (scrut === true) { -//│ tmp2 = foo$capture6.x$capture$0 + 1; -//│ foo$capture6.x$capture$0 = tmp2; -//│ lambda$here = runtime.safeCall(lambda2(foo$capture6)); -//│ return lambda$here -//│ } else { -//│ foo$capture6.tmp$capture$1 = runtime.Unit; +//│ switch (scrut) { +//│ case true: +//│ tmp2 = foo$capture6.x$capture$0 + 1; +//│ foo$capture6.x$capture$0 = tmp2; +//│ lambda$here = runtime.safeCall(lambda2(foo$capture6)); +//│ return lambda$here; +//│ break; +//│ default: +//│ foo$capture6.tmp$capture$1 = runtime.Unit; +//│ break; //│ } //│ return runtime.LoopEnd //│ }; @@ -85,7 +88,14 @@ fun foo() = //│ capture.tmp$capture$1 = undefined; //│ tmp2 = while$1(capture); //│ tmp3 = tmp2 !== runtime.LoopEnd; -//│ if (tmp3 === true) { return tmp2 } else { return capture.tmp$capture$1 } +//│ switch (tmp3) { +//│ case true: +//│ return tmp2; +//│ break; +//│ default: +//│ return capture.tmp$capture$1; +//│ break; +//│ } //│ }; :expect 2 diff --git a/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls b/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls index 1ab37b7a93..e960371bf7 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls @@ -30,10 +30,13 @@ foo(10) //│ let M3, foo1, M$; //│ M$ = function M$(isMut, y) { //│ let tmp; -//│ if (isMut === true) { -//│ tmp = new M3(y); -//│ } else { -//│ tmp = globalThis.Object.freeze(new M3(y)); +//│ switch (isMut) { +//│ case true: +//│ tmp = new M3(y); +//│ break; +//│ default: +//│ tmp = globalThis.Object.freeze(new M3(y)); +//│ break; //│ } //│ return tmp //│ }; diff --git a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls index 44708ae13a..3cd61bd3ec 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls @@ -30,31 +30,42 @@ hi(0) //│ if (stackDelayRes instanceof runtime.EffectSig.class) { //│ return runtime.unwind(stackDelayRes, 1, hi, null, null, 0, null, 3, n, scrut, tmp1) //│ } -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ n = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ tmp1 = runtime.resumeArr.at(saveOffset); -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp1 = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } //│ main: while (true) { -//│ if (pc === 0) { -//│ scrut = n == 0; -//│ if (scrut === true) { -//│ return 0 -//│ } else { -//│ tmp1 = n - 1; -//│ pc = 2; -//│ continue main -//│ } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) {} else if (pc === 2) { -//│ return hi(tmp1) +//│ switch (pc) { +//│ case 0: +//│ scrut = n == 0; +//│ switch (scrut) { +//│ case true: +//│ return 0; +//│ break; +//│ default: +//│ tmp1 = n - 1; +//│ pc = 2; +//│ continue main; +//│ break; +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ break; +//│ case 2: +//│ return hi(tmp1); +//│ break; //│ } //│ break; //│ } @@ -86,45 +97,60 @@ sum(10000) //│ if (stackDelayRes instanceof runtime.EffectSig.class) { //│ return runtime.unwind(stackDelayRes, 1, sum1, null, null, 0, null, 4, n, scrut, tmp2, tmp3) //│ } -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ n = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ tmp2 = runtime.resumeArr.at(saveOffset); -//│ if (pc === 2) { -//│ tmp3 = runtime.resumeValue; -//│ } else { -//│ saveOffset = runtime.resumeIdx + 3; -//│ tmp3 = runtime.resumeArr.at(saveOffset); -//│ } -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp2 = runtime.resumeArr.at(saveOffset); +//│ switch (pc) { +//│ case 2: +//│ tmp3 = runtime.resumeValue; +//│ break; +//│ default: +//│ saveOffset = runtime.resumeIdx + 3; +//│ tmp3 = runtime.resumeArr.at(saveOffset); +//│ break; +//│ } +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } //│ main: while (true) { -//│ if (pc === 0) { -//│ scrut = n == 0; -//│ if (scrut === true) { -//│ return 0 -//│ } else { -//│ tmp2 = n - 1; -//│ pc = 3; -//│ continue main -//│ } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) {} else if (pc === 2) { -//│ return n + tmp3 -//│ } else if (pc === 3) { -//│ tmp3 = sum1(tmp2); -//│ runtime.stackDepth = curDepth; -//│ if (tmp3 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp3, 1, sum1, null, null, 2, null, 4, n, scrut, tmp2, tmp3) -//│ } -//│ pc = 2; -//│ continue main +//│ switch (pc) { +//│ case 0: +//│ scrut = n == 0; +//│ switch (scrut) { +//│ case true: +//│ return 0; +//│ break; +//│ default: +//│ tmp2 = n - 1; +//│ pc = 3; +//│ continue main; +//│ break; +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ break; +//│ case 2: +//│ return n + tmp3; +//│ break; +//│ case 3: +//│ tmp3 = sum1(tmp2); +//│ runtime.stackDepth = curDepth; +//│ if (tmp3 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp3, 1, sum1, null, null, 2, null, 4, n, scrut, tmp2, tmp3) +//│ } +//│ pc = 2; +//│ continue main; +//│ break; //│ } //│ break; //│ } @@ -236,26 +262,38 @@ fun max(a, b) = if a < b then b else a //│ let max; //│ max = function max(a, b) { //│ let scrut, pc, saveOffset; -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ a = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ b = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; +//│ switch (runtime.isResuming) { +//│ case true: +//│ pc = runtime.resumePc; +//│ a = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ b = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 2; +//│ scrut = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ break; +//│ default: +//│ pc = 0; +//│ break; //│ } //│ main: while (true) { -//│ if (pc === 0) { -//│ scrut = a < b; -//│ if (scrut === true) { -//│ return b -//│ } else { return a } -//│ pc = 1; -//│ continue main -//│ } else if (pc === 1) {} +//│ switch (pc) { +//│ case 0: +//│ scrut = a < b; +//│ switch (scrut) { +//│ case true: +//│ return b; +//│ break; +//│ default: +//│ return a; +//│ break; +//│ } +//│ pc = 1; +//│ continue main; +//│ break; +//│ case 1: +//│ break; +//│ } //│ break; //│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls index 01db6e89df..a371a7fa56 100644 --- a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls @@ -137,16 +137,19 @@ fun popByIndex(start, end, acc, lft) = //│ popByIndex = function popByIndex(start, end, acc, lft) { //│ let scrut, tmp34, tmp35, tmp36; //│ scrut = start >= end; -//│ if (scrut === true) { -//│ return acc -//│ } else { -//│ tmp34 = start + 1; -//│ tmp35 = runtime.safeCall(lft.at(start)); -//│ tmp36 = globalThis.Object.freeze([ -//│ ...acc, -//│ tmp35 -//│ ]); -//│ return popByIndex(tmp34, end, tmp36, lft) +//│ switch (scrut) { +//│ case true: +//│ return acc; +//│ break; +//│ default: +//│ tmp34 = start + 1; +//│ tmp35 = runtime.safeCall(lft.at(start)); +//│ tmp36 = globalThis.Object.freeze([ +//│ ...acc, +//│ tmp35 +//│ ]); +//│ return popByIndex(tmp34, end, tmp36, lft); +//│ break; //│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/JoinPoints.mls b/hkmc2/shared/src/test/mlscript/ucs/general/JoinPoints.mls index 789353450b..7214d4358f 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/JoinPoints.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/JoinPoints.mls @@ -7,9 +7,14 @@ x => if x is 0 then 1 //│ JS (unsanitized): //│ let lambda; //│ lambda = (undefined, function (x) { -//│ if (x === 0) { -//│ return 1 -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ switch (x) { +//│ case 0: +//│ return 1; +//│ break; +//│ default: +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ break; +//│ } //│ }); //│ lambda //│ = fun @@ -27,11 +32,14 @@ x => if x is [[0]] then 1 //│ element0$ = runtime.Tuple.get(x, 0); //│ if (runtime.Tuple.isArrayLike(element0$) && element0$.length === 1) { //│ element0$1 = runtime.Tuple.get(element0$, 0); -//│ if (element0$1 === 0) { -//│ tmp = 1; -//│ break split_root$ -//│ } else { -//│ break split_default$ +//│ switch (element0$1) { +//│ case 0: +//│ tmp = 1; +//│ break split_root$; +//│ break; +//│ default: +//│ break split_default$; +//│ break; //│ } //│ } else { //│ break split_default$ @@ -74,11 +82,14 @@ fun crazy(v) = //│ argument0$4 = argument0$3.value; //│ if (argument0$4 instanceof S1.class) { //│ argument0$5 = argument0$4.value; -//│ if (argument0$5 === 0) { -//│ tmp = "bruh!"; -//│ break split_root$ -//│ } else { -//│ break split_1$ +//│ switch (argument0$5) { +//│ case 0: +//│ tmp = "bruh!"; +//│ break split_root$; +//│ break; +//│ default: +//│ break split_1$; +//│ break; //│ } //│ } else { //│ break split_1$ diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls b/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls index 1c74e8bfff..2095db7e9e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls @@ -29,13 +29,23 @@ true and test(42) //│ split_root$2: { //│ split_1$2: { //│ scrut4 = true; -//│ if (scrut4 === true) { -//│ scrut5 = test(42); -//│ if (scrut5 === true) { -//│ tmp4 = true; -//│ break split_root$2 -//│ } else { break split_1$2 } -//│ } else { break split_1$2 } +//│ switch (scrut4) { +//│ case true: +//│ scrut5 = test(42); +//│ switch (scrut5) { +//│ case true: +//│ tmp4 = true; +//│ break split_root$2; +//│ break; +//│ default: +//│ break split_1$2; +//│ break; +//│ } +//│ break; +//│ default: +//│ break split_1$2; +//│ break; +//│ } //│ } //│ tmp4 = false; //│ } @@ -46,7 +56,7 @@ true and test(42) :fixme true or test(42) //│ ╔══[ERROR] Logical `or` is not yet supported. -//│ ║ l.47: true or test(42) +//│ ║ l.57: true or test(42) //│ ╙── ^^^^^^^^^^^^^^^^ //│ > 42 //│ = false diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls index 4bba48a888..eb7e92da53 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls @@ -10,13 +10,23 @@ let z = 0 :sjs if x === 0 then 1 else 2 //│ JS (unsanitized): -//│ let scrut; scrut = x === 0; if (scrut === true) { 1 } else { 2 } +//│ let scrut; scrut = x === 0; switch (scrut) { case true: 1; break; default: 2; break; } //│ = 1 :sjs let a = if x === 0 then 1 else 2 //│ JS (unsanitized): -//│ let a, scrut1, tmp; scrut1 = x === 0; if (scrut1 === true) { tmp = 1; } else { tmp = 2; } a = tmp; +//│ let a, scrut1, tmp; +//│ scrut1 = x === 0; +//│ switch (scrut1) { +//│ case true: +//│ tmp = 1; +//│ break; +//│ default: +//│ tmp = 2; +//│ break; +//│ } +//│ a = tmp; //│ a = 1 :sjs @@ -24,7 +34,14 @@ print of if x === 0 then 1 else 2 //│ JS (unsanitized): //│ let scrut2, tmp1; //│ scrut2 = x === 0; -//│ if (scrut2 === true) { tmp1 = 1; } else { tmp1 = 2; } +//│ switch (scrut2) { +//│ case true: +//│ tmp1 = 1; +//│ break; +//│ default: +//│ tmp1 = 2; +//│ break; +//│ } //│ Predef.print(tmp1) //│ > 1 @@ -41,27 +58,41 @@ if x is //│ let tmp2; //│ split_root$: { //│ split_1$: { -//│ if (x === 0) { -//│ if (y === 0) { -//│ if (z === 0) { -//│ tmp2 = "000"; -//│ break split_root$ -//│ } else if (z === 1) { -//│ tmp2 = "001"; -//│ break split_root$ -//│ } else { -//│ break split_1$ +//│ switch (x) { +//│ case 0: +//│ switch (y) { +//│ case 0: +//│ switch (z) { +//│ case 0: +//│ tmp2 = "000"; +//│ break split_root$; +//│ break; +//│ case 1: +//│ tmp2 = "001"; +//│ break split_root$; +//│ break; +//│ default: +//│ break split_1$; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp2 = "01"; +//│ break split_root$; +//│ break; +//│ default: +//│ break split_1$; +//│ break; //│ } -//│ } else if (y === 1) { -//│ tmp2 = "01"; -//│ break split_root$ -//│ } else { -//│ break split_1$ -//│ } -//│ } else if (x === 1) { -//│ tmp2 = "1"; -//│ break split_root$ -//│ } else { break split_1$ } +//│ break; +//│ case 1: +//│ tmp2 = "1"; +//│ break split_root$; +//│ break; +//│ default: +//│ break split_1$; +//│ break; +//│ } //│ } //│ tmp2 = ""; //│ } @@ -81,27 +112,41 @@ let qqq = if x is //│ let qqq, tmp3; //│ split_root$1: { //│ split_1$1: { -//│ if (x === 0) { -//│ if (y === 0) { -//│ if (z === 0) { -//│ tmp3 = "000"; -//│ break split_root$1 -//│ } else if (z === 1) { -//│ tmp3 = "001"; -//│ break split_root$1 -//│ } else { -//│ break split_1$1 +//│ switch (x) { +//│ case 0: +//│ switch (y) { +//│ case 0: +//│ switch (z) { +//│ case 0: +//│ tmp3 = "000"; +//│ break split_root$1; +//│ break; +//│ case 1: +//│ tmp3 = "001"; +//│ break split_root$1; +//│ break; +//│ default: +//│ break split_1$1; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp3 = "01"; +//│ break split_root$1; +//│ break; +//│ default: +//│ break split_1$1; +//│ break; //│ } -//│ } else if (y === 1) { -//│ tmp3 = "01"; -//│ break split_root$1 -//│ } else { -//│ break split_1$1 -//│ } -//│ } else if (x === 1) { -//│ tmp3 = "1"; -//│ break split_root$1 -//│ } else { break split_1$1 } +//│ break; +//│ case 1: +//│ tmp3 = "1"; +//│ break split_root$1; +//│ break; +//│ default: +//│ break split_1$1; +//│ break; +//│ } //│ } //│ tmp3 = ""; //│ } @@ -121,27 +166,41 @@ print of if x is //│ let tmp4; //│ split_root$2: { //│ split_1$2: { -//│ if (x === 0) { -//│ if (y === 0) { -//│ if (z === 0) { -//│ tmp4 = "000"; -//│ break split_root$2 -//│ } else if (z === 1) { -//│ tmp4 = "001"; -//│ break split_root$2 -//│ } else { -//│ break split_1$2 +//│ switch (x) { +//│ case 0: +//│ switch (y) { +//│ case 0: +//│ switch (z) { +//│ case 0: +//│ tmp4 = "000"; +//│ break split_root$2; +//│ break; +//│ case 1: +//│ tmp4 = "001"; +//│ break split_root$2; +//│ break; +//│ default: +//│ break split_1$2; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp4 = "01"; +//│ break split_root$2; +//│ break; +//│ default: +//│ break split_1$2; +//│ break; //│ } -//│ } else if (y === 1) { -//│ tmp4 = "01"; -//│ break split_root$2 -//│ } else { -//│ break split_1$2 -//│ } -//│ } else if (x === 1) { -//│ tmp4 = "1"; -//│ break split_root$2 -//│ } else { break split_1$2 } +//│ break; +//│ case 1: +//│ tmp4 = "1"; +//│ break split_root$2; +//│ break; +//│ default: +//│ break split_1$2; +//│ break; +//│ } //│ } //│ tmp4 = ""; //│ } @@ -164,27 +223,41 @@ fun foo(x, y, z) = //│ let tmp5; //│ split_root$3: { //│ split_1$3: { -//│ if (x1 === 0) { -//│ if (y1 === 0) { -//│ if (z1 === 0) { -//│ tmp5 = "000"; -//│ break split_root$3 -//│ } else if (z1 === 1) { -//│ tmp5 = "001"; -//│ break split_root$3 -//│ } else { -//│ break split_1$3 +//│ switch (x1) { +//│ case 0: +//│ switch (y1) { +//│ case 0: +//│ switch (z1) { +//│ case 0: +//│ tmp5 = "000"; +//│ break split_root$3; +//│ break; +//│ case 1: +//│ tmp5 = "001"; +//│ break split_root$3; +//│ break; +//│ default: +//│ break split_1$3; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp5 = "01"; +//│ break split_root$3; +//│ break; +//│ default: +//│ break split_1$3; +//│ break; //│ } -//│ } else if (y1 === 1) { -//│ tmp5 = "01"; -//│ break split_root$3 -//│ } else { -//│ break split_1$3 -//│ } -//│ } else if (x1 === 1) { -//│ tmp5 = "1"; -//│ break split_root$3 -//│ } else { break split_1$3 } +//│ break; +//│ case 1: +//│ tmp5 = "1"; +//│ break split_root$3; +//│ break; +//│ default: +//│ break split_1$3; +//│ break; +//│ } //│ } //│ tmp5 = ""; //│ } @@ -210,39 +283,60 @@ print of if //│ split_1$3: { //│ split_2$: { //│ split_3$: { -//│ if (x === 0) { -//│ if (y === 0) { -//│ if (z === 0) { -//│ tmp5 = "000"; -//│ break split_root$3 -//│ } else if (z === 1) { -//│ break split_1$3 -//│ } else { -//│ break split_2$ +//│ switch (x) { +//│ case 0: +//│ switch (y) { +//│ case 0: +//│ switch (z) { +//│ case 0: +//│ tmp5 = "000"; +//│ break split_root$3; +//│ break; +//│ case 1: +//│ break split_1$3; +//│ break; +//│ default: +//│ break split_2$; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp5 = "01_"; +//│ break split_root$3; +//│ break; +//│ default: +//│ switch (z) { +//│ case 1: +//│ break split_1$3; +//│ break; +//│ default: +//│ switch (y) { +//│ case 2: +//│ break split_3$; +//│ break; +//│ default: +//│ break split_2$; +//│ break; +//│ } +//│ break; +//│ } +//│ break; //│ } -//│ } else if (y === 1) { -//│ tmp5 = "01_"; -//│ break split_root$3 -//│ } else { -//│ if (z === 1) { -//│ break split_1$3 -//│ } else { -//│ if (y === 2) { -//│ break split_3$ -//│ } else { -//│ break split_2$ -//│ } +//│ break; +//│ case 1: +//│ tmp5 = "1__"; +//│ break split_root$3; +//│ break; +//│ default: +//│ switch (y) { +//│ case 2: +//│ break split_3$; +//│ break; +//│ default: +//│ break split_2$; +//│ break; //│ } -//│ } -//│ } else if (x === 1) { -//│ tmp5 = "1__"; -//│ break split_root$3 -//│ } else { -//│ if (y === 2) { -//│ break split_3$ -//│ } else { -//│ break split_2$ -//│ } +//│ break; //│ } //│ } //│ tmp5 = "_2_"; @@ -274,39 +368,60 @@ fun foo(x, y, z) = if //│ split_1$4: { //│ split_2$1: { //│ split_3$1: { -//│ if (x1 === 0) { -//│ if (y1 === 0) { -//│ if (z1 === 0) { -//│ tmp6 = "000"; -//│ break split_root$4 -//│ } else if (z1 === 1) { -//│ break split_1$4 -//│ } else { -//│ break split_2$1 +//│ switch (x1) { +//│ case 0: +//│ switch (y1) { +//│ case 0: +//│ switch (z1) { +//│ case 0: +//│ tmp6 = "000"; +//│ break split_root$4; +//│ break; +//│ case 1: +//│ break split_1$4; +//│ break; +//│ default: +//│ break split_2$1; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp6 = "01_"; +//│ break split_root$4; +//│ break; +//│ default: +//│ switch (z1) { +//│ case 1: +//│ break split_1$4; +//│ break; +//│ default: +//│ switch (y1) { +//│ case 2: +//│ break split_3$1; +//│ break; +//│ default: +//│ break split_2$1; +//│ break; +//│ } +//│ break; +//│ } +//│ break; //│ } -//│ } else if (y1 === 1) { -//│ tmp6 = "01_"; -//│ break split_root$4 -//│ } else { -//│ if (z1 === 1) { -//│ break split_1$4 -//│ } else { -//│ if (y1 === 2) { -//│ break split_3$1 -//│ } else { -//│ break split_2$1 -//│ } +//│ break; +//│ case 1: +//│ tmp6 = "1__"; +//│ break split_root$4; +//│ break; +//│ default: +//│ switch (y1) { +//│ case 2: +//│ break split_3$1; +//│ break; +//│ default: +//│ break split_2$1; +//│ break; //│ } -//│ } -//│ } else if (x1 === 1) { -//│ tmp6 = "1__"; -//│ break split_root$4 -//│ } else { -//│ if (y1 === 2) { -//│ break split_3$1 -//│ } else { -//│ break split_2$1 -//│ } +//│ break; //│ } //│ } //│ tmp6 = "_2_"; @@ -339,27 +454,41 @@ fun foo(x, y, z) = if x is //│ let value, tmp6; //│ split_root$4: { //│ split_1$4: { -//│ if (x1 === 0) { -//│ if (y1 === 0) { -//│ if (z1 === 0) { -//│ tmp6 = "000"; -//│ break split_root$4 -//│ } else if (z1 === 1) { -//│ tmp6 = "001"; -//│ break split_root$4 -//│ } else { -//│ break split_1$4 +//│ switch (x1) { +//│ case 0: +//│ switch (y1) { +//│ case 0: +//│ switch (z1) { +//│ case 0: +//│ tmp6 = "000"; +//│ break split_root$4; +//│ break; +//│ case 1: +//│ tmp6 = "001"; +//│ break split_root$4; +//│ break; +//│ default: +//│ break split_1$4; +//│ break; +//│ } +//│ break; +//│ case 1: +//│ tmp6 = "01"; +//│ break split_root$4; +//│ break; +//│ default: +//│ break split_1$4; +//│ break; //│ } -//│ } else if (y1 === 1) { -//│ tmp6 = "01"; -//│ break split_root$4 -//│ } else { -//│ break split_1$4 -//│ } -//│ } else if (x1 === 1) { -//│ tmp6 = "1"; -//│ break split_root$4 -//│ } else { break split_1$4 } +//│ break; +//│ case 1: +//│ tmp6 = "1"; +//│ break split_root$4; +//│ break; +//│ default: +//│ break split_1$4; +//│ break; +//│ } //│ } //│ value = "hello"; //│ tmp6 = expensive_call(value); @@ -466,6 +595,13 @@ let y = if true then 1 else 2 //│ JS (unsanitized): //│ let y1, scrut3, tmp6; //│ scrut3 = true; -//│ if (scrut3 === true) { tmp6 = 1; } else { tmp6 = 2; } +//│ switch (scrut3) { +//│ case true: +//│ tmp6 = 1; +//│ break; +//│ default: +//│ tmp6 = 2; +//│ break; +//│ } //│ y1 = tmp6; //│ y = 1 From 13bf90d3ebadf673b41a889700c6eb93cbc03eb6 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 9 Dec 2025 19:38:37 +0800 Subject: [PATCH 10/14] Lazy state creation --- .../scala/hkmc2/codegen/HandlerLowering.scala | 115 ++++++++++-------- .../src/test/mlscript/handlers/Debugging.mls | 22 ++-- .../src/test/mlscript/handlers/Effects.mls | 39 ++---- .../test/mlscript/handlers/EffectsHygiene.mls | 77 ++++-------- .../mlscript/handlers/RecursiveHandlers.mls | 4 + .../test/mlscript/handlers/StackSafety.mls | 36 ++---- .../test/mlscript/lifter/StackSafetyLift.mls | 36 ++---- 7 files changed, 130 insertions(+), 199 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 6cab031c99..f694574e30 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -130,14 +130,26 @@ 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 + + abstract class LazyId: + private var id: Opt[StateId] = N + protected def getImpl: StateId + def get: StateId = id match + case S(value) => value + case N => + val value = getImpl + id = S(value) + value + def isUsed: Bool = id.isDefined + def transitionOrBlk(blk: => Block) = + if isUsed then StateTransition(get) else blk - private class FreshId: + private class IdAllocator: var id: Int = 0 def apply() = val tmp = id id += 1 tmp - private val freshId = FreshId() // blk: the block of code within this state // sym: the variable to which the resumed value should set @@ -246,20 +258,24 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, private def partitionBlock(blk: Block)(using h: HandlerCtx): PartitionedBlock = val result = mutable.HashMap.empty[StateId, BlockPartition] - val freshId = FreshId() + val allocId = new IdAllocator() // * 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)(using labelIds: Map[Symbol, (StateId, StateId)], afterEnd: Option[StateId]): Block = boundary: + 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 // sym: the local that stores the result def doNewEffectPartition(sym: Local, res: Result, rst: Block) = - val stateId = freshId() - result(stateId) = BlockPartition(go(rst), S(sym)) + val stateId = allocId() + result(stateId) = BlockPartition(go(rst)(using partitioned = true), S(sym)) val newBlock = blockBuilder .assign(sym, res) .ifthen( @@ -269,6 +285,16 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, ) .rest(StateTransition(stateId)) boundary.break(newBlock) + class RestLazyId(rst: Block) extends LazyId: + def getImpl: StateId = + go(rst)(using partitioned = true) match + case StateTransition(uid) => uid + case newRst => + val id = allocId() + result(id) = BlockPartition(newRst, N) + id + def transitionSoft: Block = transitionOrBlk(go(rst)) + val nonTrivialBlockChecker = new BlockDataTransformer(SymbolSubst()): override def applyBlock(b: Block) = b match // Special handling for tail calls @@ -291,29 +317,21 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, blk match case Match(scrut, arms, dflt, rest) => - val newRest = go(rest) - val restId: StateId = newRest match - case StateTransition(uid) => uid - case _ => - val id = freshId() - result(id) = BlockPartition(newRest, N) - id + 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, StateTransition(restId)) + Match(scrut, newArms, newDflt, restId.transitionSoft) case Label(label, loop, body, rest) => - val startId = freshId() // start of body - val newRest = go(rest) - val endId: StateId = newRest match // start of rest - case StateTransition(uid) => uid - case _ => - val id = freshId() - result(id) = BlockPartition(newRest, N) - id - val newBody = go(body)(using labelIds + (label -> (startId, endId)), S(endId)) - result(startId) = BlockPartition(newBody, N) - StateTransition(startId) + 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), N) + StateTransition(startId.get) + else + Label(label, loop, newBody, restId.transitionSoft) case Break(label) => val (start, end) = labelIds.get(label) match @@ -323,7 +341,10 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, source = Diagnostic.Source.Compilation)) return blk case S(value) => value - StateTransition(end) + if partitioned then + StateTransition(end.get) + else + Break(label) case Continue(label) => val (start, end) = labelIds.get(label) match @@ -333,32 +354,28 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, source = Diagnostic.Source.Compilation)) return blk case S(value) => value - StateTransition(start) - - // An optimization to omit useless state - case Begin(End(_), blk) => go(blk) + if partitioned then + StateTransition(start.get) + else + Continue(label) case Begin(sub, rest) => - val newRest = go(rest) - newRest match - case StateTransition(uid) => go(sub)(using afterEnd = S(uid)) - case _ => - val id = freshId() - result(id) = BlockPartition(newRest, N) - go(sub)(using afterEnd = S(id)) - - case End(_) => afterEnd match - case None => blk - case Some(id) => StateTransition(id) + 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 // 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. - // If the constructor is non-trivial, we will append `return thisVar;` afterwards. - // Erasing the implicit return early here is sound since trivial constructor will - // do the trivial transformation instead of continuing the instrumentation. - case Return(_, true) => afterEnd match - case None => End() - case Some(id) => StateTransition(id) + // 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) // identity cases @@ -373,8 +390,8 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, case Throw(_) => blk case _: HandleBlock => lastWords("unexpected handleBlock") // already translated at this point - val initId = freshId() - val initPart = BlockPartition(go(blk)(using Map(), N), N) + val initId = allocId() + val initPart = BlockPartition(go(blk)(using Map(), N, false), N) result(initId) = initPart PartitionedBlock(initId, Map.from(result)) diff --git a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls index 91251ad53d..4067fdb003 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls @@ -42,7 +42,7 @@ fun f() = //│ saveOffset = runtime.resumeIdx + 3; //│ scrut = runtime.resumeArr.at(saveOffset); //│ switch (pc) { -//│ case 4: +//│ case 1: //│ tmp1 = runtime.resumeValue; //│ break; //│ default: @@ -76,28 +76,30 @@ fun f() = //│ case true: //│ tmp1 = Predef.raiseUnhandledEffect(); //│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp1, 1, f, f$debugInfo, null, 4, null, 6, i, j, k, scrut, tmp1, tmp2) +//│ return runtime.unwind(tmp1, 1, f, f$debugInfo, null, 1, null, 6, i, j, k, scrut, tmp1, tmp2) //│ } -//│ pc = 4; +//│ pc = 1; //│ continue main; //│ break; //│ default: //│ tmp2 = runtime.Unit; -//│ pc = 1; -//│ continue main; //│ break; //│ } -//│ pc = 1; +//│ pc = 3; //│ continue main; //│ break; //│ case 1: -//│ return j / i; +//│ pc = 4; +//│ continue main; //│ break; //│ case 2: -//│ pc = 1; +//│ pc = 3; //│ continue main; //│ break; //│ case 3: +//│ return j / i; +//│ break; +//│ case 4: //│ tmp2 = Predef.print(tmp1); //│ if (tmp2 instanceof runtime.EffectSig.class) { //│ return runtime.unwind(tmp2, 1, f, f$debugInfo, null, 2, null, 6, i, j, k, scrut, tmp1, tmp2) @@ -105,10 +107,6 @@ fun f() = //│ pc = 2; //│ continue main; //│ break; -//│ case 4: -//│ pc = 3; -//│ continue main; -//│ break; //│ } //│ break; //│ } diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 1cebd096eb..7b42afd513 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -195,37 +195,16 @@ if true do //│ return 3 //│ }; //│ handleBlock$11 = (undefined, function () { -//│ let scrut, pc; -//│ switch (runtime.isResuming) { +//│ let scrut; +//│ scrut = true; +//│ switch (scrut) { //│ case true: -//│ pc = runtime.resumePc; -//│ scrut = runtime.resumeArr.at(runtime.resumeIdx); -//│ runtime.isResuming = false; +//│ return f(); //│ break; //│ default: -//│ pc = 0; +//│ return runtime.Unit; //│ break; //│ } -//│ main: while (true) { -//│ switch (pc) { -//│ case 0: -//│ scrut = true; -//│ switch (scrut) { -//│ case true: -//│ return f(); -//│ break; -//│ default: -//│ return runtime.Unit; -//│ break; -//│ } -//│ pc = 1; -//│ continue main; -//│ break; -//│ case 1: -//│ break; -//│ } -//│ break; -//│ } //│ }); //│ tmp11 = runtime.enterHandleBlock(h11, handleBlock$11); //│ if (tmp11 instanceof runtime.EffectSig.class) { @@ -408,16 +387,16 @@ foo(h) //│ ═══[WARNING] Modules are not yet lifted. //│ ═══[WARNING] Modules are not yet lifted. //│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. -//│ ║ l.387: module A with +//│ ║ l.366: module A with //│ ╙── ^ //│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. -//│ ║ l.391: module A with +//│ ║ l.370: module A with //│ ╙── ^ //│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. -//│ ║ l.395: module A with +//│ ║ l.374: module A with //│ ╙── ^ //│ ╔══[WARNING] Unexpected nested class: lambdas may not function correctly. -//│ ║ l.399: module A with +//│ ║ l.378: module A with //│ ╙── ^ //│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls index 1917e72f85..3da9bff992 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls @@ -26,62 +26,35 @@ fun foo(h): module M = //│ JS (unsanitized): //│ let foo; //│ foo = function foo(h) { -//│ let A2, A3, scrut, pc, saveOffset; -//│ switch (runtime.isResuming) { +//│ let A2, A3, scrut; +//│ scrut = false; +//│ switch (scrut) { //│ case true: -//│ pc = runtime.resumePc; -//│ A2 = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ A3 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ h = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 3; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ runtime.isResuming = false; +//│ globalThis.Object.freeze(class A { +//│ static { +//│ A2 = this +//│ } +//│ constructor() { +//│ runtime.Unit; +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "A"]; +//│ }); +//│ return A2; //│ break; //│ default: -//│ pc = 0; -//│ break; -//│ } -//│ main: while (true) { -//│ switch (pc) { -//│ case 0: -//│ scrut = false; -//│ switch (scrut) { -//│ case true: -//│ globalThis.Object.freeze(class A { -//│ static { -//│ A2 = this -//│ } -//│ constructor() { -//│ runtime.Unit; -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "A"]; -//│ }); -//│ return A2; -//│ break; -//│ default: -//│ globalThis.Object.freeze(class A1 { -//│ static { -//│ A3 = this -//│ } -//│ constructor() { -//│ runtime.Unit; -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "A"]; -//│ }); -//│ return A3; -//│ break; +//│ globalThis.Object.freeze(class A1 { +//│ static { +//│ A3 = this +//│ } +//│ constructor() { +//│ runtime.Unit; //│ } -//│ pc = 1; -//│ continue main; -//│ break; -//│ case 1: -//│ break; -//│ } -//│ break; +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "A"]; +//│ }); +//│ return A3; +//│ break; //│ } //│ }; //│ ═══[WARNING] Modules are not yet lifted. diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index aaa6f94723..812fbcb439 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -354,6 +354,10 @@ str //│ continue main; //│ break; //│ case 1: +//│ pc = 2; +//│ continue main; +//│ break; +//│ case 2: //│ return runtime.safeCall(h11.perform(runtime.Unit)); //│ break; //│ } diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index cf20654374..b3c6706898 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -46,17 +46,9 @@ hi(0) //│ break; //│ default: //│ tmp1 = n - 1; -//│ pc = 2; -//│ continue main; +//│ return hi(tmp1); //│ break; //│ } -//│ pc = 1; -//│ continue main; -//│ break; -//│ case 1: -//│ break; -//│ case 2: -//│ return hi(tmp1); //│ break; //│ } //│ break; @@ -97,7 +89,7 @@ sum(10000) //│ saveOffset = runtime.resumeIdx + 2; //│ tmp2 = runtime.resumeArr.at(saveOffset); //│ switch (pc) { -//│ case 2: +//│ case 1: //│ tmp3 = runtime.resumeValue; //│ break; //│ default: @@ -121,27 +113,19 @@ sum(10000) //│ break; //│ default: //│ tmp2 = n - 1; -//│ pc = 3; +//│ tmp3 = sum(tmp2); +//│ runtime.stackDepth = curDepth; +//│ if (tmp3 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp3, 1, sum, null, null, 1, null, 4, n, scrut, tmp2, tmp3) +//│ } +//│ pc = 1; //│ continue main; //│ break; //│ } -//│ pc = 1; -//│ continue main; //│ break; //│ case 1: -//│ break; -//│ case 2: //│ return n + tmp3; //│ break; -//│ case 3: -//│ tmp3 = sum(tmp2); -//│ runtime.stackDepth = curDepth; -//│ if (tmp3 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp3, 1, sum, null, null, 2, null, 4, n, scrut, tmp2, tmp3) -//│ } -//│ pc = 2; -//│ continue main; -//│ break; //│ } //│ break; //│ } @@ -277,10 +261,6 @@ fun max(a, b) = if a < b then b else a //│ return a; //│ break; //│ } -//│ pc = 1; -//│ continue main; -//│ break; -//│ case 1: //│ break; //│ } //│ break; diff --git a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls index 3cd61bd3ec..8fd27093d9 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls @@ -54,17 +54,9 @@ hi(0) //│ break; //│ default: //│ tmp1 = n - 1; -//│ pc = 2; -//│ continue main; +//│ return hi(tmp1); //│ break; //│ } -//│ pc = 1; -//│ continue main; -//│ break; -//│ case 1: -//│ break; -//│ case 2: -//│ return hi(tmp1); //│ break; //│ } //│ break; @@ -106,7 +98,7 @@ sum(10000) //│ saveOffset = runtime.resumeIdx + 2; //│ tmp2 = runtime.resumeArr.at(saveOffset); //│ switch (pc) { -//│ case 2: +//│ case 1: //│ tmp3 = runtime.resumeValue; //│ break; //│ default: @@ -130,27 +122,19 @@ sum(10000) //│ break; //│ default: //│ tmp2 = n - 1; -//│ pc = 3; +//│ tmp3 = sum1(tmp2); +//│ runtime.stackDepth = curDepth; +//│ if (tmp3 instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(tmp3, 1, sum1, null, null, 1, null, 4, n, scrut, tmp2, tmp3) +//│ } +//│ pc = 1; //│ continue main; //│ break; //│ } -//│ pc = 1; -//│ continue main; //│ break; //│ case 1: -//│ break; -//│ case 2: //│ return n + tmp3; //│ break; -//│ case 3: -//│ tmp3 = sum1(tmp2); -//│ runtime.stackDepth = curDepth; -//│ if (tmp3 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp3, 1, sum1, null, null, 2, null, 4, n, scrut, tmp2, tmp3) -//│ } -//│ pc = 2; -//│ continue main; -//│ break; //│ } //│ break; //│ } @@ -288,10 +272,6 @@ fun max(a, b) = if a < b then b else a //│ return a; //│ break; //│ } -//│ pc = 1; -//│ continue main; -//│ break; -//│ case 1: //│ break; //│ } //│ break; From 4cbf8af503c4620256a4a1dd759edcfd6c7c9b5a Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 9 Dec 2025 21:36:43 +0800 Subject: [PATCH 11/14] Simplify resumeValue --- .../scala/hkmc2/codegen/HandlerLowering.scala | 50 ++++----- .../src/test/mlscript/handlers/Debugging.mls | 52 ++++----- .../mlscript/handlers/RecursiveHandlers.mls | 104 +++++++----------- .../test/mlscript/handlers/StackSafety.mls | 21 ++-- .../test/mlscript/lifter/StackSafetyLift.mls | 21 ++-- 5 files changed, 93 insertions(+), 155 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index f694574e30..a1806fda57 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -101,6 +101,8 @@ class HandlerPaths(using Elaborator.State): 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): @@ -153,7 +155,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, // blk: the block of code within this state // sym: the variable to which the resumed value should set - case class BlockPartition(blk: Block, sym: Opt[Local]) + case class BlockPartition(blk: Block) case class PartitionedBlock(entry: StateId, states: Map[StateId, BlockPartition]) // Tries to remove states that jump directly to other states @@ -272,43 +274,39 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, 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): StateId = blk match + case StateTransition(uid) => uid + case _ => + val id = allocId() + result(id) = BlockPartition(blk) + id + // sym: the local that stores the result - def doNewEffectPartition(sym: Local, res: Result, rst: Block) = - val stateId = allocId() - result(stateId) = BlockPartition(go(rst)(using partitioned = true), S(sym)) + def doNewEffectPartition(res: Result, rst: Block) = + val stateId = forceId(go(rst)(using partitioned = true)) val newBlock = blockBuilder - .assign(sym, res) + .assignFieldN(paths.runtimePath, paths.resumeValueIdent, res) .ifthen( - sym.asPath, + paths.resumeValue, Case.Cls(paths.effectSigSym, paths.effectSigPath), - h.doUnwind(sym.asPath, stateId)(using paths) + h.doUnwind(paths.resumeValue, stateId)(using paths) ) .rest(StateTransition(stateId)) boundary.break(newBlock) class RestLazyId(rst: Block) extends LazyId: - def getImpl: StateId = - go(rst)(using partitioned = true) match - case StateTransition(uid) => uid - case newRst => - val id = allocId() - result(id) = BlockPartition(newRst, N) - id + def getImpl: StateId = forceId(go(rst)(using partitioned = true)) 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 Assign(lhs, EffectfulResult(r), rest) => - // Optimization to reuse lhs instead of fresh local - doNewEffectPartition(lhs, r, rest) case _ => super.applyBlock(b) override def applyResult(r: Result)(k: Result => Block) = r match case EffectfulResult(r) => // Fallback case, this may lead to unnecessary vars if it is assign-like // FIXME: This fall back case might be not needed at all. - val l = freshTmp() - doNewEffectPartition(l, r, k(Value.Ref(l))) + doNewEffectPartition(r, k(paths.resumeValue)) case _ => super.applyResult(r)(k) // If current block contains direct effectful result the following call will early exit. @@ -328,7 +326,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, 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), N) + result(startId.get) = BlockPartition(Begin(newBody, restId.transitionSoft)) StateTransition(startId.get) else Label(label, loop, newBody, restId.transitionSoft) @@ -391,7 +389,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, case _: HandleBlock => lastWords("unexpected handleBlock") // already translated at this point val initId = allocId() - val initPart = BlockPartition(go(blk)(using Map(), N, false), N) + val initPart = BlockPartition(go(blk)(using Map(), N, false)) result(initId) = initPart PartitionedBlock(initId, Map.from(result)) @@ -523,18 +521,10 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, val computeOff = Assign(getSavedTmp, Call(State.builtinOpsMap("+").asPath, paths.runtimePath.selSN("resumeIdx").asArg :: intLit(off).asArg :: Nil)(false, false), _) (computeOff, DynSelect(paths.runtimePath.selSN("resumeArr"), getSavedTmp.asPath, true)) - val restoreMap = mutable.HashMap.empty[Local, mutable.ArrayBuffer[StateId]] - parts.states.foreach: (id, part) => - part.sym.foreach: l => - restoreMap.getOrElseUpdate(l, mutable.ArrayBuffer.empty) += id val restoreVars = h.currentLocals.zipWithIndex.foldLeft(blockBuilder.assign(pcVar, paths.resumePc)): case (builder, (local, idx)) => val (computeOff, savePath) = getSaved(idx) - builder.chain(Match( - pcVar.asPath, - restoreMap.getOrElse(local, mutable.ArrayBuffer.empty).map(id => Case.Lit(Tree.IntLit(id)) -> Assign(local, paths.runtimePath.selSN("resumeValue"), End())).toList, - S(blockBuilder.chain(computeOff).assign(local, savePath).end), - _)) + builder.chain(computeOff).assign(local, savePath) Match( paths.isResuming, diff --git a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls index 4067fdb003..18ac4c0669 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls @@ -41,24 +41,10 @@ fun f() = //│ k = runtime.resumeArr.at(saveOffset); //│ saveOffset = runtime.resumeIdx + 3; //│ scrut = runtime.resumeArr.at(saveOffset); -//│ switch (pc) { -//│ case 1: -//│ tmp1 = runtime.resumeValue; -//│ break; -//│ default: -//│ saveOffset = runtime.resumeIdx + 4; -//│ tmp1 = runtime.resumeArr.at(saveOffset); -//│ break; -//│ } -//│ switch (pc) { -//│ case 2: -//│ tmp2 = runtime.resumeValue; -//│ break; -//│ default: -//│ saveOffset = runtime.resumeIdx + 5; -//│ tmp2 = runtime.resumeArr.at(saveOffset); -//│ break; -//│ } +//│ saveOffset = runtime.resumeIdx + 4; +//│ tmp1 = runtime.resumeArr.at(saveOffset); +//│ saveOffset = runtime.resumeIdx + 5; +//│ tmp2 = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; //│ default: @@ -74,39 +60,41 @@ fun f() = //│ scrut = i == 0; //│ switch (scrut) { //│ case true: -//│ tmp1 = Predef.raiseUnhandledEffect(); -//│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp1, 1, f, f$debugInfo, null, 1, null, 6, i, j, k, scrut, tmp1, tmp2) +//│ runtime.resumeValue = Predef.raiseUnhandledEffect(); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, f, f$debugInfo, null, 4, null, 6, i, j, k, scrut, tmp1, tmp2) //│ } -//│ pc = 1; +//│ pc = 4; //│ continue main; //│ break; //│ default: //│ tmp2 = runtime.Unit; //│ break; //│ } -//│ pc = 3; +//│ pc = 1; //│ continue main; //│ break; //│ case 1: -//│ pc = 4; -//│ continue main; +//│ return j / i; //│ break; //│ case 2: -//│ pc = 3; +//│ tmp2 = runtime.resumeValue; +//│ pc = 1; //│ continue main; //│ break; //│ case 3: -//│ return j / i; -//│ break; -//│ case 4: -//│ tmp2 = Predef.print(tmp1); -//│ if (tmp2 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp2, 1, f, f$debugInfo, null, 2, null, 6, i, j, k, scrut, tmp1, tmp2) +//│ runtime.resumeValue = Predef.print(tmp1); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, f, f$debugInfo, null, 2, null, 6, i, j, k, scrut, tmp1, tmp2) //│ } //│ pc = 2; //│ continue main; //│ break; +//│ case 4: +//│ tmp1 = runtime.resumeValue; +//│ pc = 3; +//│ continue main; +//│ break; //│ } //│ break; //│ } diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 812fbcb439..82e2d18da7 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -170,15 +170,8 @@ str //│ k = runtime.resumeArr.at(runtime.resumeIdx); //│ saveOffset = runtime.resumeIdx + 1; //│ tmp13 = runtime.resumeArr.at(saveOffset); -//│ switch (pc) { -//│ case 1: -//│ tmp14 = runtime.resumeValue; -//│ break; -//│ default: -//│ saveOffset = runtime.resumeIdx + 2; -//│ tmp14 = runtime.resumeArr.at(saveOffset); -//│ break; -//│ } +//│ saveOffset = runtime.resumeIdx + 2; +//│ tmp14 = runtime.resumeArr.at(saveOffset); //│ saveOffset = runtime.resumeIdx + 3; //│ tmp15 = runtime.resumeArr.at(saveOffset); //│ saveOffset = runtime.resumeIdx + 4; @@ -196,14 +189,15 @@ str //│ case 0: //│ tmp13 = str + "A"; //│ str = tmp13; -//│ tmp14 = runtime.safeCall(k(arg)); -//│ if (tmp14 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp14, 1, Handler$h1$perform$1, null, null, 1, null, 6, k, tmp13, tmp14, tmp15, arg, Handler$h1$$instance) +//│ runtime.resumeValue = runtime.safeCall(k(arg)); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, Handler$h1$perform$1, null, null, 1, null, 6, k, tmp13, tmp14, tmp15, arg, Handler$h1$$instance) //│ } //│ pc = 1; //│ continue main; //│ break; //│ case 1: +//│ tmp14 = runtime.resumeValue; //│ tmp15 = str + "A"; //│ str = tmp15; //│ return runtime.Unit; @@ -251,15 +245,8 @@ str //│ tmp13 = runtime.resumeArr.at(saveOffset); //│ saveOffset = runtime.resumeIdx + 2; //│ tmp14 = runtime.resumeArr.at(saveOffset); -//│ switch (pc) { -//│ case 1: -//│ tmp15 = runtime.resumeValue; -//│ break; -//│ default: -//│ saveOffset = runtime.resumeIdx + 3; -//│ tmp15 = runtime.resumeArr.at(saveOffset); -//│ break; -//│ } +//│ saveOffset = runtime.resumeIdx + 3; +//│ tmp15 = runtime.resumeArr.at(saveOffset); //│ saveOffset = runtime.resumeIdx + 4; //│ tmp16 = runtime.resumeArr.at(saveOffset); //│ saveOffset = runtime.resumeIdx + 5; @@ -280,14 +267,15 @@ str //│ tmp13 = str + "B"; //│ tmp14 = str + tmp13; //│ str = tmp14; -//│ tmp15 = runtime.safeCall(k(arg)); -//│ if (tmp15 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp15, 1, Handler$h2$perform$1, null, null, 1, null, 8, k, tmp13, tmp14, tmp15, tmp16, tmp17, arg, Handler$h2$$instance) +//│ runtime.resumeValue = runtime.safeCall(k(arg)); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, Handler$h2$perform$1, null, null, 1, null, 8, k, tmp13, tmp14, tmp15, tmp16, tmp17, arg, Handler$h2$$instance) //│ } //│ pc = 1; //│ continue main; //│ break; //│ case 1: +//│ tmp15 = runtime.resumeValue; //│ tmp16 = str + "B"; //│ tmp17 = str + tmp16; //│ str = tmp17; @@ -327,14 +315,7 @@ str //│ switch (runtime.isResuming) { //│ case true: //│ pc = runtime.resumePc; -//│ switch (pc) { -//│ case 1: -//│ tmp13 = runtime.resumeValue; -//│ break; -//│ default: -//│ tmp13 = runtime.resumeArr.at(runtime.resumeIdx); -//│ break; -//│ } +//│ tmp13 = runtime.resumeArr.at(runtime.resumeIdx); //│ saveOffset = runtime.resumeIdx + 1; //│ h22 = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; @@ -346,19 +327,20 @@ str //│ main: while (true) { //│ switch (pc) { //│ case 0: -//│ tmp13 = runtime.safeCall(h22.perform(runtime.Unit)); -//│ if (tmp13 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp13, 1, handleBlock$$2, null, null, 1, null, 2, tmp13, h22) +//│ runtime.resumeValue = runtime.safeCall(h22.perform(runtime.Unit)); +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, handleBlock$$2, null, null, 2, null, 2, tmp13, h22) //│ } -//│ pc = 1; +//│ pc = 2; //│ continue main; //│ break; //│ case 1: -//│ pc = 2; -//│ continue main; +//│ return runtime.safeCall(h11.perform(runtime.Unit)); //│ break; //│ case 2: -//│ return runtime.safeCall(h11.perform(runtime.Unit)); +//│ tmp13 = runtime.resumeValue; +//│ pc = 1; +//│ continue main; //│ break; //│ } //│ break; @@ -374,23 +356,9 @@ str //│ switch (runtime.isResuming) { //│ case true: //│ pc = runtime.resumePc; -//│ switch (pc) { -//│ case 1: -//│ h22 = runtime.resumeValue; -//│ break; -//│ default: -//│ h22 = runtime.resumeArr.at(runtime.resumeIdx); -//│ break; -//│ } -//│ switch (pc) { -//│ case 2: -//│ tmp13 = runtime.resumeValue; -//│ break; -//│ default: -//│ saveOffset = runtime.resumeIdx + 1; -//│ tmp13 = runtime.resumeArr.at(saveOffset); -//│ break; -//│ } +//│ h22 = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ tmp13 = runtime.resumeArr.at(saveOffset); //│ saveOffset = runtime.resumeIdx + 2; //│ handleBlock$$here = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; @@ -402,25 +370,27 @@ str //│ main: while (true) { //│ switch (pc) { //│ case 0: -//│ h22 = new Handler$h2$3(); -//│ if (h22 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(h22, 1, handleBlock$10, null, null, 1, null, 3, h22, tmp13, handleBlock$$here) +//│ 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, 3, h22, tmp13, handleBlock$$here) //│ } -//│ pc = 1; +//│ 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)); -//│ tmp13 = runtime.enterHandleBlock(h22, handleBlock$$here); -//│ if (tmp13 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp13, 1, handleBlock$10, null, null, 2, null, 3, h22, tmp13, handleBlock$$here) +//│ 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, 3, h22, tmp13, handleBlock$$here) //│ } -//│ pc = 2; +//│ pc = 1; //│ continue main; //│ break; -//│ case 2: -//│ return tmp13; -//│ break; //│ } //│ break; //│ } diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index b3c6706898..ff1e323169 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -73,7 +73,7 @@ sum(10000) //│ JS (unsanitized): //│ let sum, tmp1, $_stack$_safe$_body$_1; //│ sum = function sum(n) { -//│ let scrut, tmp2, tmp3, pc, saveOffset, curDepth, stackDelayRes; +//│ let scrut, tmp2, tmp3, pc, saveOffset, tmp4, curDepth, stackDelayRes; //│ curDepth = runtime.stackDepth; //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); @@ -88,15 +88,8 @@ sum(10000) //│ scrut = runtime.resumeArr.at(saveOffset); //│ saveOffset = runtime.resumeIdx + 2; //│ tmp2 = runtime.resumeArr.at(saveOffset); -//│ switch (pc) { -//│ case 1: -//│ tmp3 = runtime.resumeValue; -//│ break; -//│ default: -//│ saveOffset = runtime.resumeIdx + 3; -//│ tmp3 = runtime.resumeArr.at(saveOffset); -//│ break; -//│ } +//│ saveOffset = runtime.resumeIdx + 3; +//│ tmp3 = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; //│ default: @@ -113,10 +106,11 @@ sum(10000) //│ break; //│ default: //│ tmp2 = n - 1; -//│ tmp3 = sum(tmp2); +//│ tmp4 = sum(tmp2); //│ runtime.stackDepth = curDepth; -//│ if (tmp3 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp3, 1, sum, null, null, 1, null, 4, n, scrut, tmp2, tmp3) +//│ runtime.resumeValue = tmp4; +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, sum, null, null, 1, null, 4, n, scrut, tmp2, tmp3) //│ } //│ pc = 1; //│ continue main; @@ -124,6 +118,7 @@ sum(10000) //│ } //│ break; //│ case 1: +//│ tmp3 = runtime.resumeValue; //│ return n + tmp3; //│ break; //│ } diff --git a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls index 8fd27093d9..93104a18fe 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls @@ -82,7 +82,7 @@ sum(10000) //│ JS (unsanitized): //│ let sum1, tmp1, $_stack$_safe$_body$_1; //│ sum1 = function sum(n) { -//│ let scrut, tmp2, tmp3, pc, saveOffset, curDepth, stackDelayRes; +//│ let scrut, tmp2, tmp3, pc, saveOffset, tmp4, curDepth, stackDelayRes; //│ curDepth = runtime.stackDepth; //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); @@ -97,15 +97,8 @@ sum(10000) //│ scrut = runtime.resumeArr.at(saveOffset); //│ saveOffset = runtime.resumeIdx + 2; //│ tmp2 = runtime.resumeArr.at(saveOffset); -//│ switch (pc) { -//│ case 1: -//│ tmp3 = runtime.resumeValue; -//│ break; -//│ default: -//│ saveOffset = runtime.resumeIdx + 3; -//│ tmp3 = runtime.resumeArr.at(saveOffset); -//│ break; -//│ } +//│ saveOffset = runtime.resumeIdx + 3; +//│ tmp3 = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; //│ default: @@ -122,10 +115,11 @@ sum(10000) //│ break; //│ default: //│ tmp2 = n - 1; -//│ tmp3 = sum1(tmp2); +//│ tmp4 = sum1(tmp2); //│ runtime.stackDepth = curDepth; -//│ if (tmp3 instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(tmp3, 1, sum1, null, null, 1, null, 4, n, scrut, tmp2, tmp3) +//│ runtime.resumeValue = tmp4; +//│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(runtime.resumeValue, 1, sum1, null, null, 1, null, 4, n, scrut, tmp2, tmp3) //│ } //│ pc = 1; //│ continue main; @@ -133,6 +127,7 @@ sum(10000) //│ } //│ break; //│ case 1: +//│ tmp3 = runtime.resumeValue; //│ return n + tmp3; //│ break; //│ } From d6e4deeb8fb14a4e1efc522fde641c02b5a06e74 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 10 Dec 2025 00:18:39 +0800 Subject: [PATCH 12/14] More precise variable restore --- .../scala/hkmc2/codegen/HandlerLowering.scala | 568 +++--------------- .../src/main/scala/hkmc2/codegen/Lifter.scala | 4 +- .../main/scala/hkmc2/codegen/Lowering.scala | 2 +- .../hkmc2/codegen/StackSafeTransform.scala | 1 - .../scala/hkmc2/codegen/UsedVarAnalyzer.scala | 2 +- .../src/test/mlscript/handlers/Debugging.mls | 14 +- .../mlscript/handlers/RecursiveHandlers.mls | 52 +- .../test/mlscript/handlers/StackSafety.mls | 18 +- .../mlscript/handlers/UserThreadsSafe.mls | 16 +- .../mlscript/handlers/UserThreadsUnsafe.mls | 16 +- .../test/mlscript/lifter/StackSafetyLift.mls | 18 +- 11 files changed, 123 insertions(+), 588 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index a1806fda57..eadbfd61da 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -55,17 +55,17 @@ object HandlerLowering: debugInfo: DebugInfo, ): def isTopLevel = currentFun.isEmpty - def doUnwind(res: Path, stateId: BigInt)(using paths: HandlerPaths) = + def doUnwind(path: Path, loc: Value, stateId: BigInt, restoreList: List[Local])(using paths: HandlerPaths) = Return(Call(paths.unwindPath, ( - res :: + path :: intLit(plCnt) :: currentFun.get :: debugInfo.debugInfoPath :: - res.toLoc.fold(unit)(locToStr(_)) :: + loc :: intLit(stateId) :: thisPath.getOrElse(unit) :: - intLit(currentLocals.length) :: - currentLocals.map(_.asPath) + intLit(restoreList.length) :: + restoreList.map(_.asPath) ).map(_.asArg))(true, true), false) // inScopeLocals: All variables that are in scope, including those that come from outer scope. @@ -133,6 +133,15 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, S(uid) case _ => N + 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(`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 @@ -154,103 +163,8 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, tmp // blk: the block of code within this state - // sym: the variable to which the resumed value should set - case class BlockPartition(blk: Block) + case class BlockPartition(blk: Block, resumable: Bool) case class PartitionedBlock(entry: StateId, states: Map[StateId, BlockPartition]) - - // 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 (no longer in use as we always include everything) - // def removeUselessStates(states: PartitionedBlock): PartitionedBlock = - // def findEdges(part: BlockPartition) = - // val edges: mutable.Set[StateId] = mutable.Set.empty - // new BlockTraverser: - // applyBlock(part.blk) - // override def applyBlock(b: Block): Unit = b match - // case StateTransition(id) => edges += id - // case _ => super.applyBlock(b) - // edges - // // build edges - // val edges = states.map((id, part) => id -> findEdges(part)) - - // val visited: mutable.Set[StateId] = mutable.Set.empty - // val remaining: mutable.Set[StateId] = mutable.Set.from(states.flatMap(part => part._2.sym.fold(N)(_ => S(part._1)))) - - // 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._1)) object EffectfulResult: def unapply(r: Result) = r match @@ -274,27 +188,30 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, 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): StateId = blk match - case StateTransition(uid) => uid + 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) + 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)) + 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), - h.doUnwind(paths.resumeValue, stateId)(using paths) + 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)) + def getImpl: StateId = forceId(go(rst)(using partitioned = true), false) def transitionSoft: Block = transitionOrBlk(go(rst)) val nonTrivialBlockChecker = new BlockDataTransformer(SymbolSubst()): @@ -326,7 +243,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, 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)) + result(startId.get) = BlockPartition(Begin(newBody, restId.transitionSoft), false) StateTransition(startId.get) else Label(label, loop, newBody, restId.transitionSoft) @@ -389,39 +306,55 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, case _: HandleBlock => lastWords("unexpected handleBlock") // already translated at this point val initId = allocId() - val initPart = BlockPartition(go(blk)(using Map(), N, false)) + // 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)) - - // extraLocals is used for things like immutable parameters, they are not mutated but they should still be added as locals for debugging - // private def createGetLocalsFn(b: Block, extraLocals: Set[Local] = Set.empty)(using h: HandlerCtx) = - // val locals = (b.userDefinedVars ++ extraLocals) -- h.debugInfo.inScopeLocals - // val localsInfo = locals.toList.sortBy(_.uid).map: s => - // FlowSymbol(s.nme) -> Instantiate(mut = true, paths.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, paths.fnLocalsPath, - // Value.Lit(Tree.StrLit(h.debugInfo.debugNme)).asArg - // :: Value.Ref(arrSym).asArg - // :: Nil - // )) - // .assign(TempSymbol(N, ""), Call(startSym.asPath.selSN("push"), thisInfo.asPath.asArg :: Nil)(false, false)) - // .ret(startSym.asPath) - - // FunDefn(N, BlockMemberSymbol("getLocals", Nil), PlainParamList(Nil) :: Nil, body) - + + private def computeRestoreList(parts: PartitionedBlock)(using HandlerCtx): List[Local] = + val localSet = summon[HandlerCtx].currentLocals.toSet + val result = mutable.HashSet.empty[Local] + + def traverseEntry(stateId: StateId) = + val traversed = mutable.HashSet.empty[StateId] + var initialized = Set.empty[Local] + + new BlockTraverserShallow(): + traversed += stateId + applyBlock(parts.states(stateId).blk) + override def applyBlock(blk: Block): Unit = blk match + case Unwind(uid, loc) => () + case StateTransition(uid) => + if !traversed.contains(uid) 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 /** @@ -494,10 +427,10 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, val parts = partitionBlock(b) if parts.states.size <= 1 && opt.stackSafety.isEmpty then return translateTrivialOrTopLevel(b) + val vars = if opt.debug then h.currentLocals else computeRestoreList(parts) h.currentStackSafetySym.foreach: fnOrCls => doUnwindMap += - fnOrCls -> - (res => h.doUnwind(res, parts.entry)(using paths)) + fnOrCls -> (res => h.doUnwind(res, fnOrCls.fold(_.toLoc, _.toLoc).fold(unit)(locToStr(_)), parts.entry, vars)(using paths)) val pcVar = freshTmp("pc") val mainLoopLbl = freshTmp("main") @@ -506,6 +439,8 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, override def applyBlock(b: Block) = b match case StateTransition(uid) => Assign(pcVar, Value.Lit(Tree.IntLit(uid)), Continue(mainLoopLbl)) + case Unwind(uid, loc) => + h.doUnwind(paths.resumeValue, loc, uid, vars)(using paths) case _ => super.applyBlock(b) val arms = parts.states.toList.map: (id, part) => @@ -521,7 +456,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, val computeOff = Assign(getSavedTmp, Call(State.builtinOpsMap("+").asPath, paths.runtimePath.selSN("resumeIdx").asArg :: intLit(off).asArg :: Nil)(false, false), _) (computeOff, DynSelect(paths.runtimePath.selSN("resumeArr"), getSavedTmp.asPath, true)) - val restoreVars = h.currentLocals.zipWithIndex.foldLeft(blockBuilder.assign(pcVar, paths.resumePc)): + val restoreVars = vars.zipWithIndex.foldLeft(blockBuilder.assign(pcVar, paths.resumePc)): case (builder, (local, idx)) => val (computeOff, savePath) = getSaved(idx) builder.chain(computeOff).assign(local, savePath) @@ -539,9 +474,6 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, h.debugInfo.nest("ctor-like block", unit, b.definedVars.toList))) private def translateTrivialOrTopLevel(b: Block)(using HandlerCtx): Block = - // We shall add back the top level effect checks here - // If said block is trivial, this function will still add the debug information, for in the case where the error - // is raised in a tail call. def topLevelCheck(l: Local, r: Result, rst: Block): Block = blockBuilder .assign(l, r) @@ -569,107 +501,6 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, case _ => super.applyResult(r)(k) trivialTransform.applyBlock(b) - // 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 - // override def applyBlock(b: Block) = b match - // case b: HandleBlock => - // die - // // This block optimizes tail-calls in the handler transformation. We do not optimize implicit returns. - // // Implicit returns are used in top level and constructor: - // // For top level, this correspond to the last statement which should also be checked for effect. - // // For constructor, we will append `return this;` after the implicit return so it is not a tail call. - // case Return(c @ Call(fun, args), false) if !handlerCtx.isHandlerBody => - // applyPath(fun): fun2 => - // applyArgs(args): args2 => - // val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun, c.mayRaiseEffects) - // if c2 is c then b else Return(c2, false) - // // Optimization to avoid generation of unnecessary variables - // case Assign(lhs, c @ Call(fun, args), rest) if c.mayRaiseEffects => - // applyPath(fun): fun2 => - // applyArgs(args): args2 => - // val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun, c.mayRaiseEffects) - // ResultPlaceholder(lhs, freshId(), c2, applyBlock(rest)) - // case Assign(lhs, c @ Instantiate(mut, cls, args), rest) => - // applyPath(cls): cls2 => - // applyArgs(args): args2 => - // val c2 = if (cls2 is cls) && (args2 is args) then c else Instantiate(mut, cls2, args2) - // ResultPlaceholder(lhs, freshId(), c2, applyBlock(rest)) - // case _ => super.applyBlock(b) - // override def applyResult(r: Result)(k: Result => Block): Block = r match - // case c @ Call(fun, args) if c.mayRaiseEffects => - // val res = freshTmp("res") - // applyPath(fun): fun2 => - // applyArgs(args): args2 => - // val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun, c.mayRaiseEffects) - // ResultPlaceholder(res, freshId(), c2, k(Value.Ref(res))) - // case c @ Instantiate(mut, cls, args) => - // val res = freshTmp("res") - // applyPath(cls): cls2 => - // applyArgs(args): args2 => - // val c2 = if (cls2 is cls) && (args2 is args) then c else Instantiate(mut, cls2, args2) - // ResultPlaceholder(res, freshId(), c2, k(Value.Ref(res))) - // case r => super.applyResult(r)(k) - // override def applyPath(p: Path)(k: Path => Block): Block = p match - // case Value.Ref(`getLocalsSym`, _) => k(handlerCtx.debugInfo.prevLocalsFn.get) // TODO: Port debug to new transformation - // 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 = - // if handlerCtx.isTopLevel then genNormalBody(b, N) - // else - // // create the doUnwind function - // val doUnwindSym = BlockMemberSymbol("doUnwind", Nil, true) - // doUnwindMap += fnOrCls -> doUnwindSym.asPath - // val pcSym = VarSymbol(Tree.Ident("pc")) - // val resSym = VarSymbol(Tree.Ident("res")) - // val doUnwindBlk = h.linkAndHandle( - // LinkState(resSym, paths.contClsPath, pcSym.asPath) - // ) - // val doUnwindDef = FunDefn( - // N, doUnwindSym, - // PlainParamList(Param.simple(resSym) :: Param.simple(pcSym) :: Nil) :: Nil, - // doUnwindBlk - // ) - // val doUnwindLazy = Lazy(doUnwindSym.asPath) - // val rst = genNormalBody(b, S(doUnwindLazy)) - - // if doUnwindLazy.isEmpty && opt.stackSafety.isEmpty then rst - // else - // blockBuilder - // .define(doUnwindDef) - // .rest(rst) - // if opt.debug then - // Define(getLocalsFn, ret) - // else - // ret - - // // moves definitions to the top level of the block - // private def thirdPass(b: Block): Block = - // // to ensure the fun and class references in the continuation class are properly scoped, - // // we move all function defns to the top level of the handler block - // val (blk, defns) = b.floatOutDefns() - // defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) - - private def locToVarStr(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)}" - // Handle block is rewritten into: // 1. Instantiation of the handler // 2. An effectful call to enterHandleBlock @@ -728,251 +559,6 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, case _ => super.applyBlock(b) transform.applyBlock(b) - - // private def genContClass(b: Block, callSelf: Opt[Result])(using h: HandlerCtx): Opt[ClsLikeDefn] = - // val clsSym = ClassSymbol( - // Tree.DummyTypeDef(syntax.Cls), - // Tree.Ident(handlerCtx.contName) - // ) - - // val pcVar = VarSymbol(pcIdent) - - // val loopLbl = freshTmp("contLoop") - // val pcSymbol = TermSymbol(ParamBind, S(clsSym), pcIdent) - - // // This maps each state id to an optional location - // // Note that the value is an Option, and None must be inserted even if the location is not known - // // so that we can use the same map to enumerate all possible state id and check if there is any state id - // val pcToLoc = collection.mutable.Map.empty[StateId, Option[Loc]] - // var containsCall = false - - // // Create the DoUnwind function - // doUnwindMap += R(clsSym) -> Select(clsSym.asPath, Tree.Ident("doUnwind"))( - // N /* this refers to the method defined in Runtime.FunctionContFrame */ - // ) - // val newPcSym = VarSymbol(Tree.Ident("newPc")) - // val resSym = VarSymbol(Tree.Ident("res")) - // val doUnwindBlk = blockBuilder - // .assign(pcSymbol, newPcSym.asPath) - // .assignFieldN(resSym.asPath.contTrace.last, nextIdent, clsSym.asPath) - // .assignFieldN(resSym.asPath.contTrace, lastIdent, clsSym.asPath) - // .ret(resSym.asPath) - - // // Replaces ResultPlaceholders to check for effects and link the effect trace - // def prepareBlock(b: Block): Block = - // val transform = new BlockTransformerShallow(SymbolSubst()): - // override def applyResult(r: Result)(k: Result => Block): Block = - // r match - // case c @ Call(Value.Ref(s: BuiltinSymbol, _), _) => () - // case c: Call if !c.mayRaiseEffects => () - // case _: Call | _: Instantiate => containsCall = true - // case _ => () - // super.applyResult(r)(k) - - // override def applyBlock(b: Block): Block = b match - // case Define(_: (ClsLikeDefn | FunDefn), rst) => applyBlock(rst) - // case ResultPlaceholder(res, uid, c, rest) => - // pcToLoc(uid) = c.toLoc - // containsCall = true - // blockBuilder - // .assign(res, c) - // .ifthen( - // res.asPath, - // Case.Cls(paths.effectSigSym, paths.effectSigPath), - // ReturnCont(res, uid) - // ) - // .chain(ResumptionPoint(res, uid, _)) - // .rest(applyBlock(rest)) - // case _ => super.applyBlock(b) - // transform.applyBlock(b) - // val actualBlock = handlerCtx.ctorThis match - // case N => prepareBlock(b) - // case S(thisPath) => Begin(prepareBlock(b), Return(thisPath, false)) - // // If there is no state id found during prepareBlock, the block is trivial. - // val trivial = pcToLoc.isEmpty - - // // there are three types of functions: - // // (1) functions that have no calls, indicated by `containsCall` - // // (2) functions that have only tail calls, indicated by `trivial` - // // (3) all other functions - // // - // // Here, (2) and (3) need a continuation class when stack safety is enabled, otherwise only (3) needs it - // // If (2) and stack safety is enabled, we can just create a continuation class with one state - - // if trivial && opt.stackSafety.isEmpty then return N // case (1) or (2) if no stack safety - // if !containsCall then return N // case (1) - - // val depthSym = freshTmp("curDepth") - // val resumedVal = VarSymbol(Tree.Ident("value$")) - - // def createResumeBod = - // val parts = - // if opt.stackSafety.isDefined then callSelf match - // case None => partitionBlock(actualBlock, true) - // case Some(value) => - // val someParts = partitionBlock(actualBlock, false) - // BlockPartition(0, Return(value, false), N) :: someParts - // else - // partitionBlock(actualBlock, false) - - // def transformPart(blk: Block): Block = - // val transform = new BlockTransformerShallow(SymbolSubst()): - // override def applyBlock(b: Block): Block = b match - // case ReturnCont(res, uid) => Return(Call( - // Select(clsSym.asPath, Tree.Ident("doUnwind"))( - // N /* this refers to the method defined in Runtime.FunctionContFrame */ - // ), - // res.asPath.asArg :: Value.Lit(Tree.IntLit(uid)).asArg :: Nil)(true, false), - // false - // ) - // case StateTransition(uid) => - // blockBuilder - // .assign(pcSymbol, Value.Lit(Tree.IntLit(uid))) - // .continue(loopLbl) - // case FnEnd() => - // blockBuilder.break(loopLbl) - // case _ => super.applyBlock(b) - // transform.applyBlock(blk) - - // // match block representing the function body - // val mainMatchCases = parts.toList.map(b => (Case.Lit(Tree.IntLit(b.id)), transformPart(b.blk))) - // val mainMatchBlk = Match( - // pcSymbol.asPath, - // mainMatchCases, - // N, - // End() - // ) - - // val tmp = freshTmp() - // val withResetDepth = - // if opt.stackSafety.isDefined && !trivial then - // AssignField(runtimePath, stackDepthIdent, depthSym.asPath, mainMatchBlk)(N) - // else mainMatchBlk - - // val lbl = blockBuilder.label(loopLbl, loop = true, withResetDepth).rest(End()) - - // def createAssignment(sym: Local) = Assign(sym, resumedVal.asPath, End()) - - // val assignedResumedCases = for - // b <- parts - // sym <- b.sym - // yield Case.Lit(Tree.IntLit(b.id)) -> createAssignment(sym) // NOTE: assume sym is in localsMap - - // // assigns the resumed value - // val body = - // if assignedResumedCases.isEmpty then - // lbl - // else - // Match( - // pcSymbol.asPath, - // assignedResumedCases, - // N, - // lbl - // ) - - // // assign cur depth - // if opt.stackSafety.isDefined && !trivial then - // Assign(depthSym, stackDepthPath, body) - // else - // body - - // val resumeBody = - // if trivial then callSelf match - // case None => actualBlock - // case Some(value) => Return(value, false) - // else createResumeBod - - - // val resumeSym = BlockMemberSymbol("resume", List()) - // val resumeFnDef = FunDefn( - // S(clsSym), // owner - // resumeSym, - // List(PlainParamList(List(Param(FldFlags.empty, resumedVal, N, Modulefulness.none)))), - // resumeBody - // ) - - // val debugMtds = if !opt.debug then Nil else - - // val getLocalsSym = BlockMemberSymbol("getLocals", List()) - - // val localsRes = h.debugInfo.prevLocalsFn match - // case Some(value) => PureCall(value, Nil) - // case None => Tuple(mut = true, Nil) - - // val getLocalsFnDef = FunDefn( - // S(clsSym), - // getLocalsSym, - // List(), - // Return(localsRes, false) - // ) - - // val getLocSym = BlockMemberSymbol("getLoc", List()) - // val getLocFnDef = FunDefn( - // S(clsSym), - // getLocSym, - // List(), - // Match(pcSymbol.asPath, pcToLoc.toSortedMap.iterator.map: (stateId, loc) => - // Case.Lit(Tree.IntLit(stateId)) -> Return(Value.Lit(loc.fold(Tree.UnitLit(true)): loc => - // val (line, _, col) = loc.origin.fph.getLineColAt(loc.spanStart) - // Tree.StrLit(s"${loc.origin.fileName.last}:${line + loc.origin.startLineNum - 1}:$col") - // ), false) - // .toList, N, End()), - // ) - - // getLocalsFnDef :: getLocFnDef :: Nil - - // val mtds = resumeFnDef :: debugMtds - - // S(ClsLikeDefn( - // N, // no owner - // clsSym, - // BlockMemberSymbol(clsSym.nme, Nil), - // syntax.Cls, - // N, - // PlainParamList({ - // val p = Param(FldFlags.empty.copy(isVal = true), pcVar, N, Modulefulness.none) - // pcVar.decl = S(p) - // p - // } :: Nil) :: Nil, - // S(paths.contClsPath), - // mtds, - // Nil, - // Nil, - // Assign(freshTmp(), PureCall( - // Value.Ref(State.builtinOpsMap("super")), // refers to runtime.FunctionContFrame which is pure - // Value.Lit(Tree.UnitLit(true)) :: Nil), End()), - // AssignField( - // clsSym.asPath, - // pcVar.id, - // Value.Ref(pcVar), - // End() - // )(S(pcSymbol)), - // N, - // N, // TODO: bufferable? - // )) - - // // Rewriites ResultPlaceholder. Checks if the result in the placeholder is an effect. - // private def genNormalBody(b: Block, 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, paths.contClsPath, 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)) - // case _ => super.applyBlock(b) - - // transform.applyBlock(b) - - - def translateTopLevel(b: Block): (Block, collection.Map[FnOrCls, Path => Return]) = doUnwindMap.clear() val ctx = HandlerCtx(N, N, 0, b.definedVars.toList, N, DebugInfo.topLevel( diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index fe616cc71e..a6e07cb8cf 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -123,7 +123,7 @@ object Lifter: * Lifts classes and functions to the top-level. Also automatically rewrites lambdas. * Assumes the input block does not have any `HandleBlock`s. */ -class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): +class Lifter()(using State, Raise): import Lifter.* /** @@ -1267,7 +1267,7 @@ class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): // so we need to desugar them again val blk = LambdaRewriter.desugar(_blk) - val analyzer = UsedVarAnalyzer(blk, handlerPaths) + val analyzer = UsedVarAnalyzer(blk) val ctx = LifterCtx .withLocals(analyzer.findUsedLocals) .withDefns(analyzer.defnsMap) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index c891981c32..de98338b51 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -947,7 +947,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): HandlerLowering(handlerPaths, opt).translateHandleBlocks(desug) val lifted = - if lift then Lifter(S(handlerPaths)).transform(withHandlers1) + if lift then Lifter().transform(withHandlers1) else withHandlers1 val (withHandlers2, doUnwindPaths) = config.effectHandlers.fold((lifted, Map.empty)): opt => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index c3dc7f151b..d28167b1fa 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -138,7 +138,6 @@ class StackSafeTransform(depthLimit: Int, paths: HandlerPaths, doUnwindMap: coll 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? - val resSym = TempSymbol(None, "stackDelayRes") blockBuilder .staticif(usedDepth, _.assign(curDepth, stackDepthPath)) .rest(newBody) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala index a6c6ca56e3..bcb2c8bcb8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala @@ -19,7 +19,7 @@ import scala.collection.mutable.Map as MutMap * * Assumes the input trees have no lambdas. */ -class UsedVarAnalyzer(b: Block, handlerPaths: Opt[HandlerPaths])(using State): +class UsedVarAnalyzer(b: Block)(using State): import Lifter.* private case class DefnMetadata( diff --git a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls index 18ac4c0669..4d2894ba0a 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls @@ -62,7 +62,7 @@ fun f() = //│ case true: //│ runtime.resumeValue = Predef.raiseUnhandledEffect(); //│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(runtime.resumeValue, 1, f, f$debugInfo, null, 4, null, 6, i, j, k, scrut, tmp1, tmp2) +//│ 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; @@ -85,7 +85,7 @@ fun f() = //│ case 3: //│ runtime.resumeValue = Predef.print(tmp1); //│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(runtime.resumeValue, 1, f, f$debugInfo, null, 2, null, 6, i, j, k, scrut, tmp1, tmp2) +//│ 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; @@ -103,7 +103,7 @@ fun f() = :re f() //│ ═══[RUNTIME ERROR] Error: Unhandled effect FatalEffect -//│ at f (pc=undefined) +//│ at f (Debugging.mls:19:14) :re fun lambda_test(f) = @@ -113,8 +113,8 @@ lambda_test(() => raiseUnhandledEffect() 100) //│ ═══[RUNTIME ERROR] Error: Unhandled effect FatalEffect -//│ at lambda (pc=undefined) -//│ at lambda_test (pc=undefined) +//│ at lambda (Debugging.mls:113:3) +//│ at lambda_test (Debugging.mls:110:3) import "../../mlscript-compile/Runtime.mls" @@ -182,8 +182,8 @@ fun f() = f() //│ > Stack Trace: -//│ > at f (pc=undefined) with locals: j=200 +//│ > at f (Debugging.mls:177:3) with locals: j=200 //│ > Stack Trace: -//│ > at f (pc=undefined) +//│ > at f (Debugging.mls:179:3) //│ > Stack Trace: //│ > at tail position diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 82e2d18da7..9f65607122 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -163,21 +163,10 @@ str //│ switch (scrut) { //│ case true: //│ Handler$h1$perform$1 = function Handler$h1$perform$(Handler$h1$$instance, arg, k) { -//│ let tmp13, tmp14, tmp15, pc, saveOffset; +//│ let tmp13, tmp14, tmp15, pc; //│ switch (runtime.isResuming) { //│ case true: //│ pc = runtime.resumePc; -//│ k = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ tmp13 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ tmp14 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 3; -//│ tmp15 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 4; -//│ arg = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 5; -//│ Handler$h1$$instance = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; //│ default: @@ -191,7 +180,7 @@ str //│ 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, null, 1, null, 6, k, tmp13, tmp14, tmp15, arg, Handler$h1$$instance) +//│ return runtime.unwind(runtime.resumeValue, 1, Handler$h1$perform$1, null, "RecursiveHandlers.mls:149:7", 1, null, 0) //│ } //│ pc = 1; //│ continue main; @@ -236,25 +225,10 @@ str //│ 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, saveOffset; +//│ let tmp13, tmp14, tmp15, tmp16, tmp17, pc; //│ switch (runtime.isResuming) { //│ case true: //│ pc = runtime.resumePc; -//│ k = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ tmp13 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ tmp14 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 3; -//│ tmp15 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 4; -//│ tmp16 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 5; -//│ tmp17 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 6; -//│ arg = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 7; -//│ Handler$h2$$instance = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; //│ default: @@ -269,7 +243,7 @@ str //│ 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, null, 1, null, 8, k, tmp13, tmp14, tmp15, tmp16, tmp17, arg, Handler$h2$$instance) +//│ return runtime.unwind(runtime.resumeValue, 1, Handler$h2$perform$1, null, "RecursiveHandlers.mls:154:7", 1, null, 0) //│ } //│ pc = 1; //│ continue main; @@ -311,13 +285,10 @@ str //│ static [definitionMetadata] = ["class", "Handler$h2$"]; //│ }); //│ handleBlock$$2 = function handleBlock$$(h22) { -//│ let tmp13, pc, saveOffset; +//│ let tmp13, pc; //│ switch (runtime.isResuming) { //│ case true: //│ pc = runtime.resumePc; -//│ tmp13 = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ h22 = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; //│ default: @@ -329,7 +300,7 @@ str //│ 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, null, 2, null, 2, tmp13, h22) +//│ return runtime.unwind(runtime.resumeValue, 1, handleBlock$$2, null, "RecursiveHandlers.mls:156:5", 2, null, 0) //│ } //│ pc = 2; //│ continue main; @@ -352,15 +323,10 @@ str //│ } //│ }); //│ handleBlock$10 = (undefined, function () { -//│ let h22, tmp13, handleBlock$$here, pc, saveOffset; +//│ let h22, tmp13, handleBlock$$here, pc; //│ switch (runtime.isResuming) { //│ case true: //│ pc = runtime.resumePc; -//│ h22 = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ tmp13 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ handleBlock$$here = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; //│ default: @@ -372,7 +338,7 @@ str //│ 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, 3, h22, tmp13, handleBlock$$here) +//│ return runtime.unwind(runtime.resumeValue, 1, handleBlock$10, null, null, 2, null, 0) //│ } //│ pc = 2; //│ continue main; @@ -386,7 +352,7 @@ str //│ 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, 3, h22, tmp13, handleBlock$$here) +//│ return runtime.unwind(runtime.resumeValue, 1, handleBlock$10, null, null, 1, null, 0) //│ } //│ pc = 1; //│ continue main; diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index ff1e323169..12342b937b 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -20,15 +20,13 @@ hi(0) //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(stackDelayRes, 1, hi, null, null, 0, null, 3, n, scrut, tmp1) +//│ return runtime.unwind(stackDelayRes, 1, hi, null, "StackSafety.mls:12:1", 0, null, 2, n, tmp1) //│ } //│ switch (runtime.isResuming) { //│ case true: //│ pc = runtime.resumePc; //│ n = runtime.resumeArr.at(runtime.resumeIdx); //│ saveOffset = runtime.resumeIdx + 1; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; //│ tmp1 = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; @@ -78,18 +76,14 @@ sum(10000) //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(stackDelayRes, 1, sum, null, null, 0, null, 4, n, scrut, tmp2, tmp3) +//│ return runtime.unwind(stackDelayRes, 1, sum, null, "StackSafety.mls:66:1", 0, null, 2, tmp2, n) //│ } //│ switch (runtime.isResuming) { //│ case true: //│ pc = runtime.resumePc; -//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ tmp2 = runtime.resumeArr.at(runtime.resumeIdx); //│ saveOffset = runtime.resumeIdx + 1; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ tmp2 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 3; -//│ tmp3 = runtime.resumeArr.at(saveOffset); +//│ n = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; //│ default: @@ -110,7 +104,7 @@ sum(10000) //│ runtime.stackDepth = curDepth; //│ runtime.resumeValue = tmp4; //│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(runtime.resumeValue, 1, sum, null, null, 1, null, 4, n, scrut, tmp2, tmp3) +//│ return runtime.unwind(runtime.resumeValue, 1, sum, null, "StackSafety.mls:69:9", 1, null, 2, tmp2, n) //│ } //│ pc = 1; //│ continue main; @@ -236,8 +230,6 @@ fun max(a, b) = if a < b then b else a //│ a = runtime.resumeArr.at(runtime.resumeIdx); //│ saveOffset = runtime.resumeIdx + 1; //│ b = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ scrut = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; //│ default: diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls index 4b88cf6870..07c119c643 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls @@ -45,11 +45,11 @@ in //│ > main end //│ > f 2 //│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$ -//│ at f (pc=undefined) -//│ at while$ (pc=undefined) +//│ at f (UserThreadsSafe.mls:19:3) +//│ at while$ (UserThreadsSafe.mls:12:7) //│ at ThreadEffect#drain (pc=undefined) -//│ at Handler$h$fork$ (pc=undefined) -//│ at Handler$h$fork$ (pc=undefined) +//│ at Handler$h$fork$ (UserThreadsSafe.mls:37:5) +//│ at Handler$h$fork$ (UserThreadsSafe.mls:37:5) // FIFO @@ -68,10 +68,10 @@ in //│ > main end //│ > f 0 //│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$2 -//│ at f (pc=undefined) -//│ at while$ (pc=undefined) +//│ at f (UserThreadsSafe.mls:19:3) +//│ at while$ (UserThreadsSafe.mls:12:7) //│ at ThreadEffect#drain (pc=undefined) -//│ at Handler$h$fork$ (pc=undefined) -//│ at Handler$h$fork$ (pc=undefined) +//│ at Handler$h$fork$ (UserThreadsSafe.mls:60:5) +//│ at Handler$h$fork$ (UserThreadsSafe.mls:60:5) diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls index 0b3d5a4a29..1d50621b5f 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls @@ -48,11 +48,11 @@ in //│ > main end //│ > f 2 //│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$ -//│ at f (pc=undefined) -//│ at while$ (pc=undefined) +//│ at f (UserThreadsUnsafe.mls:14:3) +//│ at while$ (UserThreadsUnsafe.mls:31:5) //│ at drain (pc=undefined) -//│ at Handler$h$fork$ (pc=undefined) -//│ at Handler$h$fork$ (pc=undefined) +//│ at Handler$h$fork$ (UserThreadsUnsafe.mls:40:5) +//│ at Handler$h$fork$ (UserThreadsUnsafe.mls:40:5) // FIFO @@ -71,10 +71,10 @@ in //│ > main end //│ > f 1 //│ ═══[RUNTIME ERROR] Error: Unhandled effect Handler$h$ -//│ at f (pc=undefined) -//│ at while$ (pc=undefined) +//│ at f (UserThreadsUnsafe.mls:14:3) +//│ at while$ (UserThreadsUnsafe.mls:31:5) //│ at drain (pc=undefined) -//│ at Handler$h$fork$ (pc=undefined) -//│ at Handler$h$fork$ (pc=undefined) +//│ at Handler$h$fork$ (UserThreadsUnsafe.mls:63:5) +//│ at Handler$h$fork$ (UserThreadsUnsafe.mls:63:5) diff --git a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls index 93104a18fe..359f94441b 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls @@ -28,16 +28,14 @@ hi(0) //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(stackDelayRes, 1, hi, null, null, 0, null, 3, n, scrut, tmp1) +//│ return runtime.unwind(stackDelayRes, 1, hi, null, "StackSafetyLift.mls:20:1", 0, null, 2, tmp1, n) //│ } //│ switch (runtime.isResuming) { //│ case true: //│ pc = runtime.resumePc; -//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ tmp1 = runtime.resumeArr.at(runtime.resumeIdx); //│ saveOffset = runtime.resumeIdx + 1; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ tmp1 = runtime.resumeArr.at(saveOffset); +//│ n = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; //│ default: @@ -87,18 +85,14 @@ sum(10000) //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(stackDelayRes, 1, sum1, null, null, 0, null, 4, n, scrut, tmp2, tmp3) +//│ return runtime.unwind(stackDelayRes, 1, sum1, null, "StackSafetyLift.mls:75:1", 0, null, 2, n, tmp2) //│ } //│ switch (runtime.isResuming) { //│ case true: //│ pc = runtime.resumePc; //│ n = runtime.resumeArr.at(runtime.resumeIdx); //│ saveOffset = runtime.resumeIdx + 1; -//│ scrut = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; //│ tmp2 = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 3; -//│ tmp3 = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; //│ default: @@ -119,7 +113,7 @@ sum(10000) //│ runtime.stackDepth = curDepth; //│ runtime.resumeValue = tmp4; //│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(runtime.resumeValue, 1, sum1, null, null, 1, null, 4, n, scrut, tmp2, tmp3) +//│ return runtime.unwind(runtime.resumeValue, 1, sum1, null, "StackSafetyLift.mls:78:9", 1, null, 2, n, tmp2) //│ } //│ pc = 1; //│ continue main; @@ -247,8 +241,6 @@ fun max(a, b) = if a < b then b else a //│ a = runtime.resumeArr.at(runtime.resumeIdx); //│ saveOffset = runtime.resumeIdx + 1; //│ b = runtime.resumeArr.at(saveOffset); -//│ saveOffset = runtime.resumeIdx + 2; -//│ scrut = runtime.resumeArr.at(saveOffset); //│ runtime.isResuming = false; //│ break; //│ default: From c7468590f0c478bcd2ed1cb635338ffac632ad4c Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 10 Dec 2025 02:23:44 +0800 Subject: [PATCH 13/14] do not analyze pass a resumable part --- hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index eadbfd61da..b9408c5bc1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -325,7 +325,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, override def applyBlock(blk: Block): Unit = blk match case Unwind(uid, loc) => () case StateTransition(uid) => - if !traversed.contains(uid) then + if !traversed.contains(uid) && !parts.states(uid).resumable then traversed += stateId applyBlock(parts.states(uid).blk) case Assign(lhs, rhs, rest) => From daea4cf51430db0c54710ac38bce93a8b1f550f8 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Mon, 15 Dec 2025 01:44:17 +0800 Subject: [PATCH 14/14] Proper fixes --- .../scala/hkmc2/codegen/js/JSBuilder.scala | 3 +- .../src/test/mlscript-compile/Predef.mjs | 2 +- .../src/test/mlscript/backlog/ToTriage.mls | 2 +- .../src/test/mlscript/basics/NestedBlocks.mls | 2 +- .../src/test/mlscript/codegen/BadInit.mls | 3 +- .../src/test/mlscript/codegen/Hygiene.mls | 2 +- .../test/mlscript/codegen/SelfReferences.mls | 28 +-- .../test/mlscript/codegen/SingletonInit.mls | 4 +- .../test/mlscript/lifter/StackSafetyLift.mls | 167 ++++-------------- 9 files changed, 42 insertions(+), 171 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala index 4f34c68215..79f3b09b3f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -335,7 +335,8 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: (doc" # ${getVar(sym, sym.toLoc)} = this;", fz) else (doc"", doc"") - val ctorCode = doc"${body(Begin(preCtor, ctor), endSemi = true)}$singletonInit${ + val preCtorCode = block(preCtor, true) + val ctorCode = doc"$preCtorCode$singletonInit${body(ctor, endSemi = true)}${ kind match case syntax.Obj => doc" # ${defineProperty(doc"this", "class", doc"${scope.lookup_!(isym, isym.toLoc)}")};" diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index b46e338a83..5a9d766890 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -19,9 +19,9 @@ let Predef1; new this } constructor() { + Predef.Symbols = this; this.prettyPrint = RuntimeJS.symbols.prettyPrint; this.definitionMetadata = RuntimeJS.symbols.definitionMetadata; - Predef.Symbols = this; Object.defineProperty(this, "class", { value: Symbols }); diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index aa5fe53425..49469242a7 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -99,8 +99,8 @@ object Cls(val x) with //│ new this //│ } //│ constructor(x1) { -//│ this.x = x1; //│ Cls1 = this; +//│ this.x = x1; //│ Object.defineProperty(this, "class", { //│ value: Cls //│ }); diff --git a/hkmc2/shared/src/test/mlscript/basics/NestedBlocks.mls b/hkmc2/shared/src/test/mlscript/basics/NestedBlocks.mls index 60a973e81f..ba5bbf293a 100644 --- a/hkmc2/shared/src/test/mlscript/basics/NestedBlocks.mls +++ b/hkmc2/shared/src/test/mlscript/basics/NestedBlocks.mls @@ -41,6 +41,6 @@ Oops.fakeField //│ ╔══[ERROR] Object 'Oops' does not contain member 'fakeField' //│ ║ l.40: Oops.fakeField //│ ╙── ^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'fakeField') +//│ ═══[RUNTIME ERROR] Error: Access to required field 'fakeField' yielded 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadInit.mls b/hkmc2/shared/src/test/mlscript/codegen/BadInit.mls index ed860b8b52..d8dbec0c1e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadInit.mls @@ -15,6 +15,7 @@ object Bar with //│ new this //│ } //│ constructor() { +//│ Bar1 = this; //│ this.x = 1; //│ const this$Bar = this; //│ (class Baz { @@ -30,7 +31,6 @@ object Bar with //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Baz"]; //│ }); -//│ Bar1 = this; //│ Object.defineProperty(this, "class", { //│ value: Bar //│ }); @@ -39,7 +39,6 @@ object Bar with //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["object", "Bar"]; //│ }); -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'x') // * Modules don't have this initialization problem because they set their bindings diff --git a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls index 52dd713dae..07e732ff24 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls @@ -16,8 +16,8 @@ object Test with //│ new this //│ } //│ constructor() { -//│ this.x = 12; //│ Test1 = this; +//│ this.x = 12; //│ Object.defineProperty(this, "class", { //│ value: Test //│ }); diff --git a/hkmc2/shared/src/test/mlscript/codegen/SelfReferences.mls b/hkmc2/shared/src/test/mlscript/codegen/SelfReferences.mls index 489f636df9..43322845d6 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SelfReferences.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SelfReferences.mls @@ -66,10 +66,10 @@ object Foo with //│ new this //│ } //│ constructor() { +//│ Foo11 = this; //│ let tmp; //│ tmp = Predef.id(Foo11); //│ this.self = tmp; -//│ Foo11 = this; //│ Object.defineProperty(this, "class", { //│ value: Foo10 //│ }); @@ -78,19 +78,6 @@ object Foo with //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["object", "Foo"]; //│ }); -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:159) -//│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. -//│ at Runtime.checkCall (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:602:24) -//│ at new Foo10 (REPL37:1:133) -//│ at (REPL37:1:71) -//│ at REPL37:1:367 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:927:10) -//│ at REPLServer.emit (node:events:518:28) // * This used not to work, and it could be argued it should be an initialization error @@ -98,18 +85,5 @@ object Foo with object Foo with val self = id(Foo) val lateInit = 1 -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:159) -//│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. -//│ at Runtime.checkCall (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:602:24) -//│ at new Foo12 (REPL40:1:133) -//│ at (REPL40:1:71) -//│ at REPL40:1:390 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:927:10) -//│ at REPLServer.emit (node:events:518:28) diff --git a/hkmc2/shared/src/test/mlscript/codegen/SingletonInit.mls b/hkmc2/shared/src/test/mlscript/codegen/SingletonInit.mls index ecfdaf8497..2c78c0f5e6 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SingletonInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SingletonInit.mls @@ -9,9 +9,9 @@ object A with val y = 2 print(A, "|", B) print(x, y) -//│ > undefined | undefined +//│ > A { x: 1, B: B {} } | B {} //│ > 1 undefined -//│ > undefined | B +//│ > A { x: 1, B: B, y: 2 } | B //│ > 1 2 print(A, "|", A.B) diff --git a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls index 4ae8abbf87..654ab73710 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls @@ -1,12 +1,9 @@ :js :lift -<<<<<<< HEAD :noSanityCheck -======= :noTailRec // * FIXME: Why doesn't the following work when using Predef function `(==) equals`? ->>>>>>> upstream/hkmc2 // sanity check :expect 5050 @@ -27,38 +24,33 @@ fun hi(n) = if n === 0 then 0 else hi(n - 1) hi(0) -//│ /!!!\ Option ':effectHandlers' requires ':lift' and ':noSanityCheck' //│ JS (unsanitized): //│ let hi, tmp, $_stack$_safe$_body$_; //│ hi = function hi(n) { //│ let scrut, tmp1, pc, saveOffset, stackDelayRes; -//│ loopLabel: while (true) { -//│ runtime.stackDepth = runtime.stackDepth + 1; -//│ stackDelayRes = runtime.checkDepth(); -//│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(stackDelayRes, 1, hi, null, "StackSafetyLift.mls:26:1", 0, null, 2, n, tmp1) -//│ } -//│ if (runtime.isResuming === true) { -//│ pc = runtime.resumePc; -//│ n = runtime.resumeArr.at(runtime.resumeIdx); -//│ saveOffset = runtime.resumeIdx + 1; -//│ tmp1 = runtime.resumeArr.at(saveOffset); -//│ runtime.isResuming = false; -//│ } else { -//│ pc = 0; -//│ } -//│ main: while (true) { -//│ if (pc === 0) { -//│ scrut = n === 0; -//│ if (scrut === true) { -//│ return 0 -//│ } else { -//│ tmp1 = n - 1; -//│ n = tmp1; -//│ continue loopLabel -//│ } +//│ runtime.stackDepth = runtime.stackDepth + 1; +//│ stackDelayRes = runtime.checkDepth(); +//│ if (stackDelayRes instanceof runtime.EffectSig.class) { +//│ return runtime.unwind(stackDelayRes, 1, hi, null, "StackSafetyLift.mls:23:1", 0, null, 2, n, tmp1) +//│ } +//│ if (runtime.isResuming === true) { +//│ pc = runtime.resumePc; +//│ n = runtime.resumeArr.at(runtime.resumeIdx); +//│ saveOffset = runtime.resumeIdx + 1; +//│ tmp1 = runtime.resumeArr.at(saveOffset); +//│ runtime.isResuming = false; +//│ } else { +//│ pc = 0; +//│ } +//│ main: while (true) { +//│ if (pc === 0) { +//│ scrut = n === 0; +//│ if (scrut === true) { +//│ return 0 +//│ } else { +//│ tmp1 = n - 1; +//│ return hi(tmp1) //│ } -//│ break; //│ } //│ break; //│ } @@ -80,7 +72,6 @@ fun sum(n) = else n + sum(n - 1) sum(10000) -//│ /!!!\ Option ':effectHandlers' requires ':lift' and ':noSanityCheck' //│ JS (unsanitized): //│ let sum1, tmp1, $_stack$_safe$_body$_1; //│ sum1 = function sum(n) { @@ -89,7 +80,7 @@ sum(10000) //│ runtime.stackDepth = runtime.stackDepth + 1; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(stackDelayRes, 1, sum1, null, "StackSafetyLift.mls:78:1", 0, null, 2, tmp2, n) +//│ return runtime.unwind(stackDelayRes, 1, sum1, null, "StackSafetyLift.mls:70:1", 0, null, 2, tmp2, n) //│ } //│ if (runtime.isResuming === true) { //│ pc = runtime.resumePc; @@ -112,7 +103,7 @@ sum(10000) //│ runtime.stackDepth = curDepth; //│ runtime.resumeValue = tmp4; //│ if (runtime.resumeValue instanceof runtime.EffectSig.class) { -//│ return runtime.unwind(runtime.resumeValue, 1, sum1, null, "StackSafetyLift.mls:81:9", 1, null, 2, tmp2, n) +//│ return runtime.unwind(runtime.resumeValue, 1, sum1, null, "StackSafetyLift.mls:73:9", 1, null, 2, tmp2, n) //│ } //│ pc = 1; //│ continue main @@ -132,22 +123,7 @@ sum(10000) //│ tmp1 = runtime.runStackSafe(1000, $_stack$_safe$_body$_1); //│ if (tmp1 instanceof runtime.EffectSig.class) { tmp1 = runtime.topLevelEffect(tmp1, false); } //│ tmp1 -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:159) -//│ ═══[RUNTIME ERROR] Error: Function 'sum' expected 1 argument but got 0 -//│ at Runtime.checkArgs (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:588:24) -//│ at sum (REPL13:1:87) -//│ at FunctionContFrame.resume (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:342:18) -//│ at Runtime.resumeContTrace (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1057:37) -//│ at file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1046:22 -//│ at Runtime.runStackSafe (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1123:32) -//│ at REPL13:1:1664 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: processTerm (JSBackendDiffMaker.scala:208) -//│ ═══[RUNTIME ERROR] Expected: '50005000', got: 'undefined' +//│ = 50005000 // stack-overflows without :stackSafe :re @@ -168,21 +144,8 @@ fun foo(f) = set ctr += 1 dummy(f(f)) foo(foo) -//│ /!!!\ Option ':effectHandlers' requires ':lift' and ':noSanityCheck' -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:159) -//│ ═══[RUNTIME ERROR] TypeError: f is not a function -//│ at foo (REPL19:1:1308) -//│ at $_stack$_safe$_body$_2 (REPL19:1:2023) -//│ at Runtime.enterHandleBlock (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:947:28) -//│ at Runtime.runStackSafe (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1116:22) -//│ at REPL19:1:2052 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:927:10) -//│ ctr = 1 +//│ = 0 +//│ ctr = 10001 :stackSafe 1000 :effectHandlers @@ -193,24 +156,8 @@ val foo = else n + f(n-1) f(10000) foo -//│ /!!!\ Option ':effectHandlers' requires ':lift' and ':noSanityCheck' -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:159) -//│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 0 -//│ at Runtime.checkArgs (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:588:24) -//│ at lambda (REPL23:1:109) -//│ at FunctionContFrame.resume (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:342:18) -//│ at Runtime.resumeContTrace (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1057:37) -//│ at file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1046:22 -//│ at Runtime.runStackSafe (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1123:32) -//│ at REPL23:1:1671 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: processTerm (JSBackendDiffMaker.scala:208) -//│ ═══[RUNTIME ERROR] Expected: '50005000', got: 'undefined' -//│ foo = undefined +//│ = 50005000 +//│ foo = 50005000 :re fun foo() = @@ -238,23 +185,7 @@ handle h = Eff with else n + f(n-1) resume(f(10000)) foo(h) -//│ /!!!\ Option ':effectHandlers' requires ':lift' and ':noSanityCheck' -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:159) -//│ ═══[RUNTIME ERROR] Error: Function 'lambda$' expected 3 arguments but got 0 -//│ at Runtime.checkArgs (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:588:24) -//│ at lambda$ (REPL33:1:610) -//│ at FunctionContFrame.resume (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:342:18) -//│ at Runtime.resumeContTrace (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1057:37) -//│ at file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1046:22 -//│ at Runtime.runStackSafe (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1123:32) -//│ at REPL33:1:9240 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: processTerm (JSBackendDiffMaker.scala:208) -//│ ═══[RUNTIME ERROR] Expected: '50005000', got: 'undefined' +//│ = 50005000 // function call and defn inside handler :effectHandlers @@ -270,23 +201,7 @@ handle h = Eff with in fun foo(h) = h.perform foo(h) -//│ /!!!\ Option ':effectHandlers' requires ':lift' and ':noSanityCheck' -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:159) -//│ ═══[RUNTIME ERROR] Error: Function 'lambda$' expected 3 arguments but got 0 -//│ at Runtime.checkArgs (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:588:24) -//│ at lambda$ (REPL36:1:239) -//│ at FunctionContFrame.resume (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:342:18) -//│ at Runtime.resumeContTrace (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1057:37) -//│ at file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1046:22 -//│ at Runtime.runStackSafe (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1123:32) -//│ at REPL36:1:9263 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: processTerm (JSBackendDiffMaker.scala:208) -//│ ═══[RUNTIME ERROR] Expected: '50005000', got: 'undefined' +//│ = 50005000 :re :effectHandlers @@ -299,14 +214,12 @@ handle h = Eff with else n + f(n-1) resume(f(10000)) foo(h) -//│ /!!!\ Option ':effectHandlers' requires ':lift' and ':noSanityCheck' //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded :effectHandlers :stackSafe :sjs fun max(a, b) = if a < b then b else a -//│ /!!!\ Option ':effectHandlers' requires ':lift' and ':noSanityCheck' //│ JS (unsanitized): //│ let max; //│ max = function max(a, b) { @@ -351,21 +264,5 @@ fun sum(n) = n + sum(n - 1) fun bad() = sum(10000) + sum(10000) bad() -//│ /!!!\ Option ':effectHandlers' requires ':lift' and ':noSanityCheck' -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:159) -//│ ═══[RUNTIME ERROR] Error: Function 'sum' expected 1 argument but got 0 -//│ at Runtime.checkArgs (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:588:24) -//│ at sum (REPL51:1:92) -//│ at FunctionContFrame.resume (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:342:18) -//│ at Runtime.resumeContTrace (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1057:37) -//│ at file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1046:22 -//│ at Runtime.runStackSafe (file:///home/attempt0/mlscript/head/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:1123:32) -//│ at REPL51:1:3312 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: processTerm (JSBackendDiffMaker.scala:208) -//│ ═══[RUNTIME ERROR] Expected: '100010000', got: 'undefined' +//│ = 100010000