From 9e2c4cd2ace7e889b75f5874444f4f834d161c0a Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Sun, 8 Dec 2024 16:37:12 +0800 Subject: [PATCH 001/114] Changes IR --- .../jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala | 2 ++ hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala | 9 +++++++++ .../src/main/scala/hkmc2/codegen/Lowering.scala | 11 +++++++++++ 3 files changed, 22 insertions(+) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala index d14737e390..379db2861a 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -23,6 +23,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: val silent = NullaryCommand("silent") val noSanityCheck = NullaryCommand("noSanityCheck") val traceJS = NullaryCommand("traceJS") + val handler = NullaryCommand("handler") val expect = Command("expect"): ln => ln.trim @@ -54,6 +55,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: new codegen.Lowering with codegen.LoweringSelSanityChecks(noSanityCheck.isUnset) with codegen.LoweringTraceLog(traceJS.isSet) + with codegen.LoweringHandler(handler.isSet) given Elaborator.Ctx = curCtx val jsb = new JSBuilder with JSBuilderArgNumSanityChecks(noSanityCheck.isUnset) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 95c443fdc7..86dac434ed 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -82,6 +82,8 @@ case class AssignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(sym case class Define(defn: Defn, rest: Block) extends Block with ProductWithTail +case class Handle(handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail + sealed abstract class Defn: val sym: MemberSymbol[?] @@ -107,6 +109,13 @@ final case class ClsLikeDefn( ctor: Block, ) extends Defn +final case class Handler( + sym: BlockMemberSymbol, + resumeSym: LocalSymbol & NamedSymbol, + params: Ls[ParamList], + body: Block, +) + /* Represents either unreachable code (for functions that must return a result) * or the end of a non-returning function or a REPL block */ case class End(msg: Str = "") extends BlockTail with ProductWithTail diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 508df778a7..96f2841a54 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -446,3 +446,14 @@ trait LoweringTraceLog ) |>: Ret(Value.Ref(resSym)) ) + + +trait LoweringHandler + (instrument: Bool)(using TL, Raise, Elaborator.State) + extends Lowering: + override def term(t: st)(k: Result => Block)(using Subst): Block = + if !instrument then return super.term(t)(k) + super.term(t)(k) + override def program(main: st): Program = + if !instrument then return super.program(main) + super.program(main) From e7813a95cfe4212dfe65ab3d485317a7c407c9c4 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Tue, 10 Dec 2024 22:38:36 +0800 Subject: [PATCH 002/114] initial changes --- .../src/main/scala/hkmc2/codegen/Block.scala | 2 +- .../main/scala/hkmc2/codegen/Lowering.scala | 2 ++ .../src/test/mlscript/handlers/Effects.mls | 22 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 hkmc2/shared/src/test/mlscript/handlers/Effects.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 86dac434ed..e7900c655e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -82,7 +82,7 @@ case class AssignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(sym case class Define(defn: Defn, rest: Block) extends Block with ProductWithTail -case class Handle(handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail +case class HandleBlock(handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail sealed abstract class Defn: val sym: MemberSymbol[?] diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 96f2841a54..d836b6d955 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -304,6 +304,8 @@ class Lowering(using TL, Raise, Elaborator.State): term(finallyDo)(_ => End()), k(Value.Ref(l)) ) + + case Handle(lhs, rhs, defs) => HandleBlock(???, ???, ???) case Error => End("error") diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls new file mode 100644 index 0000000000..14cdeebbd0 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -0,0 +1,22 @@ +:js + +:sjs +class Effect[A](perform: Str -> Str) +val r = handle h = Effect with + fun perform(arg, k) = "b" + 2 + 2 +in + h.perform("k") +r +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +//│ at: scala.Predef$.$qmark$qmark$qmark(Predef.scala:344) +//│ at: hkmc2.codegen.Lowering.term(Lowering.scala:308) +//│ at: hkmc2.JSBackendDiffMaker$$anon$3.hkmc2$codegen$LoweringHandler$$super$term(JSBackendDiffMaker.scala:55) +//│ at: hkmc2.codegen.LoweringHandler.term(Lowering.scala:457) +//│ at: hkmc2.codegen.LoweringHandler.term$(Lowering.scala:453) +//│ at: hkmc2.JSBackendDiffMaker$$anon$3.term(JSBackendDiffMaker.scala:55) +//│ at: hkmc2.codegen.Lowering.subTerm(Lowering.scala:321) +//│ at: hkmc2.codegen.Lowering.term(Lowering.scala:101) +//│ at: hkmc2.JSBackendDiffMaker$$anon$3.hkmc2$codegen$LoweringHandler$$super$term(JSBackendDiffMaker.scala:55) +//│ at: hkmc2.codegen.LoweringHandler.term(Lowering.scala:457) From 4444d4e1f3bd08b4b370cd2b8bc7ba57993d9431 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 11 Dec 2024 00:12:00 +0800 Subject: [PATCH 003/114] Update elaborator --- .../scala/hkmc2/semantics/Elaborator.scala | 42 ++++++++++- .../src/main/scala/hkmc2/semantics/Term.scala | 9 ++- .../src/main/scala/hkmc2/utils/utils.scala | 15 ++++ .../src/test/mlscript/parser/Handler.mls | 73 ++++++++++++++++++- 4 files changed, 132 insertions(+), 7 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 20103a46ae..e10a0c5bb4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -207,7 +207,9 @@ extends Importer: val sym = fieldOrVarSym(Handler, id) val newCtx = ctx + (id.name -> sym) - Term.Handle(sym, term(cls)(using newCtx), ObjBody(block(sts)._1)) + // NOTE: `defs` is nil because it doesn't really matter what's in there, no point wasting time transforming the defs. + // The handler has no body anyways so those defs can never be called + Term.Handle(sym, term(cls)(using newCtx), Nil) case h: Handle => raise(ErrorReport( @@ -561,10 +563,42 @@ extends Importer: raise(ErrorReport(msg"Unsupported let binding shape" -> tree.toLoc :: Nil)) go(sts, Term.Error :: acc) case (hd @ Handle(id: Ident, cls: Ident, Block(sts_), N)) :: sts => - val sym = - fieldOrVarSym(LetBind, id) + val sym = fieldOrVarSym(LetBind, id) log(s"Processing `handle` statement $id (${sym}) ${ctx.outer}") - val newAcc = Term.Handle(sym, term(cls), ObjBody(block(sts_)._1)) :: acc + + val elabed = block(sts_)._1 + + elabed.res match + case Term.Lit(UnitLit(true)) => + case trm => raise(WarningReport(msg"Terms in handler block do nothing" -> trm.toLoc :: Nil)) + + val tds = elabed.stats.map { + case td @ TermDefinition(owner, Fun, sym, params, sign, body, resSym, flags) => + val paramsRev = params.reverse + // Find last param + val (resumeParam, newParams) = paramsRev.replaceAndPopFirst( + params => { + params.params.reverse match + case head :: next => + Some((ParamList(params.flags, next.reverse, params.restParam), head)) + case Nil => None + } + ) + + resumeParam match + case None => + raise(ErrorReport(msg"Handler function is missing resumption parameter" -> td.toLoc :: Nil)) + None + case Some(value) => + val newTd = TermDefinition(owner, Fun, sym, newParams.reverse, sign, body, resSym, flags) + S(HandlerTermDefinition(value.sym, newTd)) + + case st => + raise(ErrorReport(msg"Only function definitions are allowed in handler blocks" -> st.toLoc :: Nil)) + None + }.collect { case Some(x) => x } + + val newAcc = Term.Handle(sym, term(cls), tds) :: acc ctx + (id.name -> sym) givenIn: go(sts, newAcc) case (tree @ Handle(_, _, _, N)) :: sts => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 5a67992e42..bbdb01a6d2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -38,7 +38,7 @@ enum Term extends Statement: case Ret(result: Term) case Throw(result: Term) case Try(body: Term, finallyDo: Term) - case Handle(lhs: LocalSymbol, rhs: Term, defs: ObjBody) + case Handle(lhs: LocalSymbol, rhs: Term, defs: Ls[HandlerTermDefinition]) lazy val symbol: Opt[Symbol] = this match case Ref(sym) => S(sym) @@ -124,7 +124,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: pat.paramsOpt.toList.flatMap(_.subTerms) ::: pat.body.blk :: Nil case Import(sym, pth) => Nil case Try(body, finallyDo) => body :: finallyDo :: Nil - case Handle(lhs, rhs, defs) => rhs :: defs._1 :: Nil + case Handle(lhs, rhs, defs) => rhs :: defs.flatMap(_.td.subTerms) case Neg(e) => e :: Nil protected def children: Ls[Located] = this match @@ -221,6 +221,11 @@ final case class TermDefinition( flags: TermDefFlags, ) extends Companion +final case class HandlerTermDefinition( + resumeSym: LocalSymbol & NamedSymbol, + td: TermDefinition +) + case class ObjBody(blk: Term.Blk): // override def toString: String = statmts.mkString("{ ", "; ", " }") override def toString: String = blk.showDbg diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala index 7ef428d273..ca1ad1d8a2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala @@ -74,3 +74,18 @@ extension (t: Product) if inTailPos then ": \\\n" + args.mkString("\n") else ":\n" + args.mkString("\n").indent(" ") + +extension [T](xs: Ls[T]) + def replaceFirst(repl: T => Opt[T]): List[T] = xs match + case Nil => Nil + case head :: tail => repl(head) match + case None => head :: replaceFirst(repl) + case Some(value) => value :: tail + + def replaceAndPopFirst[V](repl: T => Opt[(T, V)]): (Opt[V], Ls[T]) = xs match + case Nil => (None, Nil) + case head :: tail => repl(head) match + case N => + val next = tail.replaceAndPopFirst(repl) + (next._1, head :: next._2) + case S((replaced, popped)) => (S(popped), replaced :: tail) \ No newline at end of file diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index d09ae3a750..15a16de0c5 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ fun foo(h) = 0 handle h = Eff with fun f(r) = r(0) fun g(a, r) = r(1) -foo(h) +foo(h) :e ( @@ -67,3 +67,74 @@ foo(h) //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ║ l.61: fun g(a, r) = r(1) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ + +:el +class Eff +fun foo(h) = 0 +handle h = Eff with + fun f(r) = r(0) + fun g(a, r) = r(1) +foo(h) +//│ Elab: { Cls Eff { }; ‹› fun member:foo(Param(‹›,h,None), ) = 0; handle globalThis:block#8.h = SynthSel(Ref(globalThis:block#8),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#8#666(.)foo‹member:foo›(globalThis:block#8.h#666) } + +:el +class Eff +fun foo(h) = 0 +handle h = Eff with + fun f(r) = r(0) + fun g(a, r)()() = r(1) +foo(h) +//│ Elab: { Cls Eff { }; ‹› fun member:foo(Param(‹›,h,None), ) = 0; handle globalThis:block#9.h = SynthSel(Ref(globalThis:block#9),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#9),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#9),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#9#666(.)foo‹member:foo›(globalThis:block#9.h#666) } + +:e +class Eff +fun foo(h) = 0 +handle h = Eff with + fun f(r) = r(0) + fun g(a, r) = r(1) + val x = 24 + class Test with + fun x() = x +foo(h) +//│ ╔══[ERROR] Only function definitions are allowed in handler blocks +//│ ║ l.95: val x = 24 +//│ ╙── ^^ +//│ ╔══[ERROR] Only function definitions are allowed in handler blocks +//│ ║ l.97: fun x() = x +//│ ╙── ^ + +:e +class Eff +fun foo(h) = 0 +handle h = Eff with + fun f(r) = r(0) + fun g(a, r) = + handle e = Eff with + fun f = r(0) + fun g() = r(0) + fun h()() = r(1) + foo(h) +foo(h) +//│ ╔══[ERROR] Handler function is missing resumption parameter +//│ ║ l.113: fun f = r(0) +//│ ╙── ^^^^ +//│ ╔══[ERROR] Handler function is missing resumption parameter +//│ ║ l.114: fun g() = r(0) +//│ ╙── ^^^^ +//│ ╔══[ERROR] Handler function is missing resumption parameter +//│ ║ l.115: fun h()() = r(1) +//│ ╙── ^^^^ + +:w +:el +class Eff +fun foo(h) = 0 +handle h = Eff with + fun f(r) = r(0) + fun g(a, r) = r(1) + 12345 +foo(h) +//│ ╔══[WARNING] Terms in handler block do nothing +//│ ║ l.135: 12345 +//│ ╙── ^^^^^ +//│ Elab: { Cls Eff { }; ‹› fun member:foo(Param(‹›,h,None), ) = 0; handle globalThis:block#12.h = SynthSel(Ref(globalThis:block#12),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#12),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#12),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#12#666(.)foo‹member:foo›(globalThis:block#12.h#666) } From 4f2618906349e369e756f434aac57e7e07b9aea9 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 11 Dec 2024 00:33:11 +0800 Subject: [PATCH 004/114] update lowering --- .../src/main/scala/hkmc2/codegen/Block.scala | 4 +- .../main/scala/hkmc2/codegen/Lowering.scala | 14 +++- .../src/test/mlscript/handlers/Effects.mls | 74 +++++++++++++++---- 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index e7900c655e..969b3a7b21 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -24,6 +24,7 @@ sealed abstract class Block extends Product with AutoLocated: protected def children: Ls[Located] = ??? // Maybe extending AutoLocated is unnecessary + // TODO: implement for HandleBlock lazy val definedVars: Set[Local] = this match case _: Return | _: Throw => Set.empty case Begin(sub, rst) => sub.definedVars ++ rst.definedVars @@ -39,6 +40,7 @@ sealed abstract class Block extends Product with AutoLocated: case TryBlock(sub, fin, rst) => sub.definedVars ++ fin.definedVars ++ rst.definedVars case Label(lbl, bod, rst) => bod.definedVars ++ rst.definedVars + // TODO: implement for HandleBlock // TODO conserve if no changes def mapTail(f: BlockTail => BlockTail): Block = this match case b: BlockTail => f(b) @@ -82,7 +84,7 @@ case class AssignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(sym case class Define(defn: Defn, rest: Block) extends Block with ProductWithTail -case class HandleBlock(handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail +case class HandleBlock(lhs: Local, handlers: Ls[Handler], rest: Block) extends Block with ProductWithTail sealed abstract class Defn: val sym: MemberSymbol[?] diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index d836b6d955..cb2778586b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -174,7 +174,7 @@ class Lowering(using TL, Raise, Elaborator.State): case st.Lam(params, body) => val (paramLists, bodyBlock) = setupFunctionDef(params :: Nil, body, N) k(Value.Lam(paramLists.head, bodyBlock)) - + /* case t @ st.If(Split.Let(sym, trm, tail)) => // term(st.Blk(semantics.LetDecl(sym) :: semantics.DefineVar(sym, trm) :: Nil, st.If(tail)(t.normalized)))(k) @@ -305,7 +305,17 @@ class Lowering(using TL, Raise, Elaborator.State): k(Value.Ref(l)) ) - case Handle(lhs, rhs, defs) => HandleBlock(???, ???, ???) + case Handle(lhs, rhs, defs) => + val handlers = defs.map { + case HandlerTermDefinition(resumeSym, td) => td.body match + case None => + raise(ErrorReport(msg"Handler function definitions cannot be empty" -> td.toLoc :: Nil)) + N + case Some(bod) => + val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme)) + S(Handler(td.sym, resumeSym, paramLists, bodyBlock)) + }.collect{ case Some(v) => v } + HandleBlock(lhs, handlers, k(Value.Lit(syntax.Tree.UnitLit(true)))) case Error => End("error") diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 14cdeebbd0..bc12414df8 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -1,22 +1,66 @@ :js -:sjs +:fixme +:lot class Effect[A](perform: Str -> Str) val r = handle h = Effect with - fun perform(arg, k) = "b" - 2 + 2 + fun perform(arg, k) = k(arg) in h.perform("k") r -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -//│ at: scala.Predef$.$qmark$qmark$qmark(Predef.scala:344) -//│ at: hkmc2.codegen.Lowering.term(Lowering.scala:308) -//│ at: hkmc2.JSBackendDiffMaker$$anon$3.hkmc2$codegen$LoweringHandler$$super$term(JSBackendDiffMaker.scala:55) -//│ at: hkmc2.codegen.LoweringHandler.term(Lowering.scala:457) -//│ at: hkmc2.codegen.LoweringHandler.term$(Lowering.scala:453) -//│ at: hkmc2.JSBackendDiffMaker$$anon$3.term(JSBackendDiffMaker.scala:55) -//│ at: hkmc2.codegen.Lowering.subTerm(Lowering.scala:321) -//│ at: hkmc2.codegen.Lowering.term(Lowering.scala:101) -//│ at: hkmc2.JSBackendDiffMaker$$anon$3.hkmc2$codegen$LoweringHandler$$super$term(JSBackendDiffMaker.scala:55) -//│ at: hkmc2.codegen.LoweringHandler.term(Lowering.scala:457) +//│ Lowered: +//│ Program: +//│ imports = Nil +//│ main = Define: +//│ defn = ClsLikeDefn: +//│ sym = class:Effect +//│ k = Cls +//│ methods = Nil +//│ privateFields = Nil +//│ publicFields = Nil +//│ ctor = End of "" +//│ rest = HandleBlock: \ +//│ lhs = h +//│ handlers = Ls of +//│ Handler: +//│ sym = member:perform +//│ resumeSym = k +//│ params = Ls of +//│ ParamList: +//│ flags = ParamListFlags of false +//│ params = Ls of +//│ Param: +//│ flags = () +//│ sym = arg +//│ sign = N +//│ restParam = N +//│ body = Return: +//│ res = Call: +//│ fun = Ref of k +//│ args = Ls of +//│ Arg: +//│ spread = false +//│ value = Ref of arg +//│ implct = false +//│ rest = Assign: \ +//│ lhs = $tmp +//│ rhs = Call: +//│ fun = Select: +//│ qual = Ref of h +//│ name = Ident of "perform" +//│ args = Ls of +//│ Arg: +//│ spread = false +//│ value = Lit of StrLit of "k" +//│ rest = Define: \ +//│ defn = ValDefn: +//│ owner = S of globalThis:block#1 +//│ k = ImmutVal +//│ sym = member:r +//│ rhs = Ref of $tmp +//│ rest = Return: \ +//│ res = Select{member:r}: +//│ qual = Ref of globalThis:block#1 +//│ name = Ident of "r" +//│ implct = true +//│ /!!!\ Uncaught error: scala.MatchError: HandleBlock(h,List(Handler(member:perform,k,List(ParamList(‹›,List(Param(‹›,arg,None)),None)),Return(Call(Ref(k),List(Arg(false,Ref(arg)))),false))),Assign($tmp,Call(Select(Ref(h),Ident(perform)),List(Arg(false,Lit(StrLit(k))))),Define(ValDefn(Some(globalThis:block#1),ImmutVal,member:r,Ref($tmp)),Return(Select(Ref(globalThis:block#1),Ident(r)),true)))) (of class hkmc2.codegen.HandleBlock) From 942922dc2ae018521892e7cc59c7e45304f3bf77 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 11 Dec 2024 00:35:27 +0800 Subject: [PATCH 005/114] fix whitespace --- hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index cb2778586b..8be56a06ed 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -174,7 +174,7 @@ class Lowering(using TL, Raise, Elaborator.State): case st.Lam(params, body) => val (paramLists, bodyBlock) = setupFunctionDef(params :: Nil, body, N) k(Value.Lam(paramLists.head, bodyBlock)) - + /* case t @ st.If(Split.Let(sym, trm, tail)) => // term(st.Blk(semantics.LetDecl(sym) :: semantics.DefineVar(sym, trm) :: Nil, st.If(tail)(t.normalized)))(k) From 827c5e73ff7068e3e34e88db40cb332b3c726920 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 11 Dec 2024 01:08:18 +0800 Subject: [PATCH 006/114] refactor some code --- .../src/main/scala/hkmc2/semantics/Elaborator.scala | 9 ++------- hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index e10a0c5bb4..3ac3ffeb4b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -204,12 +204,7 @@ extends Importer: msg"Expected a body for handle bindings in expression position" -> tree.toLoc :: Nil)) - val sym = - fieldOrVarSym(Handler, id) - val newCtx = ctx + (id.name -> sym) - // NOTE: `defs` is nil because it doesn't really matter what's in there, no point wasting time transforming the defs. - // The handler has no body anyways so those defs can never be called - Term.Handle(sym, term(cls)(using newCtx), Nil) + block(Handle(id, cls, Block(sts), N) :: Nil)._1 case h: Handle => raise(ErrorReport( @@ -563,7 +558,7 @@ extends Importer: raise(ErrorReport(msg"Unsupported let binding shape" -> tree.toLoc :: Nil)) go(sts, Term.Error :: acc) case (hd @ Handle(id: Ident, cls: Ident, Block(sts_), N)) :: sts => - val sym = fieldOrVarSym(LetBind, id) + val sym = fieldOrVarSym(HandlerBind, id) log(s"Processing `handle` statement $id (${sym}) ${ctx.outer}") val elabed = block(sts_)._1 diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 4e3e7be7b4..86639378a9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -202,7 +202,7 @@ sealed abstract class Val(str: Str, desc: Str) extends ValLike(str, desc) case object ImmutVal extends Val("val", "value") case object MutVal extends Val("mut val", "mutable value") case object LetBind extends ValLike("let", "let binding") -case object Handler extends TermDefKind("handler", "handler binding") +case object HandlerBind extends TermDefKind("handler", "handler binding") case object ParamBind extends ValLike("", "parameter") case object Fun extends TermDefKind("fun", "function") sealed abstract class TypeDefKind(desc: Str) extends DeclKind(desc) From eef713131743b5aa5d70e0e74b840c6f30ae8f00 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 12 Dec 2024 19:47:48 +0800 Subject: [PATCH 007/114] Simplify test cases --- .../src/test/mlscript/handlers/Effects.mls | 24 +++----- .../src/test/mlscript/parser/Handler.mls | 61 +++++++------------ 2 files changed, 32 insertions(+), 53 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index bc12414df8..c55c08e514 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -2,8 +2,9 @@ :fixme :lot -class Effect[A](perform: Str -> Str) -val r = handle h = Effect with +abstract class Effect with + fun perform(arg: Str): Str +let r = handle h = Effect with fun perform(arg, k) = k(arg) in h.perform("k") @@ -20,7 +21,7 @@ r //│ publicFields = Nil //│ ctor = End of "" //│ rest = HandleBlock: \ -//│ lhs = h +//│ lhs = globalThis:block#1.h //│ handlers = Ls of //│ Handler: //│ sym = member:perform @@ -46,21 +47,16 @@ r //│ lhs = $tmp //│ rhs = Call: //│ fun = Select: -//│ qual = Ref of h +//│ qual = Ref of globalThis:block#1.h //│ name = Ident of "perform" //│ args = Ls of //│ Arg: //│ spread = false //│ value = Lit of StrLit of "k" -//│ rest = Define: \ -//│ defn = ValDefn: -//│ owner = S of globalThis:block#1 -//│ k = ImmutVal -//│ sym = member:r -//│ rhs = Ref of $tmp +//│ rest = Assign: \ +//│ lhs = globalThis:block#1.r +//│ rhs = Ref of $tmp //│ rest = Return: \ -//│ res = Select{member:r}: -//│ qual = Ref of globalThis:block#1 -//│ name = Ident of "r" +//│ res = Ref of globalThis:block#1.r //│ implct = true -//│ /!!!\ Uncaught error: scala.MatchError: HandleBlock(h,List(Handler(member:perform,k,List(ParamList(‹›,List(Param(‹›,arg,None)),None)),Return(Call(Ref(k),List(Arg(false,Ref(arg)))),false))),Assign($tmp,Call(Select(Ref(h),Ident(perform)),List(Arg(false,Lit(StrLit(k))))),Define(ValDefn(Some(globalThis:block#1),ImmutVal,member:r,Ref($tmp)),Return(Select(Ref(globalThis:block#1),Ident(r)),true)))) (of class hkmc2.codegen.HandleBlock) +//│ /!!!\ Uncaught error: scala.MatchError: HandleBlock(globalThis:block#1.h,List(Handler(member:perform,k,List(ParamList(‹›,List(Param(‹›,arg,None)),None)),Return(Call(Ref(k),List(Arg(false,Ref(arg)))),false))),Assign($tmp,Call(Select(Ref(globalThis:block#1.h),Ident(perform)),List(Arg(false,Lit(StrLit(k))))),Assign(globalThis:block#1.r,Ref($tmp),Return(Ref(globalThis:block#1.r),true)))) (of class hkmc2.codegen.HandleBlock) diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index 15a16de0c5..5e2dcd2b93 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -18,41 +18,41 @@ handle h = Eff //│ ╙── ^ :parseOnly +fun foo(x) = 1 handle h = Eff with fun f(r) = r(0) fun g(a, r) = r(1) //│ Parsed: +//│ TermDef(Fun,App(Ident(foo),Tup(List(Ident(x)))),Some(IntLit(1))) //│ Handle(Ident(h),Ident(Eff),Block(List(TermDef(Fun,App(Ident(f),Tup(List(Ident(r)))),Some(App(Ident(r),Tup(List(IntLit(0)))))), TermDef(Fun,App(Ident(g),Tup(List(Ident(a), Ident(r)))),Some(App(Ident(r),Tup(List(IntLit(1)))))))),None) :e -fun foo(x) = 1 handle 1 = 1 with fun f(r) = r(0) in foo(h) //│ ╔══[ERROR] Unsupported handle binding shape -//│ ║ l.30: handle 1 = 1 with +//│ ║ l.31: handle 1 = 1 with //│ ║ ^^^^^^^^^^ -//│ ║ l.31: fun f(r) = r(0) +//│ ║ l.32: fun f(r) = r(0) //│ ╙── ^^^^^^^^^^^^^^^^^ +//│ ╔══[ERROR] Name not found: foo +//│ ║ l.34: foo(h) +//│ ╙── ^^^ //│ ╔══[ERROR] Name not found: h -//│ ║ l.33: foo(h) +//│ ║ l.34: foo(h) //│ ╙── ^ -class Eff +abstract class Eff fun foo(h) = 0 + +:el handle h = Eff with fun f(r) = r(0) in foo(h) - -class Eff -fun foo(h) = 0 -handle h = Eff with - fun f(r) = r(0) - fun g(a, r) = r(1) -foo(h) +//│ Elab: { { handle globalThis:block#6.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#6),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#6.h#666) } } :e ( @@ -69,26 +69,13 @@ foo(h) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ :el -class Eff -fun foo(h) = 0 -handle h = Eff with - fun f(r) = r(0) - fun g(a, r) = r(1) -foo(h) -//│ Elab: { Cls Eff { }; ‹› fun member:foo(Param(‹›,h,None), ) = 0; handle globalThis:block#8.h = SynthSel(Ref(globalThis:block#8),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#8#666(.)foo‹member:foo›(globalThis:block#8.h#666) } - -:el -class Eff -fun foo(h) = 0 handle h = Eff with fun f(r) = r(0) fun g(a, r)()() = r(1) foo(h) -//│ Elab: { Cls Eff { }; ‹› fun member:foo(Param(‹›,h,None), ) = 0; handle globalThis:block#9.h = SynthSel(Ref(globalThis:block#9),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#9),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#9),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#9#666(.)foo‹member:foo›(globalThis:block#9.h#666) } +//│ Elab: { handle globalThis:block#8.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#8.h#666) } :e -class Eff -fun foo(h) = 0 handle h = Eff with fun f(r) = r(0) fun g(a, r) = r(1) @@ -97,15 +84,13 @@ handle h = Eff with fun x() = x foo(h) //│ ╔══[ERROR] Only function definitions are allowed in handler blocks -//│ ║ l.95: val x = 24 +//│ ║ l.82: val x = 24 //│ ╙── ^^ //│ ╔══[ERROR] Only function definitions are allowed in handler blocks -//│ ║ l.97: fun x() = x +//│ ║ l.84: fun x() = x //│ ╙── ^ :e -class Eff -fun foo(h) = 0 handle h = Eff with fun f(r) = r(0) fun g(a, r) = @@ -116,25 +101,23 @@ handle h = Eff with foo(h) foo(h) //│ ╔══[ERROR] Handler function is missing resumption parameter -//│ ║ l.113: fun f = r(0) -//│ ╙── ^^^^ +//│ ║ l.98: fun f = r(0) +//│ ╙── ^^^^ //│ ╔══[ERROR] Handler function is missing resumption parameter -//│ ║ l.114: fun g() = r(0) -//│ ╙── ^^^^ +//│ ║ l.99: fun g() = r(0) +//│ ╙── ^^^^ //│ ╔══[ERROR] Handler function is missing resumption parameter -//│ ║ l.115: fun h()() = r(1) +//│ ║ l.100: fun h()() = r(1) //│ ╙── ^^^^ :w :el -class Eff -fun foo(h) = 0 handle h = Eff with fun f(r) = r(0) fun g(a, r) = r(1) 12345 foo(h) //│ ╔══[WARNING] Terms in handler block do nothing -//│ ║ l.135: 12345 +//│ ║ l.118: 12345 //│ ╙── ^^^^^ -//│ Elab: { Cls Eff { }; ‹› fun member:foo(Param(‹›,h,None), ) = 0; handle globalThis:block#12.h = SynthSel(Ref(globalThis:block#12),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#12),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#12),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#12#666(.)foo‹member:foo›(globalThis:block#12.h#666) } +//│ Elab: { handle globalThis:block#11.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#11.h#666) } From 5b267199c52ba7e36967b166f8b416738e568425 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 12 Dec 2024 20:37:57 +0800 Subject: [PATCH 008/114] Hide handlers behind a flag --- .../src/main/scala/hkmc2/codegen/Block.scala | 7 +- .../main/scala/hkmc2/codegen/Lowering.scala | 32 +++++---- .../src/test/mlscript/handlers/Effects.mls | 72 +++++-------------- 3 files changed, 43 insertions(+), 68 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 969b3a7b21..e305825cde 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -24,7 +24,6 @@ sealed abstract class Block extends Product with AutoLocated: protected def children: Ls[Located] = ??? // Maybe extending AutoLocated is unnecessary - // TODO: implement for HandleBlock lazy val definedVars: Set[Local] = this match case _: Return | _: Throw => Set.empty case Begin(sub, rst) => sub.definedVars ++ rst.definedVars @@ -37,16 +36,18 @@ sealed abstract class Block extends Product with AutoLocated: case Break(_) => Set.empty case Continue(_) => Set.empty case Define(defn, rst) => rst.definedVars + case HandleBlock(lhs, res, handlers, bod, rst) => bod.definedVars ++ rst.definedVars + lhs case TryBlock(sub, fin, rst) => sub.definedVars ++ fin.definedVars ++ rst.definedVars case Label(lbl, bod, rst) => bod.definedVars ++ rst.definedVars - // TODO: implement for HandleBlock // TODO conserve if no changes def mapTail(f: BlockTail => BlockTail): Block = this match case b: BlockTail => f(b) case Begin(sub, rst) => Begin(sub, rst.mapTail(f)) case Assign(lhs, rhs, rst) => Assign(lhs, rhs, rst.mapTail(f)) case Define(defn, rst) => Define(defn, rst.mapTail(f)) + case HandleBlock(lhs, res, handlers, body, rest) => + HandleBlock(lhs, res, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, h.body.mapTail(f))), body.mapTail(f), rest.mapTail(f)) case Match(scrut, arms, dflt, rst) => Match(scrut, arms.map(_ -> _.mapTail(f)), dflt.map(_.mapTail(f)), rst.mapTail(f)) @@ -84,7 +85,7 @@ case class AssignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(sym case class Define(defn: Defn, rest: Block) extends Block with ProductWithTail -case class HandleBlock(lhs: Local, handlers: Ls[Handler], rest: Block) extends Block with ProductWithTail +case class HandleBlock(lhs: Local, res: Local, handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail sealed abstract class Defn: val sym: MemberSymbol[?] diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 8be56a06ed..360c26e771 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -305,17 +305,12 @@ class Lowering(using TL, Raise, Elaborator.State): k(Value.Ref(l)) ) - case Handle(lhs, rhs, defs) => - val handlers = defs.map { - case HandlerTermDefinition(resumeSym, td) => td.body match - case None => - raise(ErrorReport(msg"Handler function definitions cannot be empty" -> td.toLoc :: Nil)) - N - case Some(bod) => - val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme)) - S(Handler(td.sym, resumeSym, paramLists, bodyBlock)) - }.collect{ case Some(v) => v } - HandleBlock(lhs, handlers, k(Value.Lit(syntax.Tree.UnitLit(true)))) + case Handle(lhs, rhs, defs) => + raise(ErrorReport( + msg"Effect handlers are not enabled" -> + t.toLoc :: Nil, + source = Diagnostic.Source.Compilation)) + Assign(lhs, Value.Lit(syntax.Tree.UnitLit(true)), k(Value.Lit(syntax.Tree.UnitLit(true)))) case Error => End("error") @@ -465,7 +460,20 @@ trait LoweringHandler extends Lowering: override def term(t: st)(k: Result => Block)(using Subst): Block = if !instrument then return super.term(t)(k) - super.term(t)(k) + t match + case st.Blk(Handle(lhs, rhs, defs) :: stmts, res) => + val handlers = defs.map { + case HandlerTermDefinition(resumeSym, td) => td.body match + case None => + raise(ErrorReport(msg"Handler function definitions cannot be empty" -> td.toLoc :: Nil)) + N + case Some(bod) => + val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme)) + S(Handler(td.sym, resumeSym, paramLists, bodyBlock)) + }.collect{ case Some(v) => v } + val resSym = TempSymbol(S(t)) + HandleBlock(lhs, resSym, handlers, term(st.Blk(stmts, res))(res => Assign(resSym, res, End())), k(Value.Ref(resSym))) + case _ => super.term(t)(k) override def program(main: st): Program = if !instrument then return super.program(main) super.program(main) diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index c55c08e514..db1005d446 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -1,62 +1,28 @@ :js -:fixme -:lot abstract class Effect with fun perform(arg: Str): Str + +:ge +let r = handle h = Effect with + fun perform(arg, k) = k(arg) +in + h.perform("k") +r +//│ ╔══[COMPILATION ERROR] Effect handlers are not enabled +//│ ║ l.7: let r = handle h = Effect with +//│ ║ ^^^^^^^^^^^ +//│ ║ l.8: fun perform(arg, k) = k(arg) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of null (reading 'perform') + +:global +:handler + +:fixme let r = handle h = Effect with fun perform(arg, k) = k(arg) in h.perform("k") r -//│ Lowered: -//│ Program: -//│ imports = Nil -//│ main = Define: -//│ defn = ClsLikeDefn: -//│ sym = class:Effect -//│ k = Cls -//│ methods = Nil -//│ privateFields = Nil -//│ publicFields = Nil -//│ ctor = End of "" -//│ rest = HandleBlock: \ -//│ lhs = globalThis:block#1.h -//│ handlers = Ls of -//│ Handler: -//│ sym = member:perform -//│ resumeSym = k -//│ params = Ls of -//│ ParamList: -//│ flags = ParamListFlags of false -//│ params = Ls of -//│ Param: -//│ flags = () -//│ sym = arg -//│ sign = N -//│ restParam = N -//│ body = Return: -//│ res = Call: -//│ fun = Ref of k -//│ args = Ls of -//│ Arg: -//│ spread = false -//│ value = Ref of arg -//│ implct = false -//│ rest = Assign: \ -//│ lhs = $tmp -//│ rhs = Call: -//│ fun = Select: -//│ qual = Ref of globalThis:block#1.h -//│ name = Ident of "perform" -//│ args = Ls of -//│ Arg: -//│ spread = false -//│ value = Lit of StrLit of "k" -//│ rest = Assign: \ -//│ lhs = globalThis:block#1.r -//│ rhs = Ref of $tmp -//│ rest = Return: \ -//│ res = Ref of globalThis:block#1.r -//│ implct = true -//│ /!!!\ Uncaught error: scala.MatchError: HandleBlock(globalThis:block#1.h,List(Handler(member:perform,k,List(ParamList(‹›,List(Param(‹›,arg,None)),None)),Return(Call(Ref(k),List(Arg(false,Ref(arg)))),false))),Assign($tmp,Call(Select(Ref(globalThis:block#1.h),Ident(perform)),List(Arg(false,Lit(StrLit(k))))),Assign(globalThis:block#1.r,Ref($tmp),Return(Ref(globalThis:block#1.r),true)))) (of class hkmc2.codegen.HandleBlock) +//│ /!!!\ Uncaught error: scala.MatchError: HandleBlock(globalThis:block#3.h,$tmp,List(Handler(member:perform,k,List(ParamList(‹›,List(Param(‹›,arg,None)),None)),Return(Call(Ref(k),List(Arg(false,Ref(arg)))),false))),Assign($tmp,Call(Select(Ref(globalThis:block#3.h),Ident(perform)),List(Arg(false,Lit(StrLit(k))))),End()),Assign(globalThis:block#3.r,Ref($tmp),Return(Ref(globalThis:block#3.r),true))) (of class hkmc2.codegen.HandleBlock) From 421b6403cf4eccddbd70a02501aed4aed120481e Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 17 Dec 2024 00:00:11 +0800 Subject: [PATCH 009/114] Implement transformation for non-resumptive effects --- .../src/main/scala/hkmc2/codegen/Block.scala | 45 +++- .../scala/hkmc2/codegen/HandlerLowering.scala | 254 ++++++++++++++++++ .../main/scala/hkmc2/codegen/Lowering.scala | 18 +- .../scala/hkmc2/codegen/js/JSBuilder.scala | 10 +- .../scala/hkmc2/semantics/Elaborator.scala | 2 +- .../src/test/mlscript-compile/Predef.mjs | 117 ++++++++ .../src/test/mlscript-compile/Predef.mls | 52 ++++ .../src/test/mlscript/codegen/ClassInFun.mls | 2 +- .../src/test/mlscript/codegen/FunInClass.mls | 6 +- .../src/test/mlscript/handlers/Effects.mls | 26 +- 10 files changed, 504 insertions(+), 28 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index e305825cde..5c3684a5a6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -36,18 +36,52 @@ sealed abstract class Block extends Product with AutoLocated: case Break(_) => Set.empty case Continue(_) => Set.empty case Define(defn, rst) => rst.definedVars - case HandleBlock(lhs, res, handlers, bod, rst) => bod.definedVars ++ rst.definedVars + lhs + case HandleBlock(lhs, resIn, resOut, hdr, bod, rst) => bod.definedVars ++ rst.definedVars + lhs case TryBlock(sub, fin, rst) => sub.definedVars ++ fin.definedVars ++ rst.definedVars case Label(lbl, bod, rst) => bod.definedVars ++ rst.definedVars + // ignoring blocks inside functions and handle block + def map(f: Block => Block): Block = this match + case _: Return | _: Throw | _: End | _: Break | _: Continue => this + case Match(scrut, arms, dflt, rst) => Match(scrut, arms.map(_ -> f(_)), dflt.map(f), f(rst)) + case Label(lbl, bod, rst) => Label(lbl, f(bod), f(rst)) + case Begin(sub, rst) => Begin(f(sub), f(rst)) + case TryBlock(sub, fin, rst) => TryBlock(f(sub), f(fin), f(rst)) + case Assign(l, r, rst) => Assign(l, r, f(rst)) + case b @ AssignField(l, n, r, rst) => AssignField(l, n, r, f(rst))(b.symbol) + case Define(defn, rst) => Define(defn, f(rst)) + case HandleBlock(l, resIn, resOut, hdr, bod, rst) => HandleBlock(l, resIn, resOut, hdr, bod, f(rst)) + + def mapResult(f: Result => Opt[(Result => Block) => Block]): Block = this match + case Return(res, implct) => f(res).map(_(Return(_, implct))).getOrElse(this) + case Throw(exc) => f(exc).map(_(Throw(_))).getOrElse(this) + case Assign(l, r, rst) => f(r).map(_(Assign(l, _, rst))).getOrElse(this) + case b @ AssignField(l, n, r, rst) => f(r).map(_(AssignField(l, n, _, rst)(b.symbol))).getOrElse(this) + case _: End | _: Break | _: Continue | _: Match | _: Label | _: Begin | _: TryBlock | _: Define | _: HandleBlock => this + + def mapPath(f: Path => Path): Block = this match + case Return(res: Path, implct) => Return(f(res), implct) + case Throw(exc: Path) => Throw(f(exc)) + case Assign(l, r: Path, rst) => Assign(l, f(r), rst) + case b @ AssignField(l, n, r: Path, rst) => AssignField(l, n, f(r), rst)(b.symbol) + case Match(scrut: Path, arms, dflt, rst) => Match(f(scrut), arms, dflt, rst) + case Define(ValDefn(owner, k, sym, rhs: Path), rst) => Define(ValDefn(owner, k, sym, f(rhs)), rst) + case _: Return | _: Throw | _: Assign | _: AssignField | _: End | _: Break | _: Continue | _: Match | _: Label | _: Begin | _: TryBlock | _: Define | _: HandleBlock => this + + def mapValue(f: Value => Value): Block = + def go(p: Path): Path = p match + case sel: Select => Select(go(sel.qual), sel.name)(sel.symbol) + case v: Value => f(v) + this.mapPath(go) + // TODO conserve if no changes def mapTail(f: BlockTail => BlockTail): Block = this match case b: BlockTail => f(b) case Begin(sub, rst) => Begin(sub, rst.mapTail(f)) case Assign(lhs, rhs, rst) => Assign(lhs, rhs, rst.mapTail(f)) case Define(defn, rst) => Define(defn, rst.mapTail(f)) - case HandleBlock(lhs, res, handlers, body, rest) => - HandleBlock(lhs, res, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, h.body.mapTail(f))), body.mapTail(f), rest.mapTail(f)) + case HandleBlock(lhs, resIn, resOut, handlers, body, rest) => + HandleBlock(lhs, resIn, resOut, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, h.body.mapTail(f))), body.mapTail(f), rest.mapTail(f)) case Match(scrut, arms, dflt, rst) => Match(scrut, arms.map(_ -> _.mapTail(f)), dflt.map(_.mapTail(f)), rst.mapTail(f)) @@ -81,11 +115,11 @@ case class TryBlock(sub: Block, finallyDo: Block, rest: Block) extends Block wit case class Assign(lhs: Local, rhs: Result, rest: Block) extends Block with ProductWithTail // case class Assign(lhs: Path, rhs: Result, rest: Block) extends Block with ProductWithTail -case class AssignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(symbol: Opt[FieldSymbol]) extends Block with ProductWithTail +case class AssignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(val symbol: Opt[FieldSymbol]) extends Block with ProductWithTail case class Define(defn: Defn, rest: Block) extends Block with ProductWithTail -case class HandleBlock(lhs: Local, res: Local, handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail +case class HandleBlock(lhs: Local, resIn: Local, resOut: Local, handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail sealed abstract class Defn: val sym: MemberSymbol[?] @@ -106,6 +140,7 @@ final case class ValDefn( final case class ClsLikeDefn( sym: MemberSymbol[? <: ClassLikeDef], k: syntax.ClsLikeKind, + parentSym: Opt[Path], methods: Ls[FunDefn], privateFields: Ls[TermSymbol], publicFields: Ls[TermDefinition], diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala new file mode 100644 index 0000000000..a8c4258428 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -0,0 +1,254 @@ +package hkmc2 +package codegen + +import mlscript.utils.*, shorthands.* +import utils.* + +import hkmc2.Message.MessageContext + +import semantics.Elaborator.State + +import syntax.{Literal, Tree} +import semantics.* + +object HandlerLowering: + + private val pcIdent: Tree.Ident = Tree.Ident("pc") + private val nextIdent: Tree.Ident = Tree.Ident("next") + private val tailIdent: Tree.Ident = Tree.Ident("tail") + private val handlerIdent: Tree.Ident = Tree.Ident("handler") + private val handlerFunIdent: Tree.Ident = Tree.Ident("handlerFun") + + extension (k: Block => Block) + + def chain(other: Block => Block): Block => Block = b => k(other(b)) + def rest(b: Block): Block = k(b) + def transform(f: (Block => Block) => (Block => Block)) = f(k) + + def assign(l: Local, r: Result) = k.chain(Assign(l, r, _)) + def assignFieldN(lhs: Path, nme: Tree.Ident, rhs: Result) = k.chain(AssignField(lhs, nme, rhs, _)(N)) + def break(l: Local): Block = k.rest(Break(l)) + def continue(l: Local): Block = k.rest(Continue(l)) + def define(defn: Defn) = k.chain(Define(defn, _)) + def end() = k.rest(End()) + def ifthen(scrut: Path, cse: Case, trm: Block): Block => Block = k.chain(Match(scrut, cse -> trm :: Nil, N, _)) + def label(label: Local, body: Block) = k.chain(Label(label, body, _)) + def ret(r: Result) = k.rest(Return(r, false)) + def staticif(b: Boolean, f: (Block => Block) => (Block => Block)) = if b then k.transform(f) else k + + private def blockBuilder: Block => Block = identity + + extension (p: Path) + def selN(id: Tree.Ident) = Select(p, id)(N) + def pc = p.selN(pcIdent) + def next = p.selN(nextIdent) + def tail = p.selN(tailIdent) + def value = p.selN(Tree.Ident("value")) + def asArg = Arg(false, p) + + extension (l: Local) + def asPath: Path = Value.Ref(l) + + private case class LinkState(res: Path, cls: Path, uid: StateId) + + private case class HandlerCtx(isHandleFree: Bool, isTopLevel: Bool, linkAndHandle: LinkState => Block) + + type StateId = BigInt + +import HandlerLowering.* + +class HandlerLowering(using TL, Raise, Elaborator.State): + + private val functionHandlerCtx = HandlerCtx(true, false, state => + val tmp = freshTmp() + blockBuilder + .assignFieldN(state.res.tail, nextIdent, Instantiate(state.cls, Value.Lit(Tree.IntLit(state.uid)) :: Nil)) + .assignFieldN(state.res, tailIdent, state.res.tail.next) + .ret(state.res) + ) + private def handlerCtx(using HandlerCtx): HandlerCtx = summon + private val contClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__Cont")).selN(Tree.Ident("class")) + private val retClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__Return")).selN(Tree.Ident("class")) + private val handleEffectFun: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__handleEffect")) + private val mapPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Map")) + private val dummyClsSym = ClassSymbol( + Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), + Tree.Ident("Dummy") + ) + + private def freshTmp(dbgNme: Str = "tmp") = new TempSymbol(N, dbgNme) + + private def rtThrowMsg(msg: Str) = Throw( + Instantiate(State.globalThisSymbol.asPath.selN(Tree.Ident("Error")), + Value.Lit(Tree.StrLit(msg)) :: Nil) + ) + + object SimpleCall: + def apply(fun: Path, args: List[Path]) = Call(fun, args.map(Arg(false, _)))(true) + def unapply(res: Result) = res match + case Call(fun, args) => args.foldRight[Opt[List[Path]]](S(Nil))((arg, acc) => acc.flatMap(acc => arg match + case Arg(false, p) => S(p :: acc) + case _ => N + )).map((fun, _)) + case _ => N + + object ResumptionPoint: + private val resumptionSymbol = freshTmp("resumptionPoint") + def apply(res: Local, uid: StateId, rest: Block) = + Assign(res, SimpleCall(Value.Ref(resumptionSymbol), List(Value.Lit(Tree.IntLit(uid)))), rest) + def unapply(blk: Block) = blk match + case Assign(res, SimpleCall(Value.Ref(`resumptionSymbol`), List(Value.Lit(Tree.IntLit(uid)))), rest) => + Some(res, uid, rest) + case _ => None + + object CallPlaceholder: + private val callSymbol = freshTmp("callPlaceholder") + def apply(res: Local, uid: StateId, canRet: Bool, c: Call, rest: Block) = + Assign(res, SimpleCall(Value.Ref(callSymbol), List(Value.Lit(Tree.IntLit(uid)), Value.Lit(Tree.BoolLit(canRet)))), Assign(res, c, rest)) + def unapply(blk: Block) = blk match + case Assign(res, SimpleCall(Value.Ref(`callSymbol`), List(Value.Lit(Tree.IntLit(uid)), Value.Lit(Tree.BoolLit(canRet)))), Assign(_, c: Call, rest)) => + Some(res, uid, canRet, c, rest) + case _ => None + + private class FreshId: + var id: Int = 0 + def apply() = + val tmp = id + id += 1 + tmp + private val freshId = FreshId() + + /** + * 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 + */ + + private def translateBlock(b: Block, h: HandlerCtx): Block = + given HandlerCtx = h + val stage1 = firstPass(b) + secondPass(stage1) + + private def firstPass(b: Block)(using HandlerCtx): Block = + b.map(firstPass) match + case b: HandleBlock => translateHandleBlock(b) + case b => b.mapValue { + case Value.Lam(params, body) => Value.Lam(params, translateBlock(body, functionHandlerCtx)) + case v => v + } match { + case Return(c: Call, implct) if handlerCtx.isHandleFree => Return(c, implct) + case b => b.mapResult { + case r @ Call(Value.Ref(_: BuiltinSymbol), _) => N + case c: Call => + val res = freshTmp("res") + S(k => CallPlaceholder(res, freshId(), false, c, k(Value.Ref(res)))) + case r => N + } + } match + case Define(f: FunDefn, rst) => Define(translateFun(f), rst) + case Define(c: ClsLikeDefn, rst) => Define(translateCls(c), rst) + case b => b + + private def secondPass(b: Block)(using HandlerCtx): Block = + val cls = genContClass(b) + Define(cls, genNormalBody(b, BlockMemberSymbol(cls.sym.nme, Nil))) + + private def translateFun(f: FunDefn): FunDefn = + FunDefn(f.sym, f.params, translateBlock(f.body, functionHandlerCtx)) + + private def translateCls(cls: ClsLikeDefn): ClsLikeDefn = + cls.copy(methods = cls.methods.map(translateFun), ctor = translateBlock(cls.ctor, functionHandlerCtx)) + + // Handle block becomes a FunDefn and CallPlaceholder + private def translateHandleBlock(h: HandleBlock): Block = + val sym = BlockMemberSymbol(s"handleBlock$$${freshId()}", Nil) + val lbl = freshTmp("handlerBody") + val lblLoop = freshTmp("handlerLoop") + def replaceRet(b: Block): Block = b.map(replaceRet) match + case Return(res, implct) => + // In the case of res is effectful, it will be handled in translateBlock + Assign(h.resIn, res, Return(Instantiate(retClsPath, h.resIn.asPath :: Nil), implct)) + case b => b + val handlerBody = translateBlock(replaceRet(h.body), HandlerCtx(false, false, state => blockBuilder + .assignFieldN(state.res.tail, nextIdent, Instantiate(state.cls, Value.Lit(Tree.IntLit(state.uid)) :: Nil)) + .assignFieldN(state.res, tailIdent, state.res.tail.next) + .assign(h.resIn, state.res) + .break(lbl))) + + def getBinaryBuiltin(nme: Str) = BuiltinSymbol(nme, true, false, false) + val equalBuiltin = getBinaryBuiltin("===").asPath + + val tmp = freshTmp() + val handlerTailList = freshTmp("handlerTailList") + val unchanged = SimpleCall(equalBuiltin, tmp.asPath :: h.resIn.asPath :: Nil) + val handlerLoop = blockBuilder + .ifthen(h.resIn.asPath, Case.Cls(dummyClsSym, retClsPath), Return(h.resIn.asPath, false)) + // TODO: this should be EffectSig + .ifthen(h.resIn.asPath, Case.Cls(dummyClsSym, contClsPath), blockBuilder + .assign(tmp, h.resIn.asPath) + .assign(h.resIn, SimpleCall(handleEffectFun, h.resIn.asPath :: h.lhs.asPath :: handlerTailList.asPath :: Nil)) + .assign(tmp, unchanged) + .ifthen(tmp.asPath, Case.Lit(Tree.BoolLit(true)), rtThrowMsg("Nested effects not implemented")) + .continue(lblLoop) + ) + .break(lblLoop) + val cur: Block => Block = h.handlers.foldLeft(blockBuilder)((builder, handler) => + val lam = Value.Lam(PlainParamList(Param(FldFlags.empty, handler.resumeSym, N) :: Nil), translateBlock(handler.body, functionHandlerCtx)) + val tmp = freshTmp() + builder.define(FunDefn(handler.sym, handler.params, blockBuilder + .assign(tmp, Instantiate(contClsPath, Nil)) + .assignFieldN(tmp.asPath, tailIdent, tmp.asPath) + .assignFieldN(tmp.asPath, handlerIdent, h.lhs.asPath) + .assignFieldN(tmp.asPath, handlerFunIdent, lam) + .ret(tmp.asPath)))) + val body = h.handlers.foldLeft(cur)((builder, handler) => builder.assignFieldN(h.lhs.asPath, Tree.Ident(handler.sym.nme), handler.sym.asPath)) + .label(lbl, handlerBody) + .assign(handlerTailList, Instantiate(contClsPath, Nil)) + .label(lblLoop, handlerLoop) + .ret(h.resIn.asPath) + // TODO: fix handler loop, resume handlerTailList in __handleEffect + // TODO: implement special continuation class for nested effects + val defn = FunDefn(sym, PlainParamList(Nil) :: Nil, body) + val result = Define(defn, CallPlaceholder(h.resOut, freshId(), true, Call(sym.asPath, Nil)(true), h.rest)) + result + + private def genContClass(b: Block): ClsLikeDefn = + val sym = ClassSymbol( + Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), + Tree.Ident("Cont$" + State.suid.nextUid) + ) + sym.defn = S(ClassDef(N, syntax.Cls, sym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))))) + def removeDefn(b: Block): Block = + b.map(removeDefn) match + case Define(_: (ClsLikeDefn | FunDefn), rst) => rst + case b => b + val actualBlock = removeDefn(b) + // TODO: generate resume function using actualBlock + ClsLikeDefn(sym, syntax.Cls, S(contClsPath), FunDefn(BlockMemberSymbol("resume", Nil), + PlainParamList(Param(FldFlags.empty, VarSymbol(Tree.Ident("value$")), N) :: Nil) :: Nil, rtThrowMsg("Resume not implemented")) :: Nil, Nil, Nil, End()) + + private def genNormalBody(b: Block, clsSym: BlockMemberSymbol)(using HandlerCtx): Block = + val tmp = freshTmp("cont") + b.map(genNormalBody(_, clsSym)) match + case CallPlaceholder(res, uid, canRet, c, rest) => + blockBuilder + .assign(res, c) + .ifthen( + res.asPath, + Case.Cls(dummyClsSym, contClsPath), + handlerCtx.linkAndHandle(LinkState(res.asPath, clsSym.asPath, uid)) + ) + .staticif(canRet && !handlerCtx.isTopLevel, _.ifthen( + res.asPath, + Case.Cls(dummyClsSym, retClsPath), + blockBuilder.ret(if handlerCtx.isHandleFree then res.asPath.value else res.asPath) + )) + .rest(rest) + case b => b + + def translateTopLevel(b: Block): Block = + // TODO: we should skip continuation class on top level + translateBlock(b, HandlerCtx(true, true, _ => rtThrowMsg("Unhandled effects"))) + diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 360c26e771..ffca6d8598 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -129,7 +129,7 @@ class Lowering(using TL, Raise, Elaborator.State): case s => R(s) val publicFlds = rest2.collect: case td @ TermDefinition(k = (_: syntax.Val)) => td - Define(ClsLikeDefn(cls.sym, syntax.Cls, + Define(ClsLikeDefn(cls.sym, syntax.Cls, N, mtds.flatMap: td => td.body.map: bod => val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme)) @@ -471,9 +471,15 @@ trait LoweringHandler val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme)) S(Handler(td.sym, resumeSym, paramLists, bodyBlock)) }.collect{ case Some(v) => v } - val resSym = TempSymbol(S(t)) - HandleBlock(lhs, resSym, handlers, term(st.Blk(stmts, res))(res => Assign(resSym, res, End())), k(Value.Ref(resSym))) + val resInSym = TempSymbol(S(t), "cur") + val resOutSym = TempSymbol(S(t)) + subTerm(rhs): cls => + Assign( + lhs, + Instantiate(cls, Nil), + HandleBlock(lhs, resInSym, resOutSym, handlers, term(st.Blk(stmts, res))(res => Assign(resInSym, res, End())), k(Value.Ref(resOutSym))) + ) case _ => super.term(t)(k) - override def program(main: st): Program = - if !instrument then return super.program(main) - super.program(main) + override def topLevel(t: st): Block = + if !instrument then return super.topLevel(t) + HandlerLowering().translateTopLevel(super.topLevel(t)) 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 c1c94cbd76..54b818ab4d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -155,7 +155,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: Return(Lam(ps, block), false) val (params, bodyDoc) = setupFunction(some(sym.nme), ps, result) doc"function ${sym.nme}($params) { #{ # ${bodyDoc} #} # }" - case ClsLikeDefn(sym, syntax.Cls, mtds, privFlds, _pubFlds, ctor) => + case ClsLikeDefn(sym, syntax.Cls, parentSym, mtds, privFlds, _pubFlds, ctor) => // * Note: `_pubFlds` is not used because in JS, fields are not declared val clsDefn = sym.defn.getOrElse(die) val clsParams = clsDefn.paramsOpt.fold(Nil)(_.paramSyms) @@ -163,11 +163,11 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: val ctorCode = ctorParams.foldRight(body(ctor)): case ((sym, nme), acc) => doc"this.${sym.name} = $nme; # ${acc}" - val clsJS = doc"class ${sym.nme} { #{ ${ + val clsJS = doc"class ${sym.nme}${parentSym.map(p => s" extends ${result(p)}").getOrElse("")} { #{ ${ privFlds.map(f => doc" # #${f.nme};").mkDocument(doc"") } # constructor(${ ctorParams.unzip._2.mkDocument(", ") - }) { #{ # ${ + }) { #{ ${if parentSym.isDefined then doc" # super()" else ""} # ${ ctorCode.stripBreaks } #} # }${ mtds.map: @@ -219,7 +219,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: doc"${ths}.${sym.nme} = ${clsJS};" case N => fun match - case S(f) => doc"${f}; # ${sym.nme}.class = ${clsJS};" + case S(f) => doc"${f} # ${sym.nme}.class = ${clsJS};" case N => clsJS thisProxy match case S(proxy) if !scope.thisProxyDefined => @@ -351,7 +351,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: object JSBuilder: import scala.util.matching.Regex - private val identifierPattern: Regex = "^[A-Za-z$][A-Za-z0-9$]*$".r + private val identifierPattern: Regex = "^[A-Za-z_$][A-Za-z0-9_$]*$".r def isValidIdentifier(s: Str): Bool = identifierPattern.matches(s) && !keywords.contains(s) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 3ac3ffeb4b..2ed1fdea2e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -22,7 +22,7 @@ object Elaborator: ",", "+", "-", "*", "/", "%", "==", "!=", "<", "<=", ">", ">=", - "===", + "===", "!==", "&&", "||") private val unaryOps = Set("-", "+", "!", "~") private val aliasOps = Map( diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index d478295a65..84c362f949 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -67,6 +67,23 @@ const Predef$class = class Predef { } toString() { return "Test"; } }; + this.__Cont = function __Cont(next1, resumed1) { return new __Cont.class(next1, resumed1); }; + this.__Cont.class = class __Cont { + constructor(next, resumed) { + this.next = next; + this.resumed = resumed; + + } + toString() { return "__Cont(" + this.next + ", " + this.resumed + ")"; } + }; + this.__Return = function __Return(value1) { return new __Return.class(value1); }; + this.__Return.class = class __Return { + constructor(value) { + this.value = value; + + } + toString() { return "__Return(" + this.value + ")"; } + }; } id(x) { return x; @@ -137,6 +154,106 @@ const Predef$class = class Predef { } else { return null; } + } + __handleEffect(cur, handler, handlerTailList) { + let handlerCont, scrut, scrut1, savedNext, scrut2, savedNext1, scrut3, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + handlerCont = cur.nextHandler; + tmp8: while (true) { + if (handlerCont instanceof this.__Cont.class) { + scrut = handlerCont.nextHandler !== cur.handler; + if (scrut) { + handlerCont = handlerCont.nextHandler; + tmp = null; + continue tmp8; + } else { + tmp = null; + } + } else { + tmp = null; + } + break; + } + if (handlerCont) { + savedNext1 = handlerCont.next; + tmp1 = this.__resume(cur, handlerCont, handlerCont); + tmp2 = cur.handlerFun(tmp1) ?? null; + cur = tmp2; + scrut3 = savedNext1 !== handlerCont.next; + if (scrut3) { + handlerCont.next.next = savedNext1; + tmp3 = null; + } else { + tmp3 = null; + } + if (cur instanceof this.__Cont.class) { + return cur; + } else { + if (cur instanceof this.__Return.class) { + return cur; + } else { + tmp4 = this.__resume(handlerCont, undefined, undefined); + return tmp4(cur) ?? null; + } + } + } else { + scrut1 = handler === cur.handler; + if (scrut1) { + savedNext = handlerTailList.next; + tmp5 = this.__resume(cur, handlerTailList, handlerCont); + tmp6 = cur.handlerFun(tmp5) ?? null; + cur = tmp6; + scrut2 = savedNext !== handlerTailList.next; + if (scrut2) { + handlerTailList.next.next = savedNext; + tmp7 = null; + } else { + tmp7 = null; + } + return cur; + } else { + return cur; + } + } + } + __resume(cur1, tail, handlerCont) { + return (value) => { + let nextHandler, cont, scrut, tmp, tmp1, tmp2, tmp3; + nextHandler = cur1.nextHandler; + cont = cur1.next; + tmp4: while (true) { + if (cont instanceof this.__Cont.class) { + tmp = cont.resume(value) ?? null; + value = tmp; + if (value instanceof this.__Cont.class) { + value.tail = tail; + value.tailHandler.nextHandler = nextHandler; + return value; + } else { + if (value instanceof this.__Return.class) { + return value; + } else { + cont = cont.next; + tmp1 = null; + } + tmp2 = tmp1; + } + tmp3 = tmp2; + continue tmp4; + } else { + scrut = nextHandler !== handlerCont; + if (scrut) { + cont = nextHandler.next; + nextHandler = nextHandler.nextHandler; + tmp3 = null; + continue tmp4; + } else { + tmp3 = value; + } + } + break; + } + return tmp3; + }; } toString() { return "Predef"; } }; const Predef = new Predef$class; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 5ba9be84fb..c78722b157 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -60,3 +60,55 @@ module TraceLogger with class Test with val y = 1 +// Private definitions for algebraic effects + +abstract class __Cont(next, resumed) with + fun resume(value) + +class __Return(value) + +fun __handleEffect(cur, handler, handlerTailList) = + let handlerCont = cur.nextHandler + while handlerCont is __Cont and handlerCont.nextHandler !== cur.handler then + set handlerCont = handlerCont.nextHandler + else () + if handlerCont then + let savedNext = handlerCont.next + set cur = cur.handlerFun(__resume(cur, handlerCont, handlerCont)) + if savedNext !== handlerCont.next then + set handlerCont.next.next = savedNext + else () + if cur is __Cont then + cur + else if cur is __Return then + cur + else + __resume(handlerCont, undefined, undefined)(cur) + else if handler === cur.handler then + let savedNext = handlerTailList.next + set cur = cur.handlerFun(__resume(cur, handlerTailList, handlerCont)) + if savedNext !== handlerTailList.next then + set handlerTailList.next.next = savedNext + else () + cur + else cur + +fun __resume(cur, tail, handlerCont)(value) = + let nextHandler = cur.nextHandler + let cont = cur.next + while + cont is __Cont then + value = cont.resume(value) + if value is __Cont then + set value.tail = tail + set value.tailHandler.nextHandler = nextHandler + return value + else if value is __Return then + return value + else + set cont = cont.next + nextHandler !== handlerCont then + set cont = nextHandler.next + set nextHandler = nextHandler.nextHandler + else value + diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls index 185b4f5fc0..1190a8c65c 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls @@ -35,7 +35,7 @@ fun test(x) = //│ globalThis.Predef.checkArgs("test", 1, true, args.length); //│ let x = args[0]; //│ let tmp; -//│ function Foo(...args1) { return new Foo.class(...args1); }; +//│ function Foo(...args1) { return new Foo.class(...args1); } //│ Foo.class = class Foo { //│ constructor(a, b) { //│ this.a = a; diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls index 2096e0265d..9a4a407c7d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls @@ -25,7 +25,7 @@ fun test(a) = //│ function test(...args) { //│ globalThis.Predef.checkArgs("test", 1, true, args.length); //│ let a = args[0]; -//│ function Inner(...args1) { return new Inner.class(...args1); }; +//│ function Inner(...args1) { return new Inner.class(...args1); } //│ Inner.class = class Inner { //│ constructor(b) { //│ this.b = b; @@ -87,7 +87,7 @@ fun test(a) = //│ globalThis.Predef.checkArgs("test", 1, true, args.length); //│ let a = args[0]; //│ let tmp, tmp1; -//│ function C1(...args1) { return new C1.class(...args1); }; +//│ function C1(...args1) { return new C1.class(...args1); } //│ C1.class = class C1 { //│ constructor(b) { //│ this.b = b; @@ -98,7 +98,7 @@ fun test(a) = //│ } //│ toString() { return "C1(" + this.b + ")"; } //│ }; -//│ function C2(...args1) { return new C2.class(...args1); }; +//│ function C2(...args1) { return new C2.class(...args1); } //│ C2.class = class C2 { //│ constructor(b) { //│ this.b = b; diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index db1005d446..55b2b42042 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -4,14 +4,13 @@ abstract class Effect with fun perform(arg: Str): Str :ge -let r = handle h = Effect with +handle h = Effect with fun perform(arg, k) = k(arg) in h.perform("k") -r //│ ╔══[COMPILATION ERROR] Effect handlers are not enabled -//│ ║ l.7: let r = handle h = Effect with -//│ ║ ^^^^^^^^^^^ +//│ ║ l.7: handle h = Effect with +//│ ║ ^^^^^^^^^^^ //│ ║ l.8: fun perform(arg, k) = k(arg) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of null (reading 'perform') @@ -19,10 +18,23 @@ r :global :handler +:expect 'b' +handle h = Effect with + fun perform(arg, k) = "b" +in + h.perform("k") +//│ = 'b' + +:expect 'k' +handle h = Effect with + fun perform(arg, k) = arg +in + h.perform("k") +//│ = 'k' + :fixme -let r = handle h = Effect with +handle h = Effect with fun perform(arg, k) = k(arg) in h.perform("k") -r -//│ /!!!\ Uncaught error: scala.MatchError: HandleBlock(globalThis:block#3.h,$tmp,List(Handler(member:perform,k,List(ParamList(‹›,List(Param(‹›,arg,None)),None)),Return(Call(Ref(k),List(Arg(false,Ref(arg)))),false))),Assign($tmp,Call(Select(Ref(globalThis:block#3.h),Ident(perform)),List(Arg(false,Lit(StrLit(k))))),End()),Assign(globalThis:block#3.r,Ref($tmp),Return(Ref(globalThis:block#3.r),true))) (of class hkmc2.codegen.HandleBlock) +//│ ═══[RUNTIME ERROR] Error: Resume not implemented From 76695ba3a3e6372f0c5bd8f77c269b5840dcc928 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 17 Dec 2024 02:54:24 +0800 Subject: [PATCH 010/114] Continuation class transformation Co-authored-by: Mark Ng <55091936+CAG2Mark@users.noreply.github.com> --- .../src/main/scala/hkmc2/codegen/Block.scala | 6 +- .../scala/hkmc2/codegen/HandlerLowering.scala | 316 +++++++++++++++++- .../scala/hkmc2/codegen/js/JSBuilder.scala | 2 +- .../src/test/mlscript/handlers/Effects.mls | 213 +++++++++++- 4 files changed, 508 insertions(+), 29 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 5c3684a5a6..bf5160ec52 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -66,7 +66,11 @@ sealed abstract class Block extends Product with AutoLocated: case b @ AssignField(l, n, r: Path, rst) => AssignField(l, n, f(r), rst)(b.symbol) case Match(scrut: Path, arms, dflt, rst) => Match(f(scrut), arms, dflt, rst) case Define(ValDefn(owner, k, sym, rhs: Path), rst) => Define(ValDefn(owner, k, sym, f(rhs)), rst) - case _: Return | _: Throw | _: Assign | _: AssignField | _: End | _: Break | _: Continue | _: Match | _: Label | _: Begin | _: TryBlock | _: Define | _: HandleBlock => this + case _: Return | _: Throw | _: Assign | _: AssignField | _: End | _: Break | _: Continue | _: Match | _: Label | _: Begin | _: TryBlock | _: Define | _: HandleBlock => this.mapResult { + case r @ Call(fun, args) => S(_(Call(f(fun), args.map(arg => Arg(arg.spread, f(arg.value))))(r.isMlsFun))) + case r @ Instantiate(cls, args) => S(_(Instantiate(f(cls), args.map(f)))) + case r => N + } def mapValue(f: Value => Value): Block = def go(p: Path): Path = p match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index a8c4258428..eaa8d93601 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -8,14 +8,16 @@ import hkmc2.Message.MessageContext import semantics.Elaborator.State -import syntax.{Literal, Tree} +import syntax.{Literal, Tree, ParamBind} import semantics.* +import scala.annotation.tailrec object HandlerLowering: private val pcIdent: Tree.Ident = Tree.Ident("pc") private val nextIdent: Tree.Ident = Tree.Ident("next") private val tailIdent: Tree.Ident = Tree.Ident("tail") + private val tailHandlerIdent: Tree.Ident = Tree.Ident("tailHandler") private val handlerIdent: Tree.Ident = Tree.Ident("handler") private val handlerFunIdent: Tree.Ident = Tree.Ident("handlerFun") @@ -62,7 +64,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State): private val functionHandlerCtx = HandlerCtx(true, false, state => val tmp = freshTmp() blockBuilder - .assignFieldN(state.res.tail, nextIdent, Instantiate(state.cls, Value.Lit(Tree.IntLit(state.uid)) :: Nil)) + .assignFieldN(state.res.tail, nextIdent, Instantiate(state.cls.selN(Tree.Ident("class")), Value.Lit(Tree.IntLit(state.uid)) :: Nil)) .assignFieldN(state.res, tailIdent, state.res.tail.next) .ret(state.res) ) @@ -101,6 +103,15 @@ class HandlerLowering(using TL, Raise, Elaborator.State): Some(res, uid, rest) case _ => None + object ReturnCont: + private val returnContSymbol = freshTmp("returnCont") + def apply(res: Local, uid: StateId) = + Assign(res, SimpleCall(Value.Ref(returnContSymbol), List(Value.Lit(Tree.IntLit(uid)))), End("")) + def unapply(blk: Block) = blk match + case Assign(res, SimpleCall(Value.Ref(`returnContSymbol`), List(Value.Lit(Tree.IntLit(uid)))), _) => + Some(res, uid) + case _ => None + object CallPlaceholder: private val callSymbol = freshTmp("callPlaceholder") def apply(res: Local, uid: StateId, canRet: Bool, c: Call, rest: Block) = @@ -110,6 +121,21 @@ class HandlerLowering(using TL, Raise, Elaborator.State): Some(res, uid, canRet, c, rest) case _ => None + object StateTransition: + private val transitionSymbol = freshTmp("transition") + def apply(uid: StateId) = Return(SimpleCall(Value.Ref(transitionSymbol), List(Value.Lit(Tree.IntLit(uid)))), false) + def unapply(blk: Block) = blk match + case Return(SimpleCall(Value.Ref(`transitionSymbol`), List(Value.Lit(Tree.IntLit(uid)))), false) => + S(uid) + case _ => N + + object FnEnd: + private val fnEndSymbol = freshTmp("fnEnd") + def apply() = Return(SimpleCall(Value.Ref(fnEndSymbol), Nil), false) + def unapply(blk: Block) = blk match + case Return(SimpleCall(Value.Ref(`fnEndSymbol`), Nil), false) => true + case _ => false + private class FreshId: var id: Int = 0 def apply() = @@ -118,6 +144,164 @@ class HandlerLowering(using TL, Raise, Elaborator.State): 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 + class BlockState(val id: StateId, val blk: Block, val sym: Opt[Local]) + + def partitionBlock(blk: Block, labelIds: Map[Symbol, (StateId, StateId)] = Map.empty): Ls[BlockState] = + // for readability :) + case class PartRet(head: Block, states: Ls[BlockState]) + + // used to analyze whether to touch labels, currently unused. + val labelCallCache: scala.collection.mutable.Map[Symbol, Bool] = scala.collection.mutable.Map() + def containsCallRec(blk: Block): Bool = containsCall(blk) + @tailrec + def containsCall(blk: Block): Bool = blk match + case Match(scrut, arms, dflt, rest) => arms.find((_, blk) => containsCallRec(blk)).isDefined || containsCall(rest) + case Return(c: Call, implct) => true + case Return(_, _) => false + case Throw(c: Call) => true + case Throw(_) => false + case l @ Label(label, body, rest) => + labelBodyHasCall(l) || containsCall(rest) + case Break(label) => false + case Continue(label) => false + case Begin(sub, rest) => containsCallRec(sub) || containsCall(rest) + case TryBlock(sub, finallyDo, rest) => containsCallRec(sub) || containsCallRec(finallyDo) || containsCall(rest) + case Assign(lhs, c: Call, rest) => true + case Assign(_, _, rest) => containsCall(rest) + case AssignField(lhs, nme, c: Call, rest) => true + case AssignField(_, _, _, rest) => containsCall(rest) + case Define(defn, rest) => containsCall(rest) + case End(msg) => false + case _: HandleBlock => die // already translated at this point + + def labelBodyHasCall(blk: Label) = + val Label(label, body, rest) = blk + labelCallCache.get(label) match + case N => + val res = containsCallRec(body) + labelCallCache.addOne(label -> res) + res + case S(value) => + value + + // returns (truncated input block, child block states) + // TODO: don't split within Match, Begin and Labels when not needed, ideally keep it intact. Need careful analysis for this + // blk: The block to transform + // labelIds: maps label IDs to the state at the start of the label and the state after the label + // jumpTo: what state End should jump to, if at all + // freshState: uid generator + def go(blk: Block)(implicit labelIds: Map[Symbol, (StateId, StateId)], afterEnd: Option[StateId]): PartRet = blk match + case ResumptionPoint(result, uid, rest) => + 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)(afterEnd = S(restId)))) + val dfltParts = dflt.map(blkk => go(blkk)(afterEnd = S(restId))) + + + val states_ = restParts.states ::: armsParts.flatMap(_._2.states) + val states = dfltParts match + case N => states_ + case S(value) => value.states ::: states_ + + val newArms = armsParts.map((cse, partRet) => (cse, partRet.head)) + + restParts.head match + case StateTransition(_) => + PartRet( + Match(scrut, newArms, dfltParts.map(_.head), StateTransition(restId)), + states + ) + case _ => + PartRet( + Match(scrut, newArms, dfltParts.map(_.head), StateTransition(restId)), + BlockState(restId, restParts.head, N) :: states + ) + case l @ Label(label, body, rest) => + val startId = freshId() // start of body + + val PartRet(restNew, restParts) = go(rest) + + val endId: StateId = restNew match // start of rest + case StateTransition(uid) => uid + case _ => freshId() + + val PartRet(bodyNew, parts) = go(body)(using labelIds + (label -> (startId, endId)), S(endId)) + + restNew match + case StateTransition(_) => + PartRet( + StateTransition(startId), + BlockState(startId, bodyNew, N) :: parts ::: restParts + ) + case _ => + PartRet( + StateTransition(startId), + BlockState(startId, bodyNew, N) :: BlockState(endId, restNew, N) :: parts ::: restParts + ) + + case Break(label) => + val (start, end) = labelIds.get(label) match + case N => raise(ErrorReport( + msg"Could not find label '${label.nme}'" -> + label.toLoc :: Nil, + source = Diagnostic.Source.Compilation)) + return PartRet(blk, Nil) + case S(value) => value + PartRet(StateTransition(end), Nil) + case Continue(label) => + val (start, end) = labelIds.get(label) match + case N => raise(ErrorReport( + msg"Could not find label '${label.nme}'" -> + label.toLoc :: Nil, + source = Diagnostic.Source.Compilation)) + return PartRet(blk, Nil) + case S(value) => value + PartRet(StateTransition(start), Nil) + + case Begin(sub, rest) => + val PartRet(restNew, restParts) = go(rest) + restNew match + case StateTransition(uid) => + val PartRet(subNew, subParts) = go(sub)(afterEnd = S(uid)) + PartRet(subNew, subParts ::: restParts) + case _ => + val restId = freshId() + val PartRet(subNew, subParts) = go(sub)(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) + case End(_) | Return(Value.Lit(Tree.UnitLit(true)), true) => afterEnd match + case None => PartRet(FnEnd(), Nil) + case Some(value) => PartRet(StateTransition(value), Nil) + // 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(blk.map(_ => head), parts) + case Return(_, _) => PartRet(blk, Nil) + // ignored cases + case TryBlock(sub, finallyDo, rest) => ??? // ignore + case Throw(_) => PartRet(blk, Nil) + + val headId = freshId() + + val result = go(blk)(using labelIds, N) + BlockState(headId, result.head, N) :: result.states + /** * The actual translation: * 1. add call markers, transform class, function, lambda and sub handler blocks @@ -152,8 +336,10 @@ class HandlerLowering(using TL, Raise, Elaborator.State): case b => b private def secondPass(b: Block)(using HandlerCtx): Block = - val cls = genContClass(b) - Define(cls, genNormalBody(b, BlockMemberSymbol(cls.sym.nme, Nil))) + val cls = if handlerCtx.isTopLevel then N else genContClass(b) + cls match + case None => genNormalBody(b, BlockMemberSymbol("", Nil)) + case Some(cls) => Define(cls, genNormalBody(b, BlockMemberSymbol(cls.sym.nme, Nil))) private def translateFun(f: FunDefn): FunDefn = FunDefn(f.sym, f.params, translateBlock(f.body, functionHandlerCtx)) @@ -166,12 +352,17 @@ class HandlerLowering(using TL, Raise, Elaborator.State): val sym = BlockMemberSymbol(s"handleBlock$$${freshId()}", Nil) val lbl = freshTmp("handlerBody") val lblLoop = freshTmp("handlerLoop") - def replaceRet(b: Block): Block = b.map(replaceRet) match - case Return(res, implct) => - // In the case of res is effectful, it will be handled in translateBlock - Assign(h.resIn, res, Return(Instantiate(retClsPath, h.resIn.asPath :: Nil), implct)) - case b => b - val handlerBody = translateBlock(replaceRet(h.body), HandlerCtx(false, false, state => blockBuilder + def prepareBody(b: Block): Block = + def go(b: Block): Block = + b.map(go) match + case Return(res, implct) => + // In the case of res is effectful, it will be handled in translateBlock + Assign(h.resIn, res, Return(Instantiate(retClsPath, h.resIn.asPath :: Nil), implct)) + case Assign(h.resIn, rhs, End("")) => + Return(rhs, false) + case b => b + go(b) + val handlerBody = translateBlock(prepareBody(h.body), HandlerCtx(false, false, state => blockBuilder .assignFieldN(state.res.tail, nextIdent, Instantiate(state.cls, Value.Lit(Tree.IntLit(state.uid)) :: Nil)) .assignFieldN(state.res, tailIdent, state.res.tail.next) .assign(h.resIn, state.res) @@ -200,6 +391,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State): builder.define(FunDefn(handler.sym, handler.params, blockBuilder .assign(tmp, Instantiate(contClsPath, Nil)) .assignFieldN(tmp.asPath, tailIdent, tmp.asPath) + .assignFieldN(tmp.asPath, tailHandlerIdent, tmp.asPath) .assignFieldN(tmp.asPath, handlerIdent, h.lhs.asPath) .assignFieldN(tmp.asPath, handlerFunIdent, lam) .ret(tmp.asPath)))) @@ -214,20 +406,108 @@ class HandlerLowering(using TL, Raise, Elaborator.State): val result = Define(defn, CallPlaceholder(h.resOut, freshId(), true, Call(sym.asPath, Nil)(true), h.rest)) result - private def genContClass(b: Block): ClsLikeDefn = + private def genContClass(b: Block)(using HandlerCtx): Opt[ClsLikeDefn] = val sym = ClassSymbol( Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), Tree.Ident("Cont$" + State.suid.nextUid) ) - sym.defn = S(ClassDef(N, syntax.Cls, sym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))))) - def removeDefn(b: Block): Block = - b.map(removeDefn) match + val pcVar = VarSymbol(Tree.Ident("pc")) + sym.defn = S(ClassDef(N, syntax.Cls, sym, Nil, S(PlainParamList(Param(FldFlags.empty, pcVar, N) :: Nil)), ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))))) + + var trivial = true + def prepareBlock(b: Block): Block = + b.map(prepareBlock) match case Define(_: (ClsLikeDefn | FunDefn), rst) => rst + case CallPlaceholder(res, uid, canRet, c, rest) => + trivial = false + blockBuilder + .assign(res, c) + .ifthen( + res.asPath, + Case.Cls(dummyClsSym, contClsPath), + ReturnCont(res, uid) + ) + .staticif(canRet, _.ifthen( + res.asPath, + Case.Cls(dummyClsSym, retClsPath), + blockBuilder.ret(if handlerCtx.isHandleFree then res.asPath.value else res.asPath) + )) + .rest(ResumptionPoint(res, uid, rest)) case b => b - val actualBlock = removeDefn(b) - // TODO: generate resume function using actualBlock - ClsLikeDefn(sym, syntax.Cls, S(contClsPath), FunDefn(BlockMemberSymbol("resume", Nil), - PlainParamList(Param(FldFlags.empty, VarSymbol(Tree.Ident("value$")), N) :: Nil) :: Nil, rtThrowMsg("Resume not implemented")) :: Nil, Nil, Nil, End()) + val actualBlock = prepareBlock(b) + if trivial then return N + + val parts = partitionBlock(actualBlock) + val loopLbl = freshTmp("contLoop") + val pcSymbol = TermSymbol(ParamBind, S(sym), pcIdent) + + def transformPart(blk: Block): Block = + def f(blk: Block): Block = blk match + case ReturnCont(res, uid) => + blockBuilder + .assignFieldN(res.asPath.tail, nextIdent, sym.asPath) + .assign(pcSymbol, Value.Lit(Tree.IntLit(uid))) + .ret(res.asPath) + case StateTransition(uid) => + blockBuilder + .assign(pcSymbol, Value.Lit(Tree.IntLit(uid))) + .continue(loopLbl) + case FnEnd() => + blockBuilder.break(loopLbl) + case c => c.map(f) + f(blk) + + // HACK: remove once match is fixed + def unrollMatch(b: Match): Block = + val Match(scrut, arms, dflt, rest) = b + arms match + case head :: Nil => b + case head :: next => Match( + scrut, List(head), + S(unrollMatch(Match(scrut, next, dflt, End()))), + rest) + case Nil => rest + + // match block representing the function body + val mainMatchCases = parts.toList.map(b => (Case.Lit(Tree.IntLit(b.id)), transformPart(b.blk))) + val mainMatchBlk = unrollMatch(Match( + pcSymbol.asPath, + mainMatchCases, + N, + End() + )) + + val lbl = blockBuilder.label(loopLbl, mainMatchBlk).rest(End()) + + val resumedVal = VarSymbol(Tree.Ident("value$")) + + 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 resumeBody = + if assignedResumedCases.isEmpty then + lbl + else + unrollMatch(Match( + pcSymbol.asPath, + assignedResumedCases, + S(End()), + lbl + )) + + val resumeSym = BlockMemberSymbol("resume", List()) + val resumeFnDef = FunDefn( + resumeSym, + List(PlainParamList(List(Param(FldFlags.empty, resumedVal, N)))), + resumeBody + ) + + S(ClsLikeDefn(sym, syntax.Cls, S(contClsPath), resumeFnDef :: Nil, Nil, Nil, End())) private def genNormalBody(b: Block, clsSym: BlockMemberSymbol)(using HandlerCtx): Block = val tmp = freshTmp("cont") 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 54b818ab4d..14ad3f03ff 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -167,7 +167,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: privFlds.map(f => doc" # #${f.nme};").mkDocument(doc"") } # constructor(${ ctorParams.unzip._2.mkDocument(", ") - }) { #{ ${if parentSym.isDefined then doc" # super()" else ""} # ${ + }) { #{ ${if parentSym.isDefined then doc" # super();" else ""} # ${ ctorCode.stripBreaks } #} # }${ mtds.map: diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 55b2b42042..d4fc0b0532 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -6,8 +6,7 @@ abstract class Effect with :ge handle h = Effect with fun perform(arg, k) = k(arg) -in - h.perform("k") +h.perform("k") //│ ╔══[COMPILATION ERROR] Effect handlers are not enabled //│ ║ l.7: handle h = Effect with //│ ║ ^^^^^^^^^^^ @@ -21,20 +20,216 @@ in :expect 'b' handle h = Effect with fun perform(arg, k) = "b" -in - h.perform("k") +h.perform("k") //│ = 'b' :expect 'k' handle h = Effect with fun perform(arg, k) = arg -in - h.perform("k") +h.perform("k") //│ = 'k' -:fixme +:expect 'k' handle h = Effect with fun perform(arg, k) = k(arg) +h.perform("k") +//│ = 'k' + +:expect 'hi' +fun foo(e) = e.perform("hi") +handle h = Effect with + fun perform(arg, k) = + print(arg) + k(arg) +foo(h) +//│ > hi +//│ = 'hi' + +fun foo(e) = e.perform("hi") + +:expect 'Hello World!' +handle h = Effect with + fun perform(arg, k) = + print(arg) + k(arg) +(() => h.perform("Hello") + " World!")() +//│ > Hello +//│ = 'Hello World!' + +:expect '54321' +:fixme +let result = "" +handle h = Effect with + fun perform(arg, k) = + let v = k(arg) + result = result + arg + v in - h.perform("k") -//│ ═══[RUNTIME ERROR] Error: Resume not implemented + h.perform("1") + h.perform("2") + h.perform("3") + h.perform("4") + h.perform("5") +result +//│ ═══[RUNTIME ERROR] Expected: '54321', got: '5' +//│ ═══[RUNTIME ERROR] Expected: '54321', got: '5' + +:expect 'b' +handle h = Effect with + fun perform(arg, k) = + print(arg) + "b" +h.perform("t") +//│ > t +//│ = 'b' + +// FIXME: there should be 2 finished +:expect 'Hello World!' +fun foo(h) = + print("Entering foo") + let result = h.perform("Hello") + h.perform("?") + print("Exiting foo") + result +handle h = Effect with + fun perform(arg, k) = + print("handler called") + let result = k(arg) + print("handler finished") + result +print("mainbody start") +let result = foo(h) + " World!" +print("mainbody end") +result +//│ > mainbody start +//│ > Entering foo +//│ > handler called +//│ > handler called +//│ > Exiting foo +//│ > mainbody end +//│ > handler finished +//│ = 'Hello World!' +//│ result = 'Hello World!' + +// FIXME: there should be 2 finished +:expect 'Hello World!' +fun foo(h) = + print("Entering foo") + let result = h.perform("Hello") + h.perform("Oops!") + print("Exiting foo") + result +fun bar(h) = + print("Entering bar") + let result = foo(h) + " World" + print("Exiting bar") + result +fun foobar(h) = + print("Entering foobar") + let result = bar(h) + "!" + print("Exiting foobar") + result +handle h = Effect with + fun perform(arg, k) = + print("handler called") + let result = k(arg) + print("handler finished") + result +print("mainbody start") +let result = foobar(h) +print("mainbody end") +result +//│ > mainbody start +//│ > Entering foobar +//│ > Entering bar +//│ > Entering foo +//│ > handler called +//│ > handler called +//│ > Exiting foo +//│ > Exiting bar +//│ > Exiting foobar +//│ > mainbody end +//│ > handler finished +//│ = 'Hello World!' +//│ result = 'Hello World!' + +:fixme +handle h = Effect with + fun perform(arg, k) = arg +fun f() = 3 +f() +//│ ═══[RUNTIME ERROR] TypeError: globalThis.f is not a function + +abstract class Cell with + fun getVal(): Int + fun setVal(Int): () + +let x = 0 +let k +let m +handle h = Cell with + fun getVal(k) = k(x) + fun setVal(value, k) = + x = value + k(()) +k = h.getVal() +h.setVal(1) +m = h.getVal() +//│ k = 0 +//│ m = 1 +//│ x = 1 + +abstract class Eff with + fun get(): Int + +:expect 120 +fun fact(e, factvalue) = + if factvalue != 0 then + factvalue * fact(e, factvalue-1) + else e.get() +handle h = Eff with + fun get(k) = k(1) +fact(h, 5) +//│ = 120 + +abstract class StackDelay with + fun raise(): () + +// stack safe recursion +:expect 5050 +fun sum(depth, x) = + let new_depth = if depth > 70 then + // console.trace("Too deep, heapifying the stack") + h.raise() + else + depth + 1 + if x != 0 then + x + sum(new_depth, x - 1) + else 0 +handle h = StackDelay with + fun raise(k) = + // console.trace("Stack unwinded!") + k(10) +sum(0, 100) +//│ = 5050 + +// stack safe recursion +:expect 450015000 +fun sum(depth, x) = + let new_depth = if depth > 1000 then h.raise() else depth + 1 + if x != 0 then + x + sum(new_depth, x - 1) + else 0 +handle h = StackDelay with + fun raise(k) = // TODO: This should codegen to a simple return without instrumentation + k(10) +sum(0, 30000) +//│ = 450015000 + +:re +fun sum(x) = + if x != 0 then + x + sum(x - 1) + else 0 +sum(10000) +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded From 2263ddfd34661075e8a6dbbd30bcf9680bc5d6b6 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 17 Dec 2024 03:03:51 +0800 Subject: [PATCH 011/114] Port tests to new branch --- .../mlscript/handlers/EffectInHandler.mls | 23 ++++++ .../src/test/mlscript/handlers/NestedFun.mls | 18 +++++ .../test/mlscript/handlers/NestedHandlers.mls | 21 ++++++ .../mlscript/handlers/ReturnInHandler.mls | 33 ++++++++ .../test/mlscript/handlers/UserThreads.mls | 75 +++++++++++++++++++ 5 files changed, 170 insertions(+) create mode 100644 hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls create mode 100644 hkmc2/shared/src/test/mlscript/handlers/NestedFun.mls create mode 100644 hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls create mode 100644 hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls create mode 100644 hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls new file mode 100644 index 0000000000..7749853098 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls @@ -0,0 +1,23 @@ +:js +:handler + +class PrintEffect with + fun print(s: String): () + fun aux(s: String): () + +:e +handle h = PrintEffect with + fun print(x, k) = + print(x) + k(()) + fun aux(x, k) = + h.print(x) + k(()) + h.print(x) +h.aux(3) +//│ ╔══[ERROR] Name not found: h +//│ ║ l.14: h.print(x) +//│ ╙── ^ +//│ ╔══[ERROR] Name not found: h +//│ ║ l.16: h.print(x) +//│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedFun.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedFun.mls new file mode 100644 index 0000000000..cdf5338e05 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedFun.mls @@ -0,0 +1,18 @@ +:js +:handler + +fun sum() = 0 +fun foo(x) = + let y = 1 + fun bar() = + set y += 1 + sum() + bar() + y +foo(42) +//│ = 2 + +fun foo() = + let y = 1 + fun bar() = + set y += 1 diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls new file mode 100644 index 0000000000..b67572ca3d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -0,0 +1,21 @@ +:js +:handler + +:fixme +:expect 3 +let count = 0 +class IdEffect(f) +handle h1 = IdEffect with + fun f(x, k) = + set count += 1 + k(x) +handle h2 = IdEffect with + fun f(x, k) = + set count += 1 + k(x) +print(h1.f(3)) +print(h2.f(3)) +print("Oops") +set count += 1 +//│ ═══[RUNTIME ERROR] Error: Nested effects not implemented +//│ ═══[RUNTIME ERROR] Expected: 3, got: 0 diff --git a/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls b/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls new file mode 100644 index 0000000000..4416f003e4 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls @@ -0,0 +1,33 @@ +:js +:handler + +abstract class Effect with + fun f(): () + +fun f() = + handle h = Effect with + fun f(r) = + let m = r() + print("Bye!") + m + h.f() + return 3 +f() +//│ > Bye! +//│ = 3 + +fun f() = + handle h = Effect with + fun f(r) = + let m = r() + print("Bye!") + m + in + h.f() + return 3 + 10 + +:expect 3 +f() +//│ > Bye! +//│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls new file mode 100644 index 0000000000..a1a8040535 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls @@ -0,0 +1,75 @@ +:js +:handler + +class ThreadEffect with + fun fork(thread: () -> ()): () + fun yld(): () + +fun f(h, x)() = + print("f " + x) + h.yld() + print("f " + x) + h.yld() + print("f " + x) + +fun main(h) = + print("main start") + h.fork(f(h, 0)) + h.fork(f(h, 1)) + h.fork(f(h, 2)) + print("main end") + +// task queue +let x = [] +fun drain() = + while x.length != 0 then + x.pop()() + else + () +//│ x = [] + +// LIFO +handle h = ThreadEffect with + fun fork(thread, k) = + x.push(thread) + k() + drain() + fun yld(k) = + x.unshift(k) + drain() +in + main(h) +//│ > main start +//│ > main end +//│ > f 2 +//│ > f 1 +//│ > f 0 +//│ > f 2 +//│ > f 1 +//│ > f 0 +//│ > f 2 +//│ > f 1 +//│ > f 0 + +// FIFO +handle h = ThreadEffect with + fun fork(thread, k) = + x.unshift(thread) + k() + drain() + fun yld(k) = + x.unshift(k) + drain() +in + main(h) +//│ > main start +//│ > main end +//│ > f 0 +//│ > f 1 +//│ > f 2 +//│ > f 0 +//│ > f 1 +//│ > f 2 +//│ > f 0 +//│ > f 1 +//│ > f 2 From bdbee0ac817aa98e37212f8fa4102a150bd9583e Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 17 Dec 2024 03:28:28 +0800 Subject: [PATCH 012/114] Resume non tail resumptive handlers --- .../scala/hkmc2/codegen/HandlerLowering.scala | 2 - .../src/main/scala/hkmc2/syntax/Keyword.scala | 4 +- .../src/test/mlscript-compile/Predef.mjs | 48 +++++++++++++++---- .../src/test/mlscript-compile/Predef.mls | 19 ++++++-- .../src/test/mlscript/handlers/Effects.mls | 9 ++-- 5 files changed, 58 insertions(+), 24 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index eaa8d93601..b1bd9c5af1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -400,7 +400,6 @@ class HandlerLowering(using TL, Raise, Elaborator.State): .assign(handlerTailList, Instantiate(contClsPath, Nil)) .label(lblLoop, handlerLoop) .ret(h.resIn.asPath) - // TODO: fix handler loop, resume handlerTailList in __handleEffect // TODO: implement special continuation class for nested effects val defn = FunDefn(sym, PlainParamList(Nil) :: Nil, body) val result = Define(defn, CallPlaceholder(h.resOut, freshId(), true, Call(sym.asPath, Nil)(true), h.rest)) @@ -529,6 +528,5 @@ class HandlerLowering(using TL, Raise, Elaborator.State): case b => b def translateTopLevel(b: Block): Block = - // TODO: we should skip continuation class on top level translateBlock(b, HandlerCtx(true, true, _ => rtThrowMsg("Unhandled effects"))) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala index d0757fdd95..7c18f5f4e2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala @@ -103,8 +103,8 @@ object Keyword: val `where` = Keyword("where", N, N) val `forall` = Keyword("forall", N, N) val `exists` = Keyword("exists", N, N) - val `null` = Keyword("null", N, N) - val `undefined` = Keyword("undefined", N, N) + val `null` = Keyword("null", N, curPrec) + val `undefined` = Keyword("undefined", N, curPrec) val `abstract` = Keyword("abstract", N, N) val `constructor` = Keyword("constructor", N, N) val `virtual` = Keyword("virtual", N, N) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 84c362f949..4f024b9ba6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -156,15 +156,15 @@ const Predef$class = class Predef { } } __handleEffect(cur, handler, handlerTailList) { - let handlerCont, scrut, scrut1, savedNext, scrut2, savedNext1, scrut3, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + let handlerCont, scrut, scrut1, savedNext, scrut2, savedNext1, scrut3, scrut4, saved, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14; handlerCont = cur.nextHandler; - tmp8: while (true) { + tmp15: while (true) { if (handlerCont instanceof this.__Cont.class) { scrut = handlerCont.nextHandler !== cur.handler; if (scrut) { handlerCont = handlerCont.nextHandler; tmp = null; - continue tmp8; + continue tmp15; } else { tmp = null; } @@ -192,28 +192,56 @@ const Predef$class = class Predef { return cur; } else { tmp4 = this.__resume(handlerCont, undefined, undefined); - return tmp4(cur) ?? null; + tmp5 = tmp4(cur) ?? null; + cur = tmp5; + tmp6 = null; } + tmp7 = tmp6; } + tmp8 = tmp7; } else { scrut1 = handler === cur.handler; if (scrut1) { savedNext = handlerTailList.next; - tmp5 = this.__resume(cur, handlerTailList, handlerCont); - tmp6 = cur.handlerFun(tmp5) ?? null; - cur = tmp6; + tmp9 = this.__resume(cur, handlerTailList, handlerCont); + tmp10 = cur.handlerFun(tmp9) ?? null; + cur = tmp10; scrut2 = savedNext !== handlerTailList.next; if (scrut2) { handlerTailList.next.next = savedNext; - tmp7 = null; + tmp11 = null; } else { - tmp7 = null; + tmp11 = null; } - return cur; + tmp12 = tmp11; } else { return cur; } + tmp8 = tmp12; + } + tmp16: while (true) { + if (cur instanceof this.__Cont.class) { + return cur; + } else { + if (cur instanceof this.__Return.class) { + return cur; + } else { + scrut4 = handlerTailList.next; + if (scrut4 instanceof this.__Cont.class) { + saved = handlerTailList.next.next; + tmp13 = handlerTailList.next.resume(cur) ?? null; + cur = tmp13; + handlerTailList.next = saved; + tmp14 = null; + continue tmp16; + } else { + tmp14 = cur; + } + } + } + break; } + return tmp14; } __resume(cur1, tail, handlerCont) { return (value) => { diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index c78722b157..775804641c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -79,19 +79,28 @@ fun __handleEffect(cur, handler, handlerTailList) = set handlerCont.next.next = savedNext else () if cur is __Cont then - cur + return cur else if cur is __Return then - cur + return cur else - __resume(handlerCont, undefined, undefined)(cur) + set cur = __resume(handlerCont, undefined, undefined)(cur) else if handler === cur.handler then let savedNext = handlerTailList.next set cur = cur.handlerFun(__resume(cur, handlerTailList, handlerCont)) if savedNext !== handlerTailList.next then set handlerTailList.next.next = savedNext else () - cur - else cur + else return cur + while + cur is + __Cont then return cur + __Return then return cur + handlerTailList.next is __Cont then + let saved = handlerTailList.next.next + set cur = handlerTailList.next.resume(cur) + set handlerTailList.next = saved + else cur + fun __resume(cur, tail, handlerCont)(value) = let nextHandler = cur.nextHandler diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index d4fc0b0532..2b657bb8f0 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -57,7 +57,6 @@ handle h = Effect with //│ = 'Hello World!' :expect '54321' -:fixme let result = "" handle h = Effect with fun perform(arg, k) = @@ -71,8 +70,8 @@ in h.perform("4") h.perform("5") result -//│ ═══[RUNTIME ERROR] Expected: '54321', got: '5' -//│ ═══[RUNTIME ERROR] Expected: '54321', got: '5' +//│ = '54321' +//│ result = '54321' :expect 'b' handle h = Effect with @@ -83,7 +82,6 @@ h.perform("t") //│ > t //│ = 'b' -// FIXME: there should be 2 finished :expect 'Hello World!' fun foo(h) = print("Entering foo") @@ -108,10 +106,10 @@ result //│ > Exiting foo //│ > mainbody end //│ > handler finished +//│ > handler finished //│ = 'Hello World!' //│ result = 'Hello World!' -// FIXME: there should be 2 finished :expect 'Hello World!' fun foo(h) = print("Entering foo") @@ -150,6 +148,7 @@ result //│ > Exiting foobar //│ > mainbody end //│ > handler finished +//│ > handler finished //│ = 'Hello World!' //│ result = 'Hello World!' From e9bab2b1c918af7c18821f47343b33f935eb39fe Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 17 Dec 2024 18:36:30 +0800 Subject: [PATCH 013/114] Implement final pieces for nested handlers --- .../scala/hkmc2/codegen/HandlerLowering.scala | 16 ++- .../src/test/mlscript-compile/Predef.mjs | 45 +++++---- .../src/test/mlscript-compile/Predef.mls | 5 +- .../test/mlscript/handlers/NestedHandlers.mls | 97 +++++++++++++++---- 4 files changed, 124 insertions(+), 39 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index b1bd9c5af1..244a27c08c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -17,6 +17,7 @@ object HandlerLowering: private val pcIdent: Tree.Ident = Tree.Ident("pc") private val nextIdent: Tree.Ident = Tree.Ident("next") private val tailIdent: Tree.Ident = Tree.Ident("tail") + private val nextHandlerIdent: Tree.Ident = Tree.Ident("nextHandler") private val tailHandlerIdent: Tree.Ident = Tree.Ident("tailHandler") private val handlerIdent: Tree.Ident = Tree.Ident("handler") private val handlerFunIdent: Tree.Ident = Tree.Ident("handlerFun") @@ -45,6 +46,8 @@ object HandlerLowering: def pc = p.selN(pcIdent) def next = p.selN(nextIdent) def tail = p.selN(tailIdent) + def nextHandler = p.selN(nextHandlerIdent) + def tailHandler = p.selN(tailHandlerIdent) def value = p.selN(Tree.Ident("value")) def asArg = Arg(false, p) @@ -296,6 +299,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State): // ignored cases case TryBlock(sub, finallyDo, rest) => ??? // ignore case Throw(_) => PartRet(blk, Nil) + case _: HandleBlock => die // already translated at this point val headId = freshId() @@ -381,7 +385,16 @@ class HandlerLowering(using TL, Raise, Elaborator.State): .assign(tmp, h.resIn.asPath) .assign(h.resIn, SimpleCall(handleEffectFun, h.resIn.asPath :: h.lhs.asPath :: handlerTailList.asPath :: Nil)) .assign(tmp, unchanged) - .ifthen(tmp.asPath, Case.Lit(Tree.BoolLit(true)), rtThrowMsg("Nested effects not implemented")) + .ifthen(tmp.asPath, Case.Lit(Tree.BoolLit(true)), blockBuilder + .assign(tmp, Instantiate(contClsPath, Nil)) + .assignFieldN(tmp.asPath, nextIdent, handlerTailList.asPath.next) + .assignFieldN(tmp.asPath, handlerIdent, h.lhs.asPath) + .assignFieldN(h.resIn.asPath.tailHandler, nextHandlerIdent, tmp.asPath) + .assignFieldN(h.resIn.asPath, tailHandlerIdent, tmp.asPath) + .assignFieldN(h.resIn.asPath, tailIdent, handlerTailList.asPath.tail) + .ret(h.resIn.asPath) + // .rest(rtThrowMsg("Nested effect not implemented")) + ) .continue(lblLoop) ) .break(lblLoop) @@ -398,6 +411,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State): val body = h.handlers.foldLeft(cur)((builder, handler) => builder.assignFieldN(h.lhs.asPath, Tree.Ident(handler.sym.nme), handler.sym.asPath)) .label(lbl, handlerBody) .assign(handlerTailList, Instantiate(contClsPath, Nil)) + .assignFieldN(handlerTailList.asPath, tailIdent, handlerTailList.asPath) .label(lblLoop, handlerLoop) .ret(h.resIn.asPath) // TODO: implement special continuation class for nested effects diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 4f024b9ba6..a5cd217fee 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -156,15 +156,15 @@ const Predef$class = class Predef { } } __handleEffect(cur, handler, handlerTailList) { - let handlerCont, scrut, scrut1, savedNext, scrut2, savedNext1, scrut3, scrut4, saved, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14; + let handlerCont, scrut, scrut1, savedNext, scrut2, scrut3, savedNext1, scrut4, scrut5, saved, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; handlerCont = cur.nextHandler; - tmp15: while (true) { + tmp16: while (true) { if (handlerCont instanceof this.__Cont.class) { - scrut = handlerCont.nextHandler !== cur.handler; + scrut = handlerCont.handler !== cur.handler; if (scrut) { handlerCont = handlerCont.nextHandler; tmp = null; - continue tmp15; + continue tmp16; } else { tmp = null; } @@ -178,8 +178,8 @@ const Predef$class = class Predef { tmp1 = this.__resume(cur, handlerCont, handlerCont); tmp2 = cur.handlerFun(tmp1) ?? null; cur = tmp2; - scrut3 = savedNext1 !== handlerCont.next; - if (scrut3) { + scrut4 = savedNext1 !== handlerCont.next; + if (scrut4) { handlerCont.next.next = savedNext1; tmp3 = null; } else { @@ -209,39 +209,46 @@ const Predef$class = class Predef { scrut2 = savedNext !== handlerTailList.next; if (scrut2) { handlerTailList.next.next = savedNext; - tmp11 = null; + scrut3 = savedNext === undefined; + if (scrut3) { + handlerTailList.tail = handlerTailList.next; + tmp11 = null; + } else { + tmp11 = null; + } + tmp12 = tmp11; } else { - tmp11 = null; + tmp12 = null; } - tmp12 = tmp11; + tmp13 = tmp12; } else { return cur; } - tmp8 = tmp12; + tmp8 = tmp13; } - tmp16: while (true) { + tmp17: while (true) { if (cur instanceof this.__Cont.class) { return cur; } else { if (cur instanceof this.__Return.class) { return cur; } else { - scrut4 = handlerTailList.next; - if (scrut4 instanceof this.__Cont.class) { + scrut5 = handlerTailList.next; + if (scrut5 instanceof this.__Cont.class) { saved = handlerTailList.next.next; - tmp13 = handlerTailList.next.resume(cur) ?? null; - cur = tmp13; + tmp14 = handlerTailList.next.resume(cur) ?? null; + cur = tmp14; handlerTailList.next = saved; - tmp14 = null; - continue tmp16; + tmp15 = null; + continue tmp17; } else { - tmp14 = cur; + tmp15 = cur; } } } break; } - return tmp14; + return tmp15; } __resume(cur1, tail, handlerCont) { return (value) => { diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 775804641c..70da9e1dca 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -69,7 +69,7 @@ class __Return(value) fun __handleEffect(cur, handler, handlerTailList) = let handlerCont = cur.nextHandler - while handlerCont is __Cont and handlerCont.nextHandler !== cur.handler then + while handlerCont is __Cont and handlerCont.handler !== cur.handler then set handlerCont = handlerCont.nextHandler else () if handlerCont then @@ -89,6 +89,9 @@ fun __handleEffect(cur, handler, handlerTailList) = set cur = cur.handlerFun(__resume(cur, handlerTailList, handlerCont)) if savedNext !== handlerTailList.next then set handlerTailList.next.next = savedNext + if savedNext === undefined then + set handlerTailList.tail = handlerTailList.next + else () else () else return cur while diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls index b67572ca3d..2857204abd 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -1,21 +1,82 @@ :js :handler -:fixme -:expect 3 -let count = 0 -class IdEffect(f) -handle h1 = IdEffect with - fun f(x, k) = - set count += 1 - k(x) -handle h2 = IdEffect with - fun f(x, k) = - set count += 1 - k(x) -print(h1.f(3)) -print(h2.f(3)) -print("Oops") -set count += 1 -//│ ═══[RUNTIME ERROR] Error: Nested effects not implemented -//│ ═══[RUNTIME ERROR] Expected: 3, got: 0 +let id = 0 +class MaybeStop with + fun f(x: Bool): () +fun handleEffects(g) = + handle h1 = MaybeStop with + fun f(x, k) = + if x then + print("h1 stop") + else + set id += 1 + let cur = id + print("h1 start " + String(cur)) + let result = k(x) + print("h1 end " + String(cur)) + result + handle h2 = MaybeStop with + fun f(x, k) = + if x then + print("h2 stop") + else + set id += 1 + let cur = id + print("h2 start " + String(cur)) + let result = k(x) + print("h2 end " + String(cur)) + result + g(h1, h2) +//│ id = 0 + +fun f(h1, h2) = + h1.f(false) + h2.f(false) + h1.f(false) + h2.f(false) + h1.f(false) + h2.f(false) +handleEffects(f) +//│ > h1 start 1 +//│ > h2 start 2 +//│ > h1 start 3 +//│ > h2 start 4 +//│ > h1 start 5 +//│ > h2 start 6 +//│ > h2 end 6 +//│ > h2 end 4 +//│ > h2 end 2 +//│ > h1 end 5 +//│ > h1 end 3 +//│ > h1 end 1 +//│ = false + +fun f(h1, h2) = + h1.f(false) + h2.f(false) + h1.f(false) + h2.f(true) + h1.f(false) + h2.f(false) +handleEffects(f) +//│ > h1 start 7 +//│ > h2 start 8 +//│ > h1 start 9 +//│ > h2 stop +//│ > h2 end 8 +//│ > h1 end 9 +//│ > h1 end 7 + +fun f(h1, h2) = + h1.f(false) + h2.f(false) + h1.f(true) + h2.f(false) + h1.f(false) + h2.f(false) +handleEffects(f) +//│ > h1 start 10 +//│ > h2 start 11 +//│ > h1 stop +//│ > h1 end 10 From 4bb2d732c5150b923f2c186e0d1234f65b9c9d7d Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 17 Dec 2024 19:15:56 +0800 Subject: [PATCH 014/114] Implement Match codegen for multiple arms --- .../scala/hkmc2/codegen/HandlerLowering.scala | 22 ++------ .../scala/hkmc2/codegen/js/JSBuilder.scala | 54 ++++++------------- .../src/test/mlscript/handlers/Effects.mls | 8 +++ 3 files changed, 30 insertions(+), 54 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 244a27c08c..d9451d98b9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -414,7 +414,6 @@ class HandlerLowering(using TL, Raise, Elaborator.State): .assignFieldN(handlerTailList.asPath, tailIdent, handlerTailList.asPath) .label(lblLoop, handlerLoop) .ret(h.resIn.asPath) - // TODO: implement special continuation class for nested effects val defn = FunDefn(sym, PlainParamList(Nil) :: Nil, body) val result = Define(defn, CallPlaceholder(h.resOut, freshId(), true, Call(sym.asPath, Nil)(true), h.rest)) result @@ -469,26 +468,15 @@ class HandlerLowering(using TL, Raise, Elaborator.State): blockBuilder.break(loopLbl) case c => c.map(f) f(blk) - - // HACK: remove once match is fixed - def unrollMatch(b: Match): Block = - val Match(scrut, arms, dflt, rest) = b - arms match - case head :: Nil => b - case head :: next => Match( - scrut, List(head), - S(unrollMatch(Match(scrut, next, dflt, End()))), - rest) - case Nil => rest // match block representing the function body val mainMatchCases = parts.toList.map(b => (Case.Lit(Tree.IntLit(b.id)), transformPart(b.blk))) - val mainMatchBlk = unrollMatch(Match( + val mainMatchBlk = Match( pcSymbol.asPath, mainMatchCases, N, End() - )) + ) val lbl = blockBuilder.label(loopLbl, mainMatchBlk).rest(End()) @@ -506,12 +494,12 @@ class HandlerLowering(using TL, Raise, Elaborator.State): if assignedResumedCases.isEmpty then lbl else - unrollMatch(Match( + Match( pcSymbol.asPath, assignedResumedCases, - S(End()), + N, lbl - )) + ) val resumeSym = BlockMemberSymbol("resume", List()) val resumeFnDef = FunDefn( 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 14ad3f03ff..6465fff8c5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -230,46 +230,26 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: case Return(res, true) => doc" # ${result(res)}" case Return(res, false) => doc" # return ${result(res)};" - // TODO factor out common logic - case Match(scrut, Case.Lit(syntax.Tree.BoolLit(true)) -> trm :: Nil, els, rest) => - val t = doc" # if (${ result(scrut) }) { #{ ${ - returningTerm(trm) - } #} # }" + case Match(scrut, Nil, els, rest) => val e = els match case S(el) => - doc" else { #{ ${ returningTerm(el) } #} # }" - case N => doc"" - t :: e :: returningTerm(rest) - case Match(scrut, Case.Cls(cls, pth) -> trm :: Nil, els, rest) => + returningTerm(el) + case N => doc"" + e :: returningTerm(rest) + case Match(scrut, hd :: tl, els, rest) => val sd = result(scrut) - val test = cls match - // case _: semantics.ModuleSymbol => doc"=== ${result(pth)}" - case Elaborator.ctx.Builtins.Str => doc"typeof $sd === 'string'" - case Elaborator.ctx.Builtins.Num => doc"typeof $sd === 'number'" - case Elaborator.ctx.Builtins.Int => doc"globalThis.Number.isInteger($sd)" - case _ => doc"$sd instanceof ${result(pth)}" - val t = doc" # if ($test) { #{ ${ - returningTerm(trm) - } #} # }" - val e = els match - case S(el) => - doc" else { #{ ${ returningTerm(el) } #} # }" - case N => doc"" - t :: e :: returningTerm(rest) - case Match(scrut, Case.Lit(lit) -> trm :: Nil, els, rest) => - val t = doc" # if (${ result(scrut) } === ${lit.idStr}) { #{ ${ - returningTerm(trm) - } #} # }" - val e = els match - case S(el) => - doc" else { #{ ${ returningTerm(el) } #} # }" - case N => doc"" - t :: e :: returningTerm(rest) - case Match(scrut, Case.Tup(len, inf) -> trm :: Nil, els, rest) => - val test = doc"globalThis.Array.isArray(${ result(scrut) }) && ${ result(scrut) }.length ${if inf then ">=" else "==="} ${len}" - val t = doc" # if (${ test }) { #{ ${ - returningTerm(trm) - } #} # }" + def cond(cse: Case) = cse match + case Case.Lit(syntax.Tree.BoolLit(true)) => sd + case Case.Lit(lit) => doc"$sd === ${lit.idStr}" + case Case.Cls(cls, pth) => cls match + // case _: semantics.ModuleSymbol => doc"=== ${result(pth)}" + case Elaborator.ctx.Builtins.Str => doc"typeof $sd === 'string'" + case Elaborator.ctx.Builtins.Num => doc"typeof $sd === 'number'" + case Elaborator.ctx.Builtins.Int => doc"globalThis.Number.isInteger($sd)" + case _ => doc"$sd instanceof ${result(pth)}" + case Case.Tup(len, inf) => doc"globalThis.Array.isArray($sd) && $sd.length ${if inf then ">=" else "==="} ${len}" + val h = doc" # if (${ cond(hd._1) }) { #{ ${ returningTerm(hd._2) } #} # }" + val t = tl.foldLeft(h)((acc, arm) => acc :: doc" else if (${ cond(arm._1) }) { #{ ${ returningTerm(arm._2) } #} # }") val e = els match case S(el) => doc" else { #{ ${ returningTerm(el) } #} # }" diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 2b657bb8f0..7e384ce0ec 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -159,6 +159,14 @@ fun f() = 3 f() //│ ═══[RUNTIME ERROR] TypeError: globalThis.f is not a function +:fixme +fun f(perform) = + handle h = Effect with + fun perform(arg, k) = arg + perform() +f(() => 3) +//│ ═══[RUNTIME ERROR] Error: Function 'perform' expected 1 arguments but got 0 + abstract class Cell with fun getVal(): Int fun setVal(Int): () From 7395b4c80457581293628bff6c8cfa9e27be314d Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 18 Dec 2024 00:05:30 +0800 Subject: [PATCH 015/114] Moved most wiring code to Predef --- .../scala/hkmc2/codegen/HandlerLowering.scala | 46 +------- .../src/test/mlscript-compile/Predef.mjs | 100 +++++++++++++----- .../src/test/mlscript-compile/Predef.mls | 28 ++++- 3 files changed, 105 insertions(+), 69 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index d9451d98b9..2277cfcdd8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -75,6 +75,8 @@ class HandlerLowering(using TL, Raise, Elaborator.State): private val contClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__Cont")).selN(Tree.Ident("class")) private val retClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__Return")).selN(Tree.Ident("class")) private val handleEffectFun: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__handleEffect")) + private val mkEffectPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__mkEffect")) + private val handleBlockImplPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__handleBlockImpl")) private val mapPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Map")) private val dummyClsSym = ClassSymbol( Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), @@ -369,51 +371,13 @@ class HandlerLowering(using TL, Raise, Elaborator.State): val handlerBody = translateBlock(prepareBody(h.body), HandlerCtx(false, false, state => blockBuilder .assignFieldN(state.res.tail, nextIdent, Instantiate(state.cls, Value.Lit(Tree.IntLit(state.uid)) :: Nil)) .assignFieldN(state.res, tailIdent, state.res.tail.next) - .assign(h.resIn, state.res) - .break(lbl))) + .ret(SimpleCall(handleBlockImplPath, state.res :: h.lhs.asPath :: Nil)))) - def getBinaryBuiltin(nme: Str) = BuiltinSymbol(nme, true, false, false) - val equalBuiltin = getBinaryBuiltin("===").asPath - - val tmp = freshTmp() - val handlerTailList = freshTmp("handlerTailList") - val unchanged = SimpleCall(equalBuiltin, tmp.asPath :: h.resIn.asPath :: Nil) - val handlerLoop = blockBuilder - .ifthen(h.resIn.asPath, Case.Cls(dummyClsSym, retClsPath), Return(h.resIn.asPath, false)) - // TODO: this should be EffectSig - .ifthen(h.resIn.asPath, Case.Cls(dummyClsSym, contClsPath), blockBuilder - .assign(tmp, h.resIn.asPath) - .assign(h.resIn, SimpleCall(handleEffectFun, h.resIn.asPath :: h.lhs.asPath :: handlerTailList.asPath :: Nil)) - .assign(tmp, unchanged) - .ifthen(tmp.asPath, Case.Lit(Tree.BoolLit(true)), blockBuilder - .assign(tmp, Instantiate(contClsPath, Nil)) - .assignFieldN(tmp.asPath, nextIdent, handlerTailList.asPath.next) - .assignFieldN(tmp.asPath, handlerIdent, h.lhs.asPath) - .assignFieldN(h.resIn.asPath.tailHandler, nextHandlerIdent, tmp.asPath) - .assignFieldN(h.resIn.asPath, tailHandlerIdent, tmp.asPath) - .assignFieldN(h.resIn.asPath, tailIdent, handlerTailList.asPath.tail) - .ret(h.resIn.asPath) - // .rest(rtThrowMsg("Nested effect not implemented")) - ) - .continue(lblLoop) - ) - .break(lblLoop) val cur: Block => Block = h.handlers.foldLeft(blockBuilder)((builder, handler) => val lam = Value.Lam(PlainParamList(Param(FldFlags.empty, handler.resumeSym, N) :: Nil), translateBlock(handler.body, functionHandlerCtx)) val tmp = freshTmp() - builder.define(FunDefn(handler.sym, handler.params, blockBuilder - .assign(tmp, Instantiate(contClsPath, Nil)) - .assignFieldN(tmp.asPath, tailIdent, tmp.asPath) - .assignFieldN(tmp.asPath, tailHandlerIdent, tmp.asPath) - .assignFieldN(tmp.asPath, handlerIdent, h.lhs.asPath) - .assignFieldN(tmp.asPath, handlerFunIdent, lam) - .ret(tmp.asPath)))) - val body = h.handlers.foldLeft(cur)((builder, handler) => builder.assignFieldN(h.lhs.asPath, Tree.Ident(handler.sym.nme), handler.sym.asPath)) - .label(lbl, handlerBody) - .assign(handlerTailList, Instantiate(contClsPath, Nil)) - .assignFieldN(handlerTailList.asPath, tailIdent, handlerTailList.asPath) - .label(lblLoop, handlerLoop) - .ret(h.resIn.asPath) + builder.define(FunDefn(handler.sym, handler.params, Return(SimpleCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false)))) + val body = h.handlers.foldLeft(cur)((builder, handler) => builder.assignFieldN(h.lhs.asPath, Tree.Ident(handler.sym.nme), handler.sym.asPath)).rest(handlerBody) val defn = FunDefn(sym, PlainParamList(Nil) :: Nil, body) val result = Define(defn, CallPlaceholder(h.resOut, freshId(), true, Call(sym.asPath, Nil)(true), h.rest)) result diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index a5cd217fee..915cb23bfe 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -155,12 +155,58 @@ const Predef$class = class Predef { return null; } } - __handleEffect(cur, handler, handlerTailList) { + __mkEffect(handler, handlerFun) { + let res, tmp; + tmp = new this.__Cont.class(undefined, undefined); + res = tmp; + res.tail = res; + res.tailHandler = res; + res.handler = handler; + res.handlerFun = handlerFun; + return res; + } + __handleBlockImpl(cur, handler1) { + let handlerTailList, nxt, scrut, handlerCont, tmp, tmp1, tmp2, tmp3, tmp4; + tmp = new this.__Cont.class(undefined, undefined); + handlerTailList = tmp; + handlerTailList.tail = handlerTailList; + tmp5: while (true) { + if (cur instanceof this.__Return.class) { + return cur; + } else { + if (cur instanceof this.__Cont.class) { + tmp1 = this.__handleEffect(cur, handler1, handlerTailList); + nxt = tmp1; + scrut = cur === nxt; + if (scrut) { + tmp2 = new this.__Cont.class(undefined, undefined); + handlerCont = tmp2; + handlerCont.next = handlerTailList.next; + handlerCont.handler = handler1; + cur.tailHandler.nextHandler = handlerCont; + cur.tailHandler = handlerCont; + cur.tail = handlerTailList.tail; + return cur; + } else { + cur = nxt; + tmp3 = null; + } + tmp4 = tmp3; + continue tmp5; + } else { + return cur; + } + } + break; + } + return tmp4; + } + __handleEffect(cur1, handler2, handlerTailList) { let handlerCont, scrut, scrut1, savedNext, scrut2, scrut3, savedNext1, scrut4, scrut5, saved, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; - handlerCont = cur.nextHandler; + handlerCont = cur1.nextHandler; tmp16: while (true) { if (handlerCont instanceof this.__Cont.class) { - scrut = handlerCont.handler !== cur.handler; + scrut = handlerCont.handler !== cur1.handler; if (scrut) { handlerCont = handlerCont.nextHandler; tmp = null; @@ -175,9 +221,9 @@ const Predef$class = class Predef { } if (handlerCont) { savedNext1 = handlerCont.next; - tmp1 = this.__resume(cur, handlerCont, handlerCont); - tmp2 = cur.handlerFun(tmp1) ?? null; - cur = tmp2; + tmp1 = this.__resume(cur1, handlerCont, handlerCont); + tmp2 = cur1.handlerFun(tmp1) ?? null; + cur1 = tmp2; scrut4 = savedNext1 !== handlerCont.next; if (scrut4) { handlerCont.next.next = savedNext1; @@ -185,27 +231,27 @@ const Predef$class = class Predef { } else { tmp3 = null; } - if (cur instanceof this.__Cont.class) { - return cur; + if (cur1 instanceof this.__Cont.class) { + return cur1; } else { - if (cur instanceof this.__Return.class) { - return cur; + if (cur1 instanceof this.__Return.class) { + return cur1; } else { tmp4 = this.__resume(handlerCont, undefined, undefined); - tmp5 = tmp4(cur) ?? null; - cur = tmp5; + tmp5 = tmp4(cur1) ?? null; + cur1 = tmp5; tmp6 = null; } tmp7 = tmp6; } tmp8 = tmp7; } else { - scrut1 = handler === cur.handler; + scrut1 = handler2 === cur1.handler; if (scrut1) { savedNext = handlerTailList.next; - tmp9 = this.__resume(cur, handlerTailList, handlerCont); - tmp10 = cur.handlerFun(tmp9) ?? null; - cur = tmp10; + tmp9 = this.__resume(cur1, handlerTailList, handlerCont); + tmp10 = cur1.handlerFun(tmp9) ?? null; + cur1 = tmp10; scrut2 = savedNext !== handlerTailList.next; if (scrut2) { handlerTailList.next.next = savedNext; @@ -222,27 +268,27 @@ const Predef$class = class Predef { } tmp13 = tmp12; } else { - return cur; + return cur1; } tmp8 = tmp13; } tmp17: while (true) { - if (cur instanceof this.__Cont.class) { - return cur; + if (cur1 instanceof this.__Cont.class) { + return cur1; } else { - if (cur instanceof this.__Return.class) { - return cur; + if (cur1 instanceof this.__Return.class) { + return cur1; } else { scrut5 = handlerTailList.next; if (scrut5 instanceof this.__Cont.class) { saved = handlerTailList.next.next; - tmp14 = handlerTailList.next.resume(cur) ?? null; - cur = tmp14; + tmp14 = handlerTailList.next.resume(cur1) ?? null; + cur1 = tmp14; handlerTailList.next = saved; tmp15 = null; continue tmp17; } else { - tmp15 = cur; + tmp15 = cur1; } } } @@ -250,11 +296,11 @@ const Predef$class = class Predef { } return tmp15; } - __resume(cur1, tail, handlerCont) { + __resume(cur2, tail, handlerCont) { return (value) => { let nextHandler, cont, scrut, tmp, tmp1, tmp2, tmp3; - nextHandler = cur1.nextHandler; - cont = cur1.next; + nextHandler = cur2.nextHandler; + cont = cur2.next; tmp4: while (true) { if (cont instanceof this.__Cont.class) { tmp = cont.resume(value) ?? null; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 70da9e1dca..9851bc5f2f 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -67,6 +67,33 @@ abstract class __Cont(next, resumed) with class __Return(value) +fun __mkEffect(handler, handlerFun) = + let res = new __Cont(undefined, undefined) + set res.tail = res + set res.tailHandler = res + set res.handler = handler + set res.handlerFun = handlerFun + res + +fun __handleBlockImpl(cur, handler) = + let handlerTailList = new __Cont(undefined, undefined) + set handlerTailList.tail = handlerTailList + while cur is + __Return then return cur + __Cont then + let nxt = __handleEffect(cur, handler, handlerTailList) + if cur === nxt then + let handlerCont = new __Cont(undefined, undefined) + set handlerCont.next = handlerTailList.next + set handlerCont.handler = handler + set cur.tailHandler.nextHandler = handlerCont + set cur.tailHandler = handlerCont + set cur.tail = handlerTailList.tail + return cur + else + set cur = nxt + else return cur + fun __handleEffect(cur, handler, handlerTailList) = let handlerCont = cur.nextHandler while handlerCont is __Cont and handlerCont.handler !== cur.handler then @@ -103,7 +130,6 @@ fun __handleEffect(cur, handler, handlerTailList) = set cur = handlerTailList.next.resume(cur) set handlerTailList.next = saved else cur - fun __resume(cur, tail, handlerCont)(value) = let nextHandler = cur.nextHandler From 5ef77f274e386c8dca0eccb3f1c8e0da7c56962d Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 19 Dec 2024 18:21:37 +0800 Subject: [PATCH 016/114] Changes to IR --- .../src/main/scala/hkmc2/codegen/Block.scala | 20 +++++++++++-------- .../scala/hkmc2/codegen/HandlerLowering.scala | 15 +++++++------- .../main/scala/hkmc2/codegen/Lowering.scala | 9 ++------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index bf5160ec52..80400e8361 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -36,13 +36,14 @@ sealed abstract class Block extends Product with AutoLocated: case Break(_) => Set.empty case Continue(_) => Set.empty case Define(defn, rst) => rst.definedVars - case HandleBlock(lhs, resIn, resOut, hdr, bod, rst) => bod.definedVars ++ rst.definedVars + lhs + case HandleBlock(lhs, res, cls, hdr, bod, rst) => bod.definedVars ++ rst.definedVars + lhs + case HandleBlockReturn(_) => Set.empty case TryBlock(sub, fin, rst) => sub.definedVars ++ fin.definedVars ++ rst.definedVars case Label(lbl, bod, rst) => bod.definedVars ++ rst.definedVars // ignoring blocks inside functions and handle block def map(f: Block => Block): Block = this match - case _: Return | _: Throw | _: End | _: Break | _: Continue => this + case _: Return | _: Throw | _: End | _: Break | _: Continue | _: HandleBlockReturn => this case Match(scrut, arms, dflt, rst) => Match(scrut, arms.map(_ -> f(_)), dflt.map(f), f(rst)) case Label(lbl, bod, rst) => Label(lbl, f(bod), f(rst)) case Begin(sub, rst) => Begin(f(sub), f(rst)) @@ -50,14 +51,15 @@ sealed abstract class Block extends Product with AutoLocated: case Assign(l, r, rst) => Assign(l, r, f(rst)) case b @ AssignField(l, n, r, rst) => AssignField(l, n, r, f(rst))(b.symbol) case Define(defn, rst) => Define(defn, f(rst)) - case HandleBlock(l, resIn, resOut, hdr, bod, rst) => HandleBlock(l, resIn, resOut, hdr, bod, f(rst)) + case HandleBlock(l, res, cls, hdr, bod, rst) => HandleBlock(l, res, cls, hdr, bod, f(rst)) def mapResult(f: Result => Opt[(Result => Block) => Block]): Block = this match case Return(res, implct) => f(res).map(_(Return(_, implct))).getOrElse(this) case Throw(exc) => f(exc).map(_(Throw(_))).getOrElse(this) case Assign(l, r, rst) => f(r).map(_(Assign(l, _, rst))).getOrElse(this) case b @ AssignField(l, n, r, rst) => f(r).map(_(AssignField(l, n, _, rst)(b.symbol))).getOrElse(this) - case _: End | _: Break | _: Continue | _: Match | _: Label | _: Begin | _: TryBlock | _: Define | _: HandleBlock => this + case HandleBlockReturn(res) => f(res).map(_(HandleBlockReturn(_))).getOrElse(this) + case _: End | _: Break | _: Continue | _: Match | _: Label | _: Begin | _: TryBlock | _: Define | _: HandleBlock | _: HandleBlockReturn => this def mapPath(f: Path => Path): Block = this match case Return(res: Path, implct) => Return(f(res), implct) @@ -66,7 +68,8 @@ sealed abstract class Block extends Product with AutoLocated: case b @ AssignField(l, n, r: Path, rst) => AssignField(l, n, f(r), rst)(b.symbol) case Match(scrut: Path, arms, dflt, rst) => Match(f(scrut), arms, dflt, rst) case Define(ValDefn(owner, k, sym, rhs: Path), rst) => Define(ValDefn(owner, k, sym, f(rhs)), rst) - case _: Return | _: Throw | _: Assign | _: AssignField | _: End | _: Break | _: Continue | _: Match | _: Label | _: Begin | _: TryBlock | _: Define | _: HandleBlock => this.mapResult { + case HandleBlockReturn(res: Path) => HandleBlockReturn(f(res)) + case _: Return | _: Throw | _: Assign | _: AssignField | _: End | _: Break | _: Continue | _: Match | _: Label | _: Begin | _: TryBlock | _: Define | _: HandleBlock | _: HandleBlockReturn => this.mapResult { case r @ Call(fun, args) => S(_(Call(f(fun), args.map(arg => Arg(arg.spread, f(arg.value))))(r.isMlsFun))) case r @ Instantiate(cls, args) => S(_(Instantiate(f(cls), args.map(f)))) case r => N @@ -84,8 +87,8 @@ sealed abstract class Block extends Product with AutoLocated: case Begin(sub, rst) => Begin(sub, rst.mapTail(f)) case Assign(lhs, rhs, rst) => Assign(lhs, rhs, rst.mapTail(f)) case Define(defn, rst) => Define(defn, rst.mapTail(f)) - case HandleBlock(lhs, resIn, resOut, handlers, body, rest) => - HandleBlock(lhs, resIn, resOut, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, h.body.mapTail(f))), body.mapTail(f), rest.mapTail(f)) + case HandleBlock(lhs, res, cls, handlers, body, rest) => + HandleBlock(lhs, res, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, h.body.mapTail(f))), body.mapTail(f), rest.mapTail(f)) case Match(scrut, arms, dflt, rst) => Match(scrut, arms.map(_ -> _.mapTail(f)), dflt.map(_.mapTail(f)), rst.mapTail(f)) @@ -123,7 +126,8 @@ case class AssignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(val case class Define(defn: Defn, rest: Block) extends Block with ProductWithTail -case class HandleBlock(lhs: Local, resIn: Local, resOut: Local, handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail +case class HandleBlock(lhs: Local, res: Local, cls: Path, handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail +case class HandleBlockReturn(res: Result) extends BlockTail sealed abstract class Defn: val sym: MemberSymbol[?] diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 2277cfcdd8..81565e79c0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -180,7 +180,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State): case AssignField(_, _, _, rest) => containsCall(rest) case Define(defn, rest) => containsCall(rest) case End(msg) => false - case _: HandleBlock => die // already translated at this point + case _: HandleBlock | _: HandleBlockReturn => die // already translated at this point def labelBodyHasCall(blk: Label) = val Label(label, body, rest) = blk @@ -301,7 +301,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State): // ignored cases case TryBlock(sub, finallyDo, rest) => ??? // ignore case Throw(_) => PartRet(blk, Nil) - case _: HandleBlock => die // already translated at this point + case _: HandleBlock | _: HandleBlockReturn => die // already translated at this point val headId = freshId() @@ -358,14 +358,15 @@ class HandlerLowering(using TL, Raise, Elaborator.State): val sym = BlockMemberSymbol(s"handleBlock$$${freshId()}", Nil) val lbl = freshTmp("handlerBody") val lblLoop = freshTmp("handlerLoop") + val tmp = freshTmp("retCont") def prepareBody(b: Block): Block = def go(b: Block): Block = b.map(go) match case Return(res, implct) => // In the case of res is effectful, it will be handled in translateBlock - Assign(h.resIn, res, Return(Instantiate(retClsPath, h.resIn.asPath :: Nil), implct)) - case Assign(h.resIn, rhs, End("")) => - Return(rhs, false) + Assign(tmp, res, Return(Instantiate(retClsPath, tmp.asPath :: Nil), implct)) + case HandleBlockReturn(res) => + Return(res, false) case b => b go(b) val handlerBody = translateBlock(prepareBody(h.body), HandlerCtx(false, false, state => blockBuilder @@ -376,10 +377,10 @@ class HandlerLowering(using TL, Raise, Elaborator.State): val cur: Block => Block = h.handlers.foldLeft(blockBuilder)((builder, handler) => val lam = Value.Lam(PlainParamList(Param(FldFlags.empty, handler.resumeSym, N) :: Nil), translateBlock(handler.body, functionHandlerCtx)) val tmp = freshTmp() - builder.define(FunDefn(handler.sym, handler.params, Return(SimpleCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false)))) + builder.define(FunDefn(handler.sym, handler.params, Return(SimpleCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false)))).assign(h.lhs, Instantiate(h.cls, Nil)) val body = h.handlers.foldLeft(cur)((builder, handler) => builder.assignFieldN(h.lhs.asPath, Tree.Ident(handler.sym.nme), handler.sym.asPath)).rest(handlerBody) val defn = FunDefn(sym, PlainParamList(Nil) :: Nil, body) - val result = Define(defn, CallPlaceholder(h.resOut, freshId(), true, Call(sym.asPath, Nil)(true), h.rest)) + val result = Define(defn, CallPlaceholder(h.res, freshId(), true, Call(sym.asPath, Nil)(true), h.rest)) result private def genContClass(b: Block)(using HandlerCtx): Opt[ClsLikeDefn] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index ffca6d8598..1fae6892eb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -471,14 +471,9 @@ trait LoweringHandler val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme)) S(Handler(td.sym, resumeSym, paramLists, bodyBlock)) }.collect{ case Some(v) => v } - val resInSym = TempSymbol(S(t), "cur") - val resOutSym = TempSymbol(S(t)) + val resSym = TempSymbol(S(t)) subTerm(rhs): cls => - Assign( - lhs, - Instantiate(cls, Nil), - HandleBlock(lhs, resInSym, resOutSym, handlers, term(st.Blk(stmts, res))(res => Assign(resInSym, res, End())), k(Value.Ref(resOutSym))) - ) + HandleBlock(lhs, resSym, cls, handlers, term(st.Blk(stmts, res))(HandleBlockReturn(_)), k(Value.Ref(resSym))) case _ => super.term(t)(k) override def topLevel(t: st): Block = if !instrument then return super.topLevel(t) From 018deddd4990a564fe3d2a998bf781d9c93b1351 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 19 Dec 2024 18:26:58 +0800 Subject: [PATCH 017/114] Changes IR --- .../shared/src/main/scala/hkmc2/codegen/Block.scala | 12 +++++++----- .../src/main/scala/hkmc2/codegen/Lowering.scala | 9 +++++---- hkmc2/shared/src/test/mlscript/handlers/Effects.mls | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index e305825cde..c4a2a44093 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -36,7 +36,8 @@ sealed abstract class Block extends Product with AutoLocated: case Break(_) => Set.empty case Continue(_) => Set.empty case Define(defn, rst) => rst.definedVars - case HandleBlock(lhs, res, handlers, bod, rst) => bod.definedVars ++ rst.definedVars + lhs + case HandleBlock(lhs, res, cls, hdr, bod, rst) => bod.definedVars ++ rst.definedVars + lhs + case HandleBlockReturn(_) => Set.empty case TryBlock(sub, fin, rst) => sub.definedVars ++ fin.definedVars ++ rst.definedVars case Label(lbl, bod, rst) => bod.definedVars ++ rst.definedVars @@ -46,8 +47,8 @@ sealed abstract class Block extends Product with AutoLocated: case Begin(sub, rst) => Begin(sub, rst.mapTail(f)) case Assign(lhs, rhs, rst) => Assign(lhs, rhs, rst.mapTail(f)) case Define(defn, rst) => Define(defn, rst.mapTail(f)) - case HandleBlock(lhs, res, handlers, body, rest) => - HandleBlock(lhs, res, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, h.body.mapTail(f))), body.mapTail(f), rest.mapTail(f)) + case HandleBlock(lhs, res, cls, handlers, body, rest) => + HandleBlock(lhs, res, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, h.body.mapTail(f))), body.mapTail(f), rest.mapTail(f)) case Match(scrut, arms, dflt, rst) => Match(scrut, arms.map(_ -> _.mapTail(f)), dflt.map(_.mapTail(f)), rst.mapTail(f)) @@ -81,11 +82,12 @@ case class TryBlock(sub: Block, finallyDo: Block, rest: Block) extends Block wit case class Assign(lhs: Local, rhs: Result, rest: Block) extends Block with ProductWithTail // case class Assign(lhs: Path, rhs: Result, rest: Block) extends Block with ProductWithTail -case class AssignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(symbol: Opt[FieldSymbol]) extends Block with ProductWithTail +case class AssignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(val symbol: Opt[FieldSymbol]) extends Block with ProductWithTail case class Define(defn: Defn, rest: Block) extends Block with ProductWithTail -case class HandleBlock(lhs: Local, res: Local, handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail +case class HandleBlock(lhs: Local, res: Local, cls: Path, handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail +case class HandleBlockReturn(res: Result) extends BlockTail sealed abstract class Defn: val sym: MemberSymbol[?] diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 360c26e771..6f4032f2be 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -472,8 +472,9 @@ trait LoweringHandler S(Handler(td.sym, resumeSym, paramLists, bodyBlock)) }.collect{ case Some(v) => v } val resSym = TempSymbol(S(t)) - HandleBlock(lhs, resSym, handlers, term(st.Blk(stmts, res))(res => Assign(resSym, res, End())), k(Value.Ref(resSym))) + subTerm(rhs): cls => + HandleBlock(lhs, resSym, cls, handlers, term(st.Blk(stmts, res))(HandleBlockReturn(_)), k(Value.Ref(resSym))) case _ => super.term(t)(k) - override def program(main: st): Program = - if !instrument then return super.program(main) - super.program(main) + override def topLevel(t: st): Block = + if !instrument then return super.topLevel(t) + super.topLevel(t) diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index db1005d446..ce795ef130 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -25,4 +25,4 @@ let r = handle h = Effect with in h.perform("k") r -//│ /!!!\ Uncaught error: scala.MatchError: HandleBlock(globalThis:block#3.h,$tmp,List(Handler(member:perform,k,List(ParamList(‹›,List(Param(‹›,arg,None)),None)),Return(Call(Ref(k),List(Arg(false,Ref(arg)))),false))),Assign($tmp,Call(Select(Ref(globalThis:block#3.h),Ident(perform)),List(Arg(false,Lit(StrLit(k))))),End()),Assign(globalThis:block#3.r,Ref($tmp),Return(Ref(globalThis:block#3.r),true))) (of class hkmc2.codegen.HandleBlock) +//│ /!!!\ Uncaught error: scala.MatchError: HandleBlock(globalThis:block#3.h,$tmp,Select(Ref(globalThis:block#1),Ident(Effect)),List(Handler(member:perform,k,List(ParamList(‹›,List(Param(‹›,arg,None)),None)),Return(Call(Ref(k),List(Arg(false,Ref(arg)))),false))),HandleBlockReturn(Call(Select(Ref(globalThis:block#3.h),Ident(perform)),List(Arg(false,Lit(StrLit(k)))))),Assign(globalThis:block#3.r,Ref($tmp),Return(Ref(globalThis:block#3.r),true))) (of class hkmc2.codegen.HandleBlock) From dac193e265c1b70e1fbfd451e8c2bd0c6a1bda63 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 19 Dec 2024 18:35:29 +0800 Subject: [PATCH 018/114] Patch for JSBuilder --- .../shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala | 4 ++-- hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls | 2 +- hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls | 6 +++--- 3 files changed, 6 insertions(+), 6 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 c1c94cbd76..f7d29966ae 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -219,7 +219,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: doc"${ths}.${sym.nme} = ${clsJS};" case N => fun match - case S(f) => doc"${f}; # ${sym.nme}.class = ${clsJS};" + case S(f) => doc"${f} # ${sym.nme}.class = ${clsJS};" case N => clsJS thisProxy match case S(proxy) if !scope.thisProxyDefined => @@ -351,7 +351,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: object JSBuilder: import scala.util.matching.Regex - private val identifierPattern: Regex = "^[A-Za-z$][A-Za-z0-9$]*$".r + private val identifierPattern: Regex = "^[A-Za-z_$][A-Za-z0-9_$]*$".r def isValidIdentifier(s: Str): Bool = identifierPattern.matches(s) && !keywords.contains(s) diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls index 185b4f5fc0..1190a8c65c 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls @@ -35,7 +35,7 @@ fun test(x) = //│ globalThis.Predef.checkArgs("test", 1, true, args.length); //│ let x = args[0]; //│ let tmp; -//│ function Foo(...args1) { return new Foo.class(...args1); }; +//│ function Foo(...args1) { return new Foo.class(...args1); } //│ Foo.class = class Foo { //│ constructor(a, b) { //│ this.a = a; diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls index 2096e0265d..9a4a407c7d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls @@ -25,7 +25,7 @@ fun test(a) = //│ function test(...args) { //│ globalThis.Predef.checkArgs("test", 1, true, args.length); //│ let a = args[0]; -//│ function Inner(...args1) { return new Inner.class(...args1); }; +//│ function Inner(...args1) { return new Inner.class(...args1); } //│ Inner.class = class Inner { //│ constructor(b) { //│ this.b = b; @@ -87,7 +87,7 @@ fun test(a) = //│ globalThis.Predef.checkArgs("test", 1, true, args.length); //│ let a = args[0]; //│ let tmp, tmp1; -//│ function C1(...args1) { return new C1.class(...args1); }; +//│ function C1(...args1) { return new C1.class(...args1); } //│ C1.class = class C1 { //│ constructor(b) { //│ this.b = b; @@ -98,7 +98,7 @@ fun test(a) = //│ } //│ toString() { return "C1(" + this.b + ")"; } //│ }; -//│ function C2(...args1) { return new C2.class(...args1); }; +//│ function C2(...args1) { return new C2.class(...args1); } //│ C2.class = class C2 { //│ constructor(b) { //│ this.b = b; From 68be0721172d60efbb6c81909c41353be1970be8 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 19 Dec 2024 18:51:00 +0800 Subject: [PATCH 019/114] Fix link error --- compiler/shared/test/diff-ir/cpp/Makefile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/shared/test/diff-ir/cpp/Makefile b/compiler/shared/test/diff-ir/cpp/Makefile index 2f7a2bd6fc..45aae4802c 100644 --- a/compiler/shared/test/diff-ir/cpp/Makefile +++ b/compiler/shared/test/diff-ir/cpp/Makefile @@ -1,8 +1,9 @@ CXX := g++ -CFLAGS := $(CFLAGS) -O3 -Wall -Wextra -std=c++20 -I. -Wno-inconsistent-missing-override -I/opt/homebrew/include -LDFLAGS := $(LDFLAGS) -lmimalloc -lgmp -L/opt/homebrew/lib +CFLAGS += -O3 -Wall -Wextra -std=c++20 -I. -Wno-inconsistent-missing-override -I/opt/homebrew/include +LDFLAGS += -L/opt/homebrew/lib +LDLIBS := -lmimalloc -lgmp SRC := -INCLUDES = mlsprelude.h +INCLUDES := mlsprelude.h DST := DEFAULT_TARGET := mls TARGET := $(or $(DST),$(DEFAULT_TARGET)) @@ -23,4 +24,4 @@ clean: auto: $(TARGET) $(TARGET): $(SRC) $(INCLUDES) - $(CXX) $(CFLAGS) $(LDFLAGS) $(SRC) -o $(TARGET) + $(CXX) $(CFLAGS) $(LDFLAGS) $(SRC) $(LDLIBS) -o $(TARGET) From 6a71972146d696e3767a0191b35e3014ec60d7a8 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 19 Dec 2024 19:10:28 +0800 Subject: [PATCH 020/114] Changes to handler syntax --- .../scala/hkmc2/semantics/Elaborator.scala | 23 +++------ .../src/main/scala/hkmc2/utils/utils.scala | 15 ------ .../src/test/mlscript/handlers/Effects.mls | 6 +-- .../src/test/mlscript/parser/Handler.mls | 48 ++++++++++--------- 4 files changed, 35 insertions(+), 57 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 56b55a3e6d..1407296933 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -607,25 +607,14 @@ extends Importer: case trm => raise(WarningReport(msg"Terms in handler block do nothing" -> trm.toLoc :: Nil)) val tds = elabed.stats.map { - case td @ TermDefinition(owner, Fun, sym, params, sign, body, resSym, flags) => - val paramsRev = params.reverse - // Find last param - val (resumeParam, newParams) = paramsRev.replaceAndPopFirst( - params => { - params.params.reverse match - case head :: next => - Some((ParamList(params.flags, next.reverse, params.restParam), head)) - case Nil => None - } - ) - - resumeParam match - case None => - raise(ErrorReport(msg"Handler function is missing resumption parameter" -> td.toLoc :: Nil)) - None - case Some(value) => + case td @ TermDefinition(owner, Fun, sym, params, sign, body, resSym, flags) => + params.reverse match + case ParamList(_, value :: Nil, _) :: newParams => val newTd = TermDefinition(owner, Fun, sym, newParams.reverse, sign, body, resSym, flags) S(HandlerTermDefinition(value.sym, newTd)) + case _ => + raise(ErrorReport(msg"Handler function is missing resumption parameter" -> td.toLoc :: Nil)) + None case st => raise(ErrorReport(msg"Only function definitions are allowed in handler blocks" -> st.toLoc :: Nil)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala index ca1ad1d8a2..7ef428d273 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala @@ -74,18 +74,3 @@ extension (t: Product) if inTailPos then ": \\\n" + args.mkString("\n") else ":\n" + args.mkString("\n").indent(" ") - -extension [T](xs: Ls[T]) - def replaceFirst(repl: T => Opt[T]): List[T] = xs match - case Nil => Nil - case head :: tail => repl(head) match - case None => head :: replaceFirst(repl) - case Some(value) => value :: tail - - def replaceAndPopFirst[V](repl: T => Opt[(T, V)]): (Opt[V], Ls[T]) = xs match - case Nil => (None, Nil) - case head :: tail => repl(head) match - case N => - val next = tail.replaceAndPopFirst(repl) - (next._1, head :: next._2) - case S((replaced, popped)) => (S(popped), replaced :: tail) \ No newline at end of file diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index ce795ef130..84585e68d9 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -5,14 +5,14 @@ abstract class Effect with :ge let r = handle h = Effect with - fun perform(arg, k) = k(arg) + fun perform(arg)(k) = k(arg) in h.perform("k") r //│ ╔══[COMPILATION ERROR] Effect handlers are not enabled //│ ║ l.7: let r = handle h = Effect with //│ ║ ^^^^^^^^^^^ -//│ ║ l.8: fun perform(arg, k) = k(arg) +//│ ║ l.8: fun perform(arg)(k) = k(arg) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of null (reading 'perform') @@ -21,7 +21,7 @@ r :fixme let r = handle h = Effect with - fun perform(arg, k) = k(arg) + fun perform(arg)(k) = k(arg) in h.perform("k") r diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index 5e2dcd2b93..a1a69d5158 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -20,23 +20,23 @@ handle h = Eff :parseOnly fun foo(x) = 1 handle h = Eff with - fun f(r) = r(0) - fun g(a, r) = r(1) + fun f()(r) = r(0) + fun g(a)(r) = r(1) //│ Parsed: //│ TermDef(Fun,App(Ident(foo),Tup(List(Ident(x)))),Some(IntLit(1))) -//│ Handle(Ident(h),Ident(Eff),Block(List(TermDef(Fun,App(Ident(f),Tup(List(Ident(r)))),Some(App(Ident(r),Tup(List(IntLit(0)))))), TermDef(Fun,App(Ident(g),Tup(List(Ident(a), Ident(r)))),Some(App(Ident(r),Tup(List(IntLit(1)))))))),None) +//│ Handle(Ident(h),Ident(Eff),Block(List(TermDef(Fun,App(App(Ident(f),Tup(List())),Tup(List(Ident(r)))),Some(App(Ident(r),Tup(List(IntLit(0)))))), TermDef(Fun,App(App(Ident(g),Tup(List(Ident(a)))),Tup(List(Ident(r)))),Some(App(Ident(r),Tup(List(IntLit(1)))))))),None) :e handle 1 = 1 with - fun f(r) = r(0) + fun f()(r) = r(0) in foo(h) //│ ╔══[ERROR] Unsupported handle binding shape //│ ║ l.31: handle 1 = 1 with //│ ║ ^^^^^^^^^^ -//│ ║ l.32: fun f(r) = r(0) -//│ ╙── ^^^^^^^^^^^^^^^^^ +//│ ║ l.32: fun f()(r) = r(0) +//│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ ╔══[ERROR] Name not found: foo //│ ║ l.34: foo(h) //│ ╙── ^^^ @@ -49,7 +49,7 @@ fun foo(h) = 0 :el handle h = Eff with - fun f(r) = r(0) + fun f()(r) = r(0) in foo(h) //│ Elab: { { handle globalThis:block#6.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#6),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#6.h#666) } } @@ -57,28 +57,28 @@ in :e ( handle h = Eff with - fun f(r) = r(0) - fun g(a, r) = r(1) + fun f()(r) = r(0) + fun g(a)()(r) = r(1) ) + 1 //│ ╔══[ERROR] Expected a body for handle bindings in expression position //│ ║ l.59: handle h = Eff with //│ ║ ^^^^^^^^^^^^ -//│ ║ l.60: fun f(r) = r(0) -//│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ║ l.61: fun g(a, r) = r(1) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.60: fun f()(r) = r(0) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.61: fun g(a)()(r) = r(1) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ :el handle h = Eff with - fun f(r) = r(0) - fun g(a, r)()() = r(1) + fun f()(r) = r(0) + fun g(a)()()(r) = r(1) foo(h) //│ Elab: { handle globalThis:block#8.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#8.h#666) } :e handle h = Eff with - fun f(r) = r(0) - fun g(a, r) = r(1) + fun f()(r) = r(0) + fun g(a)(r) = r(1) val x = 24 class Test with fun x() = x @@ -92,12 +92,13 @@ foo(h) :e handle h = Eff with - fun f(r) = r(0) - fun g(a, r) = + fun f()(r) = r(0) + fun g(a)(r) = handle e = Eff with fun f = r(0) fun g() = r(0) fun h()() = r(1) + fun h2()(a, b) = r(1) foo(h) foo(h) //│ ╔══[ERROR] Handler function is missing resumption parameter @@ -109,15 +110,18 @@ foo(h) //│ ╔══[ERROR] Handler function is missing resumption parameter //│ ║ l.100: fun h()() = r(1) //│ ╙── ^^^^ +//│ ╔══[ERROR] Handler function is missing resumption parameter +//│ ║ l.101: fun h2()(a, b) = r(1) +//│ ╙── ^^^^ :w :el handle h = Eff with - fun f(r) = r(0) - fun g(a, r) = r(1) + fun f()(r) = r(0) + fun g(a)(r) = r(1) 12345 foo(h) //│ ╔══[WARNING] Terms in handler block do nothing -//│ ║ l.118: 12345 +//│ ║ l.122: 12345 //│ ╙── ^^^^^ //│ Elab: { handle globalThis:block#11.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#11.h#666) } From ea621a91553f35fd76c7a59f7f0c460e4449843f Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Fri, 20 Dec 2024 23:50:17 +0800 Subject: [PATCH 021/114] Add class inheritance in IR --- .../src/main/scala/hkmc2/codegen/Block.scala | 1 + .../scala/hkmc2/codegen/HandlerLowering.scala | 3 +- .../main/scala/hkmc2/codegen/Lowering.scala | 1 + .../scala/hkmc2/codegen/js/JSBuilder.scala | 46 ++++++++++--------- .../scala/hkmc2/semantics/Elaborator.scala | 8 ++-- .../main/scala/hkmc2/semantics/Symbol.scala | 2 +- .../src/test/mlscript-compile/Example.mjs | 1 - .../src/test/mlscript-compile/Option.mjs | 3 -- .../src/test/mlscript-compile/Predef.mjs | 4 -- .../src/test/mlscript-compile/Stack.mjs | 2 - .../shared/src/test/mlscript-compile/Str.mjs | 1 - .../test/mlscript-compile/apps/Accounting.mjs | 4 -- .../src/test/mlscript/basics/Classes.mls | 4 +- .../test/mlscript/codegen/ClassInClass.mls | 1 - .../src/test/mlscript/codegen/ClassInFun.mls | 1 - .../src/test/mlscript/codegen/FunInClass.mls | 2 - .../src/test/mlscript/codegen/Modules.mls | 5 +- .../test/mlscript/codegen/ParamClasses.mls | 3 -- .../test/mlscript/codegen/PlainClasses.mls | 2 +- .../test/mlscript/codegen/SanityChecks.mls | 4 -- .../test/mlscript/syntax/KeywordStutters.mls | 2 +- 21 files changed, 40 insertions(+), 60 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 80400e8361..b0a5871445 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -152,6 +152,7 @@ final case class ClsLikeDefn( methods: Ls[FunDefn], privateFields: Ls[TermSymbol], publicFields: Ls[TermDefinition], + preCtor: Block, ctor: Block, ) extends Defn diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 81565e79c0..a908abb536 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -473,7 +473,8 @@ class HandlerLowering(using TL, Raise, Elaborator.State): resumeBody ) - S(ClsLikeDefn(sym, syntax.Cls, S(contClsPath), resumeFnDef :: Nil, Nil, Nil, End())) + S(ClsLikeDefn(sym, syntax.Cls, S(contClsPath), resumeFnDef :: Nil, Nil, Nil, + Assign(freshTmp(), SimpleCall(Value.Ref(State.builtinOpsMap("super")), Value.Lit(Tree.UnitLit(true)) :: Value.Lit(Tree.UnitLit(true)) :: Nil), End()), End())) private def genNormalBody(b: Block, clsSym: BlockMemberSymbol)(using HandlerCtx): Block = val tmp = freshTmp("cont") diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 1fae6892eb..6c5c68f105 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -137,6 +137,7 @@ class Lowering(using TL, Raise, Elaborator.State): , privateFlds, publicFlds, + End(), term(Blk(rest2, bodBlk.res))(ImplctRet).mapTail: case Return(Value.Lit(syntax.Tree.UnitLit(true)), true) => End() case t => t 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 6465fff8c5..d4d686d7cd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -80,18 +80,21 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: else err(msg"Illegal reference to builtin symbol '${l.nme}'") case Value.Ref(l) => getVar(l) - case Call(Value.Ref(l: BuiltinSymbol), lhs :: rhs :: Nil) => + case Call(Value.Ref(l: BuiltinSymbol), lhs :: rhs :: Nil) if !l.anyarity => if l.binary then val res = doc"${result(lhs)} ${l.nme} ${result(rhs)}" if needsParens(l.nme) then doc"(${res})" else res else err(msg"Cannot call non-binary builtin symbol '${l.nme}'") - case Call(Value.Ref(l: BuiltinSymbol), rhs :: Nil) => + case Call(Value.Ref(l: BuiltinSymbol), rhs :: Nil) if !l.anyarity => if l.unary then val res = doc"${l.nme} ${result(rhs)}" if needsParens(l.nme) then doc"(${res})" else res else err(msg"Cannot call non-unary builtin symbol '${l.nme}'") case Call(Value.Ref(l: BuiltinSymbol), args) => - err(msg"Illeal arity for builtin symbol '${l.nme}'") + if l.anyarity then + val argsDoc = args.map(result).mkDocument(", ") + doc"${l.nme}(${argsDoc})" + else err(msg"Illeal arity for builtin symbol '${l.nme}'") case c @ Call(fun, args) => val base = fun match @@ -101,7 +104,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: if c.isMlsFun then doc"${base}(${argsDoc})" else doc"${base}(${argsDoc}) ?? null" case Value.Lam(ps, bod) => scope.nest givenIn: val (params, bodyDoc) = setupFunction(none, ps, bod) - doc"($params) => { #{ # ${ + doc"($params) => { #{ ${ bodyDoc } #} # }" case Select(qual, id) => @@ -154,21 +157,22 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: case (ps, block) => Return(Lam(ps, block), false) val (params, bodyDoc) = setupFunction(some(sym.nme), ps, result) - doc"function ${sym.nme}($params) { #{ # ${bodyDoc} #} # }" - case ClsLikeDefn(sym, syntax.Cls, parentSym, mtds, privFlds, _pubFlds, ctor) => + doc"function ${sym.nme}($params) { #{ ${bodyDoc} #} # }" + case ClsLikeDefn(sym, syntax.Cls, parentSym, mtds, privFlds, _pubFlds, preCtor, ctor) => // * Note: `_pubFlds` is not used because in JS, fields are not declared val clsDefn = sym.defn.getOrElse(die) val clsParams = clsDefn.paramsOpt.fold(Nil)(_.paramSyms) val ctorParams = clsParams.map(p => p -> scope.allocateName(p)) - val ctorCode = ctorParams.foldRight(body(ctor)): - case ((sym, nme), acc) => - doc"this.${sym.name} = $nme; # ${acc}" + val preCtorCode = ctorParams.foldLeft(body(preCtor)): + case (acc, (sym, nme)) => + doc"$acc # this.${sym.name} = $nme;" + val ctorCode = doc"$preCtorCode${body(ctor)}" val clsJS = doc"class ${sym.nme}${parentSym.map(p => s" extends ${result(p)}").getOrElse("")} { #{ ${ privFlds.map(f => doc" # #${f.nme};").mkDocument(doc"") } # constructor(${ ctorParams.unzip._2.mkDocument(", ") - }) { #{ ${if parentSym.isDefined then doc" # super();" else ""} # ${ - ctorCode.stripBreaks + }) { #{ ${ + ctorCode } #} # }${ mtds.map: case td @ FunDefn(_, ps :: pss, bod) => @@ -176,7 +180,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: case (ps, block) => Return(Lam(ps, block), false) val (params, bodyDoc) = setupFunction(some(td.sym.nme), ps, result) - doc" # ${td.sym.nme}($params) { #{ # ${ + doc" # ${td.sym.nme}($params) { #{ ${ bodyDoc } #} # }" .mkDocument(" ") @@ -224,9 +228,9 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: thisProxy match case S(proxy) if !scope.thisProxyDefined => scope.thisProxyDefined = true - doc" # const $proxy = this; # ${res.stripBreaks}${returningTerm(rst)}" + doc"const $proxy = this; # $res${returningTerm(rst)}" case _ => doc"$res${returningTerm(rst)}" - doc" # ${resJS}" + doc" # $resJS" case Return(res, true) => doc" # ${result(res)}" case Return(res, false) => doc" # return ${result(res)};" @@ -257,7 +261,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: t :: e :: returningTerm(rest) case Begin(sub, thn) => - doc"${returningTerm(sub)} # ${returningTerm(thn).stripBreaks}" + doc"${returningTerm(sub)}${returningTerm(thn)}" case End("") => doc"" case End(msg) => @@ -301,7 +305,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: val v = doc"this.${getVar(i._1)}" doc"""$v = await import("${i._2.toString }"); # if ($v.default !== undefined) $v = $v.default;""" - imps.mkDocument(doc" # ") :/: block(p.main) :: ( + imps.mkDocument(doc" # ") :/: block(p.main).stripBreaks :: ( exprt match case S(e) => doc"\nexport default ${e};\n" case N => doc"" @@ -310,8 +314,8 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: def block(t: Block)(using Raise, Scope): Document = val vars = t.definedVars.toSeq.filter(scope.lookup(_).isEmpty).sortBy(_.uid).iterator.map(l => l -> scope.allocateName(l)) - if vars.isEmpty then returningTerm(t).stripBreaks else - doc"let " :: vars.map: (_, nme) => + if vars.isEmpty then returningTerm(t) else + doc" # let " :: vars.map: (_, nme) => nme .toList.mkDocument(", ") :: doc";" :: returningTerm(t) @@ -430,12 +434,12 @@ trait JSBuilderArgNumSanityChecks val paramRest = params.restParam.map(p => Scope.scope.allocateName(p.sym)) val paramsStr = Scope.scope.allocateName(functionParamVarargSymbol) val functionName = JSBuilder.makeStringLiteral(name.fold("")(n => s"${JSBuilder.escapeStringCharacters(n)}")) - val checkArgsNum = doc"globalThis.Predef.checkArgs($functionName, ${params.paramCountLB}, ${params.paramCountUB.toString}, $paramsStr.length);\n" + val checkArgsNum = doc"\nglobalThis.Predef.checkArgs($functionName, ${params.paramCountLB}, ${params.paramCountUB.toString}, $paramsStr.length);" val paramsAssign = paramsList.zipWithIndex.map{(nme, i) => - doc"let ${nme} = ${paramsStr}[$i];\n"}.mkDocument("") + doc"\nlet ${nme} = ${paramsStr}[$i];"}.mkDocument("") val restAssign = paramRest match case N => doc"" - case S(p) => doc"let $p = globalThis.Predef.tupleSlice($paramsStr, ${params.paramCountLB}, 0);\n" + case S(p) => doc"\nlet $p = globalThis.Predef.tupleSlice($paramsStr, ${params.paramCountLB}, 0);" (doc"...$paramsStr", doc"$checkArgsNum$paramsAssign$restAssign${this.body(body)}") else super.setupFunction(name, params, body) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 2ed1fdea2e..6d81f48553 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -18,13 +18,15 @@ import Keyword.{`let`, `set`} object Elaborator: - private val binaryOps = Ls( + private val binaryOps = Set( ",", "+", "-", "*", "/", "%", "==", "!=", "<", "<=", ">", ">=", "===", "!==", "&&", "||") private val unaryOps = Set("-", "+", "!", "~") + private val anyOps = Set("super") + private val builtins = binaryOps ++ unaryOps ++ anyOps private val aliasOps = Map( ";" -> ",", "+." -> "+", @@ -109,8 +111,8 @@ object Elaborator: val suid = new Uid.Symbol.State val globalThisSymbol = TopLevelSymbol("globalThis") val builtinOpsMap = - val baseBuiltins = binaryOps.map: op => - op -> BuiltinSymbol(op, binary = true, unary = unaryOps(op), nullary = false) + val baseBuiltins = builtins.map: op => + op -> BuiltinSymbol(op, binary = binaryOps(op), unary = unaryOps(op), nullary = false, anyarity = anyOps(op)) .toMap baseBuiltins ++ aliasOps.map: case (alias, base) => alias -> baseBuiltins(base) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index a5de8e41ff..fec94504ee 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -84,7 +84,7 @@ class VarSymbol(val id: Ident)(using State) extends BlockLocalSymbol(id.name) wi // override def toString: Str = s"$name@$uid" class BuiltinSymbol - (val nme: Str, val binary: Bool, val unary: Bool, val nullary: Bool)(using State) + (val nme: Str, val binary: Bool, val unary: Bool, val nullary: Bool, val anyarity: Bool)(using State) extends Symbol: def toLoc: Option[Loc] = N override def toString: Str = s"builtin:$nme${State.dbgUid(uid)}" diff --git a/hkmc2/shared/src/test/mlscript-compile/Example.mjs b/hkmc2/shared/src/test/mlscript-compile/Example.mjs index cdfaf3c7f4..cd270411d3 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Example.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Example.mjs @@ -1,7 +1,6 @@ import Predef from "./Predef.mjs"; const Example$class = class Example { constructor() { - } funnySlash(f, arg) { return f(arg) ?? null; diff --git a/hkmc2/shared/src/test/mlscript-compile/Option.mjs b/hkmc2/shared/src/test/mlscript-compile/Option.mjs index 915ecac7ef..370c669dfe 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Option.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Option.mjs @@ -5,13 +5,11 @@ const Option$class = class Option { this.Some.class = class Some { constructor(value) { this.value = value; - } toString() { return "Some(" + this.value + ")"; } }; const None$class = class None { constructor() { - } toString() { return "None"; } }; @@ -22,7 +20,6 @@ const Option$class = class Option { constructor(fst, snd) { this.fst = fst; this.snd = snd; - } toString() { return "Both(" + this.fst + ", " + this.snd + ")"; } }; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 915cb23bfe..66de0ebb11 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -4,7 +4,6 @@ const Predef$class = class Predef { this.MatchResult.class = class MatchResult { constructor(captures) { this.captures = captures; - } toString() { return "MatchResult(" + this.captures + ")"; } }; @@ -12,7 +11,6 @@ const Predef$class = class Predef { this.MatchFailure.class = class MatchFailure { constructor(errors) { this.errors = errors; - } toString() { return "MatchFailure(" + this.errors + ")"; } }; @@ -72,7 +70,6 @@ const Predef$class = class Predef { constructor(next, resumed) { this.next = next; this.resumed = resumed; - } toString() { return "__Cont(" + this.next + ", " + this.resumed + ")"; } }; @@ -80,7 +77,6 @@ const Predef$class = class Predef { this.__Return.class = class __Return { constructor(value) { this.value = value; - } toString() { return "__Return(" + this.value + ")"; } }; diff --git a/hkmc2/shared/src/test/mlscript-compile/Stack.mjs b/hkmc2/shared/src/test/mlscript-compile/Stack.mjs index 44352d39ad..16ed29c7eb 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Stack.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Stack.mjs @@ -5,13 +5,11 @@ const Stack$class = class Stack { constructor(head, tail) { this.head = head; this.tail = tail; - } toString() { return "Cons(" + this.head + ", " + this.tail + ")"; } }; const Nil$class = class Nil { constructor() { - } toString() { return "Nil"; } }; diff --git a/hkmc2/shared/src/test/mlscript-compile/Str.mjs b/hkmc2/shared/src/test/mlscript-compile/Str.mjs index e085ceee9e..675362dcc6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Str.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Str.mjs @@ -1,6 +1,5 @@ const Str$class = class Str { constructor() { - } concat(a, b) { return a + b; diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs index c754bfe92f..d145643c0c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs @@ -3,13 +3,11 @@ import Str from "./../Str.mjs"; import Predef from "./../Predef.mjs"; class Num { constructor() { - } toString() { return "Num"; } } class Bool { constructor() { - } toString() { return "Bool"; } } @@ -20,11 +18,9 @@ class Accounting { this.Project.class = class Project { constructor(num) { this.num = num; - } toString() { return "Project(" + this.num + ")"; } }; - const this$Accounting = this; this.Line = function Line(name1, proj1, starting_balance1, isMatchable1) { return new Line.class(name1, proj1, starting_balance1, isMatchable1); }; this.Line.class = class Line { diff --git a/hkmc2/shared/src/test/mlscript/basics/Classes.mls b/hkmc2/shared/src/test/mlscript/basics/Classes.mls index 633cebad33..6313f3afe7 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Classes.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Classes.mls @@ -34,8 +34,8 @@ class Foo(x: Int) { log("Hello!") } //│ ║ l.32: class Foo(x: Int) { log("Hello!") } //│ ╙── ^^^^^^^^^^^^^ //│ JS: -//│ this. = class { constructor() { } toString() { return ""; } }; null -//│ > try { this. = class { constructor() { } toString() { return ""; } }; null } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ this. = class { constructor() { } toString() { return ""; } }; null +//│ > try { this. = class { constructor() { } toString() { return ""; } }; null } catch (e) { console.log('\u200B' + e + '\u200B'); } //│ > ^ //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected token '<' diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls index fcc70e08ad..c26652996d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls @@ -23,7 +23,6 @@ class Outer(a, b) with //│ this.a = a; //│ this.b = b; //│ let tmp, selRes, tmp1, tmp2, tmp3; -//│ //│ const this$Outer = this; //│ this.Inner = function Inner(...args1) { return new Inner.class(...args1); }; //│ this.Inner.class = class Inner { diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls index 1190a8c65c..dcf53e1bdc 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls @@ -40,7 +40,6 @@ fun test(x) = //│ constructor(a, b) { //│ this.a = a; //│ this.b = b; -//│ //│ } //│ toString() { return "Foo(" + this.a + ", " + this.b + ")"; } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls index 9a4a407c7d..375df5588d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls @@ -44,7 +44,6 @@ fun test(a) = //│ g(...args2) { //│ globalThis.Predef.checkArgs("g", 1, true, args2.length); //│ let d = args2[0]; -//│ //│ const this$Inner = this; //│ function h(...args1) { //│ globalThis.Predef.checkArgs("h", 1, true, args1.length); @@ -143,7 +142,6 @@ Foo(123) //│ foo(...args) { //│ globalThis.Predef.checkArgs("foo", 0, true, args.length); //│ let tmp; -//│ //│ const this$Foo = this; //│ function bar(...args1) { //│ globalThis.Predef.checkArgs("bar", 0, true, args1.length); diff --git a/hkmc2/shared/src/test/mlscript/codegen/Modules.mls b/hkmc2/shared/src/test/mlscript/codegen/Modules.mls index 6cdc3c9cda..a94dd83d9b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Modules.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Modules.mls @@ -9,7 +9,6 @@ module None //│ JS: //│ const None$class = class None { //│ constructor() { -//│ //│ } //│ toString() { return "None"; } //│ }; @@ -49,14 +48,12 @@ module M with //│ let tmp; //│ this.C = class C { //│ constructor() { -//│ //│ } //│ toString() { return "C"; } //│ }; //│ this.D = function D(...args) { return new D.class(...args); }; //│ this.D.class = class D { //│ constructor() { -//│ //│ } //│ toString() { return "D(" + + ")"; } //│ }; @@ -89,7 +86,7 @@ M.y :re M.oops //│ ╔══[ERROR] Module 'M' does not contain member 'oops' -//│ ║ l.90: M.oops +//│ ║ l.87: M.oops //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] Error: Access to required field 'oops' yielded 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls index c96aa5a941..b2a52c6518 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls @@ -12,7 +12,6 @@ class Foo() //│ this.Foo = function Foo(...args) { return new Foo.class(...args); }; //│ this.Foo.class = class Foo { //│ constructor() { -//│ //│ } //│ toString() { return "Foo(" + + ")"; } //│ }; @@ -46,7 +45,6 @@ class Foo(a) //│ this.Foo.class = class Foo { //│ constructor(a) { //│ this.a = a; -//│ //│ } //│ toString() { return "Foo(" + this.a + ")"; } //│ }; @@ -93,7 +91,6 @@ class Foo(a, b) //│ constructor(a, b) { //│ this.a = a; //│ this.b = b; -//│ //│ } //│ toString() { return "Foo(" + this.a + ", " + this.b + ")"; } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls index 550cefee91..e72cbd4e8d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls @@ -9,7 +9,7 @@ class Foo //│ JS: -//│ this.Foo = class Foo { constructor() { } toString() { return "Foo"; } }; null +//│ this.Foo = class Foo { constructor() { } toString() { return "Foo"; } }; null Foo is Foo //│ JS: diff --git a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls index c57eb3ab65..cc2b7ca4f8 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls @@ -93,7 +93,6 @@ Cls(1, 2).f(3) //│ constructor(x, y) { //│ this.x = x; //│ this.y = y; -//│ //│ } //│ f(z, p) { //│ let tmp1, tmp2; @@ -119,7 +118,6 @@ Cls(1, 2).f(3) //│ constructor(x, y) { //│ this.x = x; //│ this.y = y; -//│ //│ } //│ f(...args) { //│ globalThis.Predef.checkArgs("f", 2, true, args.length); @@ -149,7 +147,6 @@ Cls(1, 2).f(3, 4)(5) //│ constructor(x, y) { //│ this.x = x; //│ this.y = y; -//│ //│ } //│ f(...args) { //│ globalThis.Predef.checkArgs("f", 2, true, args.length); @@ -221,7 +218,6 @@ if M.A(1).y is //│ this.A.class = class A { //│ constructor(x1) { //│ this.x = x1; -//│ //│ } //│ f(...args) { //│ globalThis.Predef.checkArgs("f", 1, true, args.length); diff --git a/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls b/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls index fecf0fce12..d1310e537a 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls @@ -52,7 +52,7 @@ module \$ extends Foo //│ ╔══[PARSE ERROR] Expected end of input; found error instead //│ ║ l.46: module \$ extends Foo //│ ╙── ^ -//│ > try { const \$class = class \ { constructor() { } toString() { return "\"; } }; this.\ = new \$class; this.\.class = \$class; null } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ > try { const \$class = class \ { constructor() { } toString() { return "\"; } }; this.\ = new \$class; this.\.class = \$class; null } catch (e) { console.log('\u200B' + e + '\u200B'); } //│ > ^ //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Invalid or unexpected token From d07371e68ca60bdd4394554791d5b0e0f1c00c46 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Fri, 20 Dec 2024 23:50:17 +0800 Subject: [PATCH 022/114] Add class inheritance in IR and fixed spacing --- .../src/main/scala/hkmc2/codegen/Block.scala | 2 + .../main/scala/hkmc2/codegen/Lowering.scala | 3 +- .../scala/hkmc2/codegen/js/JSBuilder.scala | 52 ++++++++++--------- .../scala/hkmc2/semantics/Elaborator.scala | 8 +-- .../main/scala/hkmc2/semantics/Symbol.scala | 2 +- .../src/test/mlscript-compile/Example.mjs | 1 - .../src/test/mlscript-compile/Option.mjs | 3 -- .../src/test/mlscript-compile/Predef.mjs | 3 -- .../src/test/mlscript-compile/Stack.mjs | 3 -- .../shared/src/test/mlscript-compile/Str.mjs | 1 - .../test/mlscript-compile/apps/Accounting.mjs | 4 -- .../src/test/mlscript-compile/bbml/Predef.mjs | 1 - .../src/test/mlscript/basics/Classes.mls | 4 +- .../src/test/mlscript/bbml/bbCodeGen.mls | 1 - .../test/mlscript/codegen/ClassInClass.mls | 1 - .../src/test/mlscript/codegen/ClassInFun.mls | 1 - .../src/test/mlscript/codegen/FunInClass.mls | 2 - .../src/test/mlscript/codegen/Getters.mls | 2 - .../src/test/mlscript/codegen/Modules.mls | 5 +- .../test/mlscript/codegen/ParamClasses.mls | 3 -- .../test/mlscript/codegen/PlainClasses.mls | 2 +- .../test/mlscript/codegen/SanityChecks.mls | 4 -- .../test/mlscript/syntax/KeywordStutters.mls | 2 +- 23 files changed, 43 insertions(+), 67 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 2dae028a04..1c7fc4fc77 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -119,9 +119,11 @@ final case class ValDefn( final case class ClsLikeDefn( sym: MemberSymbol[? <: ClassLikeDef], k: syntax.ClsLikeKind, + parentSym: Opt[Path], methods: Ls[FunDefn], privateFields: Ls[TermSymbol], publicFields: Ls[TermDefinition], + preCtor: Block, ctor: Block, ) extends Defn diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index dce6fa3ee5..16d5fd6513 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -129,7 +129,7 @@ class Lowering(using TL, Raise, Elaborator.State): case s => R(s) val publicFlds = rest2.collect: case td @ TermDefinition(k = (_: syntax.Val)) => td - Define(ClsLikeDefn(cls.sym, syntax.Cls, + Define(ClsLikeDefn(cls.sym, syntax.Cls, N, mtds.flatMap: td => td.body.map: bod => val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme)) @@ -137,6 +137,7 @@ class Lowering(using TL, Raise, Elaborator.State): , privateFlds, publicFlds, + End(), term(Blk(rest2, bodBlk.res))(ImplctRet).mapTail: case Return(Value.Lit(syntax.Tree.UnitLit(true)), true) => End() case t => t 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 3996c9b883..9e27df6209 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -87,18 +87,21 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: else err(msg"Illegal reference to builtin symbol '${l.nme}'") case Value.Ref(l) => getVar(l) - case Call(Value.Ref(l: BuiltinSymbol), lhs :: rhs :: Nil) => + case Call(Value.Ref(l: BuiltinSymbol), lhs :: rhs :: Nil) if !l.anyarity => if l.binary then val res = doc"${operand(lhs)} ${l.nme} ${operand(rhs)}" if needsParens(l.nme) then doc"(${res})" else res else err(msg"Cannot call non-binary builtin symbol '${l.nme}'") - case Call(Value.Ref(l: BuiltinSymbol), rhs :: Nil) => + case Call(Value.Ref(l: BuiltinSymbol), rhs :: Nil) if !l.anyarity => if l.unary then val res = doc"${l.nme} ${operand(rhs)}" if needsParens(l.nme) then doc"(${res})" else res else err(msg"Cannot call non-unary builtin symbol '${l.nme}'") case Call(Value.Ref(l: BuiltinSymbol), args) => - err(msg"Illeal arity for builtin symbol '${l.nme}'") + if l.anyarity then + val argsDoc = args.map(argument).mkDocument(", ") + doc"${l.nme}(${argsDoc})" + else err(msg"Illeal arity for builtin symbol '${l.nme}'") case Call(s @ Select(_, id), lhs :: rhs :: Nil) => Elaborator.ctx.Builtins.getBuiltinOp(id.name) match @@ -112,7 +115,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: if c.isMlsFun then doc"${base}(${argsDoc})" else doc"${base}(${argsDoc}) ?? null" case Value.Lam(ps, bod) => scope.nest givenIn: val (params, bodyDoc) = setupFunction(none, ps, bod) - doc"($params) => { #{ # ${ + doc"($params) => { #{ ${ bodyDoc } #} # }" case Select(qual, id) => @@ -159,27 +162,28 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: S(defn.sym).collectFirst{ case s: InnerSymbol => s }): defn match case FunDefn(sym, Nil, body) => - doc"function ${sym.nme}() { #{ # ${this.body(body)} #} # }" + doc"function ${sym.nme}() { #{ ${this.body(body)} #} # }" case FunDefn(sym, ps :: pss, bod) => val result = pss.foldRight(bod): case (ps, block) => Return(Lam(ps, block), false) val (params, bodyDoc) = setupFunction(some(sym.nme), ps, result) - doc"function ${sym.nme}($params) { #{ # ${bodyDoc} #} # }" - case ClsLikeDefn(sym, syntax.Cls, mtds, privFlds, _pubFlds, ctor) => + doc"function ${sym.nme}($params) { #{ ${bodyDoc} #} # }" + case ClsLikeDefn(sym, syntax.Cls, parentSym, mtds, privFlds, _pubFlds, preCtor, ctor) => // * Note: `_pubFlds` is not used because in JS, fields are not declared val clsDefn = sym.defn.getOrElse(die) val clsParams = clsDefn.paramsOpt.fold(Nil)(_.paramSyms) val ctorParams = clsParams.map(p => p -> scope.allocateName(p)) - val ctorCode = ctorParams.foldRight(body(ctor)): - case ((sym, nme), acc) => - doc"this.${sym.name} = $nme; # ${acc}" - val clsJS = doc"class ${sym.nme} { #{ ${ + val preCtorCode = ctorParams.foldLeft(body(preCtor)): + case (acc, (sym, nme)) => + doc"$acc # this.${sym.name} = $nme;" + val ctorCode = doc"$preCtorCode${body(ctor)}" + val clsJS = doc"class ${sym.nme}${parentSym.map(p => s" extends ${result(p)}").getOrElse("")} { #{ ${ privFlds.map(f => doc" # #${f.nme};").mkDocument(doc"") } # constructor(${ ctorParams.unzip._2.mkDocument(", ") - }) { #{ # ${ - ctorCode.stripBreaks + }) { #{ ${ + ctorCode } #} # }${ mtds.map: case td @ FunDefn(_, ps :: pss, bod) => @@ -187,11 +191,11 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: case (ps, block) => Return(Lam(ps, block), false) val (params, bodyDoc) = setupFunction(some(td.sym.nme), ps, result) - doc" # ${td.sym.nme}($params) { #{ # ${ + doc" # ${td.sym.nme}($params) { #{ ${ bodyDoc } #} # }" case td @ FunDefn(_, Nil, bod) => - doc" # get ${td.sym.nme}() { #{ # ${ + doc" # get ${td.sym.nme}() { #{ ${ this.body(bod) } #} # }" .mkDocument(" ") @@ -239,9 +243,9 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: thisProxy match case S(proxy) if !scope.thisProxyDefined => scope.thisProxyDefined = true - doc" # const $proxy = this; # ${res.stripBreaks}${returningTerm(rst)}" + doc"const $proxy = this; # $res${returningTerm(rst)}" case _ => doc"$res${returningTerm(rst)}" - doc" # ${resJS}" + doc" # $resJS" case Return(res, true) => doc" # ${result(res)}" case Return(res, false) => doc" # return ${result(res)};" @@ -292,7 +296,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: t :: e :: returningTerm(rest) case Begin(sub, thn) => - doc"${returningTerm(sub)} # ${returningTerm(thn).stripBreaks}" + doc"${returningTerm(sub)}${returningTerm(thn)}" case End("") => doc"" case End(msg) => @@ -336,7 +340,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: val v = doc"this.${getVar(i._1)}" doc"""$v = await import("${i._2.toString }"); # if ($v.default !== undefined) $v = $v.default;""" - imps.mkDocument(doc" # ") :/: block(p.main) :: ( + imps.mkDocument(doc" # ") :/: block(p.main).stripBreaks :: ( exprt match case S(e) => doc"\nexport default ${e};\n" case N => doc"" @@ -345,8 +349,8 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: def block(t: Block)(using Raise, Scope): Document = val vars = t.definedVars.toSeq.filter(scope.lookup(_).isEmpty).sortBy(_.uid).iterator.map(l => l -> scope.allocateName(l)) - if vars.isEmpty then returningTerm(t).stripBreaks else - doc"let " :: vars.map: (_, nme) => + if vars.isEmpty then returningTerm(t) else + doc" # let " :: vars.map: (_, nme) => nme .toList.mkDocument(", ") :: doc";" :: returningTerm(t) @@ -465,12 +469,12 @@ trait JSBuilderArgNumSanityChecks val paramRest = params.restParam.map(p => Scope.scope.allocateName(p.sym)) val paramsStr = Scope.scope.allocateName(functionParamVarargSymbol) val functionName = JSBuilder.makeStringLiteral(name.fold("")(n => s"${JSBuilder.escapeStringCharacters(n)}")) - val checkArgsNum = doc"globalThis.Predef.checkArgs($functionName, ${params.paramCountLB}, ${params.paramCountUB.toString}, $paramsStr.length);\n" + val checkArgsNum = doc"\nglobalThis.Predef.checkArgs($functionName, ${params.paramCountLB}, ${params.paramCountUB.toString}, $paramsStr.length);" val paramsAssign = paramsList.zipWithIndex.map{(nme, i) => - doc"let ${nme} = ${paramsStr}[$i];\n"}.mkDocument("") + doc"\nlet ${nme} = ${paramsStr}[$i];"}.mkDocument("") val restAssign = paramRest match case N => doc"" - case S(p) => doc"let $p = globalThis.Predef.tupleSlice($paramsStr, ${params.paramCountLB}, 0);\n" + case S(p) => doc"\nlet $p = globalThis.Predef.tupleSlice($paramsStr, ${params.paramCountLB}, 0);" (doc"...$paramsStr", doc"$checkArgsNum$paramsAssign$restAssign${this.body(body)}") else super.setupFunction(name, params, body) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 1407296933..271a43b371 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -18,13 +18,15 @@ import Keyword.{`let`, `set`} object Elaborator: - private val binaryOps = Ls( + private val binaryOps = Set( ",", "+", "-", "*", "/", "%", "==", "!=", "<", "<=", ">", ">=", "===", "&&", "||") private val unaryOps = Set("-", "+", "!", "~") + private val anyOps = Set("super") + private val builtins = binaryOps ++ unaryOps ++ anyOps private val aliasOps = Map( ";" -> ",", "+." -> "+", @@ -124,8 +126,8 @@ object Elaborator: val suid = new Uid.Symbol.State val globalThisSymbol = TopLevelSymbol("globalThis") val builtinOpsMap = - val baseBuiltins = binaryOps.map: op => - op -> BuiltinSymbol(op, binary = true, unary = unaryOps(op), nullary = false) + val baseBuiltins = builtins.map: op => + op -> BuiltinSymbol(op, binary = binaryOps(op), unary = unaryOps(op), nullary = false, anyarity = anyOps(op)) .toMap baseBuiltins ++ aliasOps.map: case (alias, base) => alias -> baseBuiltins(base) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 7f6e40ac34..955f0fe5d7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -84,7 +84,7 @@ class VarSymbol(val id: Ident)(using State) extends BlockLocalSymbol(id.name) wi // override def toString: Str = s"$name@$uid" class BuiltinSymbol - (val nme: Str, val binary: Bool, val unary: Bool, val nullary: Bool)(using State) + (val nme: Str, val binary: Bool, val unary: Bool, val nullary: Bool, val anyarity: Bool)(using State) extends Symbol: def toLoc: Option[Loc] = N override def toString: Str = s"builtin:$nme${State.dbgUid(uid)}" diff --git a/hkmc2/shared/src/test/mlscript-compile/Example.mjs b/hkmc2/shared/src/test/mlscript-compile/Example.mjs index cdfaf3c7f4..cd270411d3 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Example.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Example.mjs @@ -1,7 +1,6 @@ import Predef from "./Predef.mjs"; const Example$class = class Example { constructor() { - } funnySlash(f, arg) { return f(arg) ?? null; diff --git a/hkmc2/shared/src/test/mlscript-compile/Option.mjs b/hkmc2/shared/src/test/mlscript-compile/Option.mjs index c78bb745d7..9b47104522 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Option.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Option.mjs @@ -5,13 +5,11 @@ const Option$class = class Option { this.Some.class = class Some { constructor(value) { this.value = value; - } toString() { return "Some(" + this.value + ")"; } }; const None$class = class None { constructor() { - } toString() { return "None"; } }; @@ -22,7 +20,6 @@ const Option$class = class Option { constructor(fst, snd) { this.fst = fst; this.snd = snd; - } toString() { return "Both(" + this.fst + ", " + this.snd + ")"; } }; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index d7ea60c1b8..9804ddb341 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -5,7 +5,6 @@ const Predef$class = class Predef { this.MatchResult.class = class MatchResult { constructor(captures) { this.captures = captures; - } toString() { return "MatchResult(" + this.captures + ")"; } }; @@ -13,7 +12,6 @@ const Predef$class = class Predef { this.MatchFailure.class = class MatchFailure { constructor(errors) { this.errors = errors; - } toString() { return "MatchFailure(" + this.errors + ")"; } }; @@ -62,7 +60,6 @@ const Predef$class = class Predef { }; this.TraceLogger = new TraceLogger$class; this.TraceLogger.class = TraceLogger$class; - const this$Predef = this; this.Test = class Test { constructor() { diff --git a/hkmc2/shared/src/test/mlscript-compile/Stack.mjs b/hkmc2/shared/src/test/mlscript-compile/Stack.mjs index 72f949c7db..790122e095 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Stack.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Stack.mjs @@ -6,13 +6,11 @@ const Stack$class = class Stack { constructor(head, tail) { this.head = head; this.tail = tail; - } toString() { return "Cons(" + this.head + ", " + this.tail + ")"; } }; const Nil$class = class Nil { constructor() { - } toString() { return "Nil"; } }; @@ -91,7 +89,6 @@ const Stack$class = class Stack { } zip(...xss) { let tmp, tmp1; - const this$Stack = this; function go(heads, tails) { return (caseScrut) => { diff --git a/hkmc2/shared/src/test/mlscript-compile/Str.mjs b/hkmc2/shared/src/test/mlscript-compile/Str.mjs index e085ceee9e..675362dcc6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Str.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Str.mjs @@ -1,6 +1,5 @@ const Str$class = class Str { constructor() { - } concat(a, b) { return a + b; diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs index 93ccd0e92e..c880e266eb 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs @@ -3,13 +3,11 @@ import Str from "./../Str.mjs"; import Predef from "./../Predef.mjs"; class Num { constructor() { - } toString() { return "Num"; } } class Bool { constructor() { - } toString() { return "Bool"; } } @@ -20,11 +18,9 @@ class Accounting { this.Project.class = class Project { constructor(num) { this.num = num; - } toString() { return "Project(" + this.num + ")"; } }; - const this$Accounting = this; this.Line = function Line(name1, proj1, starting_balance1, isMatchable1) { return new Line.class(name1, proj1, starting_balance1, isMatchable1); }; this.Line.class = class Line { diff --git a/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mjs index 11e17f41b0..bbf84f3fd7 100644 --- a/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mjs @@ -1,6 +1,5 @@ const Predef$class = class Predef { constructor() { - } checkArgs(functionName, expected, got) { let scrut, name, scrut1, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; diff --git a/hkmc2/shared/src/test/mlscript/basics/Classes.mls b/hkmc2/shared/src/test/mlscript/basics/Classes.mls index 9371d1ad87..a44726688a 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Classes.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Classes.mls @@ -34,8 +34,8 @@ class Foo(x: Int) { log("Hello!") } //│ ║ l.32: class Foo(x: Int) { log("Hello!") } //│ ╙── ^^^^^^^^^^^^^ //│ JS (unsanitized): -//│ this. = class { constructor() { } toString() { return ""; } }; null -//│ > try { this. = class { constructor() { } toString() { return ""; } }; null } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ this. = class { constructor() { } toString() { return ""; } }; null +//│ > try { this. = class { constructor() { } toString() { return ""; } }; null } catch (e) { console.log('\u200B' + e + '\u200B'); } //│ > ^ //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected token '<' diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls b/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls index 34af4a0aab..25e27fe324 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls @@ -64,7 +64,6 @@ class Foo(x: Int) //│ this.Foo.class = class Foo { //│ constructor(x) { //│ this.x = x; -//│ //│ } //│ toString() { return "Foo(" + this.x + ")"; } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls index 234a5af8b0..ad2a0d3dcd 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls @@ -23,7 +23,6 @@ class Outer(a, b) with //│ this.a = a; //│ this.b = b; //│ let tmp, tmp1, tmp2; -//│ //│ const this$Outer = this; //│ this.Inner = function Inner(c1) { return new Inner.class(c1); }; //│ this.Inner.class = class Inner { diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls index 79fabcab45..7337c94d93 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls @@ -36,7 +36,6 @@ fun test(x) = //│ constructor(a, b) { //│ this.a = a; //│ this.b = b; -//│ //│ } //│ toString() { return "Foo(" + this.a + ", " + this.b + ")"; } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls index 960adadb25..22acc44a6a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls @@ -38,7 +38,6 @@ fun test(a) = //│ ]; //│ } //│ g(d) { -//│ //│ const this$Inner = this; //│ function h(e) { //│ return [ @@ -132,7 +131,6 @@ Foo(123) //│ } //│ foo() { //│ let tmp; -//│ //│ const this$Foo = this; //│ function bar() { //│ return this$Foo.a; diff --git a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls index 9339b124af..6eaba6db93 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls @@ -88,7 +88,6 @@ module M with //│ JS (unsanitized): //│ const M$class = class M { //│ constructor() { -//│ //│ } //│ get t() { //│ return 0; @@ -261,7 +260,6 @@ class Foo(x) with //│ this.Foo.class = class Foo { //│ constructor(x) { //│ this.x = x; -//│ //│ } //│ get oops() { //│ return this.x; diff --git a/hkmc2/shared/src/test/mlscript/codegen/Modules.mls b/hkmc2/shared/src/test/mlscript/codegen/Modules.mls index 3a03e41718..bc192ec9cc 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Modules.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Modules.mls @@ -9,7 +9,6 @@ module None //│ JS (unsanitized): //│ const None$class = class None { //│ constructor() { -//│ //│ } //│ toString() { return "None"; } //│ }; @@ -49,14 +48,12 @@ module M with //│ let tmp; //│ this.C = class C { //│ constructor() { -//│ //│ } //│ toString() { return "C"; } //│ }; //│ this.D = function D() { return new D.class(); }; //│ this.D.class = class D { //│ constructor() { -//│ //│ } //│ toString() { return "D(" + + ")"; } //│ }; @@ -89,7 +86,7 @@ M.y :re M.oops //│ ╔══[ERROR] Module 'M' does not contain member 'oops' -//│ ║ l.90: M.oops +//│ ║ l.87: M.oops //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] Error: Access to required field 'oops' yielded 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls index 86b4312472..3a32b0b460 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls @@ -12,7 +12,6 @@ class Foo() //│ this.Foo = function Foo() { return new Foo.class(); }; //│ this.Foo.class = class Foo { //│ constructor() { -//│ //│ } //│ toString() { return "Foo(" + + ")"; } //│ }; @@ -40,7 +39,6 @@ class Foo(a) //│ this.Foo.class = class Foo { //│ constructor(a) { //│ this.a = a; -//│ //│ } //│ toString() { return "Foo(" + this.a + ")"; } //│ }; @@ -75,7 +73,6 @@ class Foo(a, b) //│ constructor(a, b) { //│ this.a = a; //│ this.b = b; -//│ //│ } //│ toString() { return "Foo(" + this.a + ", " + this.b + ")"; } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls index 0b72eaedce..14579277a3 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls @@ -9,7 +9,7 @@ class Foo //│ JS (unsanitized): -//│ this.Foo = class Foo { constructor() { } toString() { return "Foo"; } }; null +//│ this.Foo = class Foo { constructor() { } toString() { return "Foo"; } }; null Foo is Foo //│ JS (unsanitized): diff --git a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls index e1538492a8..899ad57bf1 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls @@ -93,7 +93,6 @@ Cls(1, 2).f(3) //│ constructor(x, y) { //│ this.x = x; //│ this.y = y; -//│ //│ } //│ f(z, p) { //│ let tmp1, tmp2; @@ -119,7 +118,6 @@ Cls(1, 2).f(3) //│ constructor(x, y) { //│ this.x = x; //│ this.y = y; -//│ //│ } //│ f(...args) { //│ globalThis.Predef.checkArgs("f", 2, true, args.length); @@ -149,7 +147,6 @@ Cls(1, 2).f(3, 4)(5) //│ constructor(x, y) { //│ this.x = x; //│ this.y = y; -//│ //│ } //│ f(...args) { //│ globalThis.Predef.checkArgs("f", 2, true, args.length); @@ -221,7 +218,6 @@ if M.A(1).y is //│ this.A.class = class A { //│ constructor(x1) { //│ this.x = x1; -//│ //│ } //│ f(...args) { //│ globalThis.Predef.checkArgs("f", 1, true, args.length); diff --git a/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls b/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls index fecf0fce12..d1310e537a 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls @@ -52,7 +52,7 @@ module \$ extends Foo //│ ╔══[PARSE ERROR] Expected end of input; found error instead //│ ║ l.46: module \$ extends Foo //│ ╙── ^ -//│ > try { const \$class = class \ { constructor() { } toString() { return "\"; } }; this.\ = new \$class; this.\.class = \$class; null } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ > try { const \$class = class \ { constructor() { } toString() { return "\"; } }; this.\ = new \$class; this.\.class = \$class; null } catch (e) { console.log('\u200B' + e + '\u200B'); } //│ > ^ //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Invalid or unexpected token From be15486a150cacdf39dcdcbfcb8f0c5751ff6040 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Sat, 21 Dec 2024 00:59:44 +0800 Subject: [PATCH 023/114] Remove unnecessary newline in JS Codegen --- .../scala/hkmc2/codegen/js/JSBuilder.scala | 86 +++++++------------ .../scala/hkmc2/utils/document/Document.scala | 7 ++ .../src/test/mlscript-compile/Example.mjs | 3 +- .../src/test/mlscript-compile/Option.mjs | 3 +- .../src/test/mlscript-compile/Stack.mjs | 3 +- .../shared/src/test/mlscript-compile/Str.mjs | 3 +- .../test/mlscript-compile/apps/Accounting.mjs | 6 +- .../src/test/mlscript-compile/bbml/Predef.mjs | 3 +- .../src/test/mlscript/basics/Classes.mls | 4 +- .../src/test/mlscript/codegen/Getters.mls | 3 +- .../src/test/mlscript/codegen/Modules.mls | 11 +-- .../test/mlscript/codegen/ParamClasses.mls | 3 +- .../test/mlscript/codegen/PlainClasses.mls | 2 +- .../test/mlscript/syntax/KeywordStutters.mls | 2 +- 14 files changed, 55 insertions(+), 84 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 9e27df6209..0f730abfa5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -115,9 +115,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: if c.isMlsFun then doc"${base}(${argsDoc})" else doc"${base}(${argsDoc}) ?? null" case Value.Lam(ps, bod) => scope.nest givenIn: val (params, bodyDoc) = setupFunction(none, ps, bod) - doc"($params) => { #{ ${ - bodyDoc - } #} # }" + doc"($params) => ${ braced(bodyDoc) }" case Select(qual, id) => val name = id.name doc"${result(qual)}${ @@ -162,13 +160,13 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: S(defn.sym).collectFirst{ case s: InnerSymbol => s }): defn match case FunDefn(sym, Nil, body) => - doc"function ${sym.nme}() { #{ ${this.body(body)} #} # }" + doc"function ${sym.nme}() ${ braced(this.body(body)) }" case FunDefn(sym, ps :: pss, bod) => val result = pss.foldRight(bod): case (ps, block) => Return(Lam(ps, block), false) val (params, bodyDoc) = setupFunction(some(sym.nme), ps, result) - doc"function ${sym.nme}($params) { #{ ${bodyDoc} #} # }" + doc"function ${sym.nme}($params) ${ braced(bodyDoc) }" case ClsLikeDefn(sym, syntax.Cls, parentSym, mtds, privFlds, _pubFlds, preCtor, ctor) => // * Note: `_pubFlds` is not used because in JS, fields are not declared val clsDefn = sym.defn.getOrElse(die) @@ -182,22 +180,16 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: privFlds.map(f => doc" # #${f.nme};").mkDocument(doc"") } # constructor(${ ctorParams.unzip._2.mkDocument(", ") - }) { #{ ${ - ctorCode - } #} # }${ + }) ${ braced(ctorCode) }${ mtds.map: case td @ FunDefn(_, ps :: pss, bod) => val result = pss.foldRight(bod): case (ps, block) => Return(Lam(ps, block), false) val (params, bodyDoc) = setupFunction(some(td.sym.nme), ps, result) - doc" # ${td.sym.nme}($params) { #{ ${ - bodyDoc - } #} # }" + doc" # ${td.sym.nme}($params) ${ braced(bodyDoc) }" case td @ FunDefn(_, Nil, bod) => - doc" # get ${td.sym.nme}() { #{ ${ - this.body(bod) - } #} # }" + doc" # get ${td.sym.nme}() ${ braced(body(bod)) }" .mkDocument(" ") }${ if mtds.exists(_.sym.nme == "toString") @@ -249,49 +241,28 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: case Return(res, true) => doc" # ${result(res)}" case Return(res, false) => doc" # return ${result(res)};" - // TODO factor out common logic - case Match(scrut, Case.Lit(syntax.Tree.BoolLit(true)) -> trm :: Nil, els, rest) => - val t = doc" # if (${ result(scrut) }) { #{ ${ - returningTerm(trm) - } #} # }" + case Match(scrut, Nil, els, rest) => val e = els match - case S(el) => - doc" else { #{ ${ returningTerm(el) } #} # }" - case N => doc"" - t :: e :: returningTerm(rest) - case Match(scrut, Case.Cls(cls, pth) -> trm :: Nil, els, rest) => + case S(el) => returningTerm(el) + case N => doc"" + e :: returningTerm(rest) + case Match(scrut, hd :: tl, els, rest) => val sd = result(scrut) - val test = cls match - // case _: semantics.ModuleSymbol => doc"=== ${result(pth)}" - case Elaborator.ctx.Builtins.Str => doc"typeof $sd === 'string'" - case Elaborator.ctx.Builtins.Num => doc"typeof $sd === 'number'" - case Elaborator.ctx.Builtins.Int => doc"globalThis.Number.isInteger($sd)" - case _ => doc"$sd instanceof ${result(pth)}" - val t = doc" # if ($test) { #{ ${ - returningTerm(trm) - } #} # }" - val e = els match - case S(el) => - doc" else { #{ ${ returningTerm(el) } #} # }" - case N => doc"" - t :: e :: returningTerm(rest) - case Match(scrut, Case.Lit(lit) -> trm :: Nil, els, rest) => - val t = doc" # if (${ result(scrut) } === ${lit.idStr}) { #{ ${ - returningTerm(trm) - } #} # }" + def cond(cse: Case) = cse match + case Case.Lit(syntax.Tree.BoolLit(true)) => sd + case Case.Lit(lit) => doc"$sd === ${lit.idStr}" + case Case.Cls(cls, pth) => cls match + // case _: semantics.ModuleSymbol => doc"=== ${result(pth)}" + case Elaborator.ctx.Builtins.Str => doc"typeof $sd === 'string'" + case Elaborator.ctx.Builtins.Num => doc"typeof $sd === 'number'" + case Elaborator.ctx.Builtins.Int => doc"globalThis.Number.isInteger($sd)" + case _ => doc"$sd instanceof ${result(pth)}" + case Case.Tup(len, inf) => doc"globalThis.Array.isArray($sd) && $sd.length ${if inf then ">=" else "==="} ${len}" + val h = doc" # if (${ cond(hd._1) }) ${ braced(returningTerm(hd._2)) }" + val t = tl.foldLeft(h)((acc, arm) => acc :: doc" else if (${ cond(arm._1) }) ${ braced(returningTerm(arm._2)) }") val e = els match case S(el) => - doc" else { #{ ${ returningTerm(el) } #} # }" - case N => doc"" - t :: e :: returningTerm(rest) - case Match(scrut, Case.Tup(len, inf) -> trm :: Nil, els, rest) => - val test = doc"globalThis.Array.isArray(${ result(scrut) }) && ${ result(scrut) }.length ${if inf then ">=" else "==="} ${len}" - val t = doc" # if (${ test }) { #{ ${ - returningTerm(trm) - } #} # }" - val e = els match - case S(el) => - doc" else { #{ ${ returningTerm(el) } #} # }" + doc" else ${ braced(returningTerm(el)) }" case N => doc"" t :: e :: returningTerm(rest) @@ -318,8 +289,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: } # break; #} # }${returningTerm(rst)}" case TryBlock(sub, fin, rst) => - doc" # try { #{ ${returningTerm(sub) - } #} # } finally { #{ ${returningTerm(fin)} #} # } # ${ + doc" # try ${ braced(returningTerm(sub)) } finally ${ braced(returningTerm(fin)) } # ${ returningTerm(rst).stripBreaks}" // case _ => ??? @@ -358,6 +328,12 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: def body(t: Block)(using Raise, Scope): Document = scope.nest givenIn: block(t) + def braced(t: Document)(using Raise, Scope): Document = + if t.isEmpty then + doc"{}" + else + doc"{ #{ ${t} #} # }" + def setupFunction(name: Option[Str], params: ParamList, body: Block) (using Raise, Scope): (Document, Document) = val paramsList = params.params.map(p => scope.allocateName(p.sym)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/document/Document.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/document/Document.scala index a51e471ce5..e3a1b10b80 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/document/Document.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/document/Document.scala @@ -32,6 +32,13 @@ abstract class Document { val res = h.stripBreaks if res is h then this else res :: t case _ => this + + def isEmpty: Bool = this match + case DocNil => true + case DocText("") => true + case DocGroup(doc) => doc.isEmpty + case DocCons(hd, tl) => hd.isEmpty && tl.isEmpty + case _ => false /** * Format this document on `writer` and try to set line diff --git a/hkmc2/shared/src/test/mlscript-compile/Example.mjs b/hkmc2/shared/src/test/mlscript-compile/Example.mjs index cd270411d3..99722dab26 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Example.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Example.mjs @@ -1,7 +1,6 @@ import Predef from "./Predef.mjs"; const Example$class = class Example { - constructor() { - } + constructor() {} funnySlash(f, arg) { return f(arg) ?? null; } diff --git a/hkmc2/shared/src/test/mlscript-compile/Option.mjs b/hkmc2/shared/src/test/mlscript-compile/Option.mjs index 9b47104522..95316a2f7d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Option.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Option.mjs @@ -9,8 +9,7 @@ const Option$class = class Option { toString() { return "Some(" + this.value + ")"; } }; const None$class = class None { - constructor() { - } + constructor() {} toString() { return "None"; } }; this.None = new None$class; diff --git a/hkmc2/shared/src/test/mlscript-compile/Stack.mjs b/hkmc2/shared/src/test/mlscript-compile/Stack.mjs index 790122e095..54d67a321d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Stack.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Stack.mjs @@ -10,8 +10,7 @@ const Stack$class = class Stack { toString() { return "Cons(" + this.head + ", " + this.tail + ")"; } }; const Nil$class = class Nil { - constructor() { - } + constructor() {} toString() { return "Nil"; } }; this.Nil = new Nil$class; diff --git a/hkmc2/shared/src/test/mlscript-compile/Str.mjs b/hkmc2/shared/src/test/mlscript-compile/Str.mjs index 675362dcc6..dff6c05470 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Str.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Str.mjs @@ -1,6 +1,5 @@ const Str$class = class Str { - constructor() { - } + constructor() {} concat(a, b) { return a + b; } diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs index c880e266eb..38945b0eb9 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs @@ -2,13 +2,11 @@ import fs from "fs"; import Str from "./../Str.mjs"; import Predef from "./../Predef.mjs"; class Num { - constructor() { - } + constructor() {} toString() { return "Num"; } } class Bool { - constructor() { - } + constructor() {} toString() { return "Bool"; } } class Accounting { diff --git a/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mjs index bbf84f3fd7..dcbc8f7cf0 100644 --- a/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mjs @@ -1,6 +1,5 @@ const Predef$class = class Predef { - constructor() { - } + constructor() {} checkArgs(functionName, expected, got) { let scrut, name, scrut1, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; scrut = got != expected; diff --git a/hkmc2/shared/src/test/mlscript/basics/Classes.mls b/hkmc2/shared/src/test/mlscript/basics/Classes.mls index a44726688a..465fad3a69 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Classes.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Classes.mls @@ -34,8 +34,8 @@ class Foo(x: Int) { log("Hello!") } //│ ║ l.32: class Foo(x: Int) { log("Hello!") } //│ ╙── ^^^^^^^^^^^^^ //│ JS (unsanitized): -//│ this. = class { constructor() { } toString() { return ""; } }; null -//│ > try { this. = class { constructor() { } toString() { return ""; } }; null } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ this. = class { constructor() {} toString() { return ""; } }; null +//│ > try { this. = class { constructor() {} toString() { return ""; } }; null } catch (e) { console.log('\u200B' + e + '\u200B'); } //│ > ^ //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected token '<' diff --git a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls index 6eaba6db93..6e56e95eb0 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls @@ -87,8 +87,7 @@ module M with fun t = 0 //│ JS (unsanitized): //│ const M$class = class M { -//│ constructor() { -//│ } +//│ constructor() {} //│ get t() { //│ return 0; //│ } diff --git a/hkmc2/shared/src/test/mlscript/codegen/Modules.mls b/hkmc2/shared/src/test/mlscript/codegen/Modules.mls index bc192ec9cc..a6f870de8c 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Modules.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Modules.mls @@ -8,8 +8,7 @@ module None //│ JS (unsanitized): //│ const None$class = class None { -//│ constructor() { -//│ } +//│ constructor() {} //│ toString() { return "None"; } //│ }; //│ this.None = new None$class; @@ -47,14 +46,12 @@ module M with //│ constructor() { //│ let tmp; //│ this.C = class C { -//│ constructor() { -//│ } +//│ constructor() {} //│ toString() { return "C"; } //│ }; //│ this.D = function D() { return new D.class(); }; //│ this.D.class = class D { -//│ constructor() { -//│ } +//│ constructor() {} //│ toString() { return "D(" + + ")"; } //│ }; //│ this.x = 1; @@ -86,7 +83,7 @@ M.y :re M.oops //│ ╔══[ERROR] Module 'M' does not contain member 'oops' -//│ ║ l.87: M.oops +//│ ║ l.84: M.oops //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] Error: Access to required field 'oops' yielded 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls index 3a32b0b460..9ee8798740 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls @@ -11,8 +11,7 @@ class Foo() //│ JS (unsanitized): //│ this.Foo = function Foo() { return new Foo.class(); }; //│ this.Foo.class = class Foo { -//│ constructor() { -//│ } +//│ constructor() {} //│ toString() { return "Foo(" + + ")"; } //│ }; //│ null diff --git a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls index 14579277a3..cfaca71901 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls @@ -9,7 +9,7 @@ class Foo //│ JS (unsanitized): -//│ this.Foo = class Foo { constructor() { } toString() { return "Foo"; } }; null +//│ this.Foo = class Foo { constructor() {} toString() { return "Foo"; } }; null Foo is Foo //│ JS (unsanitized): diff --git a/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls b/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls index d1310e537a..ec8615bb29 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls @@ -52,7 +52,7 @@ module \$ extends Foo //│ ╔══[PARSE ERROR] Expected end of input; found error instead //│ ║ l.46: module \$ extends Foo //│ ╙── ^ -//│ > try { const \$class = class \ { constructor() { } toString() { return "\"; } }; this.\ = new \$class; this.\.class = \$class; null } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ > try { const \$class = class \ { constructor() {} toString() { return "\"; } }; this.\ = new \$class; this.\.class = \$class; null } catch (e) { console.log('\u200B' + e + '\u200B'); } //│ > ^ //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Invalid or unexpected token From db5aa6db1852d5dedea3fa0b79978cfdb4401e8d Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Sat, 21 Dec 2024 01:12:04 +0800 Subject: [PATCH 024/114] Random typo fix --- core/shared/main/scala/utils/package.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/shared/main/scala/utils/package.scala b/core/shared/main/scala/utils/package.scala index 3393705f37..0246adc030 100644 --- a/core/shared/main/scala/utils/package.scala +++ b/core/shared/main/scala/utils/package.scala @@ -226,7 +226,7 @@ package object utils { def TODO(msg: Any): Nothing = throw new NotImplementedError( msg.toString + s" (of class ${msg.getClass().getSimpleName()})") def TODO(msg: Any, cond: Bool): Unit = if (cond) TODO(msg) - def die: Nothing = lastWords("Program reached and unexpected state.") + def die: Nothing = lastWords("Program reached an unexpected state.") def lastWords(msg: String): Nothing = throw new Exception(s"Internal Error: $msg") def wat(msg: String, wat: Any): Nothing = lastWords(s"$msg ($wat)") From ef1eabc8b628c97bffbef3366e51d181afcc7cde Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Sat, 21 Dec 2024 02:04:36 +0800 Subject: [PATCH 025/114] Small fix --- hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala | 1 + hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala | 2 +- shared/src/test/diff/nu/BadUCS.mls | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala index 60b605af02..f126bd1349 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -64,6 +64,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: new codegen.Lowering with codegen.LoweringSelSanityChecks(instrument = false) with codegen.LoweringTraceLog(instrument = false) + with codegen.LoweringHandler(handler.isSet) given Elaborator.Ctx = curCtx val jsb = new JSBuilder with JSBuilderArgNumSanityChecks(instrument = false) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index a908abb536..703e9a2db3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -371,7 +371,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State): go(b) val handlerBody = translateBlock(prepareBody(h.body), HandlerCtx(false, false, state => blockBuilder .assignFieldN(state.res.tail, nextIdent, Instantiate(state.cls, Value.Lit(Tree.IntLit(state.uid)) :: Nil)) - .assignFieldN(state.res, tailIdent, state.res.tail.next) + // .assignFieldN(state.res, tailIdent, state.res.tail.next) .ret(SimpleCall(handleBlockImplPath, state.res :: h.lhs.asPath :: Nil)))) val cur: Block => Block = h.handlers.foldLeft(blockBuilder)((builder, handler) => diff --git a/shared/src/test/diff/nu/BadUCS.mls b/shared/src/test/diff/nu/BadUCS.mls index e0bba638d5..e692b9f613 100644 --- a/shared/src/test/diff/nu/BadUCS.mls +++ b/shared/src/test/diff/nu/BadUCS.mls @@ -132,7 +132,7 @@ fun foo(x) = if x is foo() then 0 //│ ╔══[ERROR] can only match on classes and traits //│ ║ l.128: fun foo(x) = if x is foo() then 0 //│ ╙── ^^^ -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached and unexpected state. +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. module Nil class Cons[out A](head: A, tail: Cons[A] | Nil) From 61d2a27cf405def8f40610219540cd658c5d6eb6 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sat, 21 Dec 2024 16:54:09 +0800 Subject: [PATCH 026/114] Add mildly interesting test --- .../mlscript/handlers/RecursiveHandlers.mls | 56 +++++++++++++++++++ .../test/mlscript/handlers/UserThreads.mls | 18 +++--- 2 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls new file mode 100644 index 0000000000..5a38fb9fd4 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -0,0 +1,56 @@ +:js +:handler + + +abstract class Effect[A] with + fun perform(arg: A): A + + +handle h = Effect with + fun perform(arg)(k) = + print("performing " + arg) + k("ok") +h.perform("hi") +h.perform("hello") +//│ > performing hi +//│ > performing hello +//│ = 'ok' + +h +//│ = Effect { perform: [Function: perform] } + +h.perform("oops") +//│ > __Cont { +//│ > next: undefined, +//│ > resumed: undefined, +//│ > tail: [Circular *1], +//│ > tailHandler: [Circular *1], +//│ > handler: Effect { perform: [Function: perform] }, +//│ > handlerFun: [Function (anonymous)] +//│ = } + + +handle h = Effect with + fun perform(arg)(k) = + print("performing " + arg) + k of if arg > 0 + then h.perform(arg - 1) + " " + arg + else "0" +[ + print("–––"); + h.perform(2) + print("–––"); + h.perform(3) +] +//│ > ––– +//│ > performing 2 +//│ > performing 1 +//│ > performing 0 +//│ > ––– +//│ > performing 3 +//│ > performing 2 +//│ > performing 1 +//│ > performing 0 +//│ = [ '0 1 2', '0 1 2 3' ] + + diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls index 7dfba52580..265bef9b13 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls @@ -20,22 +20,20 @@ fun main(h) = print("main end") // task queue -let x = [] +let tasks = [] fun drain() = - while x.length != 0 then - x.pop()() - else - () -//│ x = [] + while tasks.length != 0 do + tasks.pop()() +//│ tasks = [] // LIFO handle h = ThreadEffect with fun fork(thread)(k) = - x.push(thread) + tasks.push(thread) k() drain() fun yld(k) = - x.unshift(k) + tasks.unshift(k) drain() in main(h) @@ -54,11 +52,11 @@ in // FIFO handle h = ThreadEffect with fun fork(thread)(k) = - x.unshift(thread) + tasks.unshift(thread) k() drain() fun yld(k) = - x.unshift(k) + tasks.unshift(k) drain() in main(h) From 564f0201be30c904f383d49c60daf1e7dafda448 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sat, 21 Dec 2024 18:30:38 +0800 Subject: [PATCH 027/114] Add more test cases and reveal some issues --- .../test/mlscript/handlers/LeakingEffects.mls | 70 +++++++++++++++++++ .../mlscript/handlers/RecursiveHandlers.mls | 58 ++++++++++----- .../mlscript/handlers/UserThreadsSafe.mls | 60 ++++++++++++++++ ...{UserThreads.mls => UserThreadsUnsafe.mls} | 7 ++ 4 files changed, 178 insertions(+), 17 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls create mode 100644 hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls rename hkmc2/shared/src/test/mlscript/handlers/{UserThreads.mls => UserThreadsUnsafe.mls} (99%) diff --git a/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls b/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls new file mode 100644 index 0000000000..c3b4310e17 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls @@ -0,0 +1,70 @@ +:js +:handler + + +abstract class Effect[A] with + fun perform(arg: A): A + + +:re +let + xs = [] + count = 0 +fun main() = + handle g = Effect with + fun perform(arg1)(k1) = + print("B " + arg1) + xs.push(k1) + print(g.perform("a")) + print("—") + print(g.perform("b")) + print("—") + print(g.perform("c")) +main() +while xs.pop() is + null then "Done." + f then + set count += 1 + f(count) + print("?") +//│ > B a +//│ > 1 +//│ > — +//│ ═══[RUNTIME ERROR] Error: Unhandled effects +//│ count = 1 +//│ xs = [] + + +:re +handle h = Effect with + fun perform(arg)(k) = + print("A " + arg) + let + xs = [] + count = 0 + handle g = Effect with + fun perform(arg1)(k1) = + print("B " + arg + " " + arg1) + xs.push(k1) + k(g) + while xs.pop() is + null then "Done." + f then + set count += 1 + f(count) +let g = h.perform("hi") +print(g.perform("a")) +print("—") +print(g.perform("b")) +print("—") +print(g.perform("c")) +// set g = h.perform("hola") +// g.perform("adios") +//│ > A hi +//│ > B hi a +//│ > 1 +//│ > — +//│ ═══[RUNTIME ERROR] Error: Unhandled effects +//│ g = Effect { perform: [Function: perform] } + + diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 5a38fb9fd4..49bdef7c81 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -6,20 +6,20 @@ abstract class Effect[A] with fun perform(arg: A): A -handle h = Effect with +handle h1 = Effect with fun perform(arg)(k) = print("performing " + arg) - k("ok") -h.perform("hi") -h.perform("hello") + [k("ok")] +h1.perform("hi") +h1.perform("hello") //│ > performing hi //│ > performing hello -//│ = 'ok' +//│ = [ [ 'ok' ] ] -h +h1 //│ = Effect { perform: [Function: perform] } -h.perform("oops") +h1.perform("oops") //│ > __Cont { //│ > next: undefined, //│ > resumed: undefined, @@ -30,27 +30,51 @@ h.perform("oops") //│ = } -handle h = Effect with +:fixme +handle h2 = Effect with fun perform(arg)(k) = print("performing " + arg) k of if arg > 0 - then h.perform(arg - 1) + " " + arg + then h2.perform(arg - 1) + " " + arg else "0" [ print("–––"); - h.perform(2) + h2.perform(2) print("–––"); - h.perform(3) + h2.perform(3) ] +//│ ╔══[ERROR] Name not found: h2 +//│ ║ l.38: then h2.perform(arg - 1) + " " + arg +//│ ╙── ^^ //│ > ––– //│ > performing 2 -//│ > performing 1 -//│ > performing 0 //│ > ––– //│ > performing 3 -//│ > performing 2 -//│ > performing 1 -//│ > performing 0 -//│ = [ '0 1 2', '0 1 2 3' ] +//│ = [ undefined, undefined ] + + +let res = + handle h = Effect with + fun perform(arg)(k) = + print("A " + arg) + handle g = Effect with + fun perform(arg1)(k1) = + print("B " + arg + " " + arg1) + ["‹", k1(arg), arg1, "›"] + [k(g)] + let g = h.perform("hi") + g.perform("bye") + g.perform("friend") + set g = h.perform("hola") + g.perform("adios") + g.perform("amigos") +in res.toString() +//│ > A hi +//│ > B hi bye +//│ > B hi friend +//│ > A hola +//│ > B hola adios +//│ > B hola amigos +//│ = '‹,‹,‹,‹,hola,friend,›,bye,›,amigos,›,adios,›' diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls new file mode 100644 index 0000000000..b3de60cfa4 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls @@ -0,0 +1,60 @@ +:js +:handler +:todo + + +class ThreadEffect with + // task queue + val tasks = [] + fun drain() = + while tasks.length != 0 do + tasks.pop()() + fun fork(thread: () -> ()): () + fun yld(): () + + +fun f(h, x)() = + print("f " + x) + h.yld() + print("f " + x) + h.yld() + print("f " + x) + +fun main(h) = + print("main start") + h.fork(f(h, 0)) + h.fork(f(h, 1)) + h.fork(f(h, 2)) + print("main end") + + +// LIFO +handle h = ThreadEffect with + fun fork(thread)(k) = + this.tasks.push(thread) + k() + this.drain() + fun yld(k) = + this.tasks.unshift(k) + this.drain() +in + main(h) +//│ > main start +//│ ═══[RUNTIME ERROR] Error: Access to required field 'tasks' yielded 'undefined' + + +// FIFO +handle h = ThreadEffect with + fun fork(thread)(k) = + this.tasks.unshift(thread) + k() + this.drain() + fun yld(k) = + this.tasks.unshift(k) + this.drain() +in + main(h) +//│ > main start +//│ ═══[RUNTIME ERROR] Error: Access to required field 'tasks' yielded 'undefined' + + diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls similarity index 99% rename from hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls rename to hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls index 265bef9b13..604931d6e9 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreads.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls @@ -1,10 +1,12 @@ :js :handler + class ThreadEffect with fun fork(thread: () -> ()): () fun yld(): () + fun f(h, x)() = print("f " + x) h.yld() @@ -19,6 +21,7 @@ fun main(h) = h.fork(f(h, 2)) print("main end") + // task queue let tasks = [] fun drain() = @@ -26,6 +29,7 @@ fun drain() = tasks.pop()() //│ tasks = [] + // LIFO handle h = ThreadEffect with fun fork(thread)(k) = @@ -49,6 +53,7 @@ in //│ > f 1 //│ > f 0 + // FIFO handle h = ThreadEffect with fun fork(thread)(k) = @@ -71,3 +76,5 @@ in //│ > f 0 //│ > f 1 //│ > f 2 + + From 4905f8a345831d19865cf73de9fc44ee9f3dc4cd Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 24 Dec 2024 17:41:45 +0800 Subject: [PATCH 028/114] Use subclass for effect and separate internal class --- .../scala/hkmc2/codegen/HandlerLowering.scala | 29 ++++- .../src/test/mlscript-compile/Predef.mjs | 59 +++++++--- .../src/test/mlscript-compile/Predef.mls | 29 +++-- .../src/test/mlscript/handlers/Effects.mls | 11 +- .../src/test/mlscript/handlers/Generators.mls | 110 ++++++++++++++++++ .../test/mlscript/handlers/LeakingEffects.mls | 2 +- .../mlscript/handlers/RecursiveHandlers.mls | 13 ++- .../mlscript/handlers/ReturnInHandler.mls | 4 +- .../mlscript/handlers/UserThreadsSafe.mls | 4 +- .../mlscript/handlers/UserThreadsUnsafe.mls | 4 +- 10 files changed, 209 insertions(+), 56 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/handlers/Generators.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 703e9a2db3..4cbad2813a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -72,6 +72,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State): .ret(state.res) ) private def handlerCtx(using HandlerCtx): HandlerCtx = summon + private val effectSigPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__EffectSig")).selN(Tree.Ident("class")) private val contClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__Cont")).selN(Tree.Ident("class")) private val retClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__Return")).selN(Tree.Ident("class")) private val handleEffectFun: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__handleEffect")) @@ -374,11 +375,29 @@ class HandlerLowering(using TL, Raise, Elaborator.State): // .assignFieldN(state.res, tailIdent, state.res.tail.next) .ret(SimpleCall(handleBlockImplPath, state.res :: h.lhs.asPath :: Nil)))) - val cur: Block => Block = h.handlers.foldLeft(blockBuilder)((builder, handler) => + // TODO: better class name + val effectClsName = "Effect$" + State.suid.nextUid + val handlers = h.handlers.map(handler => val lam = Value.Lam(PlainParamList(Param(FldFlags.empty, handler.resumeSym, N) :: Nil), translateBlock(handler.body, functionHandlerCtx)) val tmp = freshTmp() - builder.define(FunDefn(handler.sym, handler.params, Return(SimpleCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false)))).assign(h.lhs, Instantiate(h.cls, Nil)) - val body = h.handlers.foldLeft(cur)((builder, handler) => builder.assignFieldN(h.lhs.asPath, Tree.Ident(handler.sym.nme), handler.sym.asPath)).rest(handlerBody) + FunDefn(handler.sym, handler.params, Return(SimpleCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false))) + + val effectClsSym = ClassSymbol( + Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), + Tree.Ident(effectClsName) + ) + effectClsSym.defn = S(ClassDef(N, syntax.Cls, effectClsSym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))))) + // TODO: it seems that our current syntax didn't know how to call super, calling it with empty param list now + val clsDefn = ClsLikeDefn(effectClsSym, syntax.Cls, S(h.cls), handlers, Nil, Nil, + Assign(freshTmp(), SimpleCall(Value.Ref(State.builtinOpsMap("super")), Nil), End()), End()) + + val body = blockBuilder.define(clsDefn).assign(h.lhs, Instantiate(Value.Ref(BlockMemberSymbol(effectClsName, Nil)), Nil)).rest(handlerBody) + + // val cur: Block => Block = h.handlers.foldLeft(blockBuilder)((builder, handler) => + // val lam = Value.Lam(PlainParamList(Param(FldFlags.empty, handler.resumeSym, N) :: Nil), translateBlock(handler.body, functionHandlerCtx)) + // val tmp = freshTmp() + // builder.define(FunDefn(handler.sym, handler.params, Return(SimpleCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false)))).assign(h.lhs, Instantiate(h.cls, Nil)) + // val body = h.handlers.foldLeft(cur)((builder, handler) => builder.assignFieldN(h.lhs.asPath, Tree.Ident(handler.sym.nme), handler.sym.asPath)).rest(handlerBody) val defn = FunDefn(sym, PlainParamList(Nil) :: Nil, body) val result = Define(defn, CallPlaceholder(h.res, freshId(), true, Call(sym.asPath, Nil)(true), h.rest)) result @@ -401,7 +420,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State): .assign(res, c) .ifthen( res.asPath, - Case.Cls(dummyClsSym, contClsPath), + Case.Cls(dummyClsSym, effectSigPath), ReturnCont(res, uid) ) .staticif(canRet, _.ifthen( @@ -484,7 +503,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State): .assign(res, c) .ifthen( res.asPath, - Case.Cls(dummyClsSym, contClsPath), + Case.Cls(dummyClsSym, effectSigPath), handlerCtx.linkAndHandle(LinkState(res.asPath, clsSym.asPath, uid)) ) .staticif(canRet && !handlerCtx.isTopLevel, _.ifthen( diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 5eb8126213..c0320fdc28 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -69,13 +69,42 @@ const Predef$class = class Predef { } toString() { return "Test"; } }; - this.__Cont = function __Cont(next1, resumed1) { return new __Cont.class(next1, resumed1); }; + this.__Cont = function __Cont(next1) { return new __Cont.class(next1); }; this.__Cont.class = class __Cont { - constructor(next, resumed) { + constructor(next) { this.next = next; + } + toString() { return "__Cont(" + this.next + ")"; } + }; + this.__TailList = function __TailList(next1, tail1) { return new __TailList.class(next1, tail1); }; + this.__TailList.class = class __TailList { + constructor(next, tail) { + this.next = next; + this.tail = tail; + } + toString() { return "__TailList(" + this.next + ", " + this.tail + ")"; } + }; + this.__HandleBlock = function __HandleBlock(next1, nextHandler1, handler1) { return new __HandleBlock.class(next1, nextHandler1, handler1); }; + this.__HandleBlock.class = class __HandleBlock { + constructor(next, nextHandler, handler) { + this.next = next; + this.nextHandler = nextHandler; + this.handler = handler; + } + toString() { return "__HandleBlock(" + this.next + ", " + this.nextHandler + ", " + this.handler + ")"; } + }; + this.__EffectSig = function __EffectSig(next1, nextHandler1, tail1, tailHandler1, resumed1, handler1, handlerFun1) { return new __EffectSig.class(next1, nextHandler1, tail1, tailHandler1, resumed1, handler1, handlerFun1); }; + this.__EffectSig.class = class __EffectSig { + constructor(next, nextHandler, tail, tailHandler, resumed, handler, handlerFun) { + this.next = next; + this.nextHandler = nextHandler; + this.tail = tail; + this.tailHandler = tailHandler; this.resumed = resumed; + this.handler = handler; + this.handlerFun = handlerFun; } - toString() { return "__Cont(" + this.next + ", " + this.resumed + ")"; } + toString() { return "__EffectSig(" + this.next + ", " + this.nextHandler + ", " + this.tail + ", " + this.tailHandler + ", " + this.resumed + ", " + this.handler + ", " + this.handlerFun + ")"; } }; this.__Return = function __Return(value1) { return new __Return.class(value1); }; this.__Return.class = class __Return { @@ -174,32 +203,28 @@ const Predef$class = class Predef { } __mkEffect(handler, handlerFun) { let res, tmp; - tmp = new this.__Cont.class(undefined, undefined); + tmp = new this.__EffectSig.class(null, null, null, null, false, handler, handlerFun); res = tmp; res.tail = res; res.tailHandler = res; - res.handler = handler; - res.handlerFun = handlerFun; return res; } __handleBlockImpl(cur, handler1) { let handlerTailList, nxt, scrut, handlerCont, tmp, tmp1, tmp2, tmp3, tmp4; - tmp = new this.__Cont.class(undefined, undefined); + tmp = new this.__TailList.class(null, null); handlerTailList = tmp; handlerTailList.tail = handlerTailList; tmp5: while (true) { if (cur instanceof this.__Return.class) { return cur; } else { - if (cur instanceof this.__Cont.class) { + if (cur instanceof this.__EffectSig.class) { tmp1 = this.__handleEffect(cur, handler1, handlerTailList); nxt = tmp1; scrut = cur === nxt; if (scrut) { - tmp2 = new this.__Cont.class(undefined, undefined); + tmp2 = new this.__HandleBlock.class(handlerTailList.next, null, handler1); handlerCont = tmp2; - handlerCont.next = handlerTailList.next; - handlerCont.handler = handler1; cur.tailHandler.nextHandler = handlerCont; cur.tailHandler = handlerCont; cur.tail = handlerTailList.tail; @@ -222,7 +247,7 @@ const Predef$class = class Predef { let handlerCont, scrut, scrut1, savedNext, scrut2, scrut3, savedNext1, scrut4, scrut5, saved, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; handlerCont = cur1.nextHandler; tmp16: while (true) { - if (handlerCont instanceof this.__Cont.class) { + if (handlerCont instanceof this.__HandleBlock.class) { scrut = handlerCont.handler !== cur1.handler; if (scrut) { handlerCont = handlerCont.nextHandler; @@ -248,13 +273,13 @@ const Predef$class = class Predef { } else { tmp3 = null; } - if (cur1 instanceof this.__Cont.class) { + if (cur1 instanceof this.__EffectSig.class) { return cur1; } else { if (cur1 instanceof this.__Return.class) { return cur1; } else { - tmp4 = this.__resume(handlerCont, undefined, undefined); + tmp4 = this.__resume(handlerCont, null, null); tmp5 = tmp4(cur1) ?? null; cur1 = tmp5; tmp6 = null; @@ -272,7 +297,7 @@ const Predef$class = class Predef { scrut2 = savedNext !== handlerTailList.next; if (scrut2) { handlerTailList.next.next = savedNext; - scrut3 = savedNext === undefined; + scrut3 = savedNext === null; if (scrut3) { handlerTailList.tail = handlerTailList.next; tmp11 = null; @@ -290,7 +315,7 @@ const Predef$class = class Predef { tmp8 = tmp13; } tmp17: while (true) { - if (cur1 instanceof this.__Cont.class) { + if (cur1 instanceof this.__EffectSig.class) { return cur1; } else { if (cur1 instanceof this.__Return.class) { @@ -322,7 +347,7 @@ const Predef$class = class Predef { if (cont instanceof this.__Cont.class) { tmp = cont.resume(value) ?? null; value = tmp; - if (value instanceof this.__Cont.class) { + if (value instanceof this.__EffectSig.class) { value.tail = tail; value.tailHandler.nextHandler = nextHandler; return value; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index a73e3632b2..2fc2f2fa96 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -69,30 +69,29 @@ class Test with // Private definitions for algebraic effects -abstract class __Cont(next, resumed) with +abstract class __Cont(next) with fun resume(value) +class __TailList(next, tail) +class __HandleBlock(next, nextHandler, handler) +class __EffectSig(next, nextHandler, tail, tailHandler, resumed, handler, handlerFun) class __Return(value) fun __mkEffect(handler, handlerFun) = - let res = new __Cont(undefined, undefined) + let res = new __EffectSig(null, null, null, null, false, handler, handlerFun) set res.tail = res set res.tailHandler = res - set res.handler = handler - set res.handlerFun = handlerFun res fun __handleBlockImpl(cur, handler) = - let handlerTailList = new __Cont(undefined, undefined) + let handlerTailList = new __TailList(null, null) set handlerTailList.tail = handlerTailList while cur is __Return then return cur - __Cont then + __EffectSig then let nxt = __handleEffect(cur, handler, handlerTailList) if cur === nxt then - let handlerCont = new __Cont(undefined, undefined) - set handlerCont.next = handlerTailList.next - set handlerCont.handler = handler + let handlerCont = new __HandleBlock(handlerTailList.next, null, handler) set cur.tailHandler.nextHandler = handlerCont set cur.tailHandler = handlerCont set cur.tail = handlerTailList.tail @@ -103,7 +102,7 @@ fun __handleBlockImpl(cur, handler) = fun __handleEffect(cur, handler, handlerTailList) = let handlerCont = cur.nextHandler - while handlerCont is __Cont and handlerCont.handler !== cur.handler then + while handlerCont is __HandleBlock and handlerCont.handler !== cur.handler then set handlerCont = handlerCont.nextHandler else () if handlerCont then @@ -112,25 +111,25 @@ fun __handleEffect(cur, handler, handlerTailList) = if savedNext !== handlerCont.next then set handlerCont.next.next = savedNext else () - if cur is __Cont then + if cur is __EffectSig then return cur else if cur is __Return then return cur else - set cur = __resume(handlerCont, undefined, undefined)(cur) + set cur = __resume(handlerCont, null, null)(cur) else if handler === cur.handler then let savedNext = handlerTailList.next set cur = cur.handlerFun(__resume(cur, handlerTailList, handlerCont)) if savedNext !== handlerTailList.next then set handlerTailList.next.next = savedNext - if savedNext === undefined then + if savedNext === null then set handlerTailList.tail = handlerTailList.next else () else () else return cur while cur is - __Cont then return cur + __EffectSig then return cur __Return then return cur handlerTailList.next is __Cont then let saved = handlerTailList.next.next @@ -144,7 +143,7 @@ fun __resume(cur, tail, handlerCont)(value) = while cont is __Cont then value = cont.resume(value) - if value is __Cont then + if value is __EffectSig then set value.tail = tail set value.tailHandler.nextHandler = nextHandler return value diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 9294150b2c..a26c3aac76 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -158,13 +158,12 @@ fun f() = 3 f() //│ ═══[RUNTIME ERROR] TypeError: globalThis.f is not a function -:fixme fun f(perform) = handle h = Effect with fun perform(arg)(k) = arg perform() f(() => 3) -//│ ═══[RUNTIME ERROR] Error: Function 'perform' expected 1 arguments but got 0 +//│ = 3 abstract class Cell with fun getVal(): Int @@ -174,7 +173,7 @@ let x = 0 let k let m handle h = Cell with - fun getVal(k) = k(x) + fun getVal()(k) = k(x) fun setVal(value)(k) = x = value k(()) @@ -194,7 +193,7 @@ fun fact(e, factvalue) = factvalue * fact(e, factvalue-1) else e.get() handle h = Eff with - fun get(k) = k(1) + fun get()(k) = k(1) fact(h, 5) //│ = 120 @@ -213,7 +212,7 @@ fun sum(depth, x) = x + sum(new_depth, x - 1) else 0 handle h = StackDelay with - fun raise(k) = + fun raise()(k) = // console.trace("Stack unwinded!") k(10) sum(0, 100) @@ -227,7 +226,7 @@ fun sum(depth, x) = x + sum(new_depth, x - 1) else 0 handle h = StackDelay with - fun raise(k) = // TODO: This should codegen to a simple return without instrumentation + fun raise()(k) = // TODO: This should codegen to a simple return without instrumentation k(10) sum(0, 30000) //│ = 450015000 diff --git a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls new file mode 100644 index 0000000000..e733ab15ea --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls @@ -0,0 +1,110 @@ +:js +:handler +import "../../mlscript-compile/Stack.mls" + +abstract class Generator with + fun produce(result): () + +fun permutations_impl(gen, l1, l2) = + print("called with") + print(l1) + print(l2) + if l2 is + [f, ...t] do + print("l2 list match") + print(f) + print(t) + handle genWithPrefix = Generator with + fun produce(result)(resume) = + result.unshift(f) + gen.produce(result) + resume(()) + print("l2 enter handle") + permutations_impl(genWithPrefix, [], l1.concat(t)) + l1.push(f) + print("l2 appended") + print(l1) + permutations_impl(gen, l1, t) + [] and l1 is [] do + gen.produce([]) +fun permutations(gen, l) = + permutations_impl(gen, [], l) + +let res = [] +handle gen = Generator with + fun produce(result)(resume) = + res.push(result) + resume(()) +in permutations(gen, [1, 2, 3]) +res +//│ > called with +//│ > +//│ > 1,2,3 +//│ > l2 list match +//│ > 1 +//│ > 2,3 +//│ > l2 enter handle +//│ > called with +//│ > +//│ > 2,3 +//│ > l2 list match +//│ > 2 +//│ > 3 +//│ > l2 enter handle +//│ > called with +//│ > +//│ > 3 +//│ > l2 list match +//│ > 3 +//│ > +//│ > l2 enter handle +//│ > called with +//│ > +//│ > +//│ > l2 appended +//│ > 3 +//│ > called with +//│ > 3 +//│ > +//│ = [ [ 1, 2, 3 ] ] +//│ res = [ [ 1, 2, 3 ] ] + + +fun permutations_foreach(l, f) = + handle gen = Generator with + fun produce(result)(resume) = + f(result) + resume(()) + permutations(gen, l) + +permutations_foreach([1, 2, 3], print) +//│ > called with +//│ > +//│ > 1,2,3 +//│ > l2 list match +//│ > 1 +//│ > 2,3 +//│ > l2 enter handle +//│ > called with +//│ > +//│ > 2,3 +//│ > l2 list match +//│ > 2 +//│ > 3 +//│ > l2 enter handle +//│ > called with +//│ > +//│ > 3 +//│ > l2 list match +//│ > 3 +//│ > +//│ > l2 enter handle +//│ > called with +//│ > +//│ > +//│ > 1,2,3 +//│ > l2 appended +//│ > 3 +//│ > called with +//│ > 3 +//│ > diff --git a/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls b/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls index c3b4310e17..d3d43e0d7c 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls @@ -65,6 +65,6 @@ print(g.perform("c")) //│ > 1 //│ > — //│ ═══[RUNTIME ERROR] Error: Unhandled effects -//│ g = Effect { perform: [Function: perform] } +//│ g = Effect$480 {} diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 49bdef7c81..7664c24794 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -17,15 +17,16 @@ h1.perform("hello") //│ = [ [ 'ok' ] ] h1 -//│ = Effect { perform: [Function: perform] } +//│ = Effect$178 {} h1.perform("oops") -//│ > __Cont { -//│ > next: undefined, -//│ > resumed: undefined, +//│ > __EffectSig { +//│ > next: null, +//│ > nextHandler: null, //│ > tail: [Circular *1], //│ > tailHandler: [Circular *1], -//│ > handler: Effect { perform: [Function: perform] }, +//│ > resumed: false, +//│ > handler: Effect$178 {}, //│ > handlerFun: [Function (anonymous)] //│ = } @@ -44,7 +45,7 @@ handle h2 = Effect with h2.perform(3) ] //│ ╔══[ERROR] Name not found: h2 -//│ ║ l.38: then h2.perform(arg - 1) + " " + arg +//│ ║ l.39: then h2.perform(arg - 1) + " " + arg //│ ╙── ^^ //│ > ––– //│ > performing 2 diff --git a/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls b/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls index 4416f003e4..abc072573d 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls @@ -6,7 +6,7 @@ abstract class Effect with fun f() = handle h = Effect with - fun f(r) = + fun f()(r) = let m = r() print("Bye!") m @@ -18,7 +18,7 @@ f() fun f() = handle h = Effect with - fun f(r) = + fun f()(r) = let m = r() print("Bye!") m diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls index b3de60cfa4..331a4473b1 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls @@ -34,7 +34,7 @@ handle h = ThreadEffect with this.tasks.push(thread) k() this.drain() - fun yld(k) = + fun yld()(k) = this.tasks.unshift(k) this.drain() in @@ -49,7 +49,7 @@ handle h = ThreadEffect with this.tasks.unshift(thread) k() this.drain() - fun yld(k) = + fun yld()(k) = this.tasks.unshift(k) this.drain() in diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls index 604931d6e9..b4000b5b76 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls @@ -36,7 +36,7 @@ handle h = ThreadEffect with tasks.push(thread) k() drain() - fun yld(k) = + fun yld()(k) = tasks.unshift(k) drain() in @@ -60,7 +60,7 @@ handle h = ThreadEffect with tasks.unshift(thread) k() drain() - fun yld(k) = + fun yld()(k) = tasks.unshift(k) drain() in From c66c73d0d6a7e1f139a1b19c14f55518d77d913e Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 24 Dec 2024 22:06:41 +0800 Subject: [PATCH 029/114] Change how elaboration of handlers work --- .../src/main/scala/hkmc2/codegen/Block.scala | 12 +-- .../scala/hkmc2/codegen/HandlerLowering.scala | 11 +-- .../main/scala/hkmc2/codegen/Lowering.scala | 8 +- .../scala/hkmc2/codegen/js/JSBuilder.scala | 4 +- .../scala/hkmc2/semantics/Elaborator.scala | 14 +++- .../src/main/scala/hkmc2/semantics/Term.scala | 6 +- .../src/test/mlscript-compile/Predef.mjs | 46 +++++------ .../src/test/mlscript-compile/Predef.mls | 24 +++--- .../mlscript/handlers/EffectInHandler.mls | 33 ++++++-- .../src/test/mlscript/handlers/Effects.mls | 12 ++- .../src/test/mlscript/handlers/Generators.mls | 77 ++----------------- .../test/mlscript/handlers/LeakingEffects.mls | 1 - .../mlscript/handlers/RecursiveHandlers.mls | 4 +- .../mlscript/handlers/UserThreadsSafe.mls | 24 +++++- .../src/test/mlscript/parser/Handler.mls | 6 +- 15 files changed, 128 insertions(+), 154 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 2d4b4e0efb..1cb2b0dd89 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -36,7 +36,7 @@ sealed abstract class Block extends Product with AutoLocated: case Break(_) => Set.empty case Continue(_) => Set.empty case Define(defn, rst) => rst.definedVars - case HandleBlock(lhs, res, cls, hdr, bod, rst) => bod.definedVars ++ rst.definedVars + lhs + case HandleBlock(lhs, res, par, cls, hdr, bod, rst) => bod.definedVars ++ rst.definedVars + lhs case HandleBlockReturn(_) => Set.empty case TryBlock(sub, fin, rst) => sub.definedVars ++ fin.definedVars ++ rst.definedVars case Label(lbl, bod, rst) => bod.definedVars ++ rst.definedVars @@ -62,7 +62,7 @@ sealed abstract class Block extends Product with AutoLocated: case Assign(l, r, rst) => Assign(l, r, f(rst)) case b @ AssignField(l, n, r, rst) => AssignField(l, n, r, f(rst))(b.symbol) case Define(defn, rst) => Define(defn, f(rst)) - case HandleBlock(l, res, cls, hdr, bod, rst) => HandleBlock(l, res, cls, hdr, bod, f(rst)) + case HandleBlock(l, res, par, cls, hdr, bod, rst) => HandleBlock(l, res, par, cls, hdr, bod, f(rst)) def mapResult(f: Result => Opt[(Result => Block) => Block]): Block = this match case Return(res, implct) => f(res).map(_(Return(_, implct))).getOrElse(this) @@ -98,8 +98,8 @@ sealed abstract class Block extends Product with AutoLocated: case Begin(sub, rst) => Begin(sub, rst.mapTail(f)) case Assign(lhs, rhs, rst) => Assign(lhs, rhs, rst.mapTail(f)) case Define(defn, rst) => Define(defn, rst.mapTail(f)) - case HandleBlock(lhs, res, cls, handlers, body, rest) => - HandleBlock(lhs, res, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, h.body.mapTail(f))), body.mapTail(f), rest.mapTail(f)) + case HandleBlock(lhs, res, par, cls, handlers, body, rest) => + HandleBlock(lhs, res, par, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, h.body.mapTail(f))), body.mapTail(f), rest.mapTail(f)) case Match(scrut, arms, dflt, rst) => Match(scrut, arms.map(_ -> _.mapTail(f)), dflt.map(_.mapTail(f)), rst.mapTail(f)) @@ -137,7 +137,7 @@ case class AssignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(val case class Define(defn: Defn, rest: Block) extends Block with ProductWithTail -case class HandleBlock(lhs: Local, res: Local, cls: Path, handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail +case class HandleBlock(lhs: Local, res: Local, par: Path, cls: ClassSymbol, handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail case class HandleBlockReturn(res: Result) extends BlockTail sealed abstract class Defn: @@ -159,7 +159,7 @@ final case class ValDefn( final case class ClsLikeDefn( sym: MemberSymbol[? <: ClassLikeDef], k: syntax.ClsLikeKind, - parentSym: Opt[Path], + parentPath: Opt[Path], methods: Ls[FunDefn], privateFields: Ls[TermSymbol], publicFields: Ls[TermDefinition], diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 4cbad2813a..471836fad7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -375,23 +375,16 @@ class HandlerLowering(using TL, Raise, Elaborator.State): // .assignFieldN(state.res, tailIdent, state.res.tail.next) .ret(SimpleCall(handleBlockImplPath, state.res :: h.lhs.asPath :: Nil)))) - // TODO: better class name - val effectClsName = "Effect$" + State.suid.nextUid val handlers = h.handlers.map(handler => val lam = Value.Lam(PlainParamList(Param(FldFlags.empty, handler.resumeSym, N) :: Nil), translateBlock(handler.body, functionHandlerCtx)) val tmp = freshTmp() FunDefn(handler.sym, handler.params, Return(SimpleCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false))) - val effectClsSym = ClassSymbol( - Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), - Tree.Ident(effectClsName) - ) - effectClsSym.defn = S(ClassDef(N, syntax.Cls, effectClsSym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))))) // TODO: it seems that our current syntax didn't know how to call super, calling it with empty param list now - val clsDefn = ClsLikeDefn(effectClsSym, syntax.Cls, S(h.cls), handlers, Nil, Nil, + val clsDefn = ClsLikeDefn(h.cls, syntax.Cls, S(h.par), handlers, Nil, Nil, Assign(freshTmp(), SimpleCall(Value.Ref(State.builtinOpsMap("super")), Nil), End()), End()) - val body = blockBuilder.define(clsDefn).assign(h.lhs, Instantiate(Value.Ref(BlockMemberSymbol(effectClsName, Nil)), Nil)).rest(handlerBody) + val body = blockBuilder.define(clsDefn).assign(h.lhs, Instantiate(Value.Ref(BlockMemberSymbol(h.cls.id.name, Nil)), Nil)).rest(handlerBody) // val cur: Block => Block = h.handlers.foldLeft(blockBuilder)((builder, handler) => // val lam = Value.Lam(PlainParamList(Param(FldFlags.empty, handler.resumeSym, N) :: Nil), translateBlock(handler.body, functionHandlerCtx)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 88b0a44669..0ba418b069 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -310,7 +310,7 @@ class Lowering(using TL, Raise, Elaborator.State): k(Value.Ref(l)) ) - case Handle(lhs, rhs, defs) => + case Handle(lhs, rhs, clsSym, defs) => raise(ErrorReport( msg"Effect handlers are not enabled" -> t.toLoc :: Nil, @@ -486,7 +486,7 @@ trait LoweringHandler override def term(t: st)(k: Result => Block)(using Subst): Block = if !instrument then return super.term(t)(k) t match - case st.Blk(Handle(lhs, rhs, defs) :: stmts, res) => + case st.Blk(Handle(lhs, rhs, cls, defs) :: stmts, res) => val handlers = defs.map { case HandlerTermDefinition(resumeSym, td) => td.body match case None => @@ -497,8 +497,8 @@ trait LoweringHandler S(Handler(td.sym, resumeSym, paramLists, bodyBlock)) }.collect{ case Some(v) => v } val resSym = TempSymbol(S(t)) - subTerm(rhs): cls => - HandleBlock(lhs, resSym, cls, handlers, term(st.Blk(stmts, res))(HandleBlockReturn(_)), k(Value.Ref(resSym))) + subTerm(rhs): par => + HandleBlock(lhs, resSym, par, cls, handlers, term(st.Blk(stmts, res))(HandleBlockReturn(_)), k(Value.Ref(resSym))) case _ => super.term(t)(k) override def topLevel(t: st): Block = if !instrument then return super.topLevel(t) 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 74dad22e8d..8bdcd9b3e1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -167,7 +167,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: Return(Lam(ps, block), false) val (params, bodyDoc) = setupFunction(some(sym.nme), ps, result) doc"function ${sym.nme}($params) ${ braced(bodyDoc) }" - case ClsLikeDefn(sym, syntax.Cls, parentSym, mtds, privFlds, _pubFlds, preCtor, ctor) => + case ClsLikeDefn(sym, syntax.Cls, par, mtds, privFlds, _pubFlds, preCtor, ctor) => // * Note: `_pubFlds` is not used because in JS, fields are not declared val clsDefn = sym.defn.getOrElse(die) val clsParams = clsDefn.paramsOpt.fold(Nil)(_.paramSyms) @@ -176,7 +176,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: case (acc, (sym, nme)) => doc"$acc # this.${sym.name} = $nme;" val ctorCode = doc"$preCtorCode${body(ctor)}" - val clsJS = doc"class ${sym.nme}${parentSym.map(p => s" extends ${result(p)}").getOrElse("")} { #{ ${ + val clsJS = doc"class ${sym.nme}${par.map(p => s" extends ${result(p)}").getOrElse("")} { #{ ${ privFlds.map(f => doc" # #${f.nme};").mkDocument(doc"") } # constructor(${ ctorParams.unzip._2.mkDocument(", ") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 2177fc78eb..e26638bf5e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -601,8 +601,13 @@ extends Importer: case (hd @ Handle(id: Ident, cls: Ident, Block(sts_), N)) :: sts => val sym = fieldOrVarSym(HandlerBind, id) log(s"Processing `handle` statement $id (${sym}) ${ctx.outer}") + + // TODO: shouldn't need uid here + val derivedClsSym = ClassSymbol(Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), Tree.Ident(s"Effect$$${cls.name}$$${State.suid.nextUid}")) + derivedClsSym.defn = S(ClassDef(N, syntax.Cls, derivedClsSym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))))) - val elabed = block(sts_)._1 + val elabed = ctx.nest(S(derivedClsSym)).givenIn: + block(sts_)._1 elabed.res match case Term.Lit(UnitLit(true)) => @@ -623,9 +628,10 @@ extends Importer: None }.collect { case Some(x) => x } - val newAcc = Term.Handle(sym, term(cls), tds) :: acc - ctx + (id.name -> sym) givenIn: - go(sts, newAcc) + val newAcc = Term.Handle(sym, term(cls), derivedClsSym, tds) :: acc + ctx.nest(N).givenIn: + ctx + (id.name -> sym) givenIn: + go(sts, newAcc) case (tree @ Handle(_, _, _, N)) :: sts => raise(ErrorReport(msg"Unsupported handle binding shape" -> tree.toLoc :: Nil)) go(sts, Term.Error :: acc) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 77bebe7dd3..375f7f1bef 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -39,7 +39,7 @@ enum Term extends Statement: case Ret(result: Term) case Throw(result: Term) case Try(body: Term, finallyDo: Term) - case Handle(lhs: LocalSymbol, rhs: Term, defs: Ls[HandlerTermDefinition]) + case Handle(lhs: LocalSymbol, rhs: Term, derivedClsSym: ClassSymbol, defs: Ls[HandlerTermDefinition]) lazy val symbol: Opt[Symbol] = this match case Ref(sym) => S(sym) @@ -128,7 +128,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: pat.paramsOpt.toList.flatMap(_.subTerms) ::: pat.body.blk :: Nil case Import(sym, pth) => Nil case Try(body, finallyDo) => body :: finallyDo :: Nil - case Handle(lhs, rhs, defs) => rhs :: defs.flatMap(_.td.subTerms) + case Handle(lhs, rhs, derivedClsSym, defs) => rhs :: defs.flatMap(_.td.subTerms) case Neg(e) => e :: Nil protected def children: Ls[Located] = this match @@ -180,7 +180,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case Asc(term, ty) => s"${term.toString}: ${ty.toString}" case LetDecl(sym) => s"let ${sym}" case DefineVar(sym, rhs) => s"${sym} = ${rhs.showDbg}" - case Handle(lhs, rhs, defs) => s"handle ${lhs} = ${rhs} ${defs}" + case Handle(lhs, rhs, derivedClsSym, defs) => s"handle ${lhs} = ${rhs} ${defs}" case Region(name, body) => s"region ${name.nme} in ${body.showDbg}" case RegRef(reg, value) => s"(${reg.showDbg}).ref ${value.showDbg}" case Assgn(lhs, rhs) => s"${lhs.showDbg} := ${rhs.showDbg}" diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index c0320fdc28..3dc633bfa6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -210,11 +210,11 @@ const Predef$class = class Predef { return res; } __handleBlockImpl(cur, handler1) { - let handlerTailList, nxt, scrut, handlerCont, tmp, tmp1, tmp2, tmp3, tmp4; + let handlerTailList, nxt, scrut, handlerCont, scrut1, tmp, tmp1, tmp2, tmp3, tmp4, tmp5; tmp = new this.__TailList.class(null, null); handlerTailList = tmp; handlerTailList.tail = handlerTailList; - tmp5: while (true) { + tmp6: while (true) { if (cur instanceof this.__Return.class) { return cur; } else { @@ -227,21 +227,28 @@ const Predef$class = class Predef { handlerCont = tmp2; cur.tailHandler.nextHandler = handlerCont; cur.tailHandler = handlerCont; - cur.tail = handlerTailList.tail; + scrut1 = handlerTailList.next !== null; + if (scrut1) { + cur.tail = handlerTailList.tail; + tmp3 = null; + } else { + cur.tail = handlerCont; + tmp3 = null; + } return cur; } else { cur = nxt; - tmp3 = null; + tmp4 = null; } - tmp4 = tmp3; - continue tmp5; + tmp5 = tmp4; + continue tmp6; } else { return cur; } } break; } - return tmp4; + return tmp5; } __handleEffect(cur1, handler2, handlerTailList) { let handlerCont, scrut, scrut1, savedNext, scrut2, scrut3, savedNext1, scrut4, scrut5, saved, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; @@ -340,10 +347,10 @@ const Predef$class = class Predef { } __resume(cur2, tail, handlerCont) { return (value) => { - let nextHandler, cont, scrut, tmp, tmp1, tmp2, tmp3; + let nextHandler, cont, scrut, tmp, tmp1, tmp2; nextHandler = cur2.nextHandler; cont = cur2.next; - tmp4: while (true) { + tmp3: while (true) { if (cont instanceof this.__Cont.class) { tmp = cont.resume(value) ?? null; value = tmp; @@ -352,30 +359,25 @@ const Predef$class = class Predef { value.tailHandler.nextHandler = nextHandler; return value; } else { - if (value instanceof this.__Return.class) { - return value; - } else { - cont = cont.next; - tmp1 = null; - } - tmp2 = tmp1; + cont = cont.next; + tmp1 = null; } - tmp3 = tmp2; - continue tmp4; + tmp2 = tmp1; + continue tmp3; } else { scrut = nextHandler !== handlerCont; if (scrut) { cont = nextHandler.next; nextHandler = nextHandler.nextHandler; - tmp3 = null; - continue tmp4; + tmp2 = null; + continue tmp3; } else { - tmp3 = value; + tmp2 = value; } } break; } - return tmp3; + return tmp2; }; } toString() { return "Predef"; } diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 2fc2f2fa96..03691c8943 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -94,7 +94,10 @@ fun __handleBlockImpl(cur, handler) = let handlerCont = new __HandleBlock(handlerTailList.next, null, handler) set cur.tailHandler.nextHandler = handlerCont set cur.tailHandler = handlerCont - set cur.tail = handlerTailList.tail + if handlerTailList.next !== null then + set cur.tail = handlerTailList.tail + else + set cur.tail = handlerCont return cur else set cur = nxt @@ -102,15 +105,13 @@ fun __handleBlockImpl(cur, handler) = fun __handleEffect(cur, handler, handlerTailList) = let handlerCont = cur.nextHandler - while handlerCont is __HandleBlock and handlerCont.handler !== cur.handler then + while handlerCont is __HandleBlock and handlerCont.handler !== cur.handler do set handlerCont = handlerCont.nextHandler - else () if handlerCont then let savedNext = handlerCont.next set cur = cur.handlerFun(__resume(cur, handlerCont, handlerCont)) - if savedNext !== handlerCont.next then + if savedNext !== handlerCont.next do set handlerCont.next.next = savedNext - else () if cur is __EffectSig then return cur else if cur is __Return then @@ -120,13 +121,12 @@ fun __handleEffect(cur, handler, handlerTailList) = else if handler === cur.handler then let savedNext = handlerTailList.next set cur = cur.handlerFun(__resume(cur, handlerTailList, handlerCont)) - if savedNext !== handlerTailList.next then + if savedNext !== handlerTailList.next do set handlerTailList.next.next = savedNext - if savedNext === null then + if savedNext === null do set handlerTailList.tail = handlerTailList.next - else () - else () - else return cur + else + return cur while cur is __EffectSig then return cur @@ -139,6 +139,7 @@ fun __handleEffect(cur, handler, handlerTailList) = fun __resume(cur, tail, handlerCont)(value) = let nextHandler = cur.nextHandler + // let tailHandler = cur.tailHandler let cont = cur.next while cont is __Cont then @@ -146,8 +147,7 @@ fun __resume(cur, tail, handlerCont)(value) = if value is __EffectSig then set value.tail = tail set value.tailHandler.nextHandler = nextHandler - return value - else if value is __Return then + // set value.tailHandler = tailHandler return value else set cont = cont.next diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls index 4d7567f459..4593ec3265 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls @@ -2,22 +2,43 @@ :handler class PrintEffect with - fun print(s: String): () + fun p(s: String): () fun aux(s: String): () :e +// :sjs handle h = PrintEffect with - fun print(x)(k) = + fun p(x)(k) = print(x) k(()) fun aux(x)(k) = - h.print(x) + h.p(x) k(()) - h.print(x) + h.p(x) h.aux(3) //│ ╔══[ERROR] Name not found: h -//│ ║ l.14: h.print(x) +//│ ║ l.15: h.p(x) //│ ╙── ^ //│ ╔══[ERROR] Name not found: h -//│ ║ l.16: h.print(x) +//│ ║ l.17: h.p(x) //│ ╙── ^ + +handle h1 = PrintEffect with + fun p(x)(k) = + print(x) + k(()) + fun aux(x)(k) = k(()) +handle h2 = PrintEffect with + fun p(x)(k) = + print(x) + k(()) + fun aux(x)(k) = + h1.p(x) + k(()) + h1.p(x) +h1.p(4) +h2.aux(3) +h1.p(4) +//│ > 4 +//│ > 3 +//│ > 4 diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index a26c3aac76..5b1dd03909 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -107,7 +107,6 @@ result //│ > handler finished //│ > handler finished //│ = 'Hello World!' -//│ result = 'Hello World!' :expect 'Hello World!' fun foo(h) = @@ -149,7 +148,6 @@ result //│ > handler finished //│ > handler finished //│ = 'Hello World!' -//│ result = 'Hello World!' :fixme handle h = Effect with @@ -177,12 +175,12 @@ handle h = Cell with fun setVal(value)(k) = x = value k(()) -k = h.getVal() +print(h.getVal()) h.setVal(1) -m = h.getVal() -//│ k = 0 -//│ m = 1 -//│ x = 1 +print(h.getVal()) +//│ > 0 +//│ > 1 + abstract class Eff with fun get(): Int diff --git a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls index e733ab15ea..342b958b0d 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls @@ -6,70 +6,33 @@ abstract class Generator with fun produce(result): () fun permutations_impl(gen, l1, l2) = - print("called with") - print(l1) - print(l2) if l2 is [f, ...t] do - print("l2 list match") - print(f) - print(t) handle genWithPrefix = Generator with fun produce(result)(resume) = result.unshift(f) gen.produce(result) resume(()) - print("l2 enter handle") permutations_impl(genWithPrefix, [], l1.concat(t)) l1.push(f) - print("l2 appended") - print(l1) permutations_impl(gen, l1, t) [] and l1 is [] do gen.produce([]) fun permutations(gen, l) = permutations_impl(gen, [], l) +:fixme let res = [] handle gen = Generator with fun produce(result)(resume) = res.push(result) resume(()) -in permutations(gen, [1, 2, 3]) +in permutations(gen, [1, 2, 3, 4]) res -//│ > called with -//│ > -//│ > 1,2,3 -//│ > l2 list match -//│ > 1 -//│ > 2,3 -//│ > l2 enter handle -//│ > called with -//│ > -//│ > 2,3 -//│ > l2 list match -//│ > 2 -//│ > 3 -//│ > l2 enter handle -//│ > called with -//│ > -//│ > 3 -//│ > l2 list match -//│ > 3 -//│ > -//│ > l2 enter handle -//│ > called with -//│ > -//│ > -//│ > l2 appended -//│ > 3 -//│ > called with -//│ > 3 -//│ > -//│ = [ [ 1, 2, 3 ] ] -//│ res = [ [ 1, 2, 3 ] ] - +//│ ═══[RUNTIME ERROR] Error: Unhandled effects +//│ res = [ [ 1, 2, 3, 4 ] ] +// FIXME: result is wrong fun permutations_foreach(l, f) = handle gen = Generator with fun produce(result)(resume) = @@ -78,33 +41,5 @@ fun permutations_foreach(l, f) = permutations(gen, l) permutations_foreach([1, 2, 3], print) -//│ > called with -//│ > -//│ > 1,2,3 -//│ > l2 list match -//│ > 1 -//│ > 2,3 -//│ > l2 enter handle -//│ > called with -//│ > -//│ > 2,3 -//│ > l2 list match -//│ > 2 -//│ > 3 -//│ > l2 enter handle -//│ > called with -//│ > -//│ > 3 -//│ > l2 list match -//│ > 3 -//│ > -//│ > l2 enter handle -//│ > called with -//│ > -//│ > //│ > 1,2,3 -//│ > l2 appended -//│ > 3 -//│ > called with -//│ > 3 -//│ > +//│ > 1,3,2 diff --git a/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls b/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls index d3d43e0d7c..5a1cfb9b2c 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/LeakingEffects.mls @@ -65,6 +65,5 @@ print(g.perform("c")) //│ > 1 //│ > — //│ ═══[RUNTIME ERROR] Error: Unhandled effects -//│ g = Effect$480 {} diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 7664c24794..7703d09ba7 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -17,7 +17,7 @@ h1.perform("hello") //│ = [ [ 'ok' ] ] h1 -//│ = Effect$178 {} +//│ = Effect$Effect$131 {} h1.perform("oops") //│ > __EffectSig { @@ -26,7 +26,7 @@ h1.perform("oops") //│ > tail: [Circular *1], //│ > tailHandler: [Circular *1], //│ > resumed: false, -//│ > handler: Effect$178 {}, +//│ > handler: Effect$Effect$131 {}, //│ > handlerFun: [Function (anonymous)] //│ = } diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls index 331a4473b1..eeb7714b7b 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls @@ -40,7 +40,17 @@ handle h = ThreadEffect with in main(h) //│ > main start -//│ ═══[RUNTIME ERROR] Error: Access to required field 'tasks' yielded 'undefined' +//│ > main end +//│ > f 2 +//│ > f 1 +//│ > f 0 +//│ > f 2 +//│ > f 1 +//│ > f 0 +//│ > f 2 +//│ > f 1 +//│ > f 0 +//│ ═══[RUNTIME ERROR] ReferenceError: this$Effect$ThreadEffect$343 is not defined // FIFO @@ -55,6 +65,16 @@ handle h = ThreadEffect with in main(h) //│ > main start -//│ ═══[RUNTIME ERROR] Error: Access to required field 'tasks' yielded 'undefined' +//│ > main end +//│ > f 0 +//│ > f 1 +//│ > f 2 +//│ > f 0 +//│ > f 1 +//│ > f 2 +//│ > f 0 +//│ > f 1 +//│ > f 2 +//│ ═══[RUNTIME ERROR] ReferenceError: this$Effect$ThreadEffect$458 is not defined diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index a1a69d5158..81b58e0621 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { { handle globalThis:block#6.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#6),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#6.h#666) } } +//│ Elab: { { handle globalThis:block#6.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$122),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#6.h#666) } } :e ( @@ -73,7 +73,7 @@ handle h = Eff with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle globalThis:block#8.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#8.h#666) } +//│ Elab: { handle globalThis:block#8.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$151),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$151),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#8.h#666) } :e handle h = Eff with @@ -124,4 +124,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.122: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle globalThis:block#11.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#11.h#666) } +//│ Elab: { handle globalThis:block#11.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$228),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$228),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#11.h#666) } From f8764837573f5d77a9532ca7ead9b73eb2f3dd0c Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 25 Dec 2024 02:54:22 +0800 Subject: [PATCH 030/114] add test --- .../test/mlscript/handlers/NestedHandlers.mls | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls index cc8e485f1f..2960aa7186 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -80,3 +80,28 @@ handleEffects(f) //│ > h2 start 11 //│ > h1 stop //│ > h1 end 10 + +:fixme +:expect 1023 +class Eff with + fun perform(): () +class Box(n) +fun f(h, box, n) = + if n <= 1 then + h.perform() + else + handle h2 = Eff with + fun perform()(k) = + set box.n = box.n + 1 + h.perform() + k() + f(h2, box, n - 1) + f(h2, box, n - 1) +let box = Box(0) +handle h = Eff with + fun perform()(k) = + set box.n = box.n + 1 + k() +f(h, box, 10) +box.n +//│ ═══[RUNTIME ERROR] Error: Unhandled effects From 879eece839eea88ed95491031a7b3a803b50ac12 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 25 Dec 2024 16:11:21 +0800 Subject: [PATCH 031/114] Fixed one example --- .../src/test/mlscript-compile/Predef.mjs | 84 ++++++++++--------- .../src/test/mlscript-compile/Predef.mls | 29 +++---- .../mlscript/handlers/EffectInHandler.mls | 1 + .../src/test/mlscript/handlers/Generators.mls | 5 +- 4 files changed, 61 insertions(+), 58 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 3dc633bfa6..1331b27581 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -84,14 +84,15 @@ const Predef$class = class Predef { } toString() { return "__TailList(" + this.next + ", " + this.tail + ")"; } }; - this.__HandleBlock = function __HandleBlock(next1, nextHandler1, handler1) { return new __HandleBlock.class(next1, nextHandler1, handler1); }; + this.__HandleBlock = function __HandleBlock(next1, nextHandler1, tail1, handler1) { return new __HandleBlock.class(next1, nextHandler1, tail1, handler1); }; this.__HandleBlock.class = class __HandleBlock { - constructor(next, nextHandler, handler) { + constructor(next, nextHandler, tail, handler) { this.next = next; this.nextHandler = nextHandler; + this.tail = tail; this.handler = handler; } - toString() { return "__HandleBlock(" + this.next + ", " + this.nextHandler + ", " + this.handler + ")"; } + toString() { return "__HandleBlock(" + this.next + ", " + this.nextHandler + ", " + this.tail + ", " + this.handler + ")"; } }; this.__EffectSig = function __EffectSig(next1, nextHandler1, tail1, tailHandler1, resumed1, handler1, handlerFun1) { return new __EffectSig.class(next1, nextHandler1, tail1, tailHandler1, resumed1, handler1, handlerFun1); }; this.__EffectSig.class = class __EffectSig { @@ -210,45 +211,36 @@ const Predef$class = class Predef { return res; } __handleBlockImpl(cur, handler1) { - let handlerTailList, nxt, scrut, handlerCont, scrut1, tmp, tmp1, tmp2, tmp3, tmp4, tmp5; - tmp = new this.__TailList.class(null, null); - handlerTailList = tmp; - handlerTailList.tail = handlerTailList; - tmp6: while (true) { + let handlerCont, nxt, scrut, tmp, tmp1, tmp2, tmp3; + tmp = new this.__HandleBlock.class(null, null, null, handler1); + handlerCont = tmp; + handlerCont.tail = handlerCont; + tmp4: while (true) { if (cur instanceof this.__Return.class) { return cur; } else { if (cur instanceof this.__EffectSig.class) { - tmp1 = this.__handleEffect(cur, handler1, handlerTailList); + tmp1 = this.__handleEffect(cur, handler1, handlerCont); nxt = tmp1; scrut = cur === nxt; if (scrut) { - tmp2 = new this.__HandleBlock.class(handlerTailList.next, null, handler1); - handlerCont = tmp2; cur.tailHandler.nextHandler = handlerCont; cur.tailHandler = handlerCont; - scrut1 = handlerTailList.next !== null; - if (scrut1) { - cur.tail = handlerTailList.tail; - tmp3 = null; - } else { - cur.tail = handlerCont; - tmp3 = null; - } + cur.tail = handlerCont.tail; return cur; } else { cur = nxt; - tmp4 = null; + tmp2 = null; } - tmp5 = tmp4; - continue tmp6; + tmp3 = tmp2; + continue tmp4; } else { return cur; } } break; } - return tmp5; + return tmp3; } __handleEffect(cur1, handler2, handlerTailList) { let handlerCont, scrut, scrut1, savedNext, scrut2, scrut3, savedNext1, scrut4, scrut5, saved, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; @@ -270,7 +262,7 @@ const Predef$class = class Predef { } if (handlerCont) { savedNext1 = handlerCont.next; - tmp1 = this.__resume(cur1, handlerCont, handlerCont); + tmp1 = this.__resume(cur1, handlerCont, cur1.handler); tmp2 = cur1.handlerFun(tmp1) ?? null; cur1 = tmp2; scrut4 = savedNext1 !== handlerCont.next; @@ -298,7 +290,7 @@ const Predef$class = class Predef { scrut1 = handler2 === cur1.handler; if (scrut1) { savedNext = handlerTailList.next; - tmp9 = this.__resume(cur1, handlerTailList, handlerCont); + tmp9 = this.__resume(cur1, handlerTailList, handler2); tmp10 = cur1.handlerFun(tmp9) ?? null; cur1 = tmp10; scrut2 = savedNext !== handlerTailList.next; @@ -345,39 +337,51 @@ const Predef$class = class Predef { } return tmp15; } - __resume(cur2, tail, handlerCont) { + __resume(cur2, tail, handler3) { return (value) => { - let nextHandler, cont, scrut, tmp, tmp1, tmp2; + let nextHandler, tailHandler, cont, scrut, scrut1, tmp, tmp1, tmp2, tmp3; nextHandler = cur2.nextHandler; + tailHandler = cur2.tailHandler; cont = cur2.next; - tmp3: while (true) { + tmp4: while (true) { if (cont instanceof this.__Cont.class) { tmp = cont.resume(value) ?? null; value = tmp; if (value instanceof this.__EffectSig.class) { value.tail = tail; - value.tailHandler.nextHandler = nextHandler; + scrut1 = tailHandler !== cur2; + if (scrut1) { + value.tailHandler.nextHandler = nextHandler; + value.tailHandler = tailHandler; + tmp1 = null; + } else { + tmp1 = null; + } return value; } else { cont = cont.next; - tmp1 = null; + tmp2 = null; } - tmp2 = tmp1; - continue tmp3; + tmp3 = tmp2; + continue tmp4; } else { - scrut = nextHandler !== handlerCont; - if (scrut) { - cont = nextHandler.next; - nextHandler = nextHandler.nextHandler; - tmp2 = null; - continue tmp3; + if (nextHandler instanceof this.__HandleBlock.class) { + scrut = nextHandler.handler !== handler3; + if (scrut) { + cont = nextHandler.next; + nextHandler = nextHandler.nextHandler; + tmp3 = null; + continue tmp4; + } else { + tmp3 = value; + } } else { - tmp2 = value; + tmp3 = value; } } break; } - return tmp2; + return tmp3; }; } toString() { return "Predef"; } diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 03691c8943..fd828c259c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -72,7 +72,7 @@ class Test with abstract class __Cont(next) with fun resume(value) class __TailList(next, tail) -class __HandleBlock(next, nextHandler, handler) +class __HandleBlock(next, nextHandler, tail, handler) class __EffectSig(next, nextHandler, tail, tailHandler, resumed, handler, handlerFun) class __Return(value) @@ -84,20 +84,16 @@ fun __mkEffect(handler, handlerFun) = res fun __handleBlockImpl(cur, handler) = - let handlerTailList = new __TailList(null, null) - set handlerTailList.tail = handlerTailList + let handlerCont = new __HandleBlock(null, null, null, handler) + set handlerCont.tail = handlerCont while cur is __Return then return cur __EffectSig then - let nxt = __handleEffect(cur, handler, handlerTailList) + let nxt = __handleEffect(cur, handler, handlerCont) if cur === nxt then - let handlerCont = new __HandleBlock(handlerTailList.next, null, handler) set cur.tailHandler.nextHandler = handlerCont set cur.tailHandler = handlerCont - if handlerTailList.next !== null then - set cur.tail = handlerTailList.tail - else - set cur.tail = handlerCont + set cur.tail = handlerCont.tail return cur else set cur = nxt @@ -109,7 +105,7 @@ fun __handleEffect(cur, handler, handlerTailList) = set handlerCont = handlerCont.nextHandler if handlerCont then let savedNext = handlerCont.next - set cur = cur.handlerFun(__resume(cur, handlerCont, handlerCont)) + set cur = cur.handlerFun(__resume(cur, handlerCont, cur.handler)) if savedNext !== handlerCont.next do set handlerCont.next.next = savedNext if cur is __EffectSig then @@ -120,7 +116,7 @@ fun __handleEffect(cur, handler, handlerTailList) = set cur = __resume(handlerCont, null, null)(cur) else if handler === cur.handler then let savedNext = handlerTailList.next - set cur = cur.handlerFun(__resume(cur, handlerTailList, handlerCont)) + set cur = cur.handlerFun(__resume(cur, handlerTailList, handler)) if savedNext !== handlerTailList.next do set handlerTailList.next.next = savedNext if savedNext === null do @@ -137,21 +133,22 @@ fun __handleEffect(cur, handler, handlerTailList) = set handlerTailList.next = saved else cur -fun __resume(cur, tail, handlerCont)(value) = +fun __resume(cur, tail, handler)(value) = let nextHandler = cur.nextHandler - // let tailHandler = cur.tailHandler + let tailHandler = cur.tailHandler let cont = cur.next while cont is __Cont then value = cont.resume(value) if value is __EffectSig then set value.tail = tail - set value.tailHandler.nextHandler = nextHandler - // set value.tailHandler = tailHandler + if tailHandler !== cur do + set value.tailHandler.nextHandler = nextHandler + set value.tailHandler = tailHandler return value else set cont = cont.next - nextHandler !== handlerCont then + nextHandler is __HandleBlock and nextHandler.handler !== handler then set cont = nextHandler.next set nextHandler = nextHandler.nextHandler else value diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls index 4593ec3265..72805ec5b3 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls @@ -42,3 +42,4 @@ h1.p(4) //│ > 4 //│ > 3 //│ > 4 +//│ > 3 diff --git a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls index 342b958b0d..7cc860e860 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls @@ -40,6 +40,7 @@ fun permutations_foreach(l, f) = resume(()) permutations(gen, l) -permutations_foreach([1, 2, 3], print) +:fixme +res = permutations_foreach([1, 2, 3], print) //│ > 1,2,3 -//│ > 1,3,2 +//│ ═══[RUNTIME ERROR] Error: Unhandled effects From f4e728e2bb4cb43fb91b45196757f4667475da7c Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 25 Dec 2024 16:22:03 +0800 Subject: [PATCH 032/114] add minimal example of effects in handler bug --- .../test/mlscript/handlers/NestedHandlers.mls | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls index 2960aa7186..97ced8be5e 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -105,3 +105,24 @@ handle h = Eff with f(h, box, 10) box.n //│ ═══[RUNTIME ERROR] Error: Unhandled effects + +:fixme +handle h1 = Eff with + fun perform()(k) = + print("h1") + k() +handle h2 = Eff with + fun perform()(k) = + print("h2") + h1.perform() + k() +handle h3 = Eff with + fun perform()(k) = + print("h3") + h2.perform() + k() +h1.perform() +h3.perform() +//│ > h1 +//│ > h3 +//│ ═══[RUNTIME ERROR] Error: Unhandled effects From 643ec6f57e6a06c9b51429eb848a1b5ff07331a7 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 25 Dec 2024 17:34:50 +0800 Subject: [PATCH 033/114] Fixed another test --- .../src/test/mlscript-compile/Predef.mjs | 65 +++++++++++-------- .../src/test/mlscript-compile/Predef.mls | 26 ++++++-- .../test/mlscript/handlers/NestedHandlers.mls | 4 +- 3 files changed, 59 insertions(+), 36 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 1331b27581..a6660b3638 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -243,12 +243,14 @@ const Predef$class = class Predef { return tmp3; } __handleEffect(cur1, handler2, handlerTailList) { - let handlerCont, scrut, scrut1, savedNext, scrut2, scrut3, savedNext1, scrut4, scrut5, saved, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; + let prevCont, handlerCont, scrut, scrut1, savedNext, scrut2, scrut3, origTail, savedNext1, scrut4, scrut5, nxt, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; + prevCont = cur1; handlerCont = cur1.nextHandler; tmp16: while (true) { if (handlerCont instanceof this.__HandleBlock.class) { scrut = handlerCont.handler !== cur1.handler; if (scrut) { + prevCont = handlerCont; handlerCont = handlerCont.nextHandler; tmp = null; continue tmp16; @@ -261,8 +263,11 @@ const Predef$class = class Predef { break; } if (handlerCont) { + origTail = cur1.tailHandler; + prevCont.nextHandler = null; + cur1.tailHandler = prevCont; savedNext1 = handlerCont.next; - tmp1 = this.__resume(cur1, handlerCont, cur1.handler); + tmp1 = this.__resume(cur1, handlerCont); tmp2 = cur1.handlerFun(tmp1) ?? null; cur1 = tmp2; scrut4 = savedNext1 !== handlerCont.next; @@ -273,9 +278,13 @@ const Predef$class = class Predef { tmp3 = null; } if (cur1 instanceof this.__EffectSig.class) { + cur1.tailHandler.nextHandler = handlerCont; + cur1.tailHandler = origTail; return cur1; } else { if (cur1 instanceof this.__Return.class) { + cur1.tailHandler.nextHandler = handlerCont; + cur1.tailHandler = origTail; return cur1; } else { tmp4 = this.__resume(handlerCont, null, null); @@ -290,7 +299,7 @@ const Predef$class = class Predef { scrut1 = handler2 === cur1.handler; if (scrut1) { savedNext = handlerTailList.next; - tmp9 = this.__resume(cur1, handlerTailList, handler2); + tmp9 = this.__resume(cur1, handlerTailList); tmp10 = cur1.handlerFun(tmp9) ?? null; cur1 = tmp10; scrut2 = savedNext !== handlerTailList.next; @@ -322,10 +331,10 @@ const Predef$class = class Predef { } else { scrut5 = handlerTailList.next; if (scrut5 instanceof this.__Cont.class) { - saved = handlerTailList.next.next; - tmp14 = handlerTailList.next.resume(cur1) ?? null; + nxt = handlerTailList.next; + handlerTailList.next = handlerTailList.next.next; + tmp14 = nxt.resume(cur1) ?? null; cur1 = tmp14; - handlerTailList.next = saved; tmp15 = null; continue tmp17; } else { @@ -337,51 +346,53 @@ const Predef$class = class Predef { } return tmp15; } - __resume(cur2, tail, handler3) { + __resume(cur2, tail) { return (value) => { - let nextHandler, tailHandler, cont, scrut, scrut1, tmp, tmp1, tmp2, tmp3; + let scrut, nextHandler, tailHandler, cont, scrut1, tmp, tmp1, tmp2, tmp3, tmp4; + scrut = cur2.resumed; + if (scrut) { + throw Error("multiple resumption"); + } else { + tmp = null; + } + cur2.resumed = true; nextHandler = cur2.nextHandler; tailHandler = cur2.tailHandler; cont = cur2.next; - tmp4: while (true) { + tmp5: while (true) { if (cont instanceof this.__Cont.class) { - tmp = cont.resume(value) ?? null; - value = tmp; + tmp1 = cont.resume(value) ?? null; + value = tmp1; if (value instanceof this.__EffectSig.class) { value.tail = tail; scrut1 = tailHandler !== cur2; if (scrut1) { value.tailHandler.nextHandler = nextHandler; value.tailHandler = tailHandler; - tmp1 = null; + tmp2 = null; } else { - tmp1 = null; + tmp2 = null; } return value; } else { cont = cont.next; - tmp2 = null; + tmp3 = null; } - tmp3 = tmp2; - continue tmp4; + tmp4 = tmp3; + continue tmp5; } else { if (nextHandler instanceof this.__HandleBlock.class) { - scrut = nextHandler.handler !== handler3; - if (scrut) { - cont = nextHandler.next; - nextHandler = nextHandler.nextHandler; - tmp3 = null; - continue tmp4; - } else { - tmp3 = value; - } + cont = nextHandler.next; + nextHandler = nextHandler.nextHandler; + tmp4 = null; + continue tmp5; } else { - tmp3 = value; + tmp4 = value; } } break; } - return tmp3; + return tmp4; }; } toString() { return "Predef"; } diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index fd828c259c..d0b9e946a8 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -100,23 +100,32 @@ fun __handleBlockImpl(cur, handler) = else return cur fun __handleEffect(cur, handler, handlerTailList) = + let prevCont = cur let handlerCont = cur.nextHandler while handlerCont is __HandleBlock and handlerCont.handler !== cur.handler do + set prevCont = handlerCont set handlerCont = handlerCont.nextHandler if handlerCont then + let origTail = cur.tailHandler + set prevCont.nextHandler = null + set cur.tailHandler = prevCont let savedNext = handlerCont.next - set cur = cur.handlerFun(__resume(cur, handlerCont, cur.handler)) + set cur = cur.handlerFun(__resume(cur, handlerCont)) if savedNext !== handlerCont.next do set handlerCont.next.next = savedNext if cur is __EffectSig then + set cur.tailHandler.nextHandler = handlerCont + set cur.tailHandler = origTail return cur else if cur is __Return then + set cur.tailHandler.nextHandler = handlerCont + set cur.tailHandler = origTail return cur else set cur = __resume(handlerCont, null, null)(cur) else if handler === cur.handler then let savedNext = handlerTailList.next - set cur = cur.handlerFun(__resume(cur, handlerTailList, handler)) + set cur = cur.handlerFun(__resume(cur, handlerTailList)) if savedNext !== handlerTailList.next do set handlerTailList.next.next = savedNext if savedNext === null do @@ -128,12 +137,15 @@ fun __handleEffect(cur, handler, handlerTailList) = __EffectSig then return cur __Return then return cur handlerTailList.next is __Cont then - let saved = handlerTailList.next.next - set cur = handlerTailList.next.resume(cur) - set handlerTailList.next = saved + let nxt = handlerTailList.next + set handlerTailList.next = handlerTailList.next.next + set cur = nxt.resume(cur) else cur -fun __resume(cur, tail, handler)(value) = +fun __resume(cur, tail)(value) = + if cur.resumed do + throw Error("multiple resumption") + set cur.resumed = true let nextHandler = cur.nextHandler let tailHandler = cur.tailHandler let cont = cur.next @@ -148,7 +160,7 @@ fun __resume(cur, tail, handler)(value) = return value else set cont = cont.next - nextHandler is __HandleBlock and nextHandler.handler !== handler then + nextHandler is __HandleBlock then set cont = nextHandler.next set nextHandler = nextHandler.nextHandler else value diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls index 97ced8be5e..2706f613e8 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -106,7 +106,6 @@ f(h, box, 10) box.n //│ ═══[RUNTIME ERROR] Error: Unhandled effects -:fixme handle h1 = Eff with fun perform()(k) = print("h1") @@ -125,4 +124,5 @@ h1.perform() h3.perform() //│ > h1 //│ > h3 -//│ ═══[RUNTIME ERROR] Error: Unhandled effects +//│ > h2 +//│ > h1 From dc516cde379ed6e3fc39affdacc2dcbf66257ce4 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 25 Dec 2024 18:39:39 +0800 Subject: [PATCH 034/114] Fix missing thisProxy --- .../src/main/scala/hkmc2/utils/Scope.scala | 5 +-- .../src/test/mlscript/codegen/FunInClass.mls | 31 ++++++++++++++++++- .../mlscript/handlers/UserThreadsSafe.mls | 3 -- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala index a5e6672495..31cb97e323 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala @@ -67,8 +67,9 @@ class Scope def nest: Scope = Scope(Some(this), N, MutMap.empty) - def getOuterThisScope: Opt[Scope] = - curThis.fold(parent)(thisSym => parent.flatMap(_.getOuterThisScope)) + def getThisScope: Opt[Scope] = curThis.fold(parent.flatMap(_.getThisScope))(_ => S(this)) + + def getOuterThisScope: Opt[Scope] = parent.flatMap(_.getThisScope) def nestRebindThis[R](thisSym: Opt[InnerSymbol])(k: Scope ?=> R): (Opt[Str], R) = val nested = Scope(Some(this), S(thisSym), MutMap.empty) diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls index 22acc44a6a..eb3cc97dcf 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls @@ -146,4 +146,33 @@ Foo(123) //│ this.Foo(123) //│ = Foo { a: 123 } - +:sjs +class Bar(x) with + fun foo()() = + fun bar() = + x + bar() + foo()() +Bar(1) +//│ JS (unsanitized): +//│ this.Bar = function Bar(x1) { return new Bar.class(x1); }; +//│ this.Bar.class = class Bar { +//│ constructor(x) { +//│ this.x = x; +//│ let tmp; +//│ tmp = this.foo(); +//│ tmp() ?? null +//│ } +//│ foo() { +//│ return () => { +//│ const this$Bar = this; +//│ function bar() { +//│ return this$Bar.x; +//│ } +//│ return bar(); +//│ }; +//│ } +//│ toString() { return "Bar(" + this.x + ")"; } +//│ }; +//│ this.Bar(1) +//│ = Bar { x: 1 } diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls index eeb7714b7b..46b7629bf9 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls @@ -1,6 +1,5 @@ :js :handler -:todo class ThreadEffect with @@ -50,7 +49,6 @@ in //│ > f 2 //│ > f 1 //│ > f 0 -//│ ═══[RUNTIME ERROR] ReferenceError: this$Effect$ThreadEffect$343 is not defined // FIFO @@ -75,6 +73,5 @@ in //│ > f 0 //│ > f 1 //│ > f 2 -//│ ═══[RUNTIME ERROR] ReferenceError: this$Effect$ThreadEffect$458 is not defined From e6cd1ec2001913735084f82923f096c839bd13a4 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 25 Dec 2024 23:40:18 +0800 Subject: [PATCH 035/114] Fix generator --- .../src/test/mlscript-compile/Predef.mjs | 113 ++++++++++++------ .../src/test/mlscript-compile/Predef.mls | 89 +++++++++++--- .../src/test/mlscript/handlers/Generators.mls | 29 +++-- .../test/mlscript/handlers/NestedHandlers.mls | 2 +- .../mlscript/handlers/RecursiveHandlers.mls | 24 +++- 5 files changed, 197 insertions(+), 60 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index a6660b3638..2dad64b583 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -216,27 +216,23 @@ const Predef$class = class Predef { handlerCont = tmp; handlerCont.tail = handlerCont; tmp4: while (true) { - if (cur instanceof this.__Return.class) { - return cur; - } else { - if (cur instanceof this.__EffectSig.class) { - tmp1 = this.__handleEffect(cur, handler1, handlerCont); - nxt = tmp1; - scrut = cur === nxt; - if (scrut) { - cur.tailHandler.nextHandler = handlerCont; - cur.tailHandler = handlerCont; - cur.tail = handlerCont.tail; - return cur; - } else { - cur = nxt; - tmp2 = null; - } - tmp3 = tmp2; - continue tmp4; - } else { + if (cur instanceof this.__EffectSig.class) { + tmp1 = this.__handleEffect(cur, handler1, handlerCont); + nxt = tmp1; + scrut = cur === nxt; + if (scrut) { + cur.tailHandler.nextHandler = handlerCont; + cur.tailHandler = handlerCont; + cur.tail = handlerCont.tail; return cur; + } else { + cur = nxt; + tmp2 = null; } + tmp3 = tmp2; + continue tmp4; + } else { + return cur; } break; } @@ -287,7 +283,7 @@ const Predef$class = class Predef { cur1.tailHandler = origTail; return cur1; } else { - tmp4 = this.__resume(handlerCont, null, null); + tmp4 = this.__resume(handlerCont, null); tmp5 = tmp4(cur1) ?? null; cur1 = tmp5; tmp6 = null; @@ -348,7 +344,7 @@ const Predef$class = class Predef { } __resume(cur2, tail) { return (value) => { - let scrut, nextHandler, tailHandler, cont, scrut1, tmp, tmp1, tmp2, tmp3, tmp4; + let scrut, cont, scrut1, scrut2, scrut3, scrut4, nxt, scrut5, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17, tmp18, tmp19, tmp20, tmp21, tmp22; scrut = cur2.resumed; if (scrut) { throw Error("multiple resumption"); @@ -356,19 +352,17 @@ const Predef$class = class Predef { tmp = null; } cur2.resumed = true; - nextHandler = cur2.nextHandler; - tailHandler = cur2.tailHandler; cont = cur2.next; - tmp5: while (true) { + tmp23: while (true) { if (cont instanceof this.__Cont.class) { tmp1 = cont.resume(value) ?? null; value = tmp1; if (value instanceof this.__EffectSig.class) { value.tail = tail; - scrut1 = tailHandler !== cur2; + scrut1 = cur2.tailHandler !== cur2; if (scrut1) { - value.tailHandler.nextHandler = nextHandler; - value.tailHandler = tailHandler; + value.tailHandler.nextHandler = cur2.nextHandler; + value.tailHandler = cur2.tailHandler; tmp2 = null; } else { tmp2 = null; @@ -379,20 +373,69 @@ const Predef$class = class Predef { tmp3 = null; } tmp4 = tmp3; - continue tmp5; + continue tmp23; } else { - if (nextHandler instanceof this.__HandleBlock.class) { - cont = nextHandler.next; - nextHandler = nextHandler.nextHandler; - tmp4 = null; - continue tmp5; + tmp4 = null; + } + break; + } + tmp24: while (true) { + scrut2 = cur2.nextHandler; + if (scrut2 instanceof this.__HandleBlock.class) { + scrut4 = cur2.nextHandler.next; + if (scrut4 instanceof this.__Cont.class) { + nxt = cur2.nextHandler.next; + tmp5 = nxt.resume(value) ?? null; + value = tmp5; + if (value instanceof this.__EffectSig.class) { + tmp6 = cur2.tailHandler !== cur2; + tmp7 = this.assert(tmp6) ?? null; + tmp8 = cur2.nextHandler !== null; + tmp9 = this.assert(tmp8) ?? null; + scrut5 = cur2.nextHandler.next === value.tail.next; + if (scrut5) { + value.tail.next = null; + tmp10 = null; + } else { + cur2.nextHandler.next = cur2.nextHandler.next.next; + tmp10 = null; + } + value.tail = tail; + value.tailHandler.nextHandler = cur2.nextHandler; + value.tailHandler = cur2.tailHandler; + return value; + } else { + tmp11 = cur2.nextHandler.next !== cur2.nextHandler.next.next; + tmp12 = this.assert(tmp11) ?? null; + cur2.nextHandler.next = cur2.nextHandler.next.next; + tmp13 = null; + } + tmp14 = tmp13; + continue tmp24; } else { - tmp4 = value; + scrut3 = true; + if (scrut3) { + tmp15 = cur2.nextHandler.next === null; + tmp16 = this.assert(tmp15) ?? null; + tmp17 = cur2.nextHandler !== cur2.nextHandler.nextHandler; + tmp18 = this.assert(tmp17) ?? null; + cur2.nextHandler = cur2.nextHandler.nextHandler; + tmp14 = null; + continue tmp24; + } else { + tmp19 = cur2.nextHandler === null; + tmp20 = this.assert(tmp19) ?? null; + return value; + } } + } else { + tmp21 = cur2.nextHandler === null; + tmp22 = this.assert(tmp21) ?? null; + return value; } break; } - return tmp4; + return tmp14; }; } toString() { return "Predef"; } diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index d0b9e946a8..c87bc24b03 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -83,11 +83,18 @@ fun __mkEffect(handler, handlerFun) = set res.tailHandler = res res +// fun checkEffectHead(cur) = +// if cur is __EffectSig then +// let hand = cur +// while hand.nextHandler !== null do +// set hand = hand.nextHandler +// assert(hand === cur.tailHandler) +// assert(cur.resumed === false) + fun __handleBlockImpl(cur, handler) = let handlerCont = new __HandleBlock(null, null, null, handler) set handlerCont.tail = handlerCont while cur is - __Return then return cur __EffectSig then let nxt = __handleEffect(cur, handler, handlerCont) if cur === nxt then @@ -122,7 +129,7 @@ fun __handleEffect(cur, handler, handlerTailList) = set cur.tailHandler = origTail return cur else - set cur = __resume(handlerCont, null, null)(cur) + set cur = __resume(handlerCont, null)(cur) else if handler === cur.handler then let savedNext = handlerTailList.next set cur = cur.handlerFun(__resume(cur, handlerTailList)) @@ -142,26 +149,80 @@ fun __handleEffect(cur, handler, handlerTailList) = set cur = nxt.resume(cur) else cur +// fun __resume(cur, tail)(value) = +// if cur.resumed do +// throw Error("multiple resumption") +// set cur.resumed = true +// let cont = cur.next +// while +// cont is __Cont then +// value = cont.resume(value) +// if value is __EffectSig then +// set value.tail = tail +// if cur.tailHandler !== cur do +// set value.tailHandler.nextHandler = cur.nextHandler +// set value.tailHandler = cur.tailHandler +// return value +// else +// set cont = cont.next +// cur.nextHandler is __HandleBlock then +// set cont = cur.nextHandler.next +// set cur.nextHandler = cur.nextHandler.nextHandler +// else value + fun __resume(cur, tail)(value) = if cur.resumed do throw Error("multiple resumption") set cur.resumed = true - let nextHandler = cur.nextHandler - let tailHandler = cur.tailHandler + let cont = cur.next while - cont is __Cont then - value = cont.resume(value) + cont is __Cont do + // NOTE: we do not need to link to cont.next again if it is effsig + // because cont.resume will not change its own next + set value = cont.resume(value) if value is __EffectSig then set value.tail = tail - if tailHandler !== cur do - set value.tailHandler.nextHandler = nextHandler - set value.tailHandler = tailHandler + if cur.tailHandler !== cur do + set value.tailHandler.nextHandler = cur.nextHandler + set value.tailHandler = cur.tailHandler return value else set cont = cont.next - nextHandler is __HandleBlock then - set cont = nextHandler.next - set nextHandler = nextHandler.nextHandler - else value - + // We're done with the head, now resume the handle blocks + while + cur.nextHandler is __HandleBlock and + cur.nextHandler.next is __Cont then + // resuming tailHandlerList or post handler continuations + let nxt = cur.nextHandler.next + set value = nxt.resume(value) + if value is __EffectSig then + // console.dir(cur.nextHandler.next) + // console.dir(value.tail.next) + // console.dir(value) + // console.dir(cur) + // console.dir(nxt) + // console.log(nxt.resume.toString()) + assert(cur.tailHandler !== cur) + assert(cur.nextHandler !== null) + // This checks when continuation resume results in tail call to effectful func + if cur.nextHandler.next === value.tail.next then + set value.tail.next = null + else + set cur.nextHandler.next = cur.nextHandler.next.next + set value.tail = tail + set value.tailHandler.nextHandler = cur.nextHandler + set value.tailHandler = cur.tailHandler + return value + else + assert(cur.nextHandler.next !== cur.nextHandler.next.next) + set cur.nextHandler.next = cur.nextHandler.next.next + // here using else will emit the wrong code and cause null to be returned from the loop + true then + // the list is empty, go to next handle block + assert(cur.nextHandler.next === null) + assert(cur.nextHandler !== cur.nextHandler.nextHandler) + set cur.nextHandler = cur.nextHandler.nextHandler + else + assert(cur.nextHandler === null) + return value diff --git a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls index 7cc860e860..88fd16353c 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls @@ -21,26 +21,37 @@ fun permutations_impl(gen, l1, l2) = fun permutations(gen, l) = permutations_impl(gen, [], l) -:fixme let res = [] handle gen = Generator with fun produce(result)(resume) = res.push(result) resume(()) in permutations(gen, [1, 2, 3, 4]) -res -//│ ═══[RUNTIME ERROR] Error: Unhandled effects -//│ res = [ [ 1, 2, 3, 4 ] ] +//│ > [ +//│ > [ 1, 2, 3, 4 ], [ 1, 2, 4, 3 ], +//│ > [ 1, 3, 2, 4 ], [ 1, 3, 4, 2 ], +//│ > [ 1, 4, 2, 3 ], [ 1, 4, 3, 2 ], +//│ > [ 2, 1, 3, 4 ], [ 2, 1, 4, 3 ], +//│ > [ 2, 3, 1, 4 ], [ 2, 3, 4, 1 ], +//│ > [ 2, 4, 1, 3 ], [ 2, 4, 3, 1 ], +//│ > [ 3, 1, 2, 4 ], [ 3, 1, 4, 2 ], +//│ > [ 3, 2, 1, 4 ], [ 3, 2, 4, 1 ], +//│ > [ 3, 4, 1, 2 ], [ 3, 4, 2, 1 ], +//│ > [ 4, 1, 2, 3 ], [ 4, 1, 3, 2 ], +//│ > [ 4, 2, 1, 3 ], [ 4, 2, 3, 1 ], +//│ > [ 4, 3, 1, 2 ], [ 4, 3, 2, 1 ] +//│ res = ] -// FIXME: result is wrong fun permutations_foreach(l, f) = handle gen = Generator with fun produce(result)(resume) = f(result) resume(()) permutations(gen, l) - -:fixme -res = permutations_foreach([1, 2, 3], print) +permutations_foreach([1, 2, 3], print) //│ > 1,2,3 -//│ ═══[RUNTIME ERROR] Error: Unhandled effects +//│ > 1,3,2 +//│ > 2,1,3 +//│ > 2,3,1 +//│ > 3,1,2 +//│ > 3,2,1 diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls index 2706f613e8..8f2c666c53 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -104,7 +104,7 @@ handle h = Eff with k() f(h, box, 10) box.n -//│ ═══[RUNTIME ERROR] Error: Unhandled effects +//│ ═══[RUNTIME ERROR] Expected: 1023, got: 5120 handle h1 = Eff with fun perform()(k) = diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 7703d09ba7..f3993811f7 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -78,4 +78,26 @@ in res.toString() //│ > B hola amigos //│ = '‹,‹,‹,‹,hola,friend,›,bye,›,amigos,›,adios,›' - +let res = + print("A " + "hi") + handle g = Effect with + fun perform(arg1)(k1) = + print("B " + "hi" + " " + arg1) + ["‹", k1("hi"), arg1, "›"] + g.perform("bye") + g.perform("adios") + print("A " + "hola") + handle g2 = Effect with + fun perform(arg1)(k1) = + print("B " + "hola" + " " + arg1) + ["‹", k1("hola"), arg1, "›"] + g2.perform("adios") + g2.perform("amigos") +in res.toString() +//│ > A hi +//│ > B hi bye +//│ > B hi adios +//│ > A hola +//│ > B hola adios +//│ > B hola amigos +//│ = '‹,‹,‹,‹,hola,amigos,›,adios,›,adios,›,bye,›' From 8a6d15c183194fd2b72bc66c7ed8cd7c8393bfb8 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 25 Dec 2024 23:46:11 +0800 Subject: [PATCH 036/114] Fix typo --- .../shared/src/test/mlscript/handlers/RecursiveHandlers.mls | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index f3993811f7..a6598e6bf0 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -85,7 +85,7 @@ let res = print("B " + "hi" + " " + arg1) ["‹", k1("hi"), arg1, "›"] g.perform("bye") - g.perform("adios") + g.perform("friend") print("A " + "hola") handle g2 = Effect with fun perform(arg1)(k1) = @@ -96,8 +96,8 @@ let res = in res.toString() //│ > A hi //│ > B hi bye -//│ > B hi adios +//│ > B hi friend //│ > A hola //│ > B hola adios //│ > B hola amigos -//│ = '‹,‹,‹,‹,hola,amigos,›,adios,›,adios,›,bye,›' +//│ = '‹,‹,‹,‹,hola,amigos,›,adios,›,friend,›,bye,›' From 2e0705834f5e6356a4784e28c9f2082a560cd5d3 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Fri, 27 Dec 2024 17:55:37 +0800 Subject: [PATCH 037/114] Fix function calls and clases sometimes not being in scope after transformation --- .../src/main/scala/hkmc2/codegen/Block.scala | 95 ++++++++++++++++ .../scala/hkmc2/codegen/HandlerLowering.scala | 103 +++++++++++++++++- .../src/test/mlscript/handlers/Effects.mls | 15 +++ .../test/mlscript/handlers/NestedHandlers.mls | 19 ++++ .../mlscript/handlers/RecursiveHandlers.mls | 20 ++++ 5 files changed, 249 insertions(+), 3 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 1cb2b0dd89..83c33580dd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -12,6 +12,8 @@ import hkmc2.semantics.{Term => st} import syntax.{Literal, Tree} import semantics.* import semantics.Term.* +import scala.reflect.ClassTag +import sem.Elaborator.State case class Program( @@ -86,6 +88,99 @@ sealed abstract class Block extends Product with AutoLocated: case r => N } + def mapSyms(f: Local => Local)(using State): Block = + def g(t: Local) = t match + case t: TermSymbol => + TermSymbol(t.k, t.owner.map(symMap), t.id) + + case _ => f(t) + def symMap[T <: Local : ClassTag](l: T): T = g(l) match + case r : T => r + case _ => l + + + // def resMap[T <: Result] + + def pMap(p: Param) = Param(p.flags, symMap(p.sym), p.sign) + def pListMap(p: ParamList) = ParamList(p.flags, p.params.map(pMap), p.restParam.map(pMap)) + def pathMap(p: Path): Path = p match + case s @ Select(qual, name) => Select(pathMap(qual), name)(s.symbol.map(symMap)) + case Value.Ref(l) => Value.Ref(symMap(l)) + case Value.This(sym) => Value.This(symMap(sym)) + case Value.Lit(lit) => p + case Value.Lam(params, body) => Value.Lam( + pListMap(params), + body.mapSyms(symMap) + ) + case Value.Arr(elems) => Value.Arr(elems.map(a => Arg(a.spread, pathMap(a.value)))) + + def _resMap(r: Result) = r match + case p: Path => pathMap(p) + case c @ Call(fun, args) => Call(pathMap(fun), args.map(a => Arg(a.spread, pathMap(a.value))))(c.isMlsFun) + case Instantiate(cls, args) => Instantiate(pathMap(cls), args.map(pathMap)) + + def resMap[T <: Result : ClassTag](l: T): T = _resMap(l) match + case r : T => r + case _ => l + + this match + case Return(res, implct) => Return(resMap(res), implct) + case Throw(exc: Path) => Throw(pathMap(exc)) + case Assign(l, r, rst) => Assign(symMap(l), resMap(r), rst.mapSyms(symMap)) + case blk @ AssignField(l, n, r, rst) => AssignField(pathMap(l), n, _resMap(r), rst.mapSyms(symMap))(blk.symbol) + case Match(scrut, arms, dflt, rst) => + val newArms = arms.map((cse, blk) => + val newCse = cse match + case Case.Lit(lit) => cse + case Case.Cls(cls, path) => Case.Cls(symMap(cls), pathMap(path)) + case Case.Tup(len, inf) => cse + (newCse, blk.mapSyms(symMap)) + ) + Match(pathMap(scrut), newArms, dflt.map(_.mapSyms(symMap)), rst.mapSyms(symMap)) + case Define(d, rst) => + val newDefn = d match + case FunDefn(sym, params, body) => + FunDefn(symMap(sym), params.map(pListMap), body.mapSyms(symMap)) + case ValDefn(owner, k, sym, rhs) => + ValDefn(owner.map(symMap), k, symMap(sym), pathMap(rhs)) + case ClsLikeDefn(sym, k, parentPath, methods, privateFields, publicFields, preCtor, ctor) => + ClsLikeDefn( + symMap(sym), k, parentPath.map(pathMap), + methods.map(fDef => FunDefn(symMap(fDef.sym), fDef.params.map(pListMap), fDef.body.mapSyms(symMap))), + privateFields.map(symMap), + publicFields.map(t => TermDefinition( + t.owner.map(symMap), + t.k, + symMap(t.sym), + t.params.map(pListMap), + t.sign, + t.body, + symMap(t.resSym), + t.flags + )), + preCtor.mapSyms(symMap), + ctor.mapSyms(symMap) + ) + Define(newDefn, rst.mapSyms(symMap)) + + case HandleBlockReturn(res: Path) => HandleBlockReturn(pathMap(res)) + case Throw(res) => Throw(resMap(res)) + case Label(lbl, body, rest) => Label(symMap(lbl), body.mapSyms(symMap), rest.mapSyms(symMap)) + case HandleBlockReturn(res) => HandleBlockReturn(resMap(res)) + case Begin(sub, rest) => Begin(sub.mapSyms(symMap), rest.mapSyms(symMap)) + case TryBlock(sub, fin, rest) => TryBlock(sub.mapSyms(symMap), fin.mapSyms(symMap), rest.mapSyms(symMap)) + case HandleBlock(lhs, res, par, cls, handlers, body, rest) => + HandleBlock( + symMap(lhs), symMap(res), pathMap(par), symMap(cls), + handlers.map(h => Handler(symMap(h.sym), symMap(h.resumeSym), h.params.map(pListMap), body.mapSyms(symMap))), + body.mapSyms(symMap), + rest.mapSyms(symMap) + ) + case Break(s) => Break(symMap(s)) + case Continue(s) => Continue(symMap(s)) + case End(_) => this + + def mapValue(f: Value => Value): Block = def go(p: Path): Path = p match case sel: Select => Select(go(sel.qual), sel.name)(sel.symbol) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 471836fad7..993f864437 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -317,10 +317,12 @@ class HandlerLowering(using TL, Raise, Elaborator.State): * b) generate normal function body */ - private def translateBlock(b: Block, h: HandlerCtx): Block = + private def translateBlock(b: Block, h: HandlerCtx, topLevel: Bool = false): Block = given HandlerCtx = h val stage1 = firstPass(b) - secondPass(stage1) + val stage2 = secondPass(stage1) + if topLevel then stage2 + else thirdPass(stage2) private def firstPass(b: Block)(using HandlerCtx): Block = b.map(firstPass) match @@ -348,11 +350,106 @@ class HandlerLowering(using TL, Raise, Elaborator.State): case None => genNormalBody(b, BlockMemberSymbol("", Nil)) case Some(cls) => Define(cls, genNormalBody(b, BlockMemberSymbol(cls.sym.nme, Nil))) + private val thirdPassFresh = FreshId() + // moves definitions to the top level of the block + private def thirdPass(b: Block): Block = + + val (blk, defns) = popDefns(b, Nil) + val syms = defns.collect { + case ClsLikeDefn(sym, k, parentPath, methods, privateFields, publicFields, preCtor, ctor) => sym + case FunDefn(sym, params, body) => sym + } + val newSyms: Map[Symbol, Symbol] = syms.map { + case sym: BlockMemberSymbol => + sym -> BlockMemberSymbol(sym.nme + "$" + thirdPassFresh(), sym.trees) + case sym: ClassSymbol => + val newSym = ClassSymbol(sym.tree, Tree.Ident(sym.id.name + "$" + + thirdPassFresh())) + newSym.defn = sym.defn + sym -> newSym + case sym: ModuleSymbol => + sym -> ModuleSymbol(sym.tree, Tree.Ident(sym.id.name + "$" + + thirdPassFresh())) + case sym => sym -> sym + }.toMap + + val clsNmeMap = newSyms.collect { + case (c1: ClassSymbol, c2: ClassSymbol) => c1.id.name -> c2.id.name + }.toMap + + def symMap(l: Local) = newSyms.get(l) match + case None => l match + case b: BlockMemberSymbol => clsNmeMap.get(b.nme) match + case None => l + case Some(value) => BlockMemberSymbol(value, b.trees) + case _ => l + case Some(value) => value + + + val newBlk = defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) + newBlk.mapSyms(symMap) + private def translateFun(f: FunDefn): FunDefn = FunDefn(f.sym, f.params, translateBlock(f.body, functionHandlerCtx)) private def translateCls(cls: ClsLikeDefn): ClsLikeDefn = cls.copy(methods = cls.methods.map(translateFun), ctor = translateBlock(cls.ctor, functionHandlerCtx)) + + // to ensure the fun and class reference sin the continuation class is properly scoped, + // we move all function defns to the top level of the handler block + def popDefns(b: Block, acc: List[Defn]): (Block, List[Defn]) = + b match + case Match(scrut, arms, dflt, rest) => + val (armsRes, armsDefns) = arms.foldLeft[(List[(Case, Block)], List[Defn])](Nil, acc)( + (accc, d) => + val (accCases, accDefns) = accc + val (cse, blk) = d + val (resBlk, resDefns) = popDefns(blk, accDefns) + ((cse, resBlk) :: accCases, resDefns) + ) + dflt match + case None => + val (rstRes, rstDefns) = popDefns(rest, armsDefns) + (Match(scrut, armsRes, None, rstRes), rstDefns) + + case Some(dflt) => + val (dfltRes, dfltDefns) = popDefns(dflt, armsDefns) + val (rstRes, rstDefns) = popDefns(rest, dfltDefns) + (Match(scrut, armsRes, S(dfltRes), rstRes), rstDefns) + + case Return(res, implct) => (b, acc) + case Throw(exc) => (b, acc) + case Label(label, body, rest) => + val (bodyRes, bodyDefns) = popDefns(body, acc) + val (rstRes, rstDefns) = popDefns(rest, bodyDefns) + (Label(label, bodyRes, rstRes), rstDefns) + case Break(label) => (b, acc) + case Continue(label) => (b, acc) + case Begin(sub, rest) => + val (subRes, subDefns) = popDefns(sub, acc) + val (rstRes, rstDefns) = popDefns(rest, subDefns) + (Begin(subRes, rstRes), rstDefns) + case TryBlock(sub, finallyDo, rest) => + val (subRes, subDefns) = popDefns(sub, acc) + val (finallyRes, finallyDefns) = popDefns(rest, subDefns) + val (rstRes, rstDefns) = popDefns(rest, finallyDefns) + (TryBlock(subRes, finallyRes, rstRes), rstDefns) + case Assign(lhs, rhs, rest) => + val (rstRes, rstDefns) = popDefns(rest, acc) + (Assign(lhs, rhs, rstRes), rstDefns) + case a @ AssignField(path, nme, result, rest) => + val (rstRes, rstDefns) = popDefns(rest, acc) + (AssignField(path, nme, result, rstRes)(a.symbol), rstDefns) + case Define(defn, rest) => defn match + case ValDefn(owner, k, sym, rhs) => + val (rstRes, rstDefns) = popDefns(rest, acc) + (Define(defn, rstRes), rstDefns) + case _ => + val (rstRes, rstDefns) = popDefns(rest, defn :: acc) + (rstRes, rstDefns) + case HandleBlock(lhs, res, par, cls, handlers, body, rest) => + val (rstRes, rstDefns) = popDefns(rest, acc) + (HandleBlock(lhs, res, par, cls, handlers, body, rstRes), rstDefns) + case HandleBlockReturn(res) => (b, acc) + case End(msg) => (b, acc) // Handle block becomes a FunDefn and CallPlaceholder private def translateHandleBlock(h: HandleBlock): Block = @@ -508,5 +605,5 @@ class HandlerLowering(using TL, Raise, Elaborator.State): case b => b def translateTopLevel(b: Block): Block = - translateBlock(b, HandlerCtx(true, true, _ => rtThrowMsg("Unhandled effects"))) + translateBlock(b, HandlerCtx(true, true, _ => rtThrowMsg("Unhandled effects")), true) diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 5b1dd03909..f83597d681 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -236,3 +236,18 @@ fun sum(x) = else 0 sum(10000) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + +:handler +:expect 2 +fun foo(h) = + h.perform() + if true then + fun g() = 2 + g() + else + fun g() = 3 + g() +handle h = Eff with + fun perform()(k) = k(()) +foo(h) +//│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls index 8f2c666c53..99f3ff7deb 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -126,3 +126,22 @@ h3.perform() //│ > h3 //│ > h2 //│ > h1 + +:expect 11 +:handler +let d = 0 +handle h1 = Eff with + fun perform()(k) = + set d = d + 1 + k() +let i = 0 +h1.perform() +while i < 10 do + handle h2 = Eff with + fun perform()(k) = + set d = d + 1 + k() + h2.perform() + set i = i + 1 +d +//│ = 11 diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index a6598e6bf0..c97863e682 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -101,3 +101,23 @@ in res.toString() //│ > B hola adios //│ > B hola amigos //│ = '‹,‹,‹,‹,hola,amigos,›,adios,›,friend,›,bye,›' + + +:handler +let str = "" +if true do + handle h1 = Effect with + fun perform(arg)(k) = + set str = str + "A" + k(arg) + set str = str + "A" + handle h2 = Effect with + fun perform(arg)(k) = + set str = str + "B" + k(arg) + set str = str + "B" + h2.perform(()) + h1.perform(()) +str +//│ = 'BABA' +//│ str = 'BABA' From 6339e8bebc0e28ab807b21de62a9f50c5b542aea Mon Sep 17 00:00:00 2001 From: Mark Ng <55091936+CAG2Mark@users.noreply.github.com> Date: Sat, 28 Dec 2024 01:46:28 +0800 Subject: [PATCH 038/114] Update hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls Co-authored-by: Lionel Parreaux --- hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index c97863e682..c8b627dd69 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -108,7 +108,7 @@ let str = "" if true do handle h1 = Effect with fun perform(arg)(k) = - set str = str + "A" + set str += "A" k(arg) set str = str + "A" handle h2 = Effect with From a2900ecb4cc2c28175fbc642eba504f9a84de59d Mon Sep 17 00:00:00 2001 From: Mark Ng <55091936+CAG2Mark@users.noreply.github.com> Date: Sat, 28 Dec 2024 01:46:47 +0800 Subject: [PATCH 039/114] Update hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala Co-authored-by: Lionel Parreaux --- 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 993f864437..b9f5675ad8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -393,7 +393,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State): private def translateCls(cls: ClsLikeDefn): ClsLikeDefn = cls.copy(methods = cls.methods.map(translateFun), ctor = translateBlock(cls.ctor, functionHandlerCtx)) - // to ensure the fun and class reference sin the continuation class is properly scoped, + // 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 def popDefns(b: Block, acc: List[Defn]): (Block, List[Defn]) = b match From 7596cf36c92865994b95548ef26dc5e7e6c938e6 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Tue, 31 Dec 2024 23:49:01 +0800 Subject: [PATCH 040/114] set up infrastructure --- .../test/scala/hkmc2/JSBackendDiffMaker.scala | 20 +++++++++++++++++-- .../main/scala/hkmc2/codegen/Lowering.scala | 12 ++++++++--- .../hkmc2/codegen/StackSafeTransform.scala | 7 +++++++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala diff --git a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala index f126bd1349..f347a3739c 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -14,6 +14,7 @@ import utils.Scope import hkmc2.syntax.Tree.Ident import hkmc2.codegen.Path import hkmc2.Diagnostic.Source +import hkmc2.syntax.Keyword.`then` abstract class JSBackendDiffMaker extends MLsDiffMaker: @@ -28,6 +29,8 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: val handler = NullaryCommand("handler") val expect = Command("expect"): ln => ln.trim + val stackSafe = Command("stackSafe"): ln => + ln.trim private val baseScp: utils.Scope = utils.Scope.empty @@ -50,10 +53,23 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: override def run(): Unit = try super.run() finally if hostCreated then host.terminate() + private val DEFAULT_STACK_LIMT = 500 + override def processTerm(blk: semantics.Term.Blk, inImport: Bool)(using Raise): Unit = super.processTerm(blk, inImport) val outerRaise: Raise = summon var showingJSYieldedCompileError = false + val stackLimit = stackSafe.get.map(_.toIntOption) match + case None => None + case Some(value) => value match + case None => Some(DEFAULT_STACK_LIMT) + case Some(value) => + if value < 0 then + failures += 1 + output("/!\\ Stack limit must be positive, but the stack limit here is set to " + value) + Some(DEFAULT_STACK_LIMT) + else + Some(value) if showJS.isSet then given Raise = case d @ ErrorReport(source = Source.Compilation) => @@ -64,7 +80,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: new codegen.Lowering with codegen.LoweringSelSanityChecks(instrument = false) with codegen.LoweringTraceLog(instrument = false) - with codegen.LoweringHandler(handler.isSet) + with codegen.LoweringHandler(handler.isSet, stackLimit) given Elaborator.Ctx = curCtx val jsb = new JSBuilder with JSBuilderArgNumSanityChecks(instrument = false) @@ -80,7 +96,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: new codegen.Lowering with codegen.LoweringSelSanityChecks(noSanityCheck.isUnset) with codegen.LoweringTraceLog(traceJS.isSet) - with codegen.LoweringHandler(handler.isSet) + with codegen.LoweringHandler(handler.isSet, stackLimit) given Elaborator.Ctx = curCtx val jsb = new JSBuilder with JSBuilderArgNumSanityChecks(noSanityCheck.isUnset) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 0ba418b069..52b28893c3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -481,7 +481,7 @@ trait LoweringTraceLog trait LoweringHandler - (instrument: Bool)(using TL, Raise, Elaborator.State) + (instrument: Bool, stackLimit: Option[Int])(using TL, Raise, Elaborator.State) extends Lowering: override def term(t: st)(k: Result => Block)(using Subst): Block = if !instrument then return super.term(t)(k) @@ -501,5 +501,11 @@ trait LoweringHandler HandleBlock(lhs, resSym, par, cls, handlers, term(st.Blk(stmts, res))(HandleBlockReturn(_)), k(Value.Ref(resSym))) case _ => super.term(t)(k) override def topLevel(t: st): Block = - if !instrument then return super.topLevel(t) - HandlerLowering().translateTopLevel(super.topLevel(t)) + val ir = super.topLevel(t) + val stackSafePass = stackLimit match + case None => ir + case Some(lim) => + val transform = StackSafeTransform(lim) + transform.transformTopLevel(ir) + val handlerPass = if instrument then HandlerLowering().translateTopLevel(stackSafePass) else stackSafePass + handlerPass diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala new file mode 100644 index 0000000000..747e8606e7 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -0,0 +1,7 @@ +package hkmc2.codegen + +import hkmc2.codegen.* +import hkmc2.semantics.Elaborator.State + +class StackSafeTransform(depthLimit: Int)(using State): + def transformTopLevel(b: Block) = b \ No newline at end of file From 4b837eb65307268dcf6261d9b90f3cc589e5fcf8 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Mon, 6 Jan 2025 19:24:38 +0800 Subject: [PATCH 041/114] Changed and added broken test --- .../src/test/mlscript/handlers/Effects.mls | 14 +----- .../test/mlscript/handlers/GeneratorStack.mls | 48 +++++++++++++++++++ .../src/test/mlscript/handlers/Generators.mls | 1 - .../test/mlscript/handlers/NestedHandlers.mls | 1 - .../src/test/mlscript/handlers/NoHandler.mls | 14 ++++++ .../mlscript/handlers/RecursiveHandlers.mls | 4 +- 6 files changed, 66 insertions(+), 16 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls create mode 100644 hkmc2/shared/src/test/mlscript/handlers/NoHandler.mls diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index f83597d681..7c90e01585 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -1,21 +1,9 @@ :js +:handler abstract class Effect with fun perform(arg: Str): Str -:ge -handle h = Effect with - fun perform(arg)(k) = k(arg) -h.perform("k") -//│ ╔══[COMPILATION ERROR] Effect handlers are not enabled -//│ ║ l.7: handle h = Effect with -//│ ║ ^^^^^^^^^^^ -//│ ║ l.8: fun perform(arg)(k) = k(arg) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:global -:handler - :expect 'b' handle h = Effect with fun perform(arg)(k) = "b" diff --git a/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls b/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls new file mode 100644 index 0000000000..b144089b53 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls @@ -0,0 +1,48 @@ +:js +:handler +import "../../mlscript-compile/Stack.mls" +open Stack + +class Generator with + fun produce(result: Stack[Int]): () + +fun (++) concat(l1, l2) = + if l1 is + Nil then l2 + Cons(h, t) then Cons(h, t ++ l2) + +fun (:+) append(l, e) = + if l is + Nil then e :: Nil + Cons(h, t) then h :: t :+ e + +fun permutations_impl(gen, l1, l2) = + if l2 is + Nil then + if l1 is Nil then gen.produce(Nil) else () + Cons(f, t) then + handle gen2 = Generator with + fun produce(result)(resume) = + gen.produce(f :: result) + resume(()) + permutations_impl(gen2, Nil, l1 ++ t) + permutations_impl(gen, l1 :+ f, t) + +fun permutations(gen, l) = + permutations_impl(gen, Nil, l) + +// FIXME: result is discarded +handle gen = Generator with + fun produce(result)(resume) = + // print(result) + let r = resume(()) + // print(r) + result :: r +permutations(gen, 1 :: 2 :: 3 :: 4 :: Nil), Nil +//│ > Cons { +//│ > head: Cons { head: 1, tail: Cons { head: 2, tail: [Cons] } }, +//│ > tail: Cons { +//│ > head: Cons { head: 1, tail: [Cons] }, +//│ > tail: Cons { head: [Cons], tail: [Cons] } +//│ > } +//│ = } diff --git a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls index 88fd16353c..7627b30f42 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls @@ -1,6 +1,5 @@ :js :handler -import "../../mlscript-compile/Stack.mls" abstract class Generator with fun produce(result): () diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls index 99f3ff7deb..a2e4c0416c 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -128,7 +128,6 @@ h3.perform() //│ > h1 :expect 11 -:handler let d = 0 handle h1 = Eff with fun perform()(k) = diff --git a/hkmc2/shared/src/test/mlscript/handlers/NoHandler.mls b/hkmc2/shared/src/test/mlscript/handlers/NoHandler.mls new file mode 100644 index 0000000000..a8724e8812 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/NoHandler.mls @@ -0,0 +1,14 @@ +:js + +abstract class Effect with + fun perform(arg: Str): Str + +:ge +handle h = Effect with + fun perform(arg)(k) = k(arg) +h.perform("k") +//│ ╔══[COMPILATION ERROR] Effect handlers are not enabled +//│ ║ l.7: handle h = Effect with +//│ ║ ^^^^^^^^^^^ +//│ ║ l.8: fun perform(arg)(k) = k(arg) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index c8b627dd69..bac6604706 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -54,6 +54,7 @@ handle h2 = Effect with //│ = [ undefined, undefined ] +// The current implementation does not treat function call to potentially open new handler scope let res = handle h = Effect with fun perform(arg)(k) = @@ -78,6 +79,8 @@ in res.toString() //│ > B hola amigos //│ = '‹,‹,‹,‹,hola,friend,›,bye,›,amigos,›,adios,›' + +// This is the semantics we want let res = print("A " + "hi") handle g = Effect with @@ -103,7 +106,6 @@ in res.toString() //│ = '‹,‹,‹,‹,hola,amigos,›,adios,›,friend,›,bye,›' -:handler let str = "" if true do handle h1 = Effect with From 0864f27654802e288a20fb02dcc250ae0e71d6bf Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Mon, 6 Jan 2025 19:53:59 +0800 Subject: [PATCH 042/114] Update tests --- .../src/test/mlscript-compile/Predef.mls | 3 +- .../test/mlscript/handlers/GeneratorStack.mls | 58 +++++++++++++------ 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index c87bc24b03..c6fbbbbf16 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -104,7 +104,8 @@ fun __handleBlockImpl(cur, handler) = return cur else set cur = nxt - else return cur + else + return cur fun __handleEffect(cur, handler, handlerTailList) = let prevCont = cur diff --git a/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls b/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls index b144089b53..b2221888ca 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls @@ -16,10 +16,16 @@ fun (:+) append(l, e) = Nil then e :: Nil Cons(h, t) then h :: t :+ e +fun length(l) = + if l is + Nil then 0 + Cons(_, t) then 1 + length(t) + fun permutations_impl(gen, l1, l2) = if l2 is - Nil then - if l1 is Nil then gen.produce(Nil) else () + Nil and + l1 is Nil then gen.produce(Nil) + else () Cons(f, t) then handle gen2 = Generator with fun produce(result)(resume) = @@ -31,18 +37,36 @@ fun permutations_impl(gen, l1, l2) = fun permutations(gen, l) = permutations_impl(gen, Nil, l) -// FIXME: result is discarded -handle gen = Generator with - fun produce(result)(resume) = - // print(result) - let r = resume(()) - // print(r) - result :: r -permutations(gen, 1 :: 2 :: 3 :: 4 :: Nil), Nil -//│ > Cons { -//│ > head: Cons { head: 1, tail: Cons { head: 2, tail: [Cons] } }, -//│ > tail: Cons { -//│ > head: Cons { head: 1, tail: [Cons] }, -//│ > tail: Cons { head: [Cons], tail: [Cons] } -//│ > } -//│ = } +let result = + handle gen = Generator with + fun produce(result)(resume) = + print(result) + let r = resume(()) + result :: r + permutations(gen, 1 :: 2 :: 3 :: 4 :: Nil), Nil +in length(result) +//│ > Cons(1, Cons(2, Cons(3, Cons(4, Nil)))) +//│ > Cons(1, Cons(2, Cons(4, Cons(3, Nil)))) +//│ > Cons(1, Cons(3, Cons(2, Cons(4, Nil)))) +//│ > Cons(1, Cons(3, Cons(4, Cons(2, Nil)))) +//│ > Cons(1, Cons(4, Cons(2, Cons(3, Nil)))) +//│ > Cons(1, Cons(4, Cons(3, Cons(2, Nil)))) +//│ > Cons(2, Cons(1, Cons(3, Cons(4, Nil)))) +//│ > Cons(2, Cons(1, Cons(4, Cons(3, Nil)))) +//│ > Cons(2, Cons(3, Cons(1, Cons(4, Nil)))) +//│ > Cons(2, Cons(3, Cons(4, Cons(1, Nil)))) +//│ > Cons(2, Cons(4, Cons(1, Cons(3, Nil)))) +//│ > Cons(2, Cons(4, Cons(3, Cons(1, Nil)))) +//│ > Cons(3, Cons(1, Cons(2, Cons(4, Nil)))) +//│ > Cons(3, Cons(1, Cons(4, Cons(2, Nil)))) +//│ > Cons(3, Cons(2, Cons(1, Cons(4, Nil)))) +//│ > Cons(3, Cons(2, Cons(4, Cons(1, Nil)))) +//│ > Cons(3, Cons(4, Cons(1, Cons(2, Nil)))) +//│ > Cons(3, Cons(4, Cons(2, Cons(1, Nil)))) +//│ > Cons(4, Cons(1, Cons(2, Cons(3, Nil)))) +//│ > Cons(4, Cons(1, Cons(3, Cons(2, Nil)))) +//│ > Cons(4, Cons(2, Cons(1, Cons(3, Nil)))) +//│ > Cons(4, Cons(2, Cons(3, Cons(1, Nil)))) +//│ > Cons(4, Cons(3, Cons(1, Cons(2, Nil)))) +//│ > Cons(4, Cons(3, Cons(2, Cons(1, Nil)))) +//│ = 24 From e6a819bae5f1d1894e65f1710bea154aa130d794 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Mon, 6 Jan 2025 22:44:09 +0800 Subject: [PATCH 043/114] Minor style changes (? debatable improvement) --- .../src/test/mlscript-compile/Predef.mjs | 41 +++++++------- .../src/test/mlscript-compile/Predef.mls | 56 +++++++++++-------- 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 2dad64b583..aaf3cd4969 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -239,17 +239,17 @@ const Predef$class = class Predef { return tmp3; } __handleEffect(cur1, handler2, handlerTailList) { - let prevCont, handlerCont, scrut, scrut1, savedNext, scrut2, scrut3, origTail, savedNext1, scrut4, scrut5, nxt, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; + let prevCont, handlerCont, scrut, scrut1, savedNext, scrut2, scrut3, origTail, savedNext1, scrut4, scrut5, nxt, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14; prevCont = cur1; handlerCont = cur1.nextHandler; - tmp16: while (true) { + tmp15: while (true) { if (handlerCont instanceof this.__HandleBlock.class) { scrut = handlerCont.handler !== cur1.handler; if (scrut) { prevCont = handlerCont; handlerCont = handlerCont.nextHandler; tmp = null; - continue tmp16; + continue tmp15; } else { tmp = null; } @@ -288,37 +288,36 @@ const Predef$class = class Predef { cur1 = tmp5; tmp6 = null; } - tmp7 = tmp6; } - tmp8 = tmp7; + tmp7 = tmp6; } else { scrut1 = handler2 === cur1.handler; if (scrut1) { savedNext = handlerTailList.next; - tmp9 = this.__resume(cur1, handlerTailList); - tmp10 = cur1.handlerFun(tmp9) ?? null; - cur1 = tmp10; + tmp8 = this.__resume(cur1, handlerTailList); + tmp9 = cur1.handlerFun(tmp8) ?? null; + cur1 = tmp9; scrut2 = savedNext !== handlerTailList.next; if (scrut2) { handlerTailList.next.next = savedNext; scrut3 = savedNext === null; if (scrut3) { handlerTailList.tail = handlerTailList.next; - tmp11 = null; + tmp10 = null; } else { - tmp11 = null; + tmp10 = null; } - tmp12 = tmp11; + tmp11 = tmp10; } else { - tmp12 = null; + tmp11 = null; } - tmp13 = tmp12; + tmp12 = tmp11; } else { return cur1; } - tmp8 = tmp13; + tmp7 = tmp12; } - tmp17: while (true) { + tmp16: while (true) { if (cur1 instanceof this.__EffectSig.class) { return cur1; } else { @@ -329,18 +328,18 @@ const Predef$class = class Predef { if (scrut5 instanceof this.__Cont.class) { nxt = handlerTailList.next; handlerTailList.next = handlerTailList.next.next; - tmp14 = nxt.resume(cur1) ?? null; - cur1 = tmp14; - tmp15 = null; - continue tmp17; + tmp13 = nxt.resume(cur1) ?? null; + cur1 = tmp13; + tmp14 = null; + continue tmp16; } else { - tmp15 = cur1; + tmp14 = cur1; } } } break; } - return tmp15; + return tmp14; } __resume(cur2, tail) { return (value) => { diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index c6fbbbbf16..6b0a2b384c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -79,8 +79,9 @@ class __Return(value) fun __mkEffect(handler, handlerFun) = let res = new __EffectSig(null, null, null, null, false, handler, handlerFun) - set res.tail = res - set res.tailHandler = res + set + res.tail = res + res.tailHandler = res res // fun checkEffectHead(cur) = @@ -108,27 +109,33 @@ fun __handleBlockImpl(cur, handler) = return cur fun __handleEffect(cur, handler, handlerTailList) = - let prevCont = cur - let handlerCont = cur.nextHandler + let + prevCont = cur + handlerCont = cur.nextHandler while handlerCont is __HandleBlock and handlerCont.handler !== cur.handler do - set prevCont = handlerCont - set handlerCont = handlerCont.nextHandler + set + prevCont = handlerCont + handlerCont = handlerCont.nextHandler if handlerCont then let origTail = cur.tailHandler - set prevCont.nextHandler = null - set cur.tailHandler = prevCont + set + prevCont.nextHandler = null + cur.tailHandler = prevCont let savedNext = handlerCont.next set cur = cur.handlerFun(__resume(cur, handlerCont)) if savedNext !== handlerCont.next do set handlerCont.next.next = savedNext - if cur is __EffectSig then - set cur.tailHandler.nextHandler = handlerCont - set cur.tailHandler = origTail - return cur - else if cur is __Return then - set cur.tailHandler.nextHandler = handlerCont - set cur.tailHandler = origTail - return cur + if cur is + __EffectSig then + set + cur.tailHandler.nextHandler = handlerCont + cur.tailHandler = origTail + return cur + __Return then + set + cur.tailHandler.nextHandler = handlerCont + cur.tailHandler = origTail + return cur else set cur = __resume(handlerCont, null)(cur) else if handler === cur.handler then @@ -146,8 +153,9 @@ fun __handleEffect(cur, handler, handlerTailList) = __Return then return cur handlerTailList.next is __Cont then let nxt = handlerTailList.next - set handlerTailList.next = handlerTailList.next.next - set cur = nxt.resume(cur) + set + handlerTailList.next = handlerTailList.next.next + cur = nxt.resume(cur) else cur // fun __resume(cur, tail)(value) = @@ -185,8 +193,9 @@ fun __resume(cur, tail)(value) = if value is __EffectSig then set value.tail = tail if cur.tailHandler !== cur do - set value.tailHandler.nextHandler = cur.nextHandler - set value.tailHandler = cur.tailHandler + set + value.tailHandler.nextHandler = cur.nextHandler + value.tailHandler = cur.tailHandler return value else set cont = cont.next @@ -211,9 +220,10 @@ fun __resume(cur, tail)(value) = set value.tail.next = null else set cur.nextHandler.next = cur.nextHandler.next.next - set value.tail = tail - set value.tailHandler.nextHandler = cur.nextHandler - set value.tailHandler = cur.tailHandler + set + value.tail = tail + value.tailHandler.nextHandler = cur.nextHandler + value.tailHandler = cur.tailHandler return value else assert(cur.nextHandler.next !== cur.nextHandler.next.next) From af173076149a6e0d9f034afdaf685a1f9484b41d Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 7 Jan 2025 20:29:16 +0800 Subject: [PATCH 044/114] Fix elaboration scoping --- .../scala/hkmc2/semantics/Elaborator.scala | 62 ++++++++++--------- .../src/test/mlscript/handlers/Effects.mls | 18 +++--- .../test/mlscript/handlers/NestedHandlers.mls | 25 ++++++-- .../mlscript/handlers/RecursiveHandlers.mls | 20 +++--- .../src/test/mlscript/parser/Handler.mls | 11 ++-- 5 files changed, 76 insertions(+), 60 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index e26638bf5e..71711bef16 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -599,39 +599,41 @@ extends Importer: raise(ErrorReport(msg"Unsupported let binding shape" -> tree.toLoc :: Nil)) go(sts, Term.Error :: acc) case (hd @ Handle(id: Ident, cls: Ident, Block(sts_), N)) :: sts => - val sym = fieldOrVarSym(HandlerBind, id) - log(s"Processing `handle` statement $id (${sym}) ${ctx.outer}") - - // TODO: shouldn't need uid here - val derivedClsSym = ClassSymbol(Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), Tree.Ident(s"Effect$$${cls.name}$$${State.suid.nextUid}")) - derivedClsSym.defn = S(ClassDef(N, syntax.Cls, derivedClsSym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))))) + val res: Term.Blk = ctx.nest(N).givenIn: + val sym = fieldOrVarSym(HandlerBind, id) + log(s"Processing `handle` statement $id (${sym}) ${ctx.outer}") + + // TODO: shouldn't need uid here + val derivedClsSym = ClassSymbol(Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), Tree.Ident(s"Effect$$${cls.name}$$${State.suid.nextUid}")) + derivedClsSym.defn = S(ClassDef(N, syntax.Cls, derivedClsSym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))))) - val elabed = ctx.nest(S(derivedClsSym)).givenIn: - block(sts_)._1 - - elabed.res match - case Term.Lit(UnitLit(true)) => - case trm => raise(WarningReport(msg"Terms in handler block do nothing" -> trm.toLoc :: Nil)) + val elabed = ctx.nest(S(derivedClsSym)).givenIn: + block(sts_)._1 + + elabed.res match + case Term.Lit(UnitLit(true)) => + case trm => raise(WarningReport(msg"Terms in handler block do nothing" -> trm.toLoc :: Nil)) - val tds = elabed.stats.map { - case td @ TermDefinition(owner, Fun, sym, params, sign, body, resSym, flags) => - params.reverse match - case ParamList(_, value :: Nil, _) :: newParams => - val newTd = TermDefinition(owner, Fun, sym, newParams.reverse, sign, body, resSym, flags) - S(HandlerTermDefinition(value.sym, newTd)) - case _ => - raise(ErrorReport(msg"Handler function is missing resumption parameter" -> td.toLoc :: Nil)) - None - - case st => - raise(ErrorReport(msg"Only function definitions are allowed in handler blocks" -> st.toLoc :: Nil)) - None - }.collect { case Some(x) => x } + val tds = elabed.stats.map { + case td @ TermDefinition(owner, Fun, sym, params, sign, body, resSym, flags) => + params.reverse match + case ParamList(_, value :: Nil, _) :: newParams => + val newTd = TermDefinition(owner, Fun, sym, newParams.reverse, sign, body, resSym, flags) + S(HandlerTermDefinition(value.sym, newTd)) + case _ => + raise(ErrorReport(msg"Handler function is missing resumption parameter" -> td.toLoc :: Nil)) + None + + case st => + raise(ErrorReport(msg"Only function definitions are allowed in handler blocks" -> st.toLoc :: Nil)) + None + }.collect { case Some(x) => x } - val newAcc = Term.Handle(sym, term(cls), derivedClsSym, tds) :: acc - ctx.nest(N).givenIn: - ctx + (id.name -> sym) givenIn: - go(sts, newAcc) + val newAcc = Term.Handle(sym, term(cls), derivedClsSym, tds) :: acc + val newCtx = ctx + (id.name -> sym) + val body = block(sts)(using newCtx)._1 + Term.Blk(newAcc.reverse, body) + (res, ctx) case (tree @ Handle(_, _, _, N)) :: sts => raise(ErrorReport(msg"Unsupported handle binding shape" -> tree.toLoc :: Nil)) go(sts, Term.Error :: acc) diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 7c90e01585..3157e9fdb6 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -137,12 +137,11 @@ result //│ > handler finished //│ = 'Hello World!' -:fixme handle h = Effect with fun perform(arg)(k) = arg fun f() = 3 f() -//│ ═══[RUNTIME ERROR] TypeError: globalThis.f is not a function +//│ = 3 fun f(perform) = handle h = Effect with @@ -168,6 +167,7 @@ h.setVal(1) print(h.getVal()) //│ > 0 //│ > 1 +//│ x = 1 abstract class Eff with @@ -188,6 +188,10 @@ abstract class StackDelay with // stack safe recursion :expect 5050 +handle h = StackDelay with + fun raise()(k) = + // console.trace("Stack unwinded!") + k(10) fun sum(depth, x) = let new_depth = if depth > 70 then // console.trace("Too deep, heapifying the stack") @@ -197,23 +201,19 @@ fun sum(depth, x) = if x != 0 then x + sum(new_depth, x - 1) else 0 -handle h = StackDelay with - fun raise()(k) = - // console.trace("Stack unwinded!") - k(10) sum(0, 100) //│ = 5050 // stack safe recursion :expect 450015000 +handle h = StackDelay with + fun raise()(k) = // TODO: This should codegen to a simple return without instrumentation + k(10) fun sum(depth, x) = let new_depth = if depth > 1000 then h.raise() else depth + 1 if x != 0 then x + sum(new_depth, x - 1) else 0 -handle h = StackDelay with - fun raise()(k) = // TODO: This should codegen to a simple return without instrumentation - k(10) sum(0, 30000) //│ = 450015000 diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls index a2e4c0416c..3f6cddbc48 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -2,6 +2,9 @@ :handler let id = 0 +//│ id = 0 + + class MaybeStop with fun f(x: Bool): () fun handleEffects(g) = @@ -28,7 +31,7 @@ fun handleEffects(g) = print("h2 end " + String(cur)) result g(h1, h2) -//│ id = 0 + fun f(h1, h2) = h1.f(false) @@ -52,6 +55,7 @@ handleEffects(f) //│ > h1 end 1 //│ = false + fun f(h1, h2) = h1.f(false) h2.f(false) @@ -68,6 +72,7 @@ handleEffects(f) //│ > h1 end 9 //│ > h1 end 7 + fun f(h1, h2) = h1.f(false) h2.f(false) @@ -81,11 +86,15 @@ handleEffects(f) //│ > h1 stop //│ > h1 end 10 -:fixme -:expect 1023 + class Eff with fun perform(): () class Box(n) +let box = Box(0) +//│ box = Box { n: 0 } + + +:expect 5120 fun f(h, box, n) = if n <= 1 then h.perform() @@ -97,14 +106,14 @@ fun f(h, box, n) = k() f(h2, box, n - 1) f(h2, box, n - 1) -let box = Box(0) handle h = Eff with fun perform()(k) = set box.n = box.n + 1 k() f(h, box, 10) box.n -//│ ═══[RUNTIME ERROR] Expected: 1023, got: 5120 +//│ = 5120 + handle h1 = Eff with fun perform()(k) = @@ -127,8 +136,12 @@ h3.perform() //│ > h2 //│ > h1 -:expect 11 + let d = 0 +//│ d = 0 + + +:expect 11 handle h1 = Eff with fun perform()(k) = set d = d + 1 diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index bac6604706..ab40cb1566 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -16,19 +16,17 @@ h1.perform("hello") //│ > performing hello //│ = [ [ 'ok' ] ] +:e h1 -//│ = Effect$Effect$131 {} +//│ ╔══[ERROR] Name not found: h1 +//│ ║ l.20: h1 +//│ ╙── ^^ +:e h1.perform("oops") -//│ > __EffectSig { -//│ > next: null, -//│ > nextHandler: null, -//│ > tail: [Circular *1], -//│ > tailHandler: [Circular *1], -//│ > resumed: false, -//│ > handler: Effect$Effect$131 {}, -//│ > handlerFun: [Function (anonymous)] -//│ = } +//│ ╔══[ERROR] Name not found: h1 +//│ ║ l.26: h1.perform("oops") +//│ ╙── ^^ :fixme @@ -45,7 +43,7 @@ handle h2 = Effect with h2.perform(3) ] //│ ╔══[ERROR] Name not found: h2 -//│ ║ l.39: then h2.perform(arg - 1) + " " + arg +//│ ║ l.37: then h2.perform(arg - 1) + " " + arg //│ ╙── ^^ //│ > ––– //│ > performing 2 diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index 81b58e0621..67e750933c 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { { handle globalThis:block#6.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$122),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#6.h#666) } } +//│ Elab: { { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$122),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } } :e ( @@ -73,7 +73,7 @@ handle h = Eff with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle globalThis:block#8.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$151),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$151),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#8.h#666) } +//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$151),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$151),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } :e handle h = Eff with @@ -113,6 +113,9 @@ foo(h) //│ ╔══[ERROR] Handler function is missing resumption parameter //│ ║ l.101: fun h2()(a, b) = r(1) //│ ╙── ^^^^ +//│ ╔══[ERROR] Name not found: h +//│ ║ l.102: foo(h) +//│ ╙── ^ :w :el @@ -122,6 +125,6 @@ handle h = Eff with 12345 foo(h) //│ ╔══[WARNING] Terms in handler block do nothing -//│ ║ l.122: 12345 +//│ ║ l.125: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle globalThis:block#11.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$228),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$228),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#11.h#666) } +//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$228),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$228),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } From 2da74c057f856cd735e50814b3d9d14b6a2a2637 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 7 Jan 2025 23:30:42 +0800 Subject: [PATCH 045/114] Fixed some bugs --- .../src/main/scala/hkmc2/codegen/Block.scala | 3 +- .../scala/hkmc2/codegen/HandlerLowering.scala | 3 +- .../src/test/mlscript-compile/Predef.mjs | 36 +++++++------------ .../src/test/mlscript-compile/Predef.mls | 6 ---- .../src/test/mlscript/handlers/Effects.mls | 2 +- .../mlscript/handlers/MultiResumption.mls | 24 +++++++++++++ .../mlscript/handlers/ReturnInHandler.mls | 23 +++++++++++- 7 files changed, 64 insertions(+), 33 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index bb70188c72..b5317ca9c4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -44,7 +44,7 @@ sealed abstract class Block extends Product with AutoLocated: case Label(lbl, bod, rst) => bod.definedVars ++ rst.definedVars lazy val size: Int = this match - case _: Return | _: Throw | _: End | _: Break | _: Continue => 1 + case _: Return | _: Throw | _: End | _: Break | _: Continue | _: HandleBlockReturn => 1 case Begin(sub, rst) => sub.size + rst.size case Assign(_, _, rst) => 1 + rst.size case AssignField(_, _, _, rst) => 1 + rst.size @@ -53,6 +53,7 @@ sealed abstract class Block extends Product with AutoLocated: case Define(_, rst) => 1 + rst.size case TryBlock(sub, fin, rst) => 1 + sub.size + fin.size + rst.size case Label(_, bod, rst) => 1 + bod.size + rst.size + case HandleBlock(lhs, res, par, cls, handlers, bdy, rst) => 1 + handlers.map(_.body.size).sum + bdy.size + rst.size // ignoring blocks inside functions and handle block def map(f: Block => Block): Block = this match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index b9f5675ad8..5563f3b146 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -513,12 +513,13 @@ class HandlerLowering(using TL, Raise, Elaborator.State): Case.Cls(dummyClsSym, effectSigPath), ReturnCont(res, uid) ) + .chain(ResumptionPoint(res, uid, _)) .staticif(canRet, _.ifthen( res.asPath, Case.Cls(dummyClsSym, retClsPath), blockBuilder.ret(if handlerCtx.isHandleFree then res.asPath.value else res.asPath) )) - .rest(ResumptionPoint(res, uid, rest)) + .rest(rest) case b => b val actualBlock = prepareBlock(b) if trivial then return N diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 10fa73013f..43cb75320b 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -301,16 +301,10 @@ const Predef$class = class Predef { cur1.tailHandler = origTail; return cur1; } else { - if (cur1 instanceof this.__Return.class) { - cur1.tailHandler.nextHandler = handlerCont; - cur1.tailHandler = origTail; - return cur1; - } else { - tmp4 = this.__resume(handlerCont, null); - tmp5 = tmp4(cur1) ?? null; - cur1 = tmp5; - tmp6 = null; - } + tmp4 = this.__resume(handlerCont, null); + tmp5 = tmp4(cur1) ?? null; + cur1 = tmp5; + tmp6 = null; } tmp7 = tmp6; } else { @@ -344,20 +338,16 @@ const Predef$class = class Predef { if (cur1 instanceof this.__EffectSig.class) { return cur1; } else { - if (cur1 instanceof this.__Return.class) { - return cur1; + scrut5 = handlerTailList.next; + if (scrut5 instanceof this.__Cont.class) { + nxt = handlerTailList.next; + handlerTailList.next = handlerTailList.next.next; + tmp13 = nxt.resume(cur1) ?? null; + cur1 = tmp13; + tmp14 = null; + continue tmp16; } else { - scrut5 = handlerTailList.next; - if (scrut5 instanceof this.__Cont.class) { - nxt = handlerTailList.next; - handlerTailList.next = handlerTailList.next.next; - tmp13 = nxt.resume(cur1) ?? null; - cur1 = tmp13; - tmp14 = null; - continue tmp16; - } else { - tmp14 = cur1; - } + tmp14 = cur1; } } break; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 2c845c197c..cd49198bc5 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -138,11 +138,6 @@ fun __handleEffect(cur, handler, handlerTailList) = cur.tailHandler.nextHandler = handlerCont cur.tailHandler = origTail return cur - __Return then - set - cur.tailHandler.nextHandler = handlerCont - cur.tailHandler = origTail - return cur else set cur = __resume(handlerCont, null)(cur) else if handler === cur.handler then @@ -157,7 +152,6 @@ fun __handleEffect(cur, handler, handlerTailList) = while cur is __EffectSig then return cur - __Return then return cur handlerTailList.next is __Cont then let nxt = handlerTailList.next set diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 3157e9fdb6..3c0302d8b7 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -207,7 +207,7 @@ sum(0, 100) // stack safe recursion :expect 450015000 handle h = StackDelay with - fun raise()(k) = // TODO: This should codegen to a simple return without instrumentation + fun raise()(k) = k(10) fun sum(depth, x) = let new_depth = if depth > 1000 then h.raise() else depth + 1 diff --git a/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls b/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls new file mode 100644 index 0000000000..1f088e34b1 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls @@ -0,0 +1,24 @@ +:js +:handler + +class Logger with + fun info(s: Str): () + +:re +handle h = Logger with + fun info(s)(k) = + print(s) + k() + k() +h.info("a") +//│ > a +//│ ═══[RUNTIME ERROR] Error: multiple resumption + + +handle h = Logger with + fun info(s)(k) = + print(s) + k() + k() +123 +//│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls b/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls index abc072573d..d3bd2a4e2f 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls @@ -7,7 +7,7 @@ abstract class Effect with fun f() = handle h = Effect with fun f()(r) = - let m = r() + let m = r(()) print("Bye!") m h.f() @@ -31,3 +31,24 @@ fun f() = f() //│ > Bye! //│ = 3 + +let l = () => + handle h = Effect with + fun f()(r) = r(()) + return 3 + 4 +//│ l = [Function (anonymous)] + +l() +//│ = 3 + +handle h1 = Effect with + fun f()(r) = r(()) +let l = () => + handle h2 = Effect with + fun f()(r) = r(()) + h1.f() + return 3 + 4 +l() +//│ = 3 From 80e9fba13bb1a1d87154783de11d32f77e8d8047 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 7 Jan 2025 23:34:27 +0800 Subject: [PATCH 046/114] Fix consistency --- hkmc2/shared/src/test/mlscript-compile/Predef.mls | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index cd49198bc5..f51b3b91e3 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -106,9 +106,10 @@ fun __handleBlockImpl(cur, handler) = __EffectSig then let nxt = __handleEffect(cur, handler, handlerCont) if cur === nxt then - set cur.tailHandler.nextHandler = handlerCont - set cur.tailHandler = handlerCont - set cur.tail = handlerCont.tail + set + cur.tailHandler.nextHandler = handlerCont + cur.tailHandler = handlerCont + cur.tail = handlerCont.tail return cur else set cur = nxt From 964c14622237b6e3f90457594a51314632f8c2c2 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 8 Jan 2025 00:02:26 +0800 Subject: [PATCH 047/114] Fixed comments --- hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index ab40cb1566..135b4d2363 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -52,7 +52,7 @@ handle h2 = Effect with //│ = [ undefined, undefined ] -// The current implementation does not treat function call to potentially open new handler scope +// The current implementation insert new handlers surrounding the entire handle block, and hence "later" handle block become the outer one let res = handle h = Effect with fun perform(arg)(k) = @@ -78,7 +78,7 @@ in res.toString() //│ = '‹,‹,‹,‹,hola,friend,›,bye,›,amigos,›,adios,›' -// This is the semantics we want +// Output for the "later" handle block as the inner one let res = print("A " + "hi") handle g = Effect with From 6722e1a542fedc710831d4586a71cdbfd1644cf2 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 8 Jan 2025 00:42:23 +0800 Subject: [PATCH 048/114] initial --- .../main/scala/hkmc2/codegen/Lowering.scala | 1 + .../hkmc2/codegen/StackSafeTransform.scala | 159 +++++++- .../src/test/mlscript-compile/Predef.mjs | 5 + .../src/test/mlscript-compile/Predef.mls | 6 + hkmc2/shared/src/test/mlscript/HkScratch.mls | 380 ++++++++++++++++++ 5 files changed, 549 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 52b28893c3..f5787b6956 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -508,4 +508,5 @@ trait LoweringHandler val transform = StackSafeTransform(lim) transform.transformTopLevel(ir) val handlerPass = if instrument then HandlerLowering().translateTopLevel(stackSafePass) else stackSafePass + // val handlerPass = stackSafePass handlerPass diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 747e8606e7..0f66d1f0c1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -1,7 +1,162 @@ -package hkmc2.codegen +package hkmc2 + +import mlscript.utils.*, shorthands.* +import utils.* import hkmc2.codegen.* import hkmc2.semantics.Elaborator.State +import hkmc2.semantics.* +import hkmc2.syntax.Tree + class StackSafeTransform(depthLimit: Int)(using State): - def transformTopLevel(b: Block) = b \ No newline at end of file + extension (l: Local) + def asPath: Path = Value.Ref(l) + extension (p: Path) + def selN(id: Tree.Ident) = Select(p, id)(N) + def asArg = Arg(false, p) + + private val STACK_DEPTH_IDENT: Tree.Ident = Tree.Ident("__stackDepth") + private val STACK_HANDLER_IDENT: Tree.Ident = Tree.Ident("__stackHandler") + + private val stackDelayClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__StackDelay")).selN(Tree.Ident("class")) + private val stackDepthPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(STACK_DEPTH_IDENT) + private val stackHandlerPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(STACK_HANDLER_IDENT) + private val predefPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")) + + private val geqPath = State.builtinOpsMap(">=").asPath + private val addPath = State.builtinOpsMap("+").asPath + + private def intLit(n: BigInt) = Value.Lit(Tree.IntLit(n)) + + private def op(op: String, a: Path, b: Path) = + Call(State.builtinOpsMap(op).asPath, List(a.asArg, b.asArg))(true) + + // TODO: this code is copied from HandlerLowering and is quite useful. Maybe refactor it into a utils file + extension (k: Block => Block) + + def chain(other: Block => Block): Block => Block = b => k(other(b)) + def rest(b: Block): Block = k(b) + def transform(f: (Block => Block) => (Block => Block)) = f(k) + + def assign(l: Local, r: Result) = k.chain(Assign(l, r, _)) + def assignFieldN(lhs: Path, nme: Tree.Ident, rhs: Result) = k.chain(AssignField(lhs, nme, rhs, _)(N)) + def break(l: Local): Block = k.rest(Break(l)) + def continue(l: Local): Block = k.rest(Continue(l)) + def define(defn: Defn) = k.chain(Define(defn, _)) + def end() = k.rest(End()) + def ifthen(scrut: Path, cse: Case, trm: Block): Block => Block = k.chain(Match(scrut, cse -> trm :: Nil, N, _)) + def label(label: Local, body: Block) = k.chain(Label(label, body, _)) + def ret(r: Result) = k.rest(Return(r, false)) + def staticif(b: Boolean, f: (Block => Block) => (Block => Block)) = if b then k.transform(f) else k + + private def blockBuilder: Block => Block = identity + + // Increases the stack depth, assigns the call to a value, then decreases the stack depth + // then binds that value to a desired block + def extractRes(res: Result, f: Result => Block) = + res match + case c: Call if c.isMlsFun => + val tmp = TempSymbol(None, "tmp") + val depthPositive = TempSymbol(None, "depthPositive") + blockBuilder + .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) + .assign(tmp, res) + .assign(depthPositive, op(">", stackDepthPath, intLit(0))) + .ifthen( // only reduce stack depth if it's positive, since the stack depth is reset after the stack is unwound + depthPositive.asPath, Case.Lit(Tree.BoolLit(true)), + blockBuilder + .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("-", stackDepthPath, intLit(1))) + .end() + ) + .rest(f(tmp.asPath)) + case _ => f(res) + + // Rewrites anything that can contain a Call to increase the stack depth + def transform(b: Block): Block = b match + case Return(res, implct) => extractRes(res, Return(_, false)) + case Assign(lhs, rhs, rest) => extractRes(rhs, Assign(lhs, _, transform(rest))) + case b @ AssignField(lhs, nme, rhs, rest) => extractRes(rhs, AssignField(lhs, nme, _, transform(rest))(b.symbol)) + case Define(d: FunDefn, rest) => Define(rewriteFn(d), transform(rest)) + case HandleBlock(lhs, res, par, cls, handlers, body, rest) => + HandleBlock( + lhs, res, par, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, transform(h.body))), + transform(body), transform(rest) + ) + case HandleBlockReturn(res) => extractRes(res, HandleBlockReturn(_)) + case _ => b.map(transform) + + // TODO: this will just some simple analysis. some more in-depth analysis could be done at some other point + def isTrivial(b: Block): Boolean = b match + case Match(scrut, arms, dflt, rest) => + arms.foldLeft(dflt.map(isTrivial).getOrElse(true))((acc, bl) => acc && isTrivial(bl._2)) && isTrivial(rest) + case Return(res, implct) => true + case Throw(exc) => ??? + case Label(label, body, rest) => ??? + case Break(label) => ??? + case Continue(label) => ??? + case Begin(sub, rest) => ??? + case TryBlock(sub, finallyDo, rest) => ??? + case Assign(lhs, rhs, rest) => ??? + case AssignField(lhs, nme, rhs, rest) => ??? + case Define(defn, rest) => ??? + case HandleBlock(lhs, res, par, cls, handlers, body, rest) => ??? + case HandleBlockReturn(res) => ??? + case End(msg) => ??? + + + def rewriteFn(defn: FunDefn) = + val scrut1Sym = TempSymbol(None, "scrut1") + val scrut2Sym = TempSymbol(None, "scrut2") + val scrutSym = TempSymbol(None, "scrut") + val scrut1 = op(">=", stackDepthPath, intLit(depthLimit)) + val scrut2 = op("!==", stackHandlerPath, Value.Lit(Tree.UnitLit(false))) + val scrutVal = op("&&", scrut1Sym.asPath, scrut2Sym.asPath) + + val newBlk = transform(defn.body) + + val blk = blockBuilder + .assign(scrut1Sym, scrut1) // stackDepth >= depthLimit + .assign(scrut2Sym, scrut2) // stackHandler !== null + .assign(scrutSym, scrutVal) // stackDepth >= depthLimit && stackHandler !== null + .ifthen( + scrutSym.asPath, Case.Lit(Tree.BoolLit(true)), + blockBuilder.assign( // tmp = perform(undefined) + TempSymbol(None, "tmp"), + Call(Select(stackHandlerPath, Tree.Ident("perform"))(N), List(Value.Lit(Tree.UnitLit(true)).asArg))(true)).end()) + .rest(newBlk) + + FunDefn(defn.sym, defn.params, blk) + + def transformTopLevel(b: Block) = + // symbols + val resumeSym = VarSymbol(Tree.Ident("resume")) + val handlerSym = TempSymbol(None, "stackHandler"); + val resSym = TempSymbol(None, "res"); + val clsSym = ClassSymbol( + Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), + Tree.Ident("StackDelay$") + ) + clsSym.defn = S(ClassDef(N, syntax.Cls, clsSym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))))) + + // the global stack handler is created here + val handle = HandleBlock( + handlerSym, resSym, + stackDelayClsPath, clsSym, + List(Handler( + BlockMemberSymbol("resume", Nil), resumeSym, Nil, + blockBuilder + .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // Q: should this be 0 or 1 to account for some overhead? + .ret(Call(Value.Ref(resumeSym), List())(true)) + )), // create a "unit" handler, i.e. fun perform(k) = k(()) + blockBuilder + .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 + .assignFieldN(predefPath, STACK_HANDLER_IDENT, handlerSym.asPath) // assign stack handler + .rest(transform(b)), // transform the rest of the body + blockBuilder // reset the stack safety values + .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 + .assignFieldN(predefPath, STACK_HANDLER_IDENT, Value.Lit(Tree.UnitLit(false))) // set stackHandler = null + .rest(Return(Value.Ref(resSym), true)) + ) + + handle \ No newline at end of file diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 2dad64b583..14334803ff 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -114,6 +114,11 @@ const Predef$class = class Predef { } toString() { return "__Return(" + this.value + ")"; } }; + this.__StackDelay = function __StackDelay() { return new __StackDelay.class(); }; + this.__StackDelay.class = class __StackDelay { + constructor() {} + toString() { return "__StackDelay(" + + ")"; } + }; } id(x) { return x; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index c87bc24b03..e23ac5c4fe 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -226,3 +226,9 @@ fun __resume(cur, tail)(value) = else assert(cur.nextHandler === null) return value + +// stack safety +val __stackDepth = 0 +val __stackHandler = null +abstract class __StackDelay() with + fun perform() \ No newline at end of file diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index f46025be79..501fc487e4 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -7,5 +7,385 @@ :global :d +abstract class Effect with + fun perform(arg: Str): Str +//│ Elab: { Cls Effect { ‹› fun member:perform‹110›(Param(‹›,arg‹114›,Some(SynthSel(Ref(globalThis:import#Prelude‹25›),Ident(Str)))), ): globalThis:import#Prelude‹25›#666(.)Str‹member:Str‹42››; }; } +:lot +:handler +handle h = __StackDelay with + fun resume(k) = k +2 +//│ Elab: { handle globalThis:block#2‹118›.h = SynthSel(Ref(member:Predef‹108›),Ident(__StackDelay)) List(HandlerTermDefinition(k‹123›,TermDefinition(Some(class:Effect$__StackDelay$120‹121›),Fun,member:resume‹122›,List(),None,Some(Ref(k‹123›)),‹result of member:resume‹122››‹124›,‹›))); 2 } +//│ Lowered: +//│ Program: +//│ imports = Nil +//│ main = Define: +//│ defn = FunDefn: +//│ sym = member:handleBlock$0‹128› +//│ params = Ls of +//│ ParamList: +//│ flags = ParamListFlags of false +//│ params = Nil +//│ restParam = N +//│ body = Define: +//│ defn = ClsLikeDefn: +//│ sym = class:Effect$__StackDelay$120‹121› +//│ k = Cls +//│ parentPath = S of Select{member:__StackDelay‹99›}: +//│ qual = Ref of member:Predef‹108› +//│ name = Ident of "__StackDelay" +//│ methods = Ls of +//│ FunDefn: +//│ sym = member:resume‹122› +//│ params = Nil +//│ body = Return: +//│ res = Call: +//│ fun = Select: +//│ qual = Select: +//│ qual = Ref of globalThis:globalThis‹0› +//│ name = Ident of "Predef" +//│ name = Ident of "__mkEffect" +//│ args = Ls of +//│ Arg: +//│ spread = false +//│ value = Ref of globalThis:block#2‹118›.h +//│ Arg: +//│ spread = false +//│ value = Lam: +//│ params = ParamList: +//│ flags = ParamListFlags of false +//│ params = Ls of +//│ Param: +//│ flags = () +//│ sym = k‹123› +//│ sign = N +//│ restParam = N +//│ body = Return: +//│ res = Ref of k‹123› +//│ implct = false +//│ implct = false +//│ privateFields = Nil +//│ publicFields = Nil +//│ preCtor = Assign: +//│ lhs = $tmp‹144› +//│ rhs = Call: +//│ fun = Ref of builtin:super‹3› +//│ args = Nil +//│ rest = End of "" +//│ ctor = End of "" +//│ rest = Assign: \ +//│ lhs = globalThis:block#2‹118›.h +//│ rhs = Instantiate: +//│ cls = Ref of member:Effect$__StackDelay$120‹145› +//│ args = Nil +//│ rest = Return: \ +//│ res = Lit of IntLit of 2 +//│ implct = false +//│ rest = Assign: \ +//│ lhs = $tmp‹126› +//│ rhs = Call: +//│ fun = Ref of member:handleBlock$0‹128› +//│ args = Nil +//│ rest = Match: \ +//│ scrut = Ref of $tmp‹126› +//│ arms = Ls of +//│ Tuple2: +//│ _1 = Cls: +//│ cls = class:Dummy‹127› +//│ path = Select: +//│ qual = Select: +//│ qual = Select: +//│ qual = Ref of globalThis:globalThis‹0› +//│ name = Ident of "Predef" +//│ name = Ident of "__EffectSig" +//│ name = Ident of "class" +//│ _2 = Throw of Instantiate: +//│ cls = Select: +//│ qual = Ref of globalThis:globalThis‹0› +//│ name = Ident of "Error" +//│ args = Ls of +//│ Lit of StrLit of "Unhandled effects" +//│ dflt = N +//│ rest = Return: \ +//│ res = Ref of $tmp‹126› +//│ implct = true +//│ = 2 +:handler +handle h = Effect with + fun perform(arg)(k) = arg +h.perform(2) +//│ Elab: { handle globalThis:block#3‹152›.h = SynthSel(Ref(globalThis:block#1‹111›),Ident(Effect)) List(HandlerTermDefinition(k‹158›,TermDefinition(Some(class:Effect$Effect$154‹155›),Fun,member:perform‹156›,List(ParamList(‹›,List(Param(‹›,arg‹157›,None)),None)),None,Some(Ref(arg‹157›)),‹result of member:perform‹156››‹159›,‹›))); globalThis:block#3‹152›.h#666(.)perform(2) } +//│ = 2 + +:stackSafe +:handler +:sjs +fun sum(n) = + if n == 0 then 0 + else n + sum(n - 1) +sum(1000) +//│ Elab: { ‹› fun member:sum‹210›(Param(‹›,n‹211›,None), ) = if { let $scrut‹216› = builtin:==‹7›#0(n‹211›#666, 0); scrut is true -> { else 0 }; else builtin:+‹15›#0(n‹211›#666, globalThis:block#4‹209›#666(.)sum‹member:sum‹210››(builtin:-‹5›#0(n‹211›#666, 1))) }; globalThis:block#4‹209›#666(.)sum‹member:sum‹210››(1000) } +//│ JS (unsanitized): +//│ let res; +//│ function handleBlock$0() { +//│ let stackHandler, tmp, depthPositive, retCont, res1; +//│ class StackDelay$ extends globalThis.Predef.__StackDelay.class { +//│ constructor() { +//│ let tmp1; +//│ tmp1 = super(); +//│ } +//│ get resume() { +//│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { +//│ globalThis.Predef.__stackDepth = 0; +//│ return resume(); +//│ }); +//│ } +//│ toString() { return "StackDelay$"; } +//│ } +//│ stackHandler = new StackDelay$(); +//│ function Cont$331$2(pc1) { return new Cont$331$2.class(pc1); } +//│ Cont$331$2.class = class Cont$331$2 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp1; +//│ tmp1 = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 1) { +//│ res1 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 11) { +//│ globalThis.Predef.__stackDepth = 0; +//│ globalThis.Predef.__stackHandler = stackHandler; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ res1 = globalThis.sum(1000); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = this; +//│ this.pc = 1; +//│ return res1; +//│ } +//│ this.pc = 1; +//│ continue contLoop; +//│ } else if (this.pc === 1) { +//│ tmp = res1; +//│ depthPositive = globalThis.Predef.__stackDepth > 0; +//│ if (depthPositive) { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ this.pc = 12; +//│ continue contLoop; +//│ } +//│ this.pc = 12; +//│ continue contLoop; +//│ } else if (this.pc === 12) { +//│ retCont = tmp; +//│ return new globalThis.Predef.__Return.class(retCont); +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$331$2(" + this.pc + ")"; } +//│ }; +//│ function sum$1(n) { +//│ let scrut, tmp1, tmp2, scrut1, scrut2, scrut3, tmp3, depthPositive1, tmp4, depthPositive2, tmp5, depthPositive3, tmp6, depthPositive4, tmp7, res2, res3; +//│ function Cont$255$0(pc1) { return new Cont$255$0.class(pc1); } +//│ Cont$255$0.class = class Cont$255$0 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp8; +//│ tmp8 = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 3) { +//│ res3 = value$; +//│ } else if (this.pc === 2) { +//│ res2 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 4) { +//│ scrut1 = globalThis.Predef.__stackDepth >= 500; +//│ scrut2 = globalThis.Predef.__stackHandler !== undefined; +//│ scrut3 = scrut1 && scrut2; +//│ if (scrut3) { +//│ res2 = globalThis.Predef.__stackHandler.perform(null); +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = this; +//│ this.pc = 2; +//│ return res2; +//│ } +//│ this.pc = 2; +//│ continue contLoop; +//│ } +//│ this.pc = 10; +//│ continue contLoop; +//│ } else if (this.pc === 10) { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ tmp3 = n == 0; +//│ depthPositive1 = globalThis.Predef.__stackDepth > 0; +//│ if (depthPositive1) { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ this.pc = 9; +//│ continue contLoop; +//│ } +//│ this.pc = 9; +//│ continue contLoop; +//│ } else if (this.pc === 9) { +//│ scrut = tmp3; +//│ if (scrut) { +//│ return 0; +//│ } else { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ tmp4 = n - 1; +//│ depthPositive2 = globalThis.Predef.__stackDepth > 0; +//│ if (depthPositive2) { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ this.pc = 8; +//│ continue contLoop; +//│ } +//│ this.pc = 8; +//│ continue contLoop; +//│ } +//│ this.pc = 5; +//│ continue contLoop; +//│ } else if (this.pc === 5) { +//│ break contLoop; +//│ } else if (this.pc === 8) { +//│ tmp1 = tmp4; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ res3 = globalThis.sum(tmp1); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = this; +//│ this.pc = 3; +//│ return res3; +//│ } +//│ this.pc = 3; +//│ continue contLoop; +//│ } else if (this.pc === 3) { +//│ tmp5 = res3; +//│ depthPositive3 = globalThis.Predef.__stackDepth > 0; +//│ if (depthPositive3) { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ this.pc = 7; +//│ continue contLoop; +//│ } +//│ this.pc = 7; +//│ continue contLoop; +//│ } else if (this.pc === 7) { +//│ tmp2 = tmp5; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ tmp6 = n + tmp2; +//│ depthPositive4 = globalThis.Predef.__stackDepth > 0; +//│ if (depthPositive4) { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ this.pc = 6; +//│ continue contLoop; +//│ } +//│ this.pc = 6; +//│ continue contLoop; +//│ } else if (this.pc === 6) { +//│ return tmp6; +//│ } else if (this.pc === 2) { +//│ tmp7 = res2; +//│ this.pc = 10; +//│ continue contLoop; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$255$0(" + this.pc + ")"; } +//│ }; +//│ scrut1 = globalThis.Predef.__stackDepth >= 500; +//│ scrut2 = globalThis.Predef.__stackHandler !== undefined; +//│ scrut3 = scrut1 && scrut2; +//│ if (scrut3) { +//│ res2 = globalThis.Predef.__stackHandler.perform(null); +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = new Cont$255$0.class(2); +//│ res2.tail = res2.tail.next; +//│ return res2; +//│ } +//│ tmp7 = res2; +//│ } +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ tmp3 = n == 0; +//│ depthPositive1 = globalThis.Predef.__stackDepth > 0; +//│ if (depthPositive1) { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ } +//│ scrut = tmp3; +//│ if (scrut) { +//│ return 0; +//│ } else { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ tmp4 = n - 1; +//│ depthPositive2 = globalThis.Predef.__stackDepth > 0; +//│ if (depthPositive2) { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ } +//│ tmp1 = tmp4; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ res3 = globalThis.sum(tmp1); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = new Cont$255$0.class(3); +//│ res3.tail = res3.tail.next; +//│ return res3; +//│ } +//│ tmp5 = res3; +//│ depthPositive3 = globalThis.Predef.__stackDepth > 0; +//│ if (depthPositive3) { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ } +//│ tmp2 = tmp5; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ tmp6 = n + tmp2; +//│ depthPositive4 = globalThis.Predef.__stackDepth > 0; +//│ if (depthPositive4) { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ } +//│ return tmp6; +//│ } +//│ } +//│ globalThis.Predef.__stackDepth = 0; +//│ globalThis.Predef.__stackHandler = stackHandler; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ res1 = globalThis.sum(1000); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = new Cont$331$2(1); +//│ return globalThis.Predef.__handleBlockImpl(res1, stackHandler); +//│ } +//│ tmp = res1; +//│ depthPositive = globalThis.Predef.__stackDepth > 0; +//│ if (depthPositive) { +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ } +//│ retCont = tmp; +//│ return new globalThis.Predef.__Return.class(retCont); +//│ } +//│ res = handleBlock$0(); +//│ if (res instanceof this.Predef.__EffectSig.class) { +//│ throw new this.Error("Unhandled effects"); +//│ } +//│ this.Predef.__stackDepth = 0; +//│ this.Predef.__stackHandler = undefined; +//│ res +//│ FAILURE: Unexpected runtime error +//│ ═══[RUNTIME ERROR] TypeError: globalThis.sum is not a function +//│ at handleBlock$0 (REPL7:1:8355) +//│ at REPL7:1:8788 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:597:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:926:10) +//│ at REPLServer.emit (node:events:513:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:416:12) + +:sjs +if 2 > 0 then 2 +//│ Elab: { if { let $scrut‹573› = builtin:>‹19›#0(2, 0); scrut is true -> { else 2 }; } } +//│ JS (unsanitized): +//│ let scrut; scrut = 2 > 0; if (scrut) { 2 } else { throw new this.Error("match error"); } +//│ = 2 From b8c61208f0da0159bffbecbdbda277aa2517a9f5 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 8 Jan 2025 00:57:43 +0800 Subject: [PATCH 049/114] move helper function outside --- .../src/main/scala/hkmc2/codegen/Block.scala | 58 +++++++++++++++++ .../scala/hkmc2/codegen/HandlerLowering.scala | 63 +------------------ 2 files changed, 61 insertions(+), 60 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index b5317ca9c4..ea55527707 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -219,6 +219,64 @@ sealed abstract class Block extends Product with AutoLocated: case HandleBlockReturn(res) => res.freeVars case End(msg) => Set.empty + def floatOutDefns = + def rec(b: Block, acc: List[Defn]): (Block, List[Defn]) = + b match + case Match(scrut, arms, dflt, rest) => + val (armsRes, armsDefns) = arms.foldLeft[(List[(Case, Block)], List[Defn])](Nil, acc)( + (accc, d) => + val (accCases, accDefns) = accc + val (cse, blk) = d + val (resBlk, resDefns) = rec(blk, accDefns) + ((cse, resBlk) :: accCases, resDefns) + ) + dflt match + case None => + val (rstRes, rstDefns) = rec(rest, armsDefns) + (Match(scrut, armsRes, None, rstRes), rstDefns) + + case Some(dflt) => + val (dfltRes, dfltDefns) = rec(dflt, armsDefns) + val (rstRes, rstDefns) = rec(rest, dfltDefns) + (Match(scrut, armsRes, S(dfltRes), rstRes), rstDefns) + + case Return(res, implct) => (b, acc) + case Throw(exc) => (b, acc) + case Label(label, body, rest) => + val (bodyRes, bodyDefns) = rec(body, acc) + val (rstRes, rstDefns) = rec(rest, bodyDefns) + (Label(label, bodyRes, rstRes), rstDefns) + case Break(label) => (b, acc) + case Continue(label) => (b, acc) + case Begin(sub, rest) => + val (subRes, subDefns) = rec(sub, acc) + val (rstRes, rstDefns) = rec(rest, subDefns) + (Begin(subRes, rstRes), rstDefns) + case TryBlock(sub, finallyDo, rest) => + val (subRes, subDefns) = rec(sub, acc) + val (finallyRes, finallyDefns) = rec(rest, subDefns) + val (rstRes, rstDefns) = rec(rest, finallyDefns) + (TryBlock(subRes, finallyRes, rstRes), rstDefns) + case Assign(lhs, rhs, rest) => + val (rstRes, rstDefns) = rec(rest, acc) + (Assign(lhs, rhs, rstRes), rstDefns) + case a @ AssignField(path, nme, result, rest) => + val (rstRes, rstDefns) = rec(rest, acc) + (AssignField(path, nme, result, rstRes)(a.symbol), rstDefns) + case Define(defn, rest) => defn match + case ValDefn(owner, k, sym, rhs) => + val (rstRes, rstDefns) = rec(rest, acc) + (Define(defn, rstRes), rstDefns) + case _ => + val (rstRes, rstDefns) = rec(rest, defn :: acc) + (rstRes, rstDefns) + case HandleBlock(lhs, res, par, cls, handlers, body, rest) => + val (rstRes, rstDefns) = rec(rest, acc) + (HandleBlock(lhs, res, par, cls, handlers, body, rstRes), rstDefns) + case HandleBlockReturn(res) => (b, acc) + case End(msg) => (b, acc) + rec(this, Nil) + end Block sealed abstract class BlockTail extends Block diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 5563f3b146..78088c7a60 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -353,8 +353,9 @@ class HandlerLowering(using TL, Raise, Elaborator.State): private val thirdPassFresh = FreshId() // moves definitions to the top level of the block private def thirdPass(b: Block): Block = - - val (blk, defns) = popDefns(b, Nil) + // 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 val syms = defns.collect { case ClsLikeDefn(sym, k, parentPath, methods, privateFields, publicFields, preCtor, ctor) => sym case FunDefn(sym, params, body) => sym @@ -392,64 +393,6 @@ class HandlerLowering(using TL, Raise, Elaborator.State): private def translateCls(cls: ClsLikeDefn): ClsLikeDefn = cls.copy(methods = cls.methods.map(translateFun), ctor = translateBlock(cls.ctor, functionHandlerCtx)) - - // 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 - def popDefns(b: Block, acc: List[Defn]): (Block, List[Defn]) = - b match - case Match(scrut, arms, dflt, rest) => - val (armsRes, armsDefns) = arms.foldLeft[(List[(Case, Block)], List[Defn])](Nil, acc)( - (accc, d) => - val (accCases, accDefns) = accc - val (cse, blk) = d - val (resBlk, resDefns) = popDefns(blk, accDefns) - ((cse, resBlk) :: accCases, resDefns) - ) - dflt match - case None => - val (rstRes, rstDefns) = popDefns(rest, armsDefns) - (Match(scrut, armsRes, None, rstRes), rstDefns) - - case Some(dflt) => - val (dfltRes, dfltDefns) = popDefns(dflt, armsDefns) - val (rstRes, rstDefns) = popDefns(rest, dfltDefns) - (Match(scrut, armsRes, S(dfltRes), rstRes), rstDefns) - - case Return(res, implct) => (b, acc) - case Throw(exc) => (b, acc) - case Label(label, body, rest) => - val (bodyRes, bodyDefns) = popDefns(body, acc) - val (rstRes, rstDefns) = popDefns(rest, bodyDefns) - (Label(label, bodyRes, rstRes), rstDefns) - case Break(label) => (b, acc) - case Continue(label) => (b, acc) - case Begin(sub, rest) => - val (subRes, subDefns) = popDefns(sub, acc) - val (rstRes, rstDefns) = popDefns(rest, subDefns) - (Begin(subRes, rstRes), rstDefns) - case TryBlock(sub, finallyDo, rest) => - val (subRes, subDefns) = popDefns(sub, acc) - val (finallyRes, finallyDefns) = popDefns(rest, subDefns) - val (rstRes, rstDefns) = popDefns(rest, finallyDefns) - (TryBlock(subRes, finallyRes, rstRes), rstDefns) - case Assign(lhs, rhs, rest) => - val (rstRes, rstDefns) = popDefns(rest, acc) - (Assign(lhs, rhs, rstRes), rstDefns) - case a @ AssignField(path, nme, result, rest) => - val (rstRes, rstDefns) = popDefns(rest, acc) - (AssignField(path, nme, result, rstRes)(a.symbol), rstDefns) - case Define(defn, rest) => defn match - case ValDefn(owner, k, sym, rhs) => - val (rstRes, rstDefns) = popDefns(rest, acc) - (Define(defn, rstRes), rstDefns) - case _ => - val (rstRes, rstDefns) = popDefns(rest, defn :: acc) - (rstRes, rstDefns) - case HandleBlock(lhs, res, par, cls, handlers, body, rest) => - val (rstRes, rstDefns) = popDefns(rest, acc) - (HandleBlock(lhs, res, par, cls, handlers, body, rstRes), rstDefns) - case HandleBlockReturn(res) => (b, acc) - case End(msg) => (b, acc) // Handle block becomes a FunDefn and CallPlaceholder private def translateHandleBlock(h: HandleBlock): Block = From cb6892928ad201c5d858292a5ee5e3d2bc8325ae Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 8 Jan 2025 01:50:12 +0800 Subject: [PATCH 050/114] initial stack safety working --- .../src/main/scala/hkmc2/codegen/Block.scala | 60 +-- .../scala/hkmc2/codegen/HandlerLowering.scala | 2 +- .../hkmc2/codegen/StackSafeTransform.scala | 41 +- .../src/test/mlscript-compile/Predef.mjs | 2 + hkmc2/shared/src/test/mlscript/HkScratch.mls | 380 ------------------ .../test/mlscript/handlers/StackSafety.mls | 28 ++ 6 files changed, 91 insertions(+), 422 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index ea55527707..0069476874 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -219,33 +219,41 @@ sealed abstract class Block extends Product with AutoLocated: case HandleBlockReturn(res) => res.freeVars case End(msg) => Set.empty - def floatOutDefns = + def floatOutDefns(outerOnly: Bool) = def rec(b: Block, acc: List[Defn]): (Block, List[Defn]) = b match case Match(scrut, arms, dflt, rest) => - val (armsRes, armsDefns) = arms.foldLeft[(List[(Case, Block)], List[Defn])](Nil, acc)( - (accc, d) => - val (accCases, accDefns) = accc - val (cse, blk) = d - val (resBlk, resDefns) = rec(blk, accDefns) - ((cse, resBlk) :: accCases, resDefns) - ) - dflt match - case None => - val (rstRes, rstDefns) = rec(rest, armsDefns) - (Match(scrut, armsRes, None, rstRes), rstDefns) - - case Some(dflt) => - val (dfltRes, dfltDefns) = rec(dflt, armsDefns) - val (rstRes, rstDefns) = rec(rest, dfltDefns) - (Match(scrut, armsRes, S(dfltRes), rstRes), rstDefns) + if outerOnly then + val (rstRes, rstDefns) = rec(rest, acc) + (Match(scrut, arms, dflt, rstRes), rstDefns) + else + val (armsRes, armsDefns) = arms.foldLeft[(List[(Case, Block)], List[Defn])](Nil, acc)( + (accc, d) => + val (accCases, accDefns) = accc + val (cse, blk) = d + val (resBlk, resDefns) = rec(blk, accDefns) + ((cse, resBlk) :: accCases, resDefns) + ) + dflt match + case None => + val (rstRes, rstDefns) = rec(rest, armsDefns) + (Match(scrut, armsRes, None, rstRes), rstDefns) + + case Some(dflt) => + val (dfltRes, dfltDefns) = rec(dflt, armsDefns) + val (rstRes, rstDefns) = rec(rest, dfltDefns) + (Match(scrut, armsRes, S(dfltRes), rstRes), rstDefns) case Return(res, implct) => (b, acc) case Throw(exc) => (b, acc) case Label(label, body, rest) => - val (bodyRes, bodyDefns) = rec(body, acc) - val (rstRes, rstDefns) = rec(rest, bodyDefns) - (Label(label, bodyRes, rstRes), rstDefns) + if outerOnly then + val (rstRes, rstDefns) = rec(rest, acc) + (Label(label, body, rstRes), rstDefns) + else + val (bodyRes, bodyDefns) = rec(body, acc) + val (rstRes, rstDefns) = rec(rest, bodyDefns) + (Label(label, bodyRes, rstRes), rstDefns) case Break(label) => (b, acc) case Continue(label) => (b, acc) case Begin(sub, rest) => @@ -253,10 +261,14 @@ sealed abstract class Block extends Product with AutoLocated: val (rstRes, rstDefns) = rec(rest, subDefns) (Begin(subRes, rstRes), rstDefns) case TryBlock(sub, finallyDo, rest) => - val (subRes, subDefns) = rec(sub, acc) - val (finallyRes, finallyDefns) = rec(rest, subDefns) - val (rstRes, rstDefns) = rec(rest, finallyDefns) - (TryBlock(subRes, finallyRes, rstRes), rstDefns) + if outerOnly then + val (rstRes, rstDefns) = rec(rest, acc) + (TryBlock(sub, finallyDo, rstRes), rstDefns) + else + val (subRes, subDefns) = rec(sub, acc) + val (finallyRes, finallyDefns) = rec(rest, subDefns) + val (rstRes, rstDefns) = rec(rest, finallyDefns) + (TryBlock(subRes, finallyRes, rstRes), rstDefns) case Assign(lhs, rhs, rest) => val (rstRes, rstDefns) = rec(rest, acc) (Assign(lhs, rhs, rstRes), rstDefns) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 78088c7a60..c70c40278e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -355,7 +355,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State): 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 + val (blk, defns) = b.floatOutDefns(false) val syms = defns.collect { case ClsLikeDefn(sym, k, parentPath, methods, privateFields, publicFields, preCtor, ctor) => sym case FunDefn(sym, params, body) => sym diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 0f66d1f0c1..9c89e6d6b1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -24,9 +24,6 @@ class StackSafeTransform(depthLimit: Int)(using State): private val stackHandlerPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(STACK_HANDLER_IDENT) private val predefPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")) - private val geqPath = State.builtinOpsMap(">=").asPath - private val addPath = State.builtinOpsMap("+").asPath - private def intLit(n: BigInt) = Value.Lit(Tree.IntLit(n)) private def op(op: String, a: Path, b: Path) = @@ -77,7 +74,7 @@ class StackSafeTransform(depthLimit: Int)(using State): case Return(res, implct) => extractRes(res, Return(_, false)) case Assign(lhs, rhs, rest) => extractRes(rhs, Assign(lhs, _, transform(rest))) case b @ AssignField(lhs, nme, rhs, rest) => extractRes(rhs, AssignField(lhs, nme, _, transform(rest))(b.symbol)) - case Define(d: FunDefn, rest) => Define(rewriteFn(d), transform(rest)) + case Define(defn, rest) => Define(rewriteDefn(defn), transform(rest)) case HandleBlock(lhs, res, par, cls, handlers, body, rest) => HandleBlock( lhs, res, par, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, transform(h.body))), @@ -103,9 +100,17 @@ class StackSafeTransform(depthLimit: Int)(using State): case HandleBlock(lhs, res, par, cls, handlers, body, rest) => ??? case HandleBlockReturn(res) => ??? case End(msg) => ??? - - - def rewriteFn(defn: FunDefn) = + + def rewriteDefn(defn: Defn) = defn match + case d: FunDefn => rewriteFn(d) + case _: ValDefn => defn + case ClsLikeDefn(sym, k, parentPath, methods, privateFields, publicFields, preCtor, ctor) => + ClsLikeDefn( + sym, k, parentPath, methods.map(rewriteFn), privateFields, publicFields, + rewriteBlk(preCtor), rewriteBlk(ctor) // TODO: do we need to rewrite the preCtor? + ) + + def rewriteBlk(blk: Block) = val scrut1Sym = TempSymbol(None, "scrut1") val scrut2Sym = TempSymbol(None, "scrut2") val scrutSym = TempSymbol(None, "scrut") @@ -113,9 +118,9 @@ class StackSafeTransform(depthLimit: Int)(using State): val scrut2 = op("!==", stackHandlerPath, Value.Lit(Tree.UnitLit(false))) val scrutVal = op("&&", scrut1Sym.asPath, scrut2Sym.asPath) - val newBlk = transform(defn.body) + val newBody = transform(blk) - val blk = blockBuilder + blockBuilder .assign(scrut1Sym, scrut1) // stackDepth >= depthLimit .assign(scrut2Sym, scrut2) // stackHandler !== null .assign(scrutSym, scrutVal) // stackDepth >= depthLimit && stackHandler !== null @@ -123,10 +128,10 @@ class StackSafeTransform(depthLimit: Int)(using State): scrutSym.asPath, Case.Lit(Tree.BoolLit(true)), blockBuilder.assign( // tmp = perform(undefined) TempSymbol(None, "tmp"), - Call(Select(stackHandlerPath, Tree.Ident("perform"))(N), List(Value.Lit(Tree.UnitLit(true)).asArg))(true)).end()) - .rest(newBlk) - - FunDefn(defn.sym, defn.params, blk) + Call(Select(stackHandlerPath, Tree.Ident("perform"))(N), Nil)(true)).end()) + .rest(newBody) + + def rewriteFn(defn: FunDefn) = FunDefn(defn.sym, defn.params, rewriteBlk(defn.body)) def transformTopLevel(b: Block) = // symbols @@ -138,13 +143,15 @@ class StackSafeTransform(depthLimit: Int)(using State): Tree.Ident("StackDelay$") ) clsSym.defn = S(ClassDef(N, syntax.Cls, clsSym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))))) - + + val (blk, defns) = b.floatOutDefns(true) + // the global stack handler is created here val handle = HandleBlock( handlerSym, resSym, stackDelayClsPath, clsSym, List(Handler( - BlockMemberSymbol("resume", Nil), resumeSym, Nil, + BlockMemberSymbol("perform", Nil), resumeSym, List(ParamList(ParamListFlags.empty, Nil, N)), blockBuilder .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // Q: should this be 0 or 1 to account for some overhead? .ret(Call(Value.Ref(resumeSym), List())(true)) @@ -152,11 +159,11 @@ class StackSafeTransform(depthLimit: Int)(using State): blockBuilder .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 .assignFieldN(predefPath, STACK_HANDLER_IDENT, handlerSym.asPath) // assign stack handler - .rest(transform(b)), // transform the rest of the body + .rest(transform(blk)), // transform the rest of the body blockBuilder // reset the stack safety values .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 .assignFieldN(predefPath, STACK_HANDLER_IDENT, Value.Lit(Tree.UnitLit(false))) // set stackHandler = null .rest(Return(Value.Ref(resSym), true)) ) - handle \ No newline at end of file + defns.foldLeft[Block](handle)((blk, defn) => Define(rewriteDefn(defn), blk)) \ No newline at end of file diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index ba3ae4d896..fc011c71f1 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -114,6 +114,8 @@ const Predef$class = class Predef { } toString() { return "__Return(" + this.value + ")"; } }; + this.__stackDepth = 0; + this.__stackHandler = null; this.__StackDelay = function __StackDelay() { return new __StackDelay.class(); }; this.__StackDelay.class = class __StackDelay { constructor() {} diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index 501fc487e4..f46025be79 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -7,385 +7,5 @@ :global :d -abstract class Effect with - fun perform(arg: Str): Str -//│ Elab: { Cls Effect { ‹› fun member:perform‹110›(Param(‹›,arg‹114›,Some(SynthSel(Ref(globalThis:import#Prelude‹25›),Ident(Str)))), ): globalThis:import#Prelude‹25›#666(.)Str‹member:Str‹42››; }; } -:lot -:handler -handle h = __StackDelay with - fun resume(k) = k -2 -//│ Elab: { handle globalThis:block#2‹118›.h = SynthSel(Ref(member:Predef‹108›),Ident(__StackDelay)) List(HandlerTermDefinition(k‹123›,TermDefinition(Some(class:Effect$__StackDelay$120‹121›),Fun,member:resume‹122›,List(),None,Some(Ref(k‹123›)),‹result of member:resume‹122››‹124›,‹›))); 2 } -//│ Lowered: -//│ Program: -//│ imports = Nil -//│ main = Define: -//│ defn = FunDefn: -//│ sym = member:handleBlock$0‹128› -//│ params = Ls of -//│ ParamList: -//│ flags = ParamListFlags of false -//│ params = Nil -//│ restParam = N -//│ body = Define: -//│ defn = ClsLikeDefn: -//│ sym = class:Effect$__StackDelay$120‹121› -//│ k = Cls -//│ parentPath = S of Select{member:__StackDelay‹99›}: -//│ qual = Ref of member:Predef‹108› -//│ name = Ident of "__StackDelay" -//│ methods = Ls of -//│ FunDefn: -//│ sym = member:resume‹122› -//│ params = Nil -//│ body = Return: -//│ res = Call: -//│ fun = Select: -//│ qual = Select: -//│ qual = Ref of globalThis:globalThis‹0› -//│ name = Ident of "Predef" -//│ name = Ident of "__mkEffect" -//│ args = Ls of -//│ Arg: -//│ spread = false -//│ value = Ref of globalThis:block#2‹118›.h -//│ Arg: -//│ spread = false -//│ value = Lam: -//│ params = ParamList: -//│ flags = ParamListFlags of false -//│ params = Ls of -//│ Param: -//│ flags = () -//│ sym = k‹123› -//│ sign = N -//│ restParam = N -//│ body = Return: -//│ res = Ref of k‹123› -//│ implct = false -//│ implct = false -//│ privateFields = Nil -//│ publicFields = Nil -//│ preCtor = Assign: -//│ lhs = $tmp‹144› -//│ rhs = Call: -//│ fun = Ref of builtin:super‹3› -//│ args = Nil -//│ rest = End of "" -//│ ctor = End of "" -//│ rest = Assign: \ -//│ lhs = globalThis:block#2‹118›.h -//│ rhs = Instantiate: -//│ cls = Ref of member:Effect$__StackDelay$120‹145› -//│ args = Nil -//│ rest = Return: \ -//│ res = Lit of IntLit of 2 -//│ implct = false -//│ rest = Assign: \ -//│ lhs = $tmp‹126› -//│ rhs = Call: -//│ fun = Ref of member:handleBlock$0‹128› -//│ args = Nil -//│ rest = Match: \ -//│ scrut = Ref of $tmp‹126› -//│ arms = Ls of -//│ Tuple2: -//│ _1 = Cls: -//│ cls = class:Dummy‹127› -//│ path = Select: -//│ qual = Select: -//│ qual = Select: -//│ qual = Ref of globalThis:globalThis‹0› -//│ name = Ident of "Predef" -//│ name = Ident of "__EffectSig" -//│ name = Ident of "class" -//│ _2 = Throw of Instantiate: -//│ cls = Select: -//│ qual = Ref of globalThis:globalThis‹0› -//│ name = Ident of "Error" -//│ args = Ls of -//│ Lit of StrLit of "Unhandled effects" -//│ dflt = N -//│ rest = Return: \ -//│ res = Ref of $tmp‹126› -//│ implct = true -//│ = 2 -:handler -handle h = Effect with - fun perform(arg)(k) = arg -h.perform(2) -//│ Elab: { handle globalThis:block#3‹152›.h = SynthSel(Ref(globalThis:block#1‹111›),Ident(Effect)) List(HandlerTermDefinition(k‹158›,TermDefinition(Some(class:Effect$Effect$154‹155›),Fun,member:perform‹156›,List(ParamList(‹›,List(Param(‹›,arg‹157›,None)),None)),None,Some(Ref(arg‹157›)),‹result of member:perform‹156››‹159›,‹›))); globalThis:block#3‹152›.h#666(.)perform(2) } -//│ = 2 - -:stackSafe -:handler -:sjs -fun sum(n) = - if n == 0 then 0 - else n + sum(n - 1) -sum(1000) -//│ Elab: { ‹› fun member:sum‹210›(Param(‹›,n‹211›,None), ) = if { let $scrut‹216› = builtin:==‹7›#0(n‹211›#666, 0); scrut is true -> { else 0 }; else builtin:+‹15›#0(n‹211›#666, globalThis:block#4‹209›#666(.)sum‹member:sum‹210››(builtin:-‹5›#0(n‹211›#666, 1))) }; globalThis:block#4‹209›#666(.)sum‹member:sum‹210››(1000) } -//│ JS (unsanitized): -//│ let res; -//│ function handleBlock$0() { -//│ let stackHandler, tmp, depthPositive, retCont, res1; -//│ class StackDelay$ extends globalThis.Predef.__StackDelay.class { -//│ constructor() { -//│ let tmp1; -//│ tmp1 = super(); -//│ } -//│ get resume() { -//│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ globalThis.Predef.__stackDepth = 0; -//│ return resume(); -//│ }); -//│ } -//│ toString() { return "StackDelay$"; } -//│ } -//│ stackHandler = new StackDelay$(); -//│ function Cont$331$2(pc1) { return new Cont$331$2.class(pc1); } -//│ Cont$331$2.class = class Cont$331$2 extends globalThis.Predef.__Cont.class { -//│ constructor(pc) { -//│ let tmp1; -//│ tmp1 = super(null, null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 1) { -//│ res1 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 11) { -//│ globalThis.Predef.__stackDepth = 0; -//│ globalThis.Predef.__stackHandler = stackHandler; -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res1 = globalThis.sum(1000); -//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = this; -//│ this.pc = 1; -//│ return res1; -//│ } -//│ this.pc = 1; -//│ continue contLoop; -//│ } else if (this.pc === 1) { -//│ tmp = res1; -//│ depthPositive = globalThis.Predef.__stackDepth > 0; -//│ if (depthPositive) { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; -//│ this.pc = 12; -//│ continue contLoop; -//│ } -//│ this.pc = 12; -//│ continue contLoop; -//│ } else if (this.pc === 12) { -//│ retCont = tmp; -//│ return new globalThis.Predef.__Return.class(retCont); -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return "Cont$331$2(" + this.pc + ")"; } -//│ }; -//│ function sum$1(n) { -//│ let scrut, tmp1, tmp2, scrut1, scrut2, scrut3, tmp3, depthPositive1, tmp4, depthPositive2, tmp5, depthPositive3, tmp6, depthPositive4, tmp7, res2, res3; -//│ function Cont$255$0(pc1) { return new Cont$255$0.class(pc1); } -//│ Cont$255$0.class = class Cont$255$0 extends globalThis.Predef.__Cont.class { -//│ constructor(pc) { -//│ let tmp8; -//│ tmp8 = super(null, null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 3) { -//│ res3 = value$; -//│ } else if (this.pc === 2) { -//│ res2 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 4) { -//│ scrut1 = globalThis.Predef.__stackDepth >= 500; -//│ scrut2 = globalThis.Predef.__stackHandler !== undefined; -//│ scrut3 = scrut1 && scrut2; -//│ if (scrut3) { -//│ res2 = globalThis.Predef.__stackHandler.perform(null); -//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = this; -//│ this.pc = 2; -//│ return res2; -//│ } -//│ this.pc = 2; -//│ continue contLoop; -//│ } -//│ this.pc = 10; -//│ continue contLoop; -//│ } else if (this.pc === 10) { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ tmp3 = n == 0; -//│ depthPositive1 = globalThis.Predef.__stackDepth > 0; -//│ if (depthPositive1) { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; -//│ this.pc = 9; -//│ continue contLoop; -//│ } -//│ this.pc = 9; -//│ continue contLoop; -//│ } else if (this.pc === 9) { -//│ scrut = tmp3; -//│ if (scrut) { -//│ return 0; -//│ } else { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ tmp4 = n - 1; -//│ depthPositive2 = globalThis.Predef.__stackDepth > 0; -//│ if (depthPositive2) { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; -//│ this.pc = 8; -//│ continue contLoop; -//│ } -//│ this.pc = 8; -//│ continue contLoop; -//│ } -//│ this.pc = 5; -//│ continue contLoop; -//│ } else if (this.pc === 5) { -//│ break contLoop; -//│ } else if (this.pc === 8) { -//│ tmp1 = tmp4; -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res3 = globalThis.sum(tmp1); -//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = this; -//│ this.pc = 3; -//│ return res3; -//│ } -//│ this.pc = 3; -//│ continue contLoop; -//│ } else if (this.pc === 3) { -//│ tmp5 = res3; -//│ depthPositive3 = globalThis.Predef.__stackDepth > 0; -//│ if (depthPositive3) { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; -//│ this.pc = 7; -//│ continue contLoop; -//│ } -//│ this.pc = 7; -//│ continue contLoop; -//│ } else if (this.pc === 7) { -//│ tmp2 = tmp5; -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ tmp6 = n + tmp2; -//│ depthPositive4 = globalThis.Predef.__stackDepth > 0; -//│ if (depthPositive4) { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; -//│ this.pc = 6; -//│ continue contLoop; -//│ } -//│ this.pc = 6; -//│ continue contLoop; -//│ } else if (this.pc === 6) { -//│ return tmp6; -//│ } else if (this.pc === 2) { -//│ tmp7 = res2; -//│ this.pc = 10; -//│ continue contLoop; -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return "Cont$255$0(" + this.pc + ")"; } -//│ }; -//│ scrut1 = globalThis.Predef.__stackDepth >= 500; -//│ scrut2 = globalThis.Predef.__stackHandler !== undefined; -//│ scrut3 = scrut1 && scrut2; -//│ if (scrut3) { -//│ res2 = globalThis.Predef.__stackHandler.perform(null); -//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$255$0.class(2); -//│ res2.tail = res2.tail.next; -//│ return res2; -//│ } -//│ tmp7 = res2; -//│ } -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ tmp3 = n == 0; -//│ depthPositive1 = globalThis.Predef.__stackDepth > 0; -//│ if (depthPositive1) { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; -//│ } -//│ scrut = tmp3; -//│ if (scrut) { -//│ return 0; -//│ } else { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ tmp4 = n - 1; -//│ depthPositive2 = globalThis.Predef.__stackDepth > 0; -//│ if (depthPositive2) { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; -//│ } -//│ tmp1 = tmp4; -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res3 = globalThis.sum(tmp1); -//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$255$0.class(3); -//│ res3.tail = res3.tail.next; -//│ return res3; -//│ } -//│ tmp5 = res3; -//│ depthPositive3 = globalThis.Predef.__stackDepth > 0; -//│ if (depthPositive3) { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; -//│ } -//│ tmp2 = tmp5; -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ tmp6 = n + tmp2; -//│ depthPositive4 = globalThis.Predef.__stackDepth > 0; -//│ if (depthPositive4) { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; -//│ } -//│ return tmp6; -//│ } -//│ } -//│ globalThis.Predef.__stackDepth = 0; -//│ globalThis.Predef.__stackHandler = stackHandler; -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res1 = globalThis.sum(1000); -//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$331$2(1); -//│ return globalThis.Predef.__handleBlockImpl(res1, stackHandler); -//│ } -//│ tmp = res1; -//│ depthPositive = globalThis.Predef.__stackDepth > 0; -//│ if (depthPositive) { -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; -//│ } -//│ retCont = tmp; -//│ return new globalThis.Predef.__Return.class(retCont); -//│ } -//│ res = handleBlock$0(); -//│ if (res instanceof this.Predef.__EffectSig.class) { -//│ throw new this.Error("Unhandled effects"); -//│ } -//│ this.Predef.__stackDepth = 0; -//│ this.Predef.__stackHandler = undefined; -//│ res -//│ FAILURE: Unexpected runtime error -//│ ═══[RUNTIME ERROR] TypeError: globalThis.sum is not a function -//│ at handleBlock$0 (REPL7:1:8355) -//│ at REPL7:1:8788 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:597:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:926:10) -//│ at REPLServer.emit (node:events:513:28) -//│ at REPLServer.emit (node:domain:489:12) -//│ at [_onLine] [as _onLine] (node:internal/readline/interface:416:12) - -:sjs -if 2 > 0 then 2 -//│ Elab: { if { let $scrut‹573› = builtin:>‹19›#0(2, 0); scrut is true -> { else 2 }; } } -//│ JS (unsanitized): -//│ let scrut; scrut = 2 > 0; if (scrut) { 2 } else { throw new this.Error("match error"); } -//│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls new file mode 100644 index 0000000000..a9f72e7ffc --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -0,0 +1,28 @@ +:js + +:expect 5050 +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +sum(100) +//│ = 5050 + +// FIXME: __Return is leaked +:stackSafe 1000 +:handler +:expect __Return { value: 5000050000 } +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +sum(100000) +//│ = __Return { value: 5000050000 } + +:re +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +sum(100000) +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded From eb71e32f3a5592100330d134935117638a21c390 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 8 Jan 2025 02:33:56 +0800 Subject: [PATCH 051/114] fix return leaked --- .../src/main/scala/hkmc2/codegen/StackSafeTransform.scala | 6 +++++- hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 9c89e6d6b1..2cdf44632a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -134,6 +134,10 @@ class StackSafeTransform(depthLimit: Int)(using State): def rewriteFn(defn: FunDefn) = FunDefn(defn.sym, defn.params, rewriteBlk(defn.body)) def transformTopLevel(b: Block) = + def replaceReturns(b: Block): Block = b match + case Return(res, false) => HandleBlockReturn(res) + case _ => b.map(replaceReturns) + // symbols val resumeSym = VarSymbol(Tree.Ident("resume")) val handlerSym = TempSymbol(None, "stackHandler"); @@ -159,7 +163,7 @@ class StackSafeTransform(depthLimit: Int)(using State): blockBuilder .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 .assignFieldN(predefPath, STACK_HANDLER_IDENT, handlerSym.asPath) // assign stack handler - .rest(transform(blk)), // transform the rest of the body + .rest(replaceReturns(transform(blk))), // transform the rest of the body blockBuilder // reset the stack safety values .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 .assignFieldN(predefPath, STACK_HANDLER_IDENT, Value.Lit(Tree.UnitLit(false))) // set stackHandler = null diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index a9f72e7ffc..12c7bc5831 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -11,18 +11,18 @@ sum(100) // FIXME: __Return is leaked :stackSafe 1000 :handler -:expect __Return { value: 5000050000 } +:expect 50005000 fun sum(n) = if n == 0 then 0 else n + sum(n - 1) -sum(100000) -//│ = __Return { value: 5000050000 } +sum(10000) +//│ = 50005000 :re fun sum(n) = if n == 0 then 0 else n + sum(n - 1) -sum(100000) +sum(10000) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded From fe076e357acae246eb6a4dbdea011e9f1c3aaf39 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 8 Jan 2025 02:34:15 +0800 Subject: [PATCH 052/114] remove fixme --- hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls | 1 - 1 file changed, 1 deletion(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 12c7bc5831..9dc3fe8259 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -8,7 +8,6 @@ fun sum(n) = sum(100) //│ = 5050 -// FIXME: __Return is leaked :stackSafe 1000 :handler :expect 50005000 From 67617c184b359ddbd762822b76967d48220c55a3 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 8 Jan 2025 03:56:38 +0800 Subject: [PATCH 053/114] update test --- hkmc2/shared/src/test/mlscript/parser/Handler.mls | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index 10fc272ae1..c6b6caefce 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$133),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } } +//│ Elab: { { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$137),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } } :e ( @@ -73,7 +73,7 @@ handle h = Eff with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$162),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$162),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } +//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$166),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$166),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } :e handle h = Eff with @@ -127,4 +127,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.125: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$239),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$239),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } +//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$243),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$243),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } From 2393ea506eb61a750a5b34323c4249421104d8ed Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 8 Jan 2025 20:48:57 +0800 Subject: [PATCH 054/114] Remove usage of dummy class symbol --- .../test/scala/hkmc2/JSBackendDiffMaker.scala | 4 ++-- .../scala/hkmc2/codegen/HandlerLowering.scala | 17 ++++++++--------- .../src/main/scala/hkmc2/codegen/Lowering.scala | 2 +- .../shared/src/test/mlscript/decls/Prelude.mls | 2 ++ .../shared/src/test/mlscript/parser/Handler.mls | 6 +++--- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala index 5fa130c5e2..9bf0e63846 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -60,12 +60,12 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: showingJSYieldedCompileError = true outerRaise(d) case d => outerRaise(d) + given Elaborator.Ctx = curCtx val low = ltl.givenIn: new codegen.Lowering with codegen.LoweringSelSanityChecks(instrument = false) with codegen.LoweringTraceLog(instrument = false) with codegen.LoweringHandler(handler.isSet) - given Elaborator.Ctx = curCtx val jsb = new JSBuilder with JSBuilderArgNumSanityChecks(instrument = false) val le = low.program(blk) @@ -76,12 +76,12 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: output(s"JS (unsanitized):") output(jsStr) if js.isSet && !showingJSYieldedCompileError then + given Elaborator.Ctx = curCtx val low = ltl.givenIn: new codegen.Lowering with codegen.LoweringSelSanityChecks(noSanityCheck.isUnset) with codegen.LoweringTraceLog(traceJS.isSet) with codegen.LoweringHandler(handler.isSet) - given Elaborator.Ctx = curCtx val jsb = new JSBuilder with JSBuilderArgNumSanityChecks(noSanityCheck.isUnset) val le = low.program(blk) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 78088c7a60..4273546636 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -11,6 +11,7 @@ import semantics.Elaborator.State import syntax.{Literal, Tree, ParamBind} import semantics.* import scala.annotation.tailrec +import hkmc2.semantics.Elaborator.ctx object HandlerLowering: @@ -62,7 +63,7 @@ object HandlerLowering: import HandlerLowering.* -class HandlerLowering(using TL, Raise, Elaborator.State): +class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): private val functionHandlerCtx = HandlerCtx(true, false, state => val tmp = freshTmp() @@ -73,16 +74,14 @@ class HandlerLowering(using TL, Raise, Elaborator.State): ) private def handlerCtx(using HandlerCtx): HandlerCtx = summon private val effectSigPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__EffectSig")).selN(Tree.Ident("class")) + private val effectSigSym: ClassSymbol = ctx.Builtins.Predef.tree.definedSymbols.get("__EffectSig").get.asCls.get private val contClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__Cont")).selN(Tree.Ident("class")) private val retClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__Return")).selN(Tree.Ident("class")) + private val retClsSym: ClassSymbol = ctx.Builtins.Predef.tree.definedSymbols.get("__Return").get.asCls.get private val handleEffectFun: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__handleEffect")) private val mkEffectPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__mkEffect")) private val handleBlockImplPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__handleBlockImpl")) private val mapPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Map")) - private val dummyClsSym = ClassSymbol( - Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), - Tree.Ident("Dummy") - ) private def freshTmp(dbgNme: Str = "tmp") = new TempSymbol(N, dbgNme) @@ -453,13 +452,13 @@ class HandlerLowering(using TL, Raise, Elaborator.State): .assign(res, c) .ifthen( res.asPath, - Case.Cls(dummyClsSym, effectSigPath), + Case.Cls(effectSigSym, effectSigPath), ReturnCont(res, uid) ) .chain(ResumptionPoint(res, uid, _)) .staticif(canRet, _.ifthen( res.asPath, - Case.Cls(dummyClsSym, retClsPath), + Case.Cls(retClsSym, retClsPath), blockBuilder.ret(if handlerCtx.isHandleFree then res.asPath.value else res.asPath) )) .rest(rest) @@ -537,12 +536,12 @@ class HandlerLowering(using TL, Raise, Elaborator.State): .assign(res, c) .ifthen( res.asPath, - Case.Cls(dummyClsSym, effectSigPath), + Case.Cls(effectSigSym, effectSigPath), handlerCtx.linkAndHandle(LinkState(res.asPath, clsSym.asPath, uid)) ) .staticif(canRet && !handlerCtx.isTopLevel, _.ifthen( res.asPath, - Case.Cls(dummyClsSym, retClsPath), + Case.Cls(retClsSym, retClsPath), blockBuilder.ret(if handlerCtx.isHandleFree then res.asPath.value else res.asPath) )) .rest(rest) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 0ba418b069..eff0842f0b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -481,7 +481,7 @@ trait LoweringTraceLog trait LoweringHandler - (instrument: Bool)(using TL, Raise, Elaborator.State) + (instrument: Bool)(using TL, Raise, Elaborator.State, Elaborator.Ctx) extends Lowering: override def term(t: st)(k: Result => Block)(using Subst): Block = if !instrument then return super.term(t)(k) diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 10f50b4380..ae98141037 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -28,6 +28,8 @@ declare module Predef with val x = 0 class MatchResult(captures) class MatchFailure(errors) + class __EffectSig(next, nextHandler, tail, tailHandler, resumed, handler, handlerFun) + class __Return(value) // TODO: rm diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index 10fc272ae1..cf111a3285 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$133),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } } +//│ Elab: { { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$145),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } } :e ( @@ -73,7 +73,7 @@ handle h = Eff with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$162),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$162),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } +//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$174),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$174),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } :e handle h = Eff with @@ -127,4 +127,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.125: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$239),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$239),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } +//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$251),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›)), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$251),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } From 810a6395c50d756afa4fdae728fc145f5a83b6cb Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Fri, 10 Jan 2025 17:52:21 +0800 Subject: [PATCH 055/114] Remove some unused code --- .../scala/hkmc2/codegen/HandlerLowering.scala | 48 ++----------------- 1 file changed, 4 insertions(+), 44 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 4273546636..7127565fbb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -78,10 +78,8 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): private val contClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__Cont")).selN(Tree.Ident("class")) private val retClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__Return")).selN(Tree.Ident("class")) private val retClsSym: ClassSymbol = ctx.Builtins.Predef.tree.definedSymbols.get("__Return").get.asCls.get - private val handleEffectFun: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__handleEffect")) private val mkEffectPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__mkEffect")) private val handleBlockImplPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__handleBlockImpl")) - private val mapPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Map")) private def freshTmp(dbgNme: Str = "tmp") = new TempSymbol(N, dbgNme) @@ -158,40 +156,6 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): // for readability :) case class PartRet(head: Block, states: Ls[BlockState]) - // used to analyze whether to touch labels, currently unused. - val labelCallCache: scala.collection.mutable.Map[Symbol, Bool] = scala.collection.mutable.Map() - def containsCallRec(blk: Block): Bool = containsCall(blk) - @tailrec - def containsCall(blk: Block): Bool = blk match - case Match(scrut, arms, dflt, rest) => arms.find((_, blk) => containsCallRec(blk)).isDefined || containsCall(rest) - case Return(c: Call, implct) => true - case Return(_, _) => false - case Throw(c: Call) => true - case Throw(_) => false - case l @ Label(label, body, rest) => - labelBodyHasCall(l) || containsCall(rest) - case Break(label) => false - case Continue(label) => false - case Begin(sub, rest) => containsCallRec(sub) || containsCall(rest) - case TryBlock(sub, finallyDo, rest) => containsCallRec(sub) || containsCallRec(finallyDo) || containsCall(rest) - case Assign(lhs, c: Call, rest) => true - case Assign(_, _, rest) => containsCall(rest) - case AssignField(lhs, nme, c: Call, rest) => true - case AssignField(_, _, _, rest) => containsCall(rest) - case Define(defn, rest) => containsCall(rest) - case End(msg) => false - case _: HandleBlock | _: HandleBlockReturn => die // already translated at this point - - def labelBodyHasCall(blk: Label) = - val Label(label, body, rest) = blk - labelCallCache.get(label) match - case N => - val res = containsCallRec(body) - labelCallCache.addOne(label -> res) - res - case S(value) => - value - // returns (truncated input block, child block states) // TODO: don't split within Match, Begin and Labels when not needed, ideally keep it intact. Need careful analysis for this // blk: The block to transform @@ -314,13 +278,14 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): * 2. * a) generate continuation class * b) generate normal function body + * 3. float out definitions */ - private def translateBlock(b: Block, h: HandlerCtx, topLevel: Bool = false): Block = + private def translateBlock(b: Block, h: HandlerCtx): Block = given HandlerCtx = h val stage1 = firstPass(b) val stage2 = secondPass(stage1) - if topLevel then stage2 + if h.isTopLevel then stage2 else thirdPass(stage2) private def firstPass(b: Block)(using HandlerCtx): Block = @@ -425,11 +390,6 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): val body = blockBuilder.define(clsDefn).assign(h.lhs, Instantiate(Value.Ref(BlockMemberSymbol(h.cls.id.name, Nil)), Nil)).rest(handlerBody) - // val cur: Block => Block = h.handlers.foldLeft(blockBuilder)((builder, handler) => - // val lam = Value.Lam(PlainParamList(Param(FldFlags.empty, handler.resumeSym, N) :: Nil), translateBlock(handler.body, functionHandlerCtx)) - // val tmp = freshTmp() - // builder.define(FunDefn(handler.sym, handler.params, Return(SimpleCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false)))).assign(h.lhs, Instantiate(h.cls, Nil)) - // val body = h.handlers.foldLeft(cur)((builder, handler) => builder.assignFieldN(h.lhs.asPath, Tree.Ident(handler.sym.nme), handler.sym.asPath)).rest(handlerBody) val defn = FunDefn(sym, PlainParamList(Nil) :: Nil, body) val result = Define(defn, CallPlaceholder(h.res, freshId(), true, Call(sym.asPath, Nil)(true), h.rest)) result @@ -548,5 +508,5 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case b => b def translateTopLevel(b: Block): Block = - translateBlock(b, HandlerCtx(true, true, _ => rtThrowMsg("Unhandled effects")), true) + translateBlock(b, HandlerCtx(true, true, _ => rtThrowMsg("Unhandled effects"))) From b04031aa6d4cf93b225f38d2d89316440afc3abb Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Fri, 10 Jan 2025 18:13:22 +0800 Subject: [PATCH 056/114] revert parser workaround --- hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala index 9eeda89072..19ef6a8065 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala @@ -103,8 +103,8 @@ object Keyword: val `where` = Keyword("where", N, N) val `forall` = Keyword("forall", N, N) val `exists` = Keyword("exists", N, N) - val `null` = Keyword("null", N, curPrec) - val `undefined` = Keyword("undefined", N, curPrec) + val `null` = Keyword("null", N, N) + val `undefined` = Keyword("undefined", N, N) val `abstract` = Keyword("abstract", N, N) val `constructor` = Keyword("constructor", N, N) val `virtual` = Keyword("virtual", N, N) From e8113c720d00fc7f8391ac5506b3717dfafde66e Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Fri, 10 Jan 2025 18:30:58 +0800 Subject: [PATCH 057/114] predef clean up --- .../src/test/mlscript-compile/Predef.mls | 41 ++----------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index f51b3b91e3..7afe075a52 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -78,6 +78,7 @@ class Test with abstract class __Cont(next) with fun resume(value) + class __TailList(next, tail) class __HandleBlock(next, nextHandler, tail, handler) @@ -91,14 +92,6 @@ fun __mkEffect(handler, handlerFun) = res.tailHandler = res res -// fun checkEffectHead(cur) = -// if cur is __EffectSig then -// let hand = cur -// while hand.nextHandler !== null do -// set hand = hand.nextHandler -// assert(hand === cur.tailHandler) -// assert(cur.resumed === false) - fun __handleBlockImpl(cur, handler) = let handlerCont = new __HandleBlock(null, null, null, handler) set handlerCont.tail = handlerCont @@ -116,6 +109,7 @@ fun __handleBlockImpl(cur, handler) = else return cur +// return either new effect, final result or the same continuation if there is no handler fun __handleEffect(cur, handler, handlerTailList) = let prevCont = cur @@ -160,27 +154,6 @@ fun __handleEffect(cur, handler, handlerTailList) = cur = nxt.resume(cur) else cur -// fun __resume(cur, tail)(value) = -// if cur.resumed do -// throw Error("multiple resumption") -// set cur.resumed = true -// let cont = cur.next -// while -// cont is __Cont then -// value = cont.resume(value) -// if value is __EffectSig then -// set value.tail = tail -// if cur.tailHandler !== cur do -// set value.tailHandler.nextHandler = cur.nextHandler -// set value.tailHandler = cur.tailHandler -// return value -// else -// set cont = cont.next -// cur.nextHandler is __HandleBlock then -// set cont = cur.nextHandler.next -// set cur.nextHandler = cur.nextHandler.nextHandler -// else value - fun __resume(cur, tail)(value) = if cur.resumed do throw Error("multiple resumption") @@ -189,10 +162,9 @@ fun __resume(cur, tail)(value) = let cont = cur.next while cont is __Cont do - // NOTE: we do not need to link to cont.next again if it is effsig - // because cont.resume will not change its own next set value = cont.resume(value) if value is __EffectSig then + // We are returning to handler, which perform unwinding logic so tail is needed set value.tail = tail if cur.tailHandler !== cur do set @@ -201,6 +173,7 @@ fun __resume(cur, tail)(value) = return value else set cont = cont.next + // We're done with the head, now resume the handle blocks while cur.nextHandler is __HandleBlock and @@ -209,12 +182,6 @@ fun __resume(cur, tail)(value) = let nxt = cur.nextHandler.next set value = nxt.resume(value) if value is __EffectSig then - // console.dir(cur.nextHandler.next) - // console.dir(value.tail.next) - // console.dir(value) - // console.dir(cur) - // console.dir(nxt) - // console.log(nxt.resume.toString()) assert(cur.tailHandler !== cur) assert(cur.nextHandler !== null) // This checks when continuation resume results in tail call to effectful func From 78f6f54fdeb664df64dc05cb71f2d687c68be9fe Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Fri, 10 Jan 2025 19:45:08 +0800 Subject: [PATCH 058/114] Simplify Predef --- .../scala/hkmc2/semantics/Elaborator.scala | 2 +- .../src/test/mlscript-compile/Predef.mjs | 125 ++++++++---------- .../src/test/mlscript-compile/Predef.mls | 84 ++++++------ .../mlscript/handlers/MultiResumption.mls | 2 +- .../test/mlscript/handlers/NestedHandlers.mls | 29 ++++ .../src/test/mlscript/parser/Handler.mls | 6 +- 6 files changed, 131 insertions(+), 117 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 94d18f742e..55febf11b5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -634,7 +634,7 @@ extends Importer: log(s"Processing `handle` statement $id (${sym}) ${ctx.outer}") // TODO: shouldn't need uid here - val derivedClsSym = ClassSymbol(Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), Tree.Ident(s"Effect$$${cls.name}$$${State.suid.nextUid}")) + val derivedClsSym = ClassSymbol(Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), Tree.Ident(s"${cls.name}$$${id.name}$$${State.suid.nextUid}")) derivedClsSym.defn = S(ClassDef(N, syntax.Cls, derivedClsSym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))), List())) val elabed = ctx.nest(S(derivedClsSym)).givenIn: diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 43cb75320b..f670cc06bd 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -238,14 +238,14 @@ const Predef$class = class Predef { tmp = new this.__HandleBlock.class(null, null, null, handler1); handlerCont = tmp; handlerCont.tail = handlerCont; + cur.tailHandler.nextHandler = handlerCont; + cur.tailHandler = handlerCont; tmp4: while (true) { if (cur instanceof this.__EffectSig.class) { - tmp1 = this.__handleEffect(cur, handler1, handlerCont); + tmp1 = this.__handleEffect(cur); nxt = tmp1; scrut = cur === nxt; if (scrut) { - cur.tailHandler.nextHandler = handlerCont; - cur.tailHandler = handlerCont; cur.tail = handlerCont.tail; return cur; } else { @@ -261,18 +261,18 @@ const Predef$class = class Predef { } return tmp3; } - __handleEffect(cur1, handler2, handlerTailList) { - let prevCont, handlerCont, scrut, scrut1, savedNext, scrut2, scrut3, origTail, savedNext1, scrut4, scrut5, nxt, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14; + __handleEffect(cur1) { + let prevCont, handlerCont, scrut, scrut1, origTail, savedNext, scrut2, scrut3, findTail, scrut4, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9; prevCont = cur1; handlerCont = cur1.nextHandler; - tmp15: while (true) { + tmp10: while (true) { if (handlerCont instanceof this.__HandleBlock.class) { scrut = handlerCont.handler !== cur1.handler; if (scrut) { prevCont = handlerCont; handlerCont = handlerCont.nextHandler; tmp = null; - continue tmp15; + continue tmp10; } else { tmp = null; } @@ -281,85 +281,68 @@ const Predef$class = class Predef { } break; } - if (handlerCont) { - origTail = cur1.tailHandler; - prevCont.nextHandler = null; - cur1.tailHandler = prevCont; - savedNext1 = handlerCont.next; - tmp1 = this.__resume(cur1, handlerCont); - tmp2 = cur1.handlerFun(tmp1) ?? null; - cur1 = tmp2; - scrut4 = savedNext1 !== handlerCont.next; - if (scrut4) { - handlerCont.next.next = savedNext1; - tmp3 = null; - } else { - tmp3 = null; - } - if (cur1 instanceof this.__EffectSig.class) { - cur1.tailHandler.nextHandler = handlerCont; - cur1.tailHandler = origTail; - return cur1; + scrut1 = handlerCont === null; + if (scrut1) { + return cur1; + } else { + tmp1 = null; + } + origTail = cur1.tailHandler; + prevCont.nextHandler = null; + cur1.tailHandler = prevCont; + savedNext = handlerCont.next; + tmp2 = this.__resume(cur1, handlerCont); + tmp3 = cur1.handlerFun(tmp2) ?? null; + cur1 = tmp3; + scrut2 = savedNext !== handlerCont.next; + if (scrut2) { + handlerCont.next.next = savedNext; + scrut3 = handlerCont.tail === handlerCont; + if (scrut3) { + handlerCont.tail = handlerCont.next; + tmp4 = null; } else { - tmp4 = this.__resume(handlerCont, null); - tmp5 = tmp4(cur1) ?? null; - cur1 = tmp5; - tmp6 = null; + tmp4 = null; } - tmp7 = tmp6; + tmp5 = tmp4; } else { - scrut1 = handler2 === cur1.handler; - if (scrut1) { - savedNext = handlerTailList.next; - tmp8 = this.__resume(cur1, handlerTailList); - tmp9 = cur1.handlerFun(tmp8) ?? null; - cur1 = tmp9; - scrut2 = savedNext !== handlerTailList.next; - if (scrut2) { - handlerTailList.next.next = savedNext; - scrut3 = savedNext === null; - if (scrut3) { - handlerTailList.tail = handlerTailList.next; - tmp10 = null; + tmp5 = null; + } + if (cur1 instanceof this.__EffectSig.class) { + cur1.tailHandler.nextHandler = handlerCont; + cur1.tailHandler = origTail; + return cur1; + } else { + tmp6 = this.__resume(handlerCont, null); + tmp7 = tmp6(cur1) ?? null; + cur1 = tmp7; + if (cur1 instanceof this.__EffectSig.class) { + findTail = cur1; + tmp11: while (true) { + scrut4 = findTail.nextHandler; + if (scrut4 instanceof this.__HandleBlock.class) { + findTail = findTail.nextHandler; + tmp8 = null; + continue tmp11; } else { - tmp10 = null; + tmp8 = null; } - tmp11 = tmp10; - } else { - tmp11 = null; + break; } - tmp12 = tmp11; - } else { - return cur1; - } - tmp7 = tmp12; - } - tmp16: while (true) { - if (cur1 instanceof this.__EffectSig.class) { - return cur1; + cur1.tailHandler = findTail; + tmp9 = null; } else { - scrut5 = handlerTailList.next; - if (scrut5 instanceof this.__Cont.class) { - nxt = handlerTailList.next; - handlerTailList.next = handlerTailList.next.next; - tmp13 = nxt.resume(cur1) ?? null; - cur1 = tmp13; - tmp14 = null; - continue tmp16; - } else { - tmp14 = cur1; - } + tmp9 = null; } - break; + return cur1; } - return tmp14; } __resume(cur2, tail) { return (value) => { let scrut, cont, scrut1, scrut2, scrut3, scrut4, nxt, scrut5, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17, tmp18, tmp19, tmp20, tmp21, tmp22; scrut = cur2.resumed; if (scrut) { - throw Error("multiple resumption"); + throw Error("Multiple resumption"); } else { tmp = null; } diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 7afe075a52..e31d29dac6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -94,15 +94,16 @@ fun __mkEffect(handler, handlerFun) = fun __handleBlockImpl(cur, handler) = let handlerCont = new __HandleBlock(null, null, null, handler) - set handlerCont.tail = handlerCont + set + handlerCont.tail = handlerCont + cur.tailHandler.nextHandler = handlerCont + cur.tailHandler = handlerCont while cur is __EffectSig then - let nxt = __handleEffect(cur, handler, handlerCont) + let nxt = __handleEffect(cur) if cur === nxt then - set - cur.tailHandler.nextHandler = handlerCont - cur.tailHandler = handlerCont - cur.tail = handlerCont.tail + // handler not found, begin unwinding + set cur.tail = handlerCont.tail return cur else set cur = nxt @@ -110,7 +111,7 @@ fun __handleBlockImpl(cur, handler) = return cur // return either new effect, final result or the same continuation if there is no handler -fun __handleEffect(cur, handler, handlerTailList) = +fun __handleEffect(cur) = let prevCont = cur handlerCont = cur.nextHandler @@ -118,45 +119,46 @@ fun __handleEffect(cur, handler, handlerTailList) = set prevCont = handlerCont handlerCont = handlerCont.nextHandler - if handlerCont then - let origTail = cur.tailHandler + + // no matching handler + if handlerCont === null do + return cur + + // unlink the unrelated handler blocks + let origTail = cur.tailHandler + set + prevCont.nextHandler = null + cur.tailHandler = prevCont + + // handle the effect, where the handler continuation will be appended to handlerCont.next + let savedNext = handlerCont.next + set cur = cur.handlerFun(__resume(cur, handlerCont)) + if savedNext !== handlerCont.next do + set handlerCont.next.next = savedNext + // for the handle block at the bottom of the stack, tail is used for unwinding + // otherwise it is unused and it is fine to set it here as well + if handlerCont.tail === handlerCont do + set handlerCont.tail = handlerCont.next + + if cur is __EffectSig then set - prevCont.nextHandler = null - cur.tailHandler = prevCont - let savedNext = handlerCont.next - set cur = cur.handlerFun(__resume(cur, handlerCont)) - if savedNext !== handlerCont.next do - set handlerCont.next.next = savedNext - if cur is - __EffectSig then - set - cur.tailHandler.nextHandler = handlerCont - cur.tailHandler = origTail - return cur - else - set cur = __resume(handlerCont, null)(cur) - else if handler === cur.handler then - let savedNext = handlerTailList.next - set cur = cur.handlerFun(__resume(cur, handlerTailList)) - if savedNext !== handlerTailList.next do - set handlerTailList.next.next = savedNext - if savedNext === null do - set handlerTailList.tail = handlerTailList.next + cur.tailHandler.nextHandler = handlerCont + cur.tailHandler = origTail + cur else - return cur - while - cur is - __EffectSig then return cur - handlerTailList.next is __Cont then - let nxt = handlerTailList.next - set - handlerTailList.next = handlerTailList.next.next - cur = nxt.resume(cur) - else cur + // now resume the handle blocks + set cur = __resume(handlerCont, null)(cur) + if cur is __EffectSig do + // the tailHandler might have changed so we have to recompute here + let findTail = cur + while findTail.nextHandler is __HandleBlock do + set findTail = findTail.nextHandler + set cur.tailHandler = findTail + cur fun __resume(cur, tail)(value) = if cur.resumed do - throw Error("multiple resumption") + throw Error("Multiple resumption") set cur.resumed = true let cont = cur.next diff --git a/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls b/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls index 1f088e34b1..d27cec097a 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls @@ -12,7 +12,7 @@ handle h = Logger with k() h.info("a") //│ > a -//│ ═══[RUNTIME ERROR] Error: multiple resumption +//│ ═══[RUNTIME ERROR] Error: Multiple resumption handle h = Logger with diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls index 3f6cddbc48..6ad05ca19c 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -157,3 +157,32 @@ while i < 10 do set i = i + 1 d //│ = 11 + +abstract class Eff2 with + fun f1(): () + fun f2(): () + + +// Effect performed at end of handler, which trigger a special branch to handle +handle h1 = Eff2 with + fun f1()(k) = + print("h1.f1 pre") + k() + print("h1.f1 post") + fun f2()(k) = k() +handle h2 = Eff2 with + fun f1()(k) = + print("h2.f1") + k() + fun f2()(k) = + print("h2.f2 pre") + k() + print("h2.f2 post") + h1.f1() +h2.f2() +h2.f1() +//│ > h2.f2 pre +//│ > h2.f1 +//│ > h2.f2 post +//│ > h1.f1 pre +//│ > h1.f1 post diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index 7421d0f43e..2ece8a3f18 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$149),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } } +//│ Elab: { { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$149),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } } :e ( @@ -73,7 +73,7 @@ handle h = Eff with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$178),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$178),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } +//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$178),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$178),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } :e handle h = Eff with @@ -127,4 +127,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.125: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$255),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Effect$Eff$255),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } +//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$255),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$255),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } From d3fedd2895fbb77b18868d8ab2bbde5b9ca33de0 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Fri, 10 Jan 2025 22:26:35 +0800 Subject: [PATCH 059/114] Rewriting most of Predef for clean up --- .../src/test/mlscript-compile/Predef.mjs | 269 +++++++++--------- .../src/test/mlscript-compile/Predef.mls | 168 +++++------ .../src/test/mlscript/decls/Prelude.mls | 2 +- .../src/test/mlscript/parser/Handler.mls | 6 +- 4 files changed, 223 insertions(+), 222 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index f670cc06bd..94f6e6a31d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -76,36 +76,47 @@ const Predef$class = class Predef { } toString() { return "__Cont(" + this.next + ")"; } }; - this.__TailList = function __TailList(next1, tail1) { return new __TailList.class(next1, tail1); }; - this.__TailList.class = class __TailList { + this.__List = function __List(next1) { return new __List.class(next1); }; + this.__List.class = class __List { + constructor(next) { + this.next = next; + } + toString() { return "__List(" + this.next + ")"; } + }; + this.__ListWithTail = function __ListWithTail(next1, tail1) { return new __ListWithTail.class(next1, tail1); }; + this.__ListWithTail.class = class __ListWithTail { constructor(next, tail) { this.next = next; this.tail = tail; } - toString() { return "__TailList(" + this.next + ", " + this.tail + ")"; } + append(elem) { + this.tail.next = elem; + this.tail = elem; + return null; + } + toString() { return "__ListWithTail(" + this.next + ", " + this.tail + ")"; } }; - this.__HandleBlock = function __HandleBlock(next1, nextHandler1, tail1, handler1) { return new __HandleBlock.class(next1, nextHandler1, tail1, handler1); }; + this.__HandleBlock = function __HandleBlock(contHead1, lastHandlerCont1, next1, handler1) { return new __HandleBlock.class(contHead1, lastHandlerCont1, next1, handler1); }; this.__HandleBlock.class = class __HandleBlock { - constructor(next, nextHandler, tail, handler) { + constructor(contHead, lastHandlerCont, next, handler) { + this.contHead = contHead; + this.lastHandlerCont = lastHandlerCont; this.next = next; - this.nextHandler = nextHandler; - this.tail = tail; this.handler = handler; } - toString() { return "__HandleBlock(" + this.next + ", " + this.nextHandler + ", " + this.tail + ", " + this.handler + ")"; } + toString() { return "__HandleBlock(" + this.contHead + ", " + this.lastHandlerCont + ", " + this.next + ", " + this.handler + ")"; } }; - this.__EffectSig = function __EffectSig(next1, nextHandler1, tail1, tailHandler1, resumed1, handler1, handlerFun1) { return new __EffectSig.class(next1, nextHandler1, tail1, tailHandler1, resumed1, handler1, handlerFun1); }; + this.__EffectSig = function __EffectSig(next1, tail1, handleBlockList1, resumed1, handler1, handlerFun1) { return new __EffectSig.class(next1, tail1, handleBlockList1, resumed1, handler1, handlerFun1); }; this.__EffectSig.class = class __EffectSig { - constructor(next, nextHandler, tail, tailHandler, resumed, handler, handlerFun) { + constructor(next, tail, handleBlockList, resumed, handler, handlerFun) { this.next = next; - this.nextHandler = nextHandler; this.tail = tail; - this.tailHandler = tailHandler; + this.handleBlockList = handleBlockList; this.resumed = resumed; this.handler = handler; this.handlerFun = handlerFun; } - toString() { return "__EffectSig(" + this.next + ", " + this.nextHandler + ", " + this.tail + ", " + this.tailHandler + ", " + this.resumed + ", " + this.handler + ", " + this.handlerFun + ")"; } + toString() { return "__EffectSig(" + this.next + ", " + this.tail + ", " + this.handleBlockList + ", " + this.resumed + ", " + this.handler + ", " + this.handlerFun + ")"; } }; this.__Return = function __Return(value1) { return new __Return.class(value1); }; this.__Return.class = class __Return { @@ -225,54 +236,66 @@ const Predef$class = class Predef { return null; } } - __mkEffect(handler, handlerFun) { + __mkListWithTail() { let res, tmp; - tmp = new this.__EffectSig.class(null, null, null, null, false, handler, handlerFun); + tmp = new this.__ListWithTail.class(null, null); res = tmp; res.tail = res; - res.tailHandler = res; + return res; + } + __mkEffect(handler, handlerFun) { + let res, tmp, tmp1; + tmp = this.__mkListWithTail(); + tmp1 = new this.__EffectSig.class(null, null, tmp, false, handler, handlerFun); + res = tmp1; + res.tail = res; return res; } __handleBlockImpl(cur, handler1) { - let handlerCont, nxt, scrut, tmp, tmp1, tmp2, tmp3; - tmp = new this.__HandleBlock.class(null, null, null, handler1); - handlerCont = tmp; - handlerCont.tail = handlerCont; - cur.tailHandler.nextHandler = handlerCont; - cur.tailHandler = handlerCont; - tmp4: while (true) { + let handleBlock, nxt, scrut, scrut1, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; + tmp = this.__List(null); + tmp1 = new this.__HandleBlock.class(tmp, null, null, handler1); + handleBlock = tmp1; + tmp2 = cur.handleBlockList.append(handleBlock) ?? null; + tmp7: while (true) { if (cur instanceof this.__EffectSig.class) { - tmp1 = this.__handleEffect(cur); - nxt = tmp1; + tmp3 = this.__handleEffect(cur); + nxt = tmp3; scrut = cur === nxt; if (scrut) { - cur.tail = handlerCont.tail; + scrut1 = handleBlock.lastHandlerCont === null; + if (scrut1) { + cur.tail = handleBlock.contHead; + tmp4 = null; + } else { + cur.tail = handleBlock.lastHandlerCont; + tmp4 = null; + } return cur; } else { cur = nxt; - tmp2 = null; + tmp5 = null; } - tmp3 = tmp2; - continue tmp4; + tmp6 = tmp5; + continue tmp7; } else { return cur; } break; } - return tmp3; + return tmp6; } __handleEffect(cur1) { - let prevCont, handlerCont, scrut, scrut1, origTail, savedNext, scrut2, scrut3, findTail, scrut4, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9; - prevCont = cur1; - handlerCont = cur1.nextHandler; - tmp10: while (true) { - if (handlerCont instanceof this.__HandleBlock.class) { - scrut = handlerCont.handler !== cur1.handler; - if (scrut) { - prevCont = handlerCont; - handlerCont = handlerCont.nextHandler; + let prevBlock, scrut, scrut1, scrut2, handleBlock, origTailBlock, savedNext, scrut3, scrut4, tmp, tmp1, tmp2, tmp3, tmp4, tmp5; + prevBlock = cur1.handleBlockList; + tmp6: while (true) { + scrut = prevBlock.next; + if (scrut instanceof this.__HandleBlock.class) { + scrut1 = prevBlock.next.handler !== cur1.handler; + if (scrut1) { + prevBlock = prevBlock.next; tmp = null; - continue tmp10; + continue tmp6; } else { tmp = null; } @@ -281,25 +304,26 @@ const Predef$class = class Predef { } break; } - scrut1 = handlerCont === null; - if (scrut1) { + scrut2 = prevBlock.next === null; + if (scrut2) { return cur1; } else { tmp1 = null; } - origTail = cur1.tailHandler; - prevCont.nextHandler = null; - cur1.tailHandler = prevCont; - savedNext = handlerCont.next; - tmp2 = this.__resume(cur1, handlerCont); + handleBlock = prevBlock.next; + origTailBlock = cur1.handleBlockList.tail; + prevBlock.next = null; + cur1.handleBlockList.tail = prevBlock; + savedNext = handleBlock.contHead.next; + tmp2 = this.__resume(cur1, handleBlock.contHead); tmp3 = cur1.handlerFun(tmp2) ?? null; cur1 = tmp3; - scrut2 = savedNext !== handlerCont.next; - if (scrut2) { - handlerCont.next.next = savedNext; - scrut3 = handlerCont.tail === handlerCont; - if (scrut3) { - handlerCont.tail = handlerCont.next; + scrut3 = savedNext !== handleBlock.contHead.next; + if (scrut3) { + handleBlock.contHead.next.next = savedNext; + scrut4 = handleBlock.lastHandlerCont === null; + if (scrut4) { + handleBlock.lastHandlerCont = handleBlock.contHead.next; tmp4 = null; } else { tmp4 = null; @@ -309,37 +333,16 @@ const Predef$class = class Predef { tmp5 = null; } if (cur1 instanceof this.__EffectSig.class) { - cur1.tailHandler.nextHandler = handlerCont; - cur1.tailHandler = origTail; + cur1.handleBlockList.tail.next = handleBlock; + cur1.handleBlockList.tail = origTailBlock; return cur1; } else { - tmp6 = this.__resume(handlerCont, null); - tmp7 = tmp6(cur1) ?? null; - cur1 = tmp7; - if (cur1 instanceof this.__EffectSig.class) { - findTail = cur1; - tmp11: while (true) { - scrut4 = findTail.nextHandler; - if (scrut4 instanceof this.__HandleBlock.class) { - findTail = findTail.nextHandler; - tmp8 = null; - continue tmp11; - } else { - tmp8 = null; - } - break; - } - cur1.tailHandler = findTail; - tmp9 = null; - } else { - tmp9 = null; - } - return cur1; + return this.__resumeHandleBlocks(handleBlock, origTailBlock, cur1); } } __resume(cur2, tail) { return (value) => { - let scrut, cont, scrut1, scrut2, scrut3, scrut4, nxt, scrut5, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17, tmp18, tmp19, tmp20, tmp21, tmp22; + let scrut, cont, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; scrut = cur2.resumed; if (scrut) { throw Error("Multiple resumption"); @@ -348,16 +351,16 @@ const Predef$class = class Predef { } cur2.resumed = true; cont = cur2.next; - tmp23: while (true) { + tmp7: while (true) { if (cont instanceof this.__Cont.class) { tmp1 = cont.resume(value) ?? null; value = tmp1; if (value instanceof this.__EffectSig.class) { value.tail = tail; - scrut1 = cur2.tailHandler !== cur2; + scrut1 = cur2.handleBlockList.next !== null; if (scrut1) { - value.tailHandler.nextHandler = cur2.nextHandler; - value.tailHandler = cur2.tailHandler; + value.handleBlockList.tail.next = cur2.handleBlockList.next; + value.handleBlockList.tail = cur2.handleBlockList.tail; tmp2 = null; } else { tmp2 = null; @@ -368,70 +371,66 @@ const Predef$class = class Predef { tmp3 = null; } tmp4 = tmp3; - continue tmp23; + continue tmp7; } else { tmp4 = null; } break; } - tmp24: while (true) { - scrut2 = cur2.nextHandler; - if (scrut2 instanceof this.__HandleBlock.class) { - scrut4 = cur2.nextHandler.next; - if (scrut4 instanceof this.__Cont.class) { - nxt = cur2.nextHandler.next; - tmp5 = nxt.resume(value) ?? null; - value = tmp5; - if (value instanceof this.__EffectSig.class) { - tmp6 = cur2.tailHandler !== cur2; - tmp7 = this.assert(tmp6) ?? null; - tmp8 = cur2.nextHandler !== null; - tmp9 = this.assert(tmp8) ?? null; - scrut5 = cur2.nextHandler.next === value.tail.next; - if (scrut5) { - value.tail.next = null; - tmp10 = null; - } else { - cur2.nextHandler.next = cur2.nextHandler.next.next; - tmp10 = null; - } - value.tail = tail; - value.tailHandler.nextHandler = cur2.nextHandler; - value.tailHandler = cur2.tailHandler; - return value; - } else { - tmp11 = cur2.nextHandler.next !== cur2.nextHandler.next.next; - tmp12 = this.assert(tmp11) ?? null; - cur2.nextHandler.next = cur2.nextHandler.next.next; - tmp13 = null; - } - tmp14 = tmp13; - continue tmp24; + scrut2 = cur2.handleBlockList.next === null; + if (scrut2) { + return value; + } else { + tmp5 = this.__resumeHandleBlocks(cur2.handleBlockList.next, cur2.handleBlockList.tail, value); + cur2 = tmp5; + if (cur2 instanceof this.__EffectSig.class) { + cur2.tail = tail; + tmp6 = null; + } else { + tmp6 = null; + } + return cur2; + } + }; + } + __resumeHandleBlocks(handleBlock, tailHandleBlock, value) { + let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3; + tmp4: while (true) { + scrut1 = handleBlock.contHead.next; + if (scrut1 instanceof this.__Cont.class) { + tmp = handleBlock.contHead.next.resume(value) ?? null; + value = tmp; + if (value instanceof this.__EffectSig.class) { + scrut2 = handleBlock.contHead.next !== value.tail.next; + if (scrut2) { + handleBlock.contHead.next = handleBlock.contHead.next.next; + tmp1 = null; } else { - scrut3 = true; - if (scrut3) { - tmp15 = cur2.nextHandler.next === null; - tmp16 = this.assert(tmp15) ?? null; - tmp17 = cur2.nextHandler !== cur2.nextHandler.nextHandler; - tmp18 = this.assert(tmp17) ?? null; - cur2.nextHandler = cur2.nextHandler.nextHandler; - tmp14 = null; - continue tmp24; - } else { - tmp19 = cur2.nextHandler === null; - tmp20 = this.assert(tmp19) ?? null; - return value; - } + tmp1 = null; } + value.tail.next = null; + value.handleBlockList.tail.next = handleBlock; + value.handleBlockList.tail = tailHandleBlock; + return value; + } else { + handleBlock.contHead.next = handleBlock.contHead.next.next; + tmp2 = null; + } + tmp3 = tmp2; + continue tmp4; + } else { + scrut = handleBlock.next; + if (scrut instanceof this.__HandleBlock.class) { + handleBlock = handleBlock.next; + tmp3 = null; + continue tmp4; } else { - tmp21 = cur2.nextHandler === null; - tmp22 = this.assert(tmp21) ?? null; return value; } - break; } - return tmp14; - }; + break; + } + return tmp3; } toString() { return "Predef"; } }; const Predef = new Predef$class; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index e31d29dac6..2a31d68471 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -79,31 +79,39 @@ class Test with abstract class __Cont(next) with fun resume(value) -class __TailList(next, tail) -class __HandleBlock(next, nextHandler, tail, handler) +class __List(next) + +class __ListWithTail(next, tail) with + fun append(elem) = + set tail.next = elem + set tail = elem +fun __mkListWithTail() = + let res = new __ListWithTail(null, null) + set res.tail = res + res + +class __HandleBlock(contHead, lastHandlerCont, next, handler) -class __EffectSig(next, nextHandler, tail, tailHandler, resumed, handler, handlerFun) +class __EffectSig(next, tail, handleBlockList, resumed, handler, handlerFun) class __Return(value) fun __mkEffect(handler, handlerFun) = - let res = new __EffectSig(null, null, null, null, false, handler, handlerFun) - set - res.tail = res - res.tailHandler = res + let res = new __EffectSig(null, null, __mkListWithTail(), false, handler, handlerFun) + set res.tail = res res fun __handleBlockImpl(cur, handler) = - let handlerCont = new __HandleBlock(null, null, null, handler) - set - handlerCont.tail = handlerCont - cur.tailHandler.nextHandler = handlerCont - cur.tailHandler = handlerCont + let handleBlock = new __HandleBlock(__List(null), null, null, handler) + cur.handleBlockList.append(handleBlock) while cur is __EffectSig then let nxt = __handleEffect(cur) if cur === nxt then // handler not found, begin unwinding - set cur.tail = handlerCont.tail + if handleBlock.lastHandlerCont === null then + set cur.tail = handleBlock.contHead + else + set cur.tail = handleBlock.lastHandlerCont return cur else set cur = nxt @@ -112,99 +120,93 @@ fun __handleBlockImpl(cur, handler) = // return either new effect, final result or the same continuation if there is no handler fun __handleEffect(cur) = - let - prevCont = cur - handlerCont = cur.nextHandler - while handlerCont is __HandleBlock and handlerCont.handler !== cur.handler do - set - prevCont = handlerCont - handlerCont = handlerCont.nextHandler - - // no matching handler - if handlerCont === null do + // find the handle block corresponding to the current effect + let prevBlock = cur.handleBlockList + while prevBlock.next is __HandleBlock and prevBlock.next.handler !== cur.handler do + set prevBlock = prevBlock.next + + // no matching handle block + if prevBlock.next === null do return cur - - // unlink the unrelated handler blocks - let origTail = cur.tailHandler + + // the matching handle block + let handleBlock = prevBlock.next + + // unlink the unrelated handle blocks, which is never null + let origTailBlock = cur.handleBlockList.tail set - prevCont.nextHandler = null - cur.tailHandler = prevCont - - // handle the effect, where the handler continuation will be appended to handlerCont.next - let savedNext = handlerCont.next - set cur = cur.handlerFun(__resume(cur, handlerCont)) - if savedNext !== handlerCont.next do - set handlerCont.next.next = savedNext - // for the handle block at the bottom of the stack, tail is used for unwinding + prevBlock.next = null + cur.handleBlockList.tail = prevBlock + + // handle the effect, where the handler continuation will be appended to handleBlock.contHead.next + let savedNext = handleBlock.contHead.next + set cur = cur.handlerFun(__resume(cur, handleBlock.contHead)) + if savedNext !== handleBlock.contHead.next do + set handleBlock.contHead.next.next = savedNext + // for the handle block at the bottom of the stack, this is used for unwinding // otherwise it is unused and it is fine to set it here as well - if handlerCont.tail === handlerCont do - set handlerCont.tail = handlerCont.next - + if handleBlock.lastHandlerCont === null do + set handleBlock.lastHandlerCont = handleBlock.contHead.next + if cur is __EffectSig then + // relink the unlinked handle blocks set - cur.tailHandler.nextHandler = handlerCont - cur.tailHandler = origTail + cur.handleBlockList.tail.next = handleBlock + cur.handleBlockList.tail = origTailBlock cur else - // now resume the handle blocks - set cur = __resume(handlerCont, null)(cur) - if cur is __EffectSig do - // the tailHandler might have changed so we have to recompute here - let findTail = cur - while findTail.nextHandler is __HandleBlock do - set findTail = findTail.nextHandler - set cur.tailHandler = findTail - cur + // resume the unlinked handle blocks + __resumeHandleBlocks(handleBlock, origTailBlock, cur) fun __resume(cur, tail)(value) = if cur.resumed do throw Error("Multiple resumption") set cur.resumed = true - + let cont = cur.next while cont is __Cont do set value = cont.resume(value) if value is __EffectSig then - // We are returning to handler, which perform unwinding logic so tail is needed + // we are returning to handler, which performs unwinding so tail is needed set value.tail = tail - if cur.tailHandler !== cur do + if cur.handleBlockList.next !== null do set - value.tailHandler.nextHandler = cur.nextHandler - value.tailHandler = cur.tailHandler + value.handleBlockList.tail.next = cur.handleBlockList.next + value.handleBlockList.tail = cur.handleBlockList.tail return value else set cont = cont.next - + // We're done with the head, now resume the handle blocks + if cur.handleBlockList.next === null then + value + else + set cur = __resumeHandleBlocks(cur.handleBlockList.next, cur.handleBlockList.tail, value) + if cur is __EffectSig do + set cur.tail = tail + cur + +// this does not set the tail when effect is raised during resumption +fun __resumeHandleBlocks(handleBlock, tailHandleBlock, value) = while - cur.nextHandler is __HandleBlock and - cur.nextHandler.next is __Cont then - // resuming tailHandlerList or post handler continuations - let nxt = cur.nextHandler.next - set value = nxt.resume(value) - if value is __EffectSig then - assert(cur.tailHandler !== cur) - assert(cur.nextHandler !== null) - // This checks when continuation resume results in tail call to effectful func - if cur.nextHandler.next === value.tail.next then - set value.tail.next = null - else - set cur.nextHandler.next = cur.nextHandler.next.next - set - value.tail = tail - value.tailHandler.nextHandler = cur.nextHandler - value.tailHandler = cur.tailHandler - return value - else - assert(cur.nextHandler.next !== cur.nextHandler.next.next) - set cur.nextHandler.next = cur.nextHandler.next.next - // here using else will emit the wrong code and cause null to be returned from the loop - true then - // the list is empty, go to next handle block - assert(cur.nextHandler.next === null) - assert(cur.nextHandler !== cur.nextHandler.nextHandler) - set cur.nextHandler = cur.nextHandler.nextHandler + handleBlock.contHead.next is __Cont then + // resuming tailHandlerList or post handler continuations + set value = handleBlock.contHead.next.resume(value) + if value is __EffectSig then + // this checks when continuation resume results in tail call to effectful func + if handleBlock.contHead.next !== value.tail.next do + // if this is a tail call that results in effect, the continuation is already completed + // and should be removed + set handleBlock.contHead.next = handleBlock.contHead.next.next + set + value.tail.next = null + value.handleBlockList.tail.next = handleBlock + value.handleBlockList.tail = tailHandleBlock + return value + else + set handleBlock.contHead.next = handleBlock.contHead.next.next + handleBlock.next is __HandleBlock then + set handleBlock = handleBlock.next else - assert(cur.nextHandler === null) return value diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 6c5d1616ef..e8f22c639a 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -30,7 +30,7 @@ declare module Predef with val x = 0 class MatchResult(captures) class MatchFailure(errors) - class __EffectSig(next, nextHandler, tail, tailHandler, resumed, handler, handlerFun) + class __EffectSig(next, tail, handleBlockList, resumed, handler, handlerFun) class __Return(value) diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index 2ece8a3f18..d2d38757f3 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$149),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } } +//│ Elab: { { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$152),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } } :e ( @@ -73,7 +73,7 @@ handle h = Eff with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$178),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$178),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } +//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$181),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$181),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } :e handle h = Eff with @@ -127,4 +127,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.125: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$255),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$255),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } +//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$258),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$258),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } From b30d8ca96e7f81f9e2c99fe4e432ed0a5bbb6aab Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Fri, 10 Jan 2025 22:50:58 +0800 Subject: [PATCH 060/114] Change test case to todo --- .../shared/src/test/mlscript/handlers/RecursiveHandlers.mls | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 135b4d2363..aad9e9769c 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -29,7 +29,8 @@ h1.perform("oops") //│ ╙── ^^ -:fixme +// deep handler +:todo handle h2 = Effect with fun perform(arg)(k) = print("performing " + arg) @@ -43,7 +44,7 @@ handle h2 = Effect with h2.perform(3) ] //│ ╔══[ERROR] Name not found: h2 -//│ ║ l.37: then h2.perform(arg - 1) + " " + arg +//│ ║ l.38: then h2.perform(arg - 1) + " " + arg //│ ╙── ^^ //│ > ––– //│ > performing 2 From c9057ea79bc3534814819bac75e222073222363f Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Sat, 11 Jan 2025 16:19:53 +0800 Subject: [PATCH 061/114] remove unused code --- .../src/main/scala/hkmc2/codegen/HandlerLowering.scala | 6 ------ 1 file changed, 6 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index ce6030a2c0..a0c2b0365c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -18,10 +18,6 @@ object HandlerLowering: private val pcIdent: Tree.Ident = Tree.Ident("pc") private val nextIdent: Tree.Ident = Tree.Ident("next") private val tailIdent: Tree.Ident = Tree.Ident("tail") - private val nextHandlerIdent: Tree.Ident = Tree.Ident("nextHandler") - private val tailHandlerIdent: Tree.Ident = Tree.Ident("tailHandler") - private val handlerIdent: Tree.Ident = Tree.Ident("handler") - private val handlerFunIdent: Tree.Ident = Tree.Ident("handlerFun") extension (k: Block => Block) @@ -47,8 +43,6 @@ object HandlerLowering: def pc = p.selN(pcIdent) def next = p.selN(nextIdent) def tail = p.selN(tailIdent) - def nextHandler = p.selN(nextHandlerIdent) - def tailHandler = p.selN(tailHandlerIdent) def value = p.selN(Tree.Ident("value")) def asArg = Arg(false, p) From 235a6ec30cf4efda3d77014689aa295459cd8a3e Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Mon, 13 Jan 2025 18:07:05 +0800 Subject: [PATCH 062/114] Added codegen examples --- .../src/test/mlscript/handlers/Effects.mls | 87 ++++++- .../mlscript/handlers/RecursiveHandlers.mls | 237 +++++++++++++++++- 2 files changed, 321 insertions(+), 3 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 3c0302d8b7..0c8e013579 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -137,10 +137,93 @@ result //│ > handler finished //│ = 'Hello World!' +// Float out definitions +:sjs handle h = Effect with fun perform(arg)(k) = arg -fun f() = 3 -f() +if true do + fun f() = 3 + f() +//│ JS (unsanitized): +//│ let tmp; +//│ function handleBlock$0() { +//│ let h, scrut, tmp1, res; +//│ class Effect$h$1217 extends globalThis.Effect { +//│ constructor() { +//│ let tmp2; +//│ tmp2 = super(); +//│ } +//│ perform(arg) { +//│ return globalThis.Predef.__mkEffect(h, (k) => { +//│ return arg; +//│ }); +//│ } +//│ toString() { return "Effect$h$1217"; } +//│ } +//│ h = new Effect$h$1217(); +//│ function Cont$1242$1(pc1) { return new Cont$1242$1.class(pc1); } +//│ Cont$1242$1.class = class Cont$1242$1 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp2; +//│ tmp2 = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 1) { +//│ res = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 2) { +//│ scrut = true; +//│ if (scrut) { +//│ res = f$0(); +//│ if (res instanceof globalThis.Predef.__EffectSig.class) { +//│ res.tail.next = this; +//│ this.pc = 1; +//│ return res; +//│ } +//│ this.pc = 1; +//│ continue contLoop; +//│ } else { +//│ tmp1 = null; +//│ this.pc = 3; +//│ continue contLoop; +//│ } +//│ this.pc = 3; +//│ continue contLoop; +//│ } else if (this.pc === 3) { +//│ return tmp1; +//│ } else if (this.pc === 1) { +//│ tmp1 = res; +//│ this.pc = 3; +//│ continue contLoop; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$1242$1(" + this.pc + ")"; } +//│ }; +//│ function f$0() { +//│ return 3; +//│ } +//│ scrut = true; +//│ if (scrut) { +//│ res = f$0(); +//│ if (res instanceof globalThis.Predef.__EffectSig.class) { +//│ res.tail.next = new Cont$1242$1(1); +//│ return globalThis.Predef.__handleBlockImpl(res, h); +//│ } +//│ tmp1 = res; +//│ } else { +//│ tmp1 = null; +//│ } +//│ return tmp1; +//│ } +//│ tmp = handleBlock$0(); +//│ if (tmp instanceof this.Predef.__EffectSig.class) { +//│ throw new this.Error("Unhandled effects"); +//│ } +//│ tmp //│ = 3 fun f(perform) = diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index aad9e9769c..24e4f5c04f 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -104,7 +104,7 @@ in res.toString() //│ > B hola amigos //│ = '‹,‹,‹,‹,hola,amigos,›,adios,›,friend,›,bye,›' - +:sjs let str = "" if true do handle h1 = Effect with @@ -120,5 +120,240 @@ if true do h2.perform(()) h1.perform(()) str +//│ JS (unsanitized): +//│ let scrut, tmp, tmp1; +//│ this.str = ""; +//│ scrut = true; +//│ if (scrut) { +//│ function handleBlock$0() { +//│ let h1, tmp2; +//│ class Effect$h1$922 extends globalThis.Effect { +//│ constructor() { +//│ let tmp3; +//│ tmp3 = super(); +//│ } +//│ perform(arg) { +//│ return globalThis.Predef.__mkEffect(h1, (k) => { +//│ let tmp3, tmp4, tmp5, res; +//│ function Cont$1077$4(pc1) { return new Cont$1077$4.class(pc1); } +//│ Cont$1077$4.class = class Cont$1077$4 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp6; +//│ tmp6 = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 10) { +//│ res = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 11) { +//│ tmp3 = globalThis.str + "A"; +//│ globalThis.str = tmp3; +//│ res = k(arg) ?? null; +//│ if (res instanceof globalThis.Predef.__EffectSig.class) { +//│ res.tail.next = this; +//│ this.pc = 10; +//│ return res; +//│ } +//│ this.pc = 10; +//│ continue contLoop; +//│ } else if (this.pc === 10) { +//│ tmp4 = res; +//│ tmp5 = globalThis.str + "A"; +//│ globalThis.str = tmp5; +//│ return null; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$1077$4(" + this.pc + ")"; } +//│ }; +//│ tmp3 = globalThis.str + "A"; +//│ globalThis.str = tmp3; +//│ res = k(arg) ?? null; +//│ if (res instanceof globalThis.Predef.__EffectSig.class) { +//│ res.tail.next = new Cont$1077$4.class(10); +//│ res.tail = res.tail.next; +//│ return res; +//│ } +//│ tmp4 = res; +//│ tmp5 = globalThis.str + "A"; +//│ globalThis.str = tmp5; +//│ return null; +//│ }); +//│ } +//│ toString() { return "Effect$h1$922"; } +//│ } +//│ h1 = new Effect$h1$922(); +//│ function Cont$1037$3(pc1) { return new Cont$1037$3.class(pc1); } +//│ Cont$1037$3.class = class Cont$1037$3 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp3; +//│ tmp3 = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 7) { +//│ tmp2 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 8) { +//│ tmp2 = handleBlock$1$2(); +//│ if (tmp2 instanceof globalThis.Predef.__EffectSig.class) { +//│ tmp2.tail.next = this; +//│ this.pc = 7; +//│ return tmp2; +//│ } +//│ this.pc = 7; +//│ continue contLoop; +//│ } else if (this.pc === 7) { +//│ if (tmp2 instanceof globalThis.Predef.__Return.class) { +//│ return tmp2; +//│ } +//│ this.pc = 9; +//│ continue contLoop; +//│ } else if (this.pc === 9) { +//│ return tmp2; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$1037$3(" + this.pc + ")"; } +//│ }; +//│ function handleBlock$1$2() { +//│ let h2, tmp3, res, res1; +//│ class Effect$h2$932 extends globalThis.Effect { +//│ constructor() { +//│ let tmp4; +//│ tmp4 = super(); +//│ } +//│ perform(arg) { +//│ return globalThis.Predef.__mkEffect(h2, (k) => { +//│ let tmp4, tmp5, tmp6, res2; +//│ function Cont$1002$1(pc1) { return new Cont$1002$1.class(pc1); } +//│ Cont$1002$1.class = class Cont$1002$1 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp7; +//│ tmp7 = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 5) { +//│ res2 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 6) { +//│ tmp4 = globalThis.str + "B"; +//│ globalThis.str = tmp4; +//│ res2 = k(arg) ?? null; +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = this; +//│ this.pc = 5; +//│ return res2; +//│ } +//│ this.pc = 5; +//│ continue contLoop; +//│ } else if (this.pc === 5) { +//│ tmp5 = res2; +//│ tmp6 = globalThis.str + "B"; +//│ globalThis.str = tmp6; +//│ return null; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$1002$1(" + this.pc + ")"; } +//│ }; +//│ tmp4 = globalThis.str + "B"; +//│ globalThis.str = tmp4; +//│ res2 = k(arg) ?? null; +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = new Cont$1002$1.class(5); +//│ res2.tail = res2.tail.next; +//│ return res2; +//│ } +//│ tmp5 = res2; +//│ tmp6 = globalThis.str + "B"; +//│ globalThis.str = tmp6; +//│ return null; +//│ }); +//│ } +//│ toString() { return "Effect$h2$932"; } +//│ } +//│ h2 = new Effect$h2$932(); +//│ function Cont$973$0(pc1) { return new Cont$973$0.class(pc1); } +//│ Cont$973$0.class = class Cont$973$0 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp4; +//│ tmp4 = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 3) { +//│ res1 = value$; +//│ } else if (this.pc === 2) { +//│ res = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 4) { +//│ res1 = h2.perform(null) ?? null; +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = this; +//│ this.pc = 3; +//│ return res1; +//│ } +//│ this.pc = 3; +//│ continue contLoop; +//│ } else if (this.pc === 3) { +//│ tmp3 = res1; +//│ res = h1.perform(null) ?? null; +//│ if (res instanceof globalThis.Predef.__EffectSig.class) { +//│ res.tail.next = this; +//│ this.pc = 2; +//│ return res; +//│ } +//│ this.pc = 2; +//│ continue contLoop; +//│ } else if (this.pc === 2) { +//│ return res; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$973$0(" + this.pc + ")"; } +//│ }; +//│ res1 = h2.perform(null) ?? null; +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = new Cont$973$0(3); +//│ return globalThis.Predef.__handleBlockImpl(res1, h2); +//│ } +//│ tmp3 = res1; +//│ res = h1.perform(null) ?? null; +//│ if (res instanceof globalThis.Predef.__EffectSig.class) { +//│ res.tail.next = new Cont$973$0(2); +//│ return globalThis.Predef.__handleBlockImpl(res, h2); +//│ } +//│ return res; +//│ } +//│ tmp2 = handleBlock$1$2(); +//│ if (tmp2 instanceof globalThis.Predef.__EffectSig.class) { +//│ tmp2.tail.next = new Cont$1037$3(7); +//│ return globalThis.Predef.__handleBlockImpl(tmp2, h1); +//│ } +//│ if (tmp2 instanceof globalThis.Predef.__Return.class) { +//│ return tmp2; +//│ } +//│ return tmp2; +//│ } +//│ tmp = handleBlock$0(); +//│ if (tmp instanceof this.Predef.__EffectSig.class) { +//│ throw new this.Error("Unhandled effects"); +//│ } +//│ tmp1 = tmp; +//│ } else { +//│ tmp1 = null; +//│ } +//│ this.str //│ = 'BABA' //│ str = 'BABA' From ed895eb0cc9d4b29601164723858f039f1bd2128 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Mon, 13 Jan 2025 23:12:55 +0800 Subject: [PATCH 063/114] use SymbolSubst --- .../src/main/scala/hkmc2/codegen/Block.scala | 75 +++++++++---------- .../scala/hkmc2/codegen/HandlerLowering.scala | 40 +++++++--- .../main/scala/hkmc2/semantics/Symbol.scala | 43 +++++++++-- .../main/scala/hkmc2/utils/SymbolSubst.scala | 18 +++++ .../src/test/mlscript/handlers/Effects.mls | 33 ++++++++ 5 files changed, 153 insertions(+), 56 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/utils/SymbolSubst.scala diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index d4bf47d340..64e9d8bd3e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -89,29 +89,23 @@ sealed abstract class Block extends Product with AutoLocated: case r => N } - def mapSyms(f: Local => Local)(using State): Block = - def g(t: Local) = t match - case t: TermSymbol => - TermSymbol(t.k, t.owner.map(symMap), t.id) - - case _ => f(t) - def symMap[T <: Local : ClassTag](l: T): T = g(l) match - case r : T => r - case _ => l + def mapSyms(using State, SymbolSubst): Block = // def resMap[T <: Result] - def pMap(p: Param) = Param(p.flags, symMap(p.sym), p.sign) + def pMap(p: Param) = + val newSym: LocalSymbol & NamedSymbol = p.sym.subst + Param(p.flags, p.sym.subst, p.sign) def pListMap(p: ParamList) = ParamList(p.flags, p.params.map(pMap), p.restParam.map(pMap)) def pathMap(p: Path): Path = p match - case s @ Select(qual, name) => Select(pathMap(qual), name)(s.symbol.map(symMap)) - case Value.Ref(l) => Value.Ref(symMap(l)) - case Value.This(sym) => Value.This(symMap(sym)) + case s @ Select(qual, name) => Select(pathMap(qual), name)(s.symbol.map(_.subst)) + case Value.Ref(l) => Value.Ref(l.subst) + case Value.This(sym) => Value.This(sym.subst) case Value.Lit(lit) => p case Value.Lam(params, body) => Value.Lam( pListMap(params), - body.mapSyms(symMap) + body.mapSyms ) case Value.Arr(elems) => Value.Arr(elems.map(a => Arg(a.spread, pathMap(a.value)))) @@ -127,59 +121,62 @@ sealed abstract class Block extends Product with AutoLocated: this match case Return(res, implct) => Return(resMap(res), implct) case Throw(exc: Path) => Throw(pathMap(exc)) - case Assign(l, r, rst) => Assign(symMap(l), resMap(r), rst.mapSyms(symMap)) - case blk @ AssignField(l, n, r, rst) => AssignField(pathMap(l), n, _resMap(r), rst.mapSyms(symMap))(blk.symbol) + case Assign(l, r, rst) => Assign(l.subst, resMap(r), rst.mapSyms) + case blk @ AssignField(l, n, r, rst) => AssignField(pathMap(l), n, _resMap(r), rst.mapSyms)(blk.symbol) case Match(scrut, arms, dflt, rst) => val newArms = arms.map((cse, blk) => val newCse = cse match case Case.Lit(lit) => cse - case Case.Cls(cls, path) => Case.Cls(symMap(cls), pathMap(path)) + // HACK: cls.subst gives something other than ClassSymbol | ModuleSymbol and it doesn't type check... + case Case.Cls(cls, path) => cls match + case s: ClassSymbol => Case.Cls(s.subst, pathMap(path)) + case s: ModuleSymbol => Case.Cls(s.subst, pathMap(path)) case Case.Tup(len, inf) => cse - (newCse, blk.mapSyms(symMap)) + (newCse, blk.mapSyms) ) - Match(pathMap(scrut), newArms, dflt.map(_.mapSyms(symMap)), rst.mapSyms(symMap)) + Match(pathMap(scrut), newArms, dflt.map(_.mapSyms), rst.mapSyms) case Define(d, rst) => val newDefn = d match case FunDefn(sym, params, body) => - FunDefn(symMap(sym), params.map(pListMap), body.mapSyms(symMap)) + FunDefn(sym.subst, params.map(pListMap), body.mapSyms) case ValDefn(owner, k, sym, rhs) => - ValDefn(owner.map(symMap), k, symMap(sym), pathMap(rhs)) + ValDefn(owner.map(_.subst), k, sym.subst, pathMap(rhs)) case ClsLikeDefn(sym, k, parentPath, methods, privateFields, publicFields, preCtor, ctor) => ClsLikeDefn( - symMap(sym), k, parentPath.map(pathMap), - methods.map(fDef => FunDefn(symMap(fDef.sym), fDef.params.map(pListMap), fDef.body.mapSyms(symMap))), - privateFields.map(symMap), + sym.subst, k, parentPath.map(pathMap), + methods.map(fDef => FunDefn(fDef.sym.subst, fDef.params.map(pListMap), fDef.body.mapSyms)), + privateFields.map(_.subst), publicFields.map(t => TermDefinition( - t.owner.map(symMap), + t.owner.map(_.subst), t.k, - symMap(t.sym), + t.sym.subst, t.params.map(pListMap), t.sign, t.body, - symMap(t.resSym), + t.resSym.subst, t.flags, t.annotations, )), - preCtor.mapSyms(symMap), - ctor.mapSyms(symMap) + preCtor.mapSyms, + ctor.mapSyms ) - Define(newDefn, rst.mapSyms(symMap)) + Define(newDefn, rst.mapSyms) case HandleBlockReturn(res: Path) => HandleBlockReturn(pathMap(res)) case Throw(res) => Throw(resMap(res)) - case Label(lbl, body, rest) => Label(symMap(lbl), body.mapSyms(symMap), rest.mapSyms(symMap)) + case Label(lbl, body, rest) => Label(lbl.subst, body.mapSyms, rest.mapSyms) case HandleBlockReturn(res) => HandleBlockReturn(resMap(res)) - case Begin(sub, rest) => Begin(sub.mapSyms(symMap), rest.mapSyms(symMap)) - case TryBlock(sub, fin, rest) => TryBlock(sub.mapSyms(symMap), fin.mapSyms(symMap), rest.mapSyms(symMap)) + case Begin(sub, rest) => Begin(sub.mapSyms, rest.mapSyms) + case TryBlock(sub, fin, rest) => TryBlock(sub.mapSyms, fin.mapSyms, rest.mapSyms) case HandleBlock(lhs, res, par, cls, handlers, body, rest) => HandleBlock( - symMap(lhs), symMap(res), pathMap(par), symMap(cls), - handlers.map(h => Handler(symMap(h.sym), symMap(h.resumeSym), h.params.map(pListMap), body.mapSyms(symMap))), - body.mapSyms(symMap), - rest.mapSyms(symMap) + lhs.subst, res.subst, pathMap(par), cls.subst, + handlers.map(h => Handler(h.sym.subst, h.resumeSym.subst, h.params.map(pListMap), body.mapSyms)), + body.mapSyms, + rest.mapSyms ) - case Break(s) => Break(symMap(s)) - case Continue(s) => Continue(symMap(s)) + case Break(s) => Break(s.subst) + case Continue(s) => Continue(s.subst) case End(_) => this diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index a0c2b0365c..2c5afc7a0d 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 scala.annotation.tailrec import hkmc2.semantics.Elaborator.ctx +import hkmc2.utils.SymbolSubst + object HandlerLowering: private val pcIdent: Tree.Ident = Tree.Ident("pc") @@ -318,33 +320,47 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case ClsLikeDefn(sym, k, parentPath, methods, privateFields, publicFields, preCtor, ctor) => sym case FunDefn(sym, params, body) => sym } - val newSyms: Map[Symbol, Symbol] = syms.map { + + val bmsMap = syms.collect { case sym: BlockMemberSymbol => sym -> BlockMemberSymbol(sym.nme + "$" + thirdPassFresh(), sym.trees) + }.toMap + + val clsMap = syms.collect { case sym: ClassSymbol => val newSym = ClassSymbol(sym.tree, Tree.Ident(sym.id.name + "$" + + thirdPassFresh())) newSym.defn = sym.defn sym -> newSym + }.toMap + + val modMap = syms.collect { case sym: ModuleSymbol => sym -> ModuleSymbol(sym.tree, Tree.Ident(sym.id.name + "$" + + thirdPassFresh())) - case sym => sym -> sym }.toMap - val clsNmeMap = newSyms.collect { - case (c1: ClassSymbol, c2: ClassSymbol) => c1.id.name -> c2.id.name + val clsNmeMap = clsMap.map { + case (c1, c2) => c1.id.name -> c2.id.name }.toMap - def symMap(l: Local) = newSyms.get(l) match - case None => l match - case b: BlockMemberSymbol => clsNmeMap.get(b.nme) match - case None => l - case Some(value) => BlockMemberSymbol(value, b.trees) - case _ => l - case Some(value) => value + val modNmeMap = modMap.map { + case (c1, c2) => c1.id.name -> c2.id.name + } + val nmeMap = clsNmeMap ++ modNmeMap val newBlk = defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) - newBlk.mapSyms(symMap) + + given SymbolSubst with + override def mapBlockMemberSym(b: BlockMemberSymbol) = bmsMap.get(b) match + case None => clsNmeMap.get(b.nme) match + case None => b + case Some(value) => BlockMemberSymbol(value, b.trees) // TODO: refactor once BlockMemberSymbol changes are in place + case Some(value) => value + override def mapClsSym(s: ClassSymbol): ClassSymbol = clsMap.get(s).getOrElse(s) + override def mapModuleSym(s: ModuleSymbol): ModuleSymbol = modMap.get(s).getOrElse(s) + override def mapTermSym(s: TermSymbol): TermSymbol = TermSymbol(s.k, s.owner.map(_.subst), s.id) + + newBlk.mapSyms private def translateFun(f: FunDefn): FunDefn = FunDefn(f.sym, f.params, translateBlock(f.body, functionHandlerCtx)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 5e8cb10a91..d728ca777f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -9,6 +9,7 @@ import syntax.* import Elaborator.State import Tree.Ident +import hkmc2.utils.SymbolSubst abstract class Symbol(using State) extends Located: @@ -46,12 +47,14 @@ abstract class Symbol(using State) extends Located: def asClsLike: Opt[ClassSymbol | ModuleSymbol | PatternSymbol] = (asCls: Opt[ClassSymbol | ModuleSymbol | PatternSymbol]) orElse asMod orElse asPat def asTpe: Opt[TypeSymbol] = asCls orElse asAls - + override def equals(x: Any): Bool = x match case that: Symbol => uid === that.uid case _ => false override def hashCode: Int = uid.hashCode + def subst(using SymbolSubst): Symbol + end Symbol @@ -65,19 +68,29 @@ class FlowSymbol(label: Str)(using State) extends Symbol: override def toString: Str = label + State.dbgUid(uid) + override def subst(using s: SymbolSubst): FlowSymbol = s.mapFlowSym(this) + -sealed trait LocalSymbol extends Symbol +sealed trait LocalSymbol extends Symbol: + override def subst(using s: SymbolSubst): LocalSymbol sealed trait NamedSymbol extends Symbol: def name: Str def id: Ident + override def subst(using s: SymbolSubst): NamedSymbol abstract class BlockLocalSymbol(name: Str)(using State) extends FlowSymbol(name) with LocalSymbol: var decl: Opt[Declaration] = N + // HACK: for some reason, if there is no concrete implementation, Scala overrides this subst with the one in + // FlowSymbol and then gives a type error because FlowSymbol is not a subtype of BlockLocalSymbol... + // Any way to force it to be abstract? + // For now, you should be careful when extending BlockLocalSymbol. + override def subst(using SymbolSubst): BlockLocalSymbol = lastWords("tried to subst BlockLocalSymbol") class TempSymbol(val trm: Opt[Term], dbgNme: Str = "tmp")(using State) extends BlockLocalSymbol(dbgNme): val nameHints: MutSet[Str] = MutSet.empty override def toLoc: Option[Loc] = trm.flatMap(_.toLoc) override def toString: Str = s"$$${super.toString}" + override def subst(using s: SymbolSubst): BlockLocalSymbol = s.mapTempSym(this) // * When instantiating forall-qualified TVs, we need to duplicate the information @@ -88,10 +101,13 @@ class InstSymbol(val origin: Symbol)(using State) extends LocalSymbol: override def toLoc: Option[Loc] = origin.toLoc override def toString: Str = origin.toString + override def subst(using sub: SymbolSubst): InstSymbol = sub.mapInstSym(this) + class VarSymbol(val id: Ident)(using State) extends BlockLocalSymbol(id.name) with NamedSymbol: val name: Str = id.name // override def toString: Str = s"$name@$uid" + override def subst(using s: SymbolSubst): VarSymbol = s.mapVarSym(this) class BuiltinSymbol (val nme: Str, val binary: Bool, val unary: Bool, val nullary: Bool, val functionLike: Bool)(using State) @@ -99,6 +115,8 @@ class BuiltinSymbol def toLoc: Option[Loc] = N override def toString: Str = s"builtin:$nme${State.dbgUid(uid)}" + override def subst(using sub: SymbolSubst): BuiltinSymbol = sub.mapBuiltInSym(this) + /** This is the outside-facing symbol associated to a possibly-overloaded * definition living in a block – e.g., a module or class. */ @@ -129,6 +147,8 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[Tree])(using State) override val isGetter: Bool = // TODO: this should be checked based on a special syntax for getter trmImplTree.exists(t => t.k === Fun && t.paramLists.isEmpty) + override def subst(using sub: SymbolSubst): BlockMemberSymbol = sub.mapBlockMemberSym(this) + end BlockMemberSymbol @@ -136,6 +156,7 @@ sealed abstract class MemberSymbol[Defn <: Definition](using State) extends Symb def nme: Str var defn: Opt[Defn] = N val isGetter: Bool = false + def subst(using SymbolSubst): MemberSymbol[Defn] class TermSymbol(val k: TermDefKind, val owner: Opt[InnerSymbol], val id: Tree.Ident)(using State) @@ -144,9 +165,12 @@ class TermSymbol(val k: TermDefKind, val owner: Opt[InnerSymbol], val id: Tree.I def name: Str = nme def toLoc: Option[Loc] = id.toLoc override def toString: Str = s"${owner.getOrElse("")}.${id.name}" + + override def subst(using sub: SymbolSubst): TermSymbol = sub.mapTermSym(this) -sealed trait CtorSymbol extends Symbol +sealed trait CtorSymbol extends Symbol: + override def subst(using sub: SymbolSubst): CtorSymbol = sub.mapCtorSym(this) case class Extr(isTop: Bool)(using State) extends CtorSymbol: def nme: Str = if isTop then "Top" else "Bot" @@ -171,7 +195,8 @@ type FieldSymbol = TermSymbol | MemberSymbol[?] /** This is the symbol associated to specific definitions. * One overloaded `BlockMemberSymbol` may correspond to multiple `InnerSymbol`s * A `Ref(_: InnerSymbol)` represents a `this`-like reference to the current object. */ -sealed trait InnerSymbol extends Symbol +sealed trait InnerSymbol extends Symbol: + def subst(using SymbolSubst): InnerSymbol class ClassSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) extends MemberSymbol[ClassDef] with CtorSymbol with InnerSymbol: @@ -181,27 +206,35 @@ class ClassSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) /** Compute the arity. */ def arity: Int = tree.paramLists.headOption.fold(0)(_.fields.length) + override def subst(using sub: SymbolSubst): ClassSymbol = sub.mapClsSym(this) + class ModuleSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) extends MemberSymbol[ModuleDef] with CtorSymbol with InnerSymbol: def nme = id.name def toLoc: Option[Loc] = id.toLoc // TODO track source tree of module here override def toString: Str = s"module:${id.name}${State.dbgUid(uid)}" + override def subst(using sub: SymbolSubst): ModuleSymbol = sub.mapModuleSym(this) + class TypeAliasSymbol(val id: Tree.Ident)(using State) extends MemberSymbol[TypeDef]: def nme = id.name def toLoc: Option[Loc] = id.toLoc // TODO track source tree of type alias here override def toString: Str = s"module:${id.name}${State.dbgUid(uid)}" + override def subst(using sub: SymbolSubst): TypeAliasSymbol = sub.mapTypeAliasSym(this) + class PatternSymbol(val id: Tree.Ident)(using State) extends MemberSymbol[PatternDef] with CtorSymbol with InnerSymbol: def nme = id.name def toLoc: Option[Loc] = id.toLoc // TODO track source tree of pattern here override def toString: Str = s"pattern:${id.name}" + override def subst(using sub: SymbolSubst): PatternSymbol = sub.mapPatSym(this) + class TopLevelSymbol(blockNme: Str)(using State) extends MemberSymbol[ModuleDef] with InnerSymbol: def nme = blockNme def toLoc: Option[Loc] = N override def toString: Str = s"globalThis:$blockNme${State.dbgUid(uid)}" - + override def subst(using sub: SymbolSubst): TopLevelSymbol = sub.mapTopLevelSym(this) diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/SymbolSubst.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/SymbolSubst.scala new file mode 100644 index 0000000000..f0ac4f0e5e --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/SymbolSubst.scala @@ -0,0 +1,18 @@ +package hkmc2.utils + +import hkmc2.semantics.* + +trait SymbolSubst: + def mapBlockMemberSym(s: BlockMemberSymbol): BlockMemberSymbol = s + def mapFlowSym(s: FlowSymbol): FlowSymbol = s + def mapTempSym(s: TempSymbol): TempSymbol = s + def mapVarSym(s: VarSymbol): VarSymbol = s + def mapInstSym(s: InstSymbol): InstSymbol = s + def mapBuiltInSym(s: BuiltinSymbol): BuiltinSymbol = s + def mapTermSym(s: TermSymbol): TermSymbol = s + def mapCtorSym(s: CtorSymbol): CtorSymbol = s + def mapClsSym(s: ClassSymbol): ClassSymbol = s + def mapModuleSym(s: ModuleSymbol): ModuleSymbol = s + def mapTypeAliasSym(s: TypeAliasSymbol): TypeAliasSymbol = s + def mapPatSym(s: PatternSymbol): PatternSymbol = s + def mapTopLevelSym(s: TopLevelSymbol): TopLevelSymbol = s \ No newline at end of file diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 0c8e013579..f959a82937 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -322,3 +322,36 @@ handle h = Eff with fun perform()(k) = k(()) foo(h) //│ = 2 + +:handler +:expect 3 +fun foo(h) = + h.perform() + if false then + fun g() = 2 + g() + else + fun g() = 3 + g() +handle h = Eff with + fun perform()(k) = k(()) +foo(h) +//│ = 3 + +:handler +:expect 3 +:fixme +fun foo(h) = + h.perform() + if false then + class A() with + fun f() = 2 + A().f() + else + class A() with + fun f() = 3 + A().f() +handle h = Eff with + fun perform()(k) = k(()) +foo(h) +//│ ═══[RUNTIME ERROR] Expected: 3, got: 2 From be377392fcbc821180d56ed9636c15924d0c311f Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Tue, 14 Jan 2025 00:09:31 +0800 Subject: [PATCH 064/114] Update tests --- .../mlscript/handlers/RecursiveHandlers.mls | 70 ++++++++++--------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 24e4f5c04f..725564df28 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -111,12 +111,12 @@ if true do fun perform(arg)(k) = set str += "A" k(arg) - set str = str + "A" + set str += "A" handle h2 = Effect with fun perform(arg)(k) = - set str = str + "B" + set str += str + "B" k(arg) - set str = str + "B" + set str += str + "B" h2.perform(()) h1.perform(()) str @@ -135,8 +135,8 @@ str //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h1, (k) => { //│ let tmp3, tmp4, tmp5, res; -//│ function Cont$1077$4(pc1) { return new Cont$1077$4.class(pc1); } -//│ Cont$1077$4.class = class Cont$1077$4 extends globalThis.Predef.__Cont.class { +//│ function Cont$1093$4(pc1) { return new Cont$1093$4.class(pc1); } +//│ Cont$1093$4.class = class Cont$1093$4 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp6; //│ tmp6 = super(null, null); @@ -167,13 +167,13 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$1077$4(" + this.pc + ")"; } +//│ toString() { return "Cont$1093$4(" + this.pc + ")"; } //│ }; //│ tmp3 = globalThis.str + "A"; //│ globalThis.str = tmp3; //│ res = k(arg) ?? null; //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$1077$4.class(10); +//│ res.tail.next = new Cont$1093$4.class(10); //│ res.tail = res.tail.next; //│ return res; //│ } @@ -186,8 +186,8 @@ str //│ toString() { return "Effect$h1$922"; } //│ } //│ h1 = new Effect$h1$922(); -//│ function Cont$1037$3(pc1) { return new Cont$1037$3.class(pc1); } -//│ Cont$1037$3.class = class Cont$1037$3 extends globalThis.Predef.__Cont.class { +//│ function Cont$1049$3(pc1) { return new Cont$1049$3.class(pc1); } +//│ Cont$1049$3.class = class Cont$1049$3 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp3; //│ tmp3 = super(null, null); @@ -219,7 +219,7 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$1037$3(" + this.pc + ")"; } +//│ toString() { return "Cont$1049$3(" + this.pc + ")"; } //│ }; //│ function handleBlock$1$2() { //│ let h2, tmp3, res, res1; @@ -230,12 +230,12 @@ str //│ } //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h2, (k) => { -//│ let tmp4, tmp5, tmp6, res2; -//│ function Cont$1002$1(pc1) { return new Cont$1002$1.class(pc1); } -//│ Cont$1002$1.class = class Cont$1002$1 extends globalThis.Predef.__Cont.class { +//│ let tmp4, tmp5, tmp6, tmp7, tmp8, res2; +//│ function Cont$1008$1(pc1) { return new Cont$1008$1.class(pc1); } +//│ Cont$1008$1.class = class Cont$1008$1 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { -//│ let tmp7; -//│ tmp7 = super(null, null); +//│ let tmp9; +//│ tmp9 = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { @@ -245,7 +245,8 @@ str //│ contLoop: while (true) { //│ if (this.pc === 6) { //│ tmp4 = globalThis.str + "B"; -//│ globalThis.str = tmp4; +//│ tmp5 = globalThis.str + tmp4; +//│ globalThis.str = tmp5; //│ res2 = k(arg) ?? null; //│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { //│ res2.tail.next = this; @@ -255,35 +256,38 @@ str //│ this.pc = 5; //│ continue contLoop; //│ } else if (this.pc === 5) { -//│ tmp5 = res2; -//│ tmp6 = globalThis.str + "B"; -//│ globalThis.str = tmp6; +//│ tmp6 = res2; +//│ tmp7 = globalThis.str + "B"; +//│ tmp8 = globalThis.str + tmp7; +//│ globalThis.str = tmp8; //│ return null; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$1002$1(" + this.pc + ")"; } +//│ toString() { return "Cont$1008$1(" + this.pc + ")"; } //│ }; //│ tmp4 = globalThis.str + "B"; -//│ globalThis.str = tmp4; +//│ tmp5 = globalThis.str + tmp4; +//│ globalThis.str = tmp5; //│ res2 = k(arg) ?? null; //│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$1002$1.class(5); +//│ res2.tail.next = new Cont$1008$1.class(5); //│ res2.tail = res2.tail.next; //│ return res2; //│ } -//│ tmp5 = res2; -//│ tmp6 = globalThis.str + "B"; -//│ globalThis.str = tmp6; +//│ tmp6 = res2; +//│ tmp7 = globalThis.str + "B"; +//│ tmp8 = globalThis.str + tmp7; +//│ globalThis.str = tmp8; //│ return null; //│ }); //│ } //│ toString() { return "Effect$h2$932"; } //│ } //│ h2 = new Effect$h2$932(); -//│ function Cont$973$0(pc1) { return new Cont$973$0.class(pc1); } -//│ Cont$973$0.class = class Cont$973$0 extends globalThis.Predef.__Cont.class { +//│ function Cont$979$0(pc1) { return new Cont$979$0.class(pc1); } +//│ Cont$979$0.class = class Cont$979$0 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp4; //│ tmp4 = super(null, null); @@ -321,24 +325,24 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$973$0(" + this.pc + ")"; } +//│ toString() { return "Cont$979$0(" + this.pc + ")"; } //│ }; //│ res1 = h2.perform(null) ?? null; //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$973$0(3); +//│ res1.tail.next = new Cont$979$0(3); //│ return globalThis.Predef.__handleBlockImpl(res1, h2); //│ } //│ tmp3 = res1; //│ res = h1.perform(null) ?? null; //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$973$0(2); +//│ res.tail.next = new Cont$979$0(2); //│ return globalThis.Predef.__handleBlockImpl(res, h2); //│ } //│ return res; //│ } //│ tmp2 = handleBlock$1$2(); //│ if (tmp2 instanceof globalThis.Predef.__EffectSig.class) { -//│ tmp2.tail.next = new Cont$1037$3(7); +//│ tmp2.tail.next = new Cont$1049$3(7); //│ return globalThis.Predef.__handleBlockImpl(tmp2, h1); //│ } //│ if (tmp2 instanceof globalThis.Predef.__Return.class) { @@ -355,5 +359,5 @@ str //│ tmp1 = null; //│ } //│ this.str -//│ = 'BABA' -//│ str = 'BABA' +//│ = 'BABABA' +//│ str = 'BABABA' From b8d0da8ea7679b259f49b4e45e1f5062746ffe37 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Tue, 14 Jan 2025 00:12:32 +0800 Subject: [PATCH 065/114] fix whitespace --- hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 1780791cce..6bf2cdab1c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -670,7 +670,7 @@ extends Importer: val res: Term.Blk = ctx.nest(N).givenIn: val sym = fieldOrVarSym(HandlerBind, id) log(s"Processing `handle` statement $id (${sym}) ${ctx.outer}") - + // TODO: shouldn't need uid here val derivedClsSym = ClassSymbol(Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), Tree.Ident(s"${cls.name}$$${id.name}$$${State.suid.nextUid}")) derivedClsSym.defn = S(ClassDef(N, syntax.Cls, derivedClsSym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))), List())) From 7da81019cc08c59c55a8f520c9d37904345f39c6 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Tue, 14 Jan 2025 01:38:24 +0800 Subject: [PATCH 066/114] Fix symbol map issues --- .../src/main/scala/hkmc2/codegen/Block.scala | 5 -- .../scala/hkmc2/codegen/HandlerLowering.scala | 88 +++++++++++++------ .../src/test/mlscript/handlers/Effects.mls | 62 ++++++++++--- .../mlscript/handlers/RecursiveHandlers.mls | 52 +++++------ 4 files changed, 134 insertions(+), 73 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 64e9d8bd3e..e7aedc31bd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -90,10 +90,6 @@ sealed abstract class Block extends Product with AutoLocated: } def mapSyms(using State, SymbolSubst): Block = - - - // def resMap[T <: Result] - def pMap(p: Param) = val newSym: LocalSymbol & NamedSymbol = p.sym.subst Param(p.flags, p.sym.subst, p.sign) @@ -178,7 +174,6 @@ sealed abstract class Block extends Product with AutoLocated: case Break(s) => Break(s.subst) case Continue(s) => Continue(s.subst) case End(_) => this - def mapValue(f: Value => Value): Block = def go(p: Path): Path = p match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 2c5afc7a0d..ab05e29498 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -261,7 +261,8 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): // ignored cases case TryBlock(sub, finallyDo, rest) => ??? // ignore case Throw(_) => PartRet(blk, Nil) - case _: HandleBlock | _: HandleBlockReturn => die // already translated at this point + case _: HandleBlock => lastWords("unexpected handleBlock") // already translated at this point + case _: HandleBlockReturn => lastWords("unexpected handleBlockReturn") // already translated at this point val headId = freshId() @@ -279,11 +280,10 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): private def translateBlock(b: Block, h: HandlerCtx): Block = given HandlerCtx = h + val stage1 = firstPass(b) val stage2 = secondPass(stage1) - if h.isTopLevel then stage2 - else thirdPass(stage2) - + if h.isTopLevel then stage2 else thirdPass(stage2) private def firstPass(b: Block)(using HandlerCtx): Block = b.map(firstPass) match case b: HandleBlock => translateHandleBlock(b) @@ -316,45 +316,75 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): // 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 - val syms = defns.collect { + val clsDefns = defns.collect { case ClsLikeDefn(sym, k, parentPath, methods, privateFields, publicFields, preCtor, ctor) => sym + } + val funDefns = defns.collect { case FunDefn(sym, params, body) => sym } - val bmsMap = syms.collect { + def getBms = + var l: List[BlockMemberSymbol] = Nil + given SymbolSubst with + override def mapBlockMemberSym(b: BlockMemberSymbol) = + l = b :: l + b + b.mapSyms + l + + val toConvert = getBms.map(b => + val clsDefn = b.asCls + val modDefn = b.asMod + // check if this BlockMemberSymbol belongs to a definition in this block + val isThisBlock = clsDefn match + case None => modDefn match + case None => false + case Some(value) => clsDefns.contains(value) + case Some(value) => clsDefns.contains(value) + if isThisBlock then Some(b) + else None + ).collect { case Some(b) => b } + + val fnBmsMap = funDefns.map { case sym: BlockMemberSymbol => sym -> BlockMemberSymbol(sym.nme + "$" + thirdPassFresh(), sym.trees) }.toMap - val clsMap = syms.collect { - case sym: ClassSymbol => - val newSym = ClassSymbol(sym.tree, Tree.Ident(sym.id.name + "$" + + thirdPassFresh())) - newSym.defn = sym.defn - sym -> newSym - }.toMap - - val modMap = syms.collect { - case sym: ModuleSymbol => - sym -> ModuleSymbol(sym.tree, Tree.Ident(sym.id.name + "$" + + thirdPassFresh())) - }.toMap - - val clsNmeMap = clsMap.map { - case (c1, c2) => c1.id.name -> c2.id.name - }.toMap - - val modNmeMap = modMap.map { - case (c1, c2) => c1.id.name -> c2.id.name - } - - val nmeMap = clsNmeMap ++ modNmeMap + val clsBmsMap = toConvert.map(b => + b -> BlockMemberSymbol(b.nme + "$" + thirdPassFresh(), b.trees) + ).toMap + + val bmsMap = (fnBmsMap ++ clsBmsMap).toMap + + val clsMap = clsBmsMap.map { + case b1 -> b2 => b1.asCls match + case Some(value) => + val newSym = ClassSymbol(value.tree, Tree.Ident(b2.nme)) + newSym.defn = value.defn + S(value -> newSym) + case None => None + }.collect{ case Some(x) => x }.toMap + + val modMap = clsBmsMap.map { + case b1 -> b2 => b1.asMod match + case Some(value) => + val newSym = ModuleSymbol(value.tree, Tree.Ident(b2.nme)) + newSym.defn = value.defn + S(value -> newSym) + case None => None + }.collect{ case Some(x) => x }.toMap val newBlk = defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) given SymbolSubst with override def mapBlockMemberSym(b: BlockMemberSymbol) = bmsMap.get(b) match - case None => clsNmeMap.get(b.nme) match + case None => b.asCls match case None => b - case Some(value) => BlockMemberSymbol(value, b.trees) // TODO: refactor once BlockMemberSymbol changes are in place + case Some(cls) => + clsMap.get(cls) match + case None => b + case Some(sym) => + BlockMemberSymbol(sym.nme, b.trees) // TODO: properly map trees case Some(value) => value override def mapClsSym(s: ClassSymbol): ClassSymbol = clsMap.get(s).getOrElse(s) override def mapModuleSym(s: ModuleSymbol): ModuleSymbol = modMap.get(s).getOrElse(s) diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index f959a82937..7805f7c7fc 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -148,7 +148,7 @@ if true do //│ let tmp; //│ function handleBlock$0() { //│ let h, scrut, tmp1, res; -//│ class Effect$h$1217 extends globalThis.Effect { +//│ class Effect$h$1156 extends globalThis.Effect { //│ constructor() { //│ let tmp2; //│ tmp2 = super(); @@ -158,11 +158,11 @@ if true do //│ return arg; //│ }); //│ } -//│ toString() { return "Effect$h$1217"; } +//│ toString() { return "Effect$h$1156"; } //│ } -//│ h = new Effect$h$1217(); -//│ function Cont$1242$1(pc1) { return new Cont$1242$1.class(pc1); } -//│ Cont$1242$1.class = class Cont$1242$1 extends globalThis.Predef.__Cont.class { +//│ h = new Effect$h$1156(); +//│ function Cont$1181(pc1) { return new Cont$1181.class(pc1); } +//│ Cont$1181.class = class Cont$1181 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp2; //│ tmp2 = super(null, null); @@ -201,7 +201,7 @@ if true do //│ break; //│ } //│ } -//│ toString() { return "Cont$1242$1(" + this.pc + ")"; } +//│ toString() { return "Cont$1181(" + this.pc + ")"; } //│ }; //│ function f$0() { //│ return 3; @@ -210,7 +210,7 @@ if true do //│ if (scrut) { //│ res = f$0(); //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$1242$1(1); +//│ res.tail.next = new Cont$1181(1); //│ return globalThis.Predef.__handleBlockImpl(res, h); //│ } //│ tmp1 = res; @@ -230,7 +230,7 @@ fun f(perform) = handle h = Effect with fun perform(arg)(k) = arg perform() -f(() => 3) +f(() => 3) //│ = 3 abstract class Cell with @@ -339,11 +339,11 @@ foo(h) //│ = 3 :handler -:expect 3 -:fixme +:expect 123 fun foo(h) = h.perform() - if false then + fun A() = 1 + let a = if true then class A() with fun f() = 2 A().f() @@ -351,7 +351,43 @@ fun foo(h) = class A() with fun f() = 3 A().f() + let b = if false then + class A() with + fun f() = 2 + A().f() + else + class A() with + fun f() = 3 + A().f() + A() * 100 + a * 10 + b handle h = Eff with fun perform()(k) = k(()) -foo(h) -//│ ═══[RUNTIME ERROR] Expected: 3, got: 2 +foo(h) +//│ = 123 + +:handler +:expect 123 +fun foo(h) = + h.perform() + fun A() = 1 + let a = if true then + module A with + fun f() = 2 + A.f() + else + module A with + fun f() = 3 + A.f() + let b = if false then + module A with + fun f() = 2 + A.f() + else + module A with + fun f() = 3 + A.f() + A() * 100 + a * 10 + b +handle h = Eff with + fun perform()(k) = k(()) +foo(h) +//│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 725564df28..c6b992f137 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -127,7 +127,7 @@ str //│ if (scrut) { //│ function handleBlock$0() { //│ let h1, tmp2; -//│ class Effect$h1$922 extends globalThis.Effect { +//│ class Effect$h1$881 extends globalThis.Effect { //│ constructor() { //│ let tmp3; //│ tmp3 = super(); @@ -135,8 +135,8 @@ str //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h1, (k) => { //│ let tmp3, tmp4, tmp5, res; -//│ function Cont$1093$4(pc1) { return new Cont$1093$4.class(pc1); } -//│ Cont$1093$4.class = class Cont$1093$4 extends globalThis.Predef.__Cont.class { +//│ function Cont$1045(pc1) { return new Cont$1045.class(pc1); } +//│ Cont$1045.class = class Cont$1045 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp6; //│ tmp6 = super(null, null); @@ -167,13 +167,13 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$1093$4(" + this.pc + ")"; } +//│ toString() { return "Cont$1045(" + this.pc + ")"; } //│ }; //│ tmp3 = globalThis.str + "A"; //│ globalThis.str = tmp3; //│ res = k(arg) ?? null; //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$1093$4.class(10); +//│ res.tail.next = new Cont$1045.class(10); //│ res.tail = res.tail.next; //│ return res; //│ } @@ -183,11 +183,11 @@ str //│ return null; //│ }); //│ } -//│ toString() { return "Effect$h1$922"; } +//│ toString() { return "Effect$h1$881"; } //│ } -//│ h1 = new Effect$h1$922(); -//│ function Cont$1049$3(pc1) { return new Cont$1049$3.class(pc1); } -//│ Cont$1049$3.class = class Cont$1049$3 extends globalThis.Predef.__Cont.class { +//│ h1 = new Effect$h1$881(); +//│ function Cont$1003(pc1) { return new Cont$1003.class(pc1); } +//│ Cont$1003.class = class Cont$1003 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp3; //│ tmp3 = super(null, null); @@ -199,7 +199,7 @@ str //│ } //│ contLoop: while (true) { //│ if (this.pc === 8) { -//│ tmp2 = handleBlock$1$2(); +//│ tmp2 = handleBlock$1$0(); //│ if (tmp2 instanceof globalThis.Predef.__EffectSig.class) { //│ tmp2.tail.next = this; //│ this.pc = 7; @@ -219,11 +219,11 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$1049$3(" + this.pc + ")"; } +//│ toString() { return "Cont$1003(" + this.pc + ")"; } //│ }; -//│ function handleBlock$1$2() { +//│ function handleBlock$1$0() { //│ let h2, tmp3, res, res1; -//│ class Effect$h2$932 extends globalThis.Effect { +//│ class Effect$h2$891 extends globalThis.Effect { //│ constructor() { //│ let tmp4; //│ tmp4 = super(); @@ -231,8 +231,8 @@ str //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h2, (k) => { //│ let tmp4, tmp5, tmp6, tmp7, tmp8, res2; -//│ function Cont$1008$1(pc1) { return new Cont$1008$1.class(pc1); } -//│ Cont$1008$1.class = class Cont$1008$1 extends globalThis.Predef.__Cont.class { +//│ function Cont$964(pc1) { return new Cont$964.class(pc1); } +//│ Cont$964.class = class Cont$964 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp9; //│ tmp9 = super(null, null); @@ -265,14 +265,14 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$1008$1(" + this.pc + ")"; } +//│ toString() { return "Cont$964(" + this.pc + ")"; } //│ }; //│ tmp4 = globalThis.str + "B"; //│ tmp5 = globalThis.str + tmp4; //│ globalThis.str = tmp5; //│ res2 = k(arg) ?? null; //│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$1008$1.class(5); +//│ res2.tail.next = new Cont$964.class(5); //│ res2.tail = res2.tail.next; //│ return res2; //│ } @@ -283,11 +283,11 @@ str //│ return null; //│ }); //│ } -//│ toString() { return "Effect$h2$932"; } +//│ toString() { return "Effect$h2$891"; } //│ } -//│ h2 = new Effect$h2$932(); -//│ function Cont$979$0(pc1) { return new Cont$979$0.class(pc1); } -//│ Cont$979$0.class = class Cont$979$0 extends globalThis.Predef.__Cont.class { +//│ h2 = new Effect$h2$891(); +//│ function Cont$938(pc1) { return new Cont$938.class(pc1); } +//│ Cont$938.class = class Cont$938 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp4; //│ tmp4 = super(null, null); @@ -325,24 +325,24 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$979$0(" + this.pc + ")"; } +//│ toString() { return "Cont$938(" + this.pc + ")"; } //│ }; //│ res1 = h2.perform(null) ?? null; //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$979$0(3); +//│ res1.tail.next = new Cont$938(3); //│ return globalThis.Predef.__handleBlockImpl(res1, h2); //│ } //│ tmp3 = res1; //│ res = h1.perform(null) ?? null; //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$979$0(2); +//│ res.tail.next = new Cont$938(2); //│ return globalThis.Predef.__handleBlockImpl(res, h2); //│ } //│ return res; //│ } -//│ tmp2 = handleBlock$1$2(); +//│ tmp2 = handleBlock$1$0(); //│ if (tmp2 instanceof globalThis.Predef.__EffectSig.class) { -//│ tmp2.tail.next = new Cont$1049$3(7); +//│ tmp2.tail.next = new Cont$1003(7); //│ return globalThis.Predef.__handleBlockImpl(tmp2, h1); //│ } //│ if (tmp2 instanceof globalThis.Predef.__Return.class) { From 21843b4e89b1f4133911f3bb8d584227f4db9dd3 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 14 Jan 2025 10:48:11 +0800 Subject: [PATCH 067/114] Fix overriding hack --- .../main/scala/hkmc2/semantics/Symbol.scala | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index d728ca777f..08660b27a8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -68,29 +68,25 @@ class FlowSymbol(label: Str)(using State) extends Symbol: override def toString: Str = label + State.dbgUid(uid) - override def subst(using s: SymbolSubst): FlowSymbol = s.mapFlowSym(this) + def subst(using s: SymbolSubst): FlowSymbol = s.mapFlowSym(this) sealed trait LocalSymbol extends Symbol: - override def subst(using s: SymbolSubst): LocalSymbol + def subst(using s: SymbolSubst): LocalSymbol sealed trait NamedSymbol extends Symbol: def name: Str def id: Ident - override def subst(using s: SymbolSubst): NamedSymbol + def subst(using s: SymbolSubst): NamedSymbol -abstract class BlockLocalSymbol(name: Str)(using State) extends FlowSymbol(name) with LocalSymbol: +abstract class BlockLocalSymbol(name: Str)(using State) extends FlowSymbol(name): + self: LocalSymbol => // * using `with LocalSymbol` in the `extends` clause makes Scala think there's a bad override var decl: Opt[Declaration] = N - // HACK: for some reason, if there is no concrete implementation, Scala overrides this subst with the one in - // FlowSymbol and then gives a type error because FlowSymbol is not a subtype of BlockLocalSymbol... - // Any way to force it to be abstract? - // For now, you should be careful when extending BlockLocalSymbol. - override def subst(using SymbolSubst): BlockLocalSymbol = lastWords("tried to subst BlockLocalSymbol") -class TempSymbol(val trm: Opt[Term], dbgNme: Str = "tmp")(using State) extends BlockLocalSymbol(dbgNme): +class TempSymbol(val trm: Opt[Term], dbgNme: Str = "tmp")(using State) extends BlockLocalSymbol(dbgNme) with LocalSymbol: val nameHints: MutSet[Str] = MutSet.empty override def toLoc: Option[Loc] = trm.flatMap(_.toLoc) override def toString: Str = s"$$${super.toString}" - override def subst(using s: SymbolSubst): BlockLocalSymbol = s.mapTempSym(this) + override def subst(using s: SymbolSubst): TempSymbol = s.mapTempSym(this) // * When instantiating forall-qualified TVs, we need to duplicate the information @@ -101,10 +97,10 @@ class InstSymbol(val origin: Symbol)(using State) extends LocalSymbol: override def toLoc: Option[Loc] = origin.toLoc override def toString: Str = origin.toString - override def subst(using sub: SymbolSubst): InstSymbol = sub.mapInstSym(this) + def subst(using sub: SymbolSubst): InstSymbol = sub.mapInstSym(this) -class VarSymbol(val id: Ident)(using State) extends BlockLocalSymbol(id.name) with NamedSymbol: +class VarSymbol(val id: Ident)(using State) extends BlockLocalSymbol(id.name) with NamedSymbol with LocalSymbol: val name: Str = id.name // override def toString: Str = s"$name@$uid" override def subst(using s: SymbolSubst): VarSymbol = s.mapVarSym(this) @@ -115,7 +111,7 @@ class BuiltinSymbol def toLoc: Option[Loc] = N override def toString: Str = s"builtin:$nme${State.dbgUid(uid)}" - override def subst(using sub: SymbolSubst): BuiltinSymbol = sub.mapBuiltInSym(this) + def subst(using sub: SymbolSubst): BuiltinSymbol = sub.mapBuiltInSym(this) /** This is the outside-facing symbol associated to a possibly-overloaded @@ -147,7 +143,7 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[Tree])(using State) override val isGetter: Bool = // TODO: this should be checked based on a special syntax for getter trmImplTree.exists(t => t.k === Fun && t.paramLists.isEmpty) - override def subst(using sub: SymbolSubst): BlockMemberSymbol = sub.mapBlockMemberSym(this) + def subst(using sub: SymbolSubst): BlockMemberSymbol = sub.mapBlockMemberSym(this) end BlockMemberSymbol @@ -166,11 +162,11 @@ class TermSymbol(val k: TermDefKind, val owner: Opt[InnerSymbol], val id: Tree.I def toLoc: Option[Loc] = id.toLoc override def toString: Str = s"${owner.getOrElse("")}.${id.name}" - override def subst(using sub: SymbolSubst): TermSymbol = sub.mapTermSym(this) + def subst(using sub: SymbolSubst): TermSymbol = sub.mapTermSym(this) sealed trait CtorSymbol extends Symbol: - override def subst(using sub: SymbolSubst): CtorSymbol = sub.mapCtorSym(this) + def subst(using sub: SymbolSubst): CtorSymbol = sub.mapCtorSym(this) case class Extr(isTop: Bool)(using State) extends CtorSymbol: def nme: Str = if isTop then "Top" else "Bot" @@ -221,7 +217,7 @@ class TypeAliasSymbol(val id: Tree.Ident)(using State) extends MemberSymbol[Type def toLoc: Option[Loc] = id.toLoc // TODO track source tree of type alias here override def toString: Str = s"module:${id.name}${State.dbgUid(uid)}" - override def subst(using sub: SymbolSubst): TypeAliasSymbol = sub.mapTypeAliasSym(this) + def subst(using sub: SymbolSubst): TypeAliasSymbol = sub.mapTypeAliasSym(this) class PatternSymbol(val id: Tree.Ident)(using State) extends MemberSymbol[PatternDef] with CtorSymbol with InnerSymbol: @@ -237,4 +233,4 @@ class TopLevelSymbol(blockNme: Str)(using State) def toLoc: Option[Loc] = N override def toString: Str = s"globalThis:$blockNme${State.dbgUid(uid)}" - override def subst(using sub: SymbolSubst): TopLevelSymbol = sub.mapTopLevelSym(this) + def subst(using sub: SymbolSubst): TopLevelSymbol = sub.mapTopLevelSym(this) From 862bd6a82913c67ff6c60de9058690e5b8c7ad16 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Tue, 14 Jan 2025 16:16:06 +0800 Subject: [PATCH 068/114] Address PR comments --- .../src/main/scala/hkmc2/codegen/Block.scala | 7 +--- .../scala/hkmc2/codegen/HandlerLowering.scala | 40 +++++++++---------- .../main/scala/hkmc2/semantics/Symbol.scala | 7 +++- .../src/test/mlscript/handlers/Effects.mls | 2 +- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index e7aedc31bd..798e197c07 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -123,10 +123,7 @@ sealed abstract class Block extends Product with AutoLocated: val newArms = arms.map((cse, blk) => val newCse = cse match case Case.Lit(lit) => cse - // HACK: cls.subst gives something other than ClassSymbol | ModuleSymbol and it doesn't type check... - case Case.Cls(cls, path) => cls match - case s: ClassSymbol => Case.Cls(s.subst, pathMap(path)) - case s: ModuleSymbol => Case.Cls(s.subst, pathMap(path)) + case Case.Cls(cls, path) => Case.Cls(cls.subst, pathMap(path)) case Case.Tup(len, inf) => cse (newCse, blk.mapSyms) ) @@ -358,7 +355,7 @@ case class End(msg: Str = "") extends BlockTail with ProductWithTail enum Case: case Lit(lit: Literal) - case Cls(cls: ClassSymbol | ModuleSymbol, path: Path) + case Cls(cls: ClassLikeSymbol | ModuleSymbol, path: Path) case Tup(len: Int, inf: Bool) lazy val freeVars: Set[Local] = this match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index ab05e29498..5ffdd7c259 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -152,12 +152,14 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): // for readability :) case class PartRet(head: Block, states: Ls[BlockState]) - // returns (truncated input block, child block states) - // TODO: don't split within Match, Begin and Labels when not needed, ideally keep it intact. Need careful analysis for this - // blk: The block to transform - // labelIds: maps label IDs to the state at the start of the label and the state after the label - // jumpTo: what state End should jump to, if at all - // freshState: uid generator + /* + * returns (truncated input block, child block states) + * TODO: don't split within Match, Begin and Labels when not needed, ideally keep it intact. Need careful analysis for this + * blk: The block to transform + * labelIds: maps label IDs to the state at the start of the label and the state after the label + * jumpTo: what state End should jump to, if at all + * freshState: uid generator + */ def go(blk: Block)(implicit labelIds: Map[Symbol, (StateId, StateId)], afterEnd: Option[StateId]): PartRet = blk match case ResumptionPoint(result, uid, rest) => val PartRet(head, states) = go(rest) @@ -279,11 +281,11 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): */ private def translateBlock(b: Block, h: HandlerCtx): Block = - given HandlerCtx = h - + given HandlerCtx = h val stage1 = firstPass(b) val stage2 = secondPass(stage1) if h.isTopLevel then stage2 else thirdPass(stage2) + private def firstPass(b: Block)(using HandlerCtx): Block = b.map(firstPass) match case b: HandleBlock => translateHandleBlock(b) @@ -316,12 +318,11 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): // 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 - val clsDefns = defns.collect { + val clsDefns = defns.collect: case ClsLikeDefn(sym, k, parentPath, methods, privateFields, publicFields, preCtor, ctor) => sym - } - val funDefns = defns.collect { + + val funDefns = defns.collect: case FunDefn(sym, params, body) => sym - } def getBms = var l: List[BlockMemberSymbol] = Nil @@ -345,10 +346,9 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): else None ).collect { case Some(b) => b } - val fnBmsMap = funDefns.map { - case sym: BlockMemberSymbol => - sym -> BlockMemberSymbol(sym.nme + "$" + thirdPassFresh(), sym.trees) - }.toMap + val fnBmsMap = funDefns.map(b => + b -> BlockMemberSymbol(b.nme + "$" + thirdPassFresh(), b.trees) + ).toMap val clsBmsMap = toConvert.map(b => b -> BlockMemberSymbol(b.nme + "$" + thirdPassFresh(), b.trees) @@ -356,23 +356,23 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): val bmsMap = (fnBmsMap ++ clsBmsMap).toMap - val clsMap = clsBmsMap.map { + val clsMap = clsBmsMap.map: case b1 -> b2 => b1.asCls match case Some(value) => val newSym = ClassSymbol(value.tree, Tree.Ident(b2.nme)) newSym.defn = value.defn S(value -> newSym) case None => None - }.collect{ case Some(x) => x }.toMap + .collect{ case Some(x) => x }.toMap - val modMap = clsBmsMap.map { + val modMap = clsBmsMap.map: case b1 -> b2 => b1.asMod match case Some(value) => val newSym = ModuleSymbol(value.tree, Tree.Ident(b2.nme)) newSym.defn = value.defn S(value -> newSym) case None => None - }.collect{ case Some(x) => x }.toMap + .collect{ case Some(x) => x }.toMap val newBlk = defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 08660b27a8..54ff661dbd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -187,6 +187,9 @@ type TypeSymbol = ClassSymbol | TypeAliasSymbol type FieldSymbol = TermSymbol | MemberSymbol[?] +abstract class ClassLikeSymbol(using State) extends MemberSymbol[ClassDef | ModuleDef]: + override def subst(using sub: SymbolSubst): ClassLikeSymbol + /** This is the symbol associated to specific definitions. * One overloaded `BlockMemberSymbol` may correspond to multiple `InnerSymbol`s @@ -195,7 +198,7 @@ sealed trait InnerSymbol extends Symbol: def subst(using SymbolSubst): InnerSymbol class ClassSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) - extends MemberSymbol[ClassDef] with CtorSymbol with InnerSymbol: + extends ClassLikeSymbol with CtorSymbol with InnerSymbol: def nme = id.name def toLoc: Option[Loc] = id.toLoc // TODO track source tree of classe here override def toString: Str = s"class:$nme${State.dbgUid(uid)}" @@ -205,7 +208,7 @@ class ClassSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) override def subst(using sub: SymbolSubst): ClassSymbol = sub.mapClsSym(this) class ModuleSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) - extends MemberSymbol[ModuleDef] with CtorSymbol with InnerSymbol: + extends ClassLikeSymbol with CtorSymbol with InnerSymbol: def nme = id.name def toLoc: Option[Loc] = id.toLoc // TODO track source tree of module here override def toString: Str = s"module:${id.name}${State.dbgUid(uid)}" diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 7805f7c7fc..f942b6d530 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -230,7 +230,7 @@ fun f(perform) = handle h = Effect with fun perform(arg)(k) = arg perform() -f(() => 3) +f(() => 3) //│ = 3 abstract class Cell with From ac70aea1c49a355713c8e0bda2b1b4191a71acc9 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Tue, 14 Jan 2025 17:08:39 +0800 Subject: [PATCH 069/114] Address PR comments --- .../src/main/scala/hkmc2/codegen/Block.scala | 2 +- .../scala/hkmc2/codegen/HandlerLowering.scala | 70 +++++++++---------- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 798e197c07..0f35f69b2e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -355,7 +355,7 @@ case class End(msg: Str = "") extends BlockTail with ProductWithTail enum Case: case Lit(lit: Literal) - case Cls(cls: ClassLikeSymbol | ModuleSymbol, path: Path) + case Cls(cls: ClassLikeSymbol, path: Path) case Tup(len: Int, inf: Bool) lazy val freeVars: Set[Local] = this match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 5ffdd7c259..007b5f38c0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -152,14 +152,12 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): // for readability :) case class PartRet(head: Block, states: Ls[BlockState]) - /* - * returns (truncated input block, child block states) - * TODO: don't split within Match, Begin and Labels when not needed, ideally keep it intact. Need careful analysis for this - * blk: The block to transform - * labelIds: maps label IDs to the state at the start of the label and the state after the label - * jumpTo: what state End should jump to, if at all - * freshState: uid generator - */ + // * 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 + // * jumpTo: what state End should jump to, if at all + // * freshState: uid generator + // 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 = blk match case ResumptionPoint(result, uid, rest) => val PartRet(head, states) = go(rest) @@ -334,45 +332,45 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): l val toConvert = getBms.map(b => - val clsDefn = b.asCls - val modDefn = b.asMod - // check if this BlockMemberSymbol belongs to a definition in this block - val isThisBlock = clsDefn match - case None => modDefn match - case None => false + val clsDefn = b.asCls + val modDefn = b.asMod + // check if this BlockMemberSymbol belongs to a definition in this block + val isThisBlock = clsDefn match + case None => modDefn match + case None => false + case Some(value) => clsDefns.contains(value) case Some(value) => clsDefns.contains(value) - case Some(value) => clsDefns.contains(value) - if isThisBlock then Some(b) - else None - ).collect { case Some(b) => b } + if isThisBlock then Some(b) + else None + ).collect { case Some(b) => b } val fnBmsMap = funDefns.map(b => - b -> BlockMemberSymbol(b.nme + "$" + thirdPassFresh(), b.trees) - ).toMap + b -> BlockMemberSymbol(b.nme + "$" + thirdPassFresh(), b.trees) + ).toMap val clsBmsMap = toConvert.map(b => - b -> BlockMemberSymbol(b.nme + "$" + thirdPassFresh(), b.trees) - ).toMap + b -> BlockMemberSymbol(b.nme + "$" + thirdPassFresh(), b.trees) + ).toMap val bmsMap = (fnBmsMap ++ clsBmsMap).toMap val clsMap = clsBmsMap.map: - case b1 -> b2 => b1.asCls match - case Some(value) => - val newSym = ClassSymbol(value.tree, Tree.Ident(b2.nme)) - newSym.defn = value.defn - S(value -> newSym) - case None => None - .collect{ case Some(x) => x }.toMap + case b1 -> b2 => b1.asCls match + case Some(value) => + val newSym = ClassSymbol(value.tree, Tree.Ident(b2.nme)) + newSym.defn = value.defn + S(value -> newSym) + case None => None + .collect{ case Some(x) => x }.toMap val modMap = clsBmsMap.map: - case b1 -> b2 => b1.asMod match - case Some(value) => - val newSym = ModuleSymbol(value.tree, Tree.Ident(b2.nme)) - newSym.defn = value.defn - S(value -> newSym) - case None => None - .collect{ case Some(x) => x }.toMap + case b1 -> b2 => b1.asMod match + case Some(value) => + val newSym = ModuleSymbol(value.tree, Tree.Ident(b2.nme)) + newSym.defn = value.defn + S(value -> newSym) + case None => None + .collect{ case Some(x) => x }.toMap val newBlk = defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) From 051bc0909112672a29815f0972e2f97d4212bb86 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 14 Jan 2025 17:32:44 +0800 Subject: [PATCH 070/114] Fix inaccurate inheritance clause. --- hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 54ff661dbd..b8999de3f8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -187,8 +187,9 @@ type TypeSymbol = ClassSymbol | TypeAliasSymbol type FieldSymbol = TermSymbol | MemberSymbol[?] -abstract class ClassLikeSymbol(using State) extends MemberSymbol[ClassDef | ModuleDef]: - override def subst(using sub: SymbolSubst): ClassLikeSymbol +sealed trait ClassLikeSymbol extends Symbol: + self: MemberSymbol[? <: ClassDef | ModuleDef] => + def subst(using sub: SymbolSubst): ClassLikeSymbol /** This is the symbol associated to specific definitions. @@ -198,7 +199,7 @@ sealed trait InnerSymbol extends Symbol: def subst(using SymbolSubst): InnerSymbol class ClassSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) - extends ClassLikeSymbol with CtorSymbol with InnerSymbol: + extends MemberSymbol[ClassDef] with ClassLikeSymbol with CtorSymbol with InnerSymbol: def nme = id.name def toLoc: Option[Loc] = id.toLoc // TODO track source tree of classe here override def toString: Str = s"class:$nme${State.dbgUid(uid)}" @@ -208,7 +209,7 @@ class ClassSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) override def subst(using sub: SymbolSubst): ClassSymbol = sub.mapClsSym(this) class ModuleSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) - extends ClassLikeSymbol with CtorSymbol with InnerSymbol: + extends MemberSymbol[ModuleDef] with ClassLikeSymbol with CtorSymbol with InnerSymbol: def nme = id.name def toLoc: Option[Loc] = id.toLoc // TODO track source tree of module here override def toString: Str = s"module:${id.name}${State.dbgUid(uid)}" From e4f492206ee5314ab45d1f1bce7a2d359d61fee5 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Tue, 14 Jan 2025 19:07:19 +0800 Subject: [PATCH 071/114] style change --- .../scala/hkmc2/codegen/HandlerLowering.scala | 103 ++++++++++-------- .../src/test/mlscript/handlers/Effects.mls | 4 - 2 files changed, 57 insertions(+), 50 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 007b5f38c0..26007f64fa 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -64,9 +64,9 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): private val functionHandlerCtx = HandlerCtx(true, false, state => val tmp = freshTmp() blockBuilder - .assignFieldN(state.res.tail, nextIdent, Instantiate(state.cls.selN(Tree.Ident("class")), Value.Lit(Tree.IntLit(state.uid)) :: Nil)) - .assignFieldN(state.res, tailIdent, state.res.tail.next) - .ret(state.res) + .assignFieldN(state.res.tail, nextIdent, Instantiate(state.cls.selN(Tree.Ident("class")), Value.Lit(Tree.IntLit(state.uid)) :: Nil)) + .assignFieldN(state.res, tailIdent, state.res.tail.next) + .ret(state.res) ) private def handlerCtx(using HandlerCtx): HandlerCtx = summon private val effectSigPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__EffectSig")).selN(Tree.Ident("class")) @@ -87,10 +87,12 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): object SimpleCall: def apply(fun: Path, args: List[Path]) = Call(fun, args.map(Arg(false, _)))(true) def unapply(res: Result) = res match - case Call(fun, args) => args.foldRight[Opt[List[Path]]](S(Nil))((arg, acc) => acc.flatMap(acc => arg match - case Arg(false, p) => S(p :: acc) - case _ => N - )).map((fun, _)) + case Call(fun, args) => args.foldRight[Opt[List[Path]]](S(Nil)): (arg, acc) => + acc.flatMap: acc => + arg match + case Arg(false, p) => S(p :: acc) + case _ => N + .map((fun, _)) case _ => N object ResumptionPoint: @@ -292,13 +294,12 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case v => v } match { case Return(c: Call, implct) if handlerCtx.isHandleFree => Return(c, implct) - case b => b.mapResult { + case b => b.mapResult: case r @ Call(Value.Ref(_: BuiltinSymbol), _) => N case c: Call => val res = freshTmp("res") S(k => CallPlaceholder(res, freshId(), false, c, k(Value.Ref(res)))) case r => N - } } match case Define(f: FunDefn, rst) => Define(translateFun(f), rst) case Define(c: ClsLikeDefn, rst) => Define(translateCls(c), rst) @@ -331,7 +332,8 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): b.mapSyms l - val toConvert = getBms.map(b => + val toConvert = getBms + .map: b => val clsDefn = b.asCls val modDefn = b.asMod // check if this BlockMemberSymbol belongs to a definition in this block @@ -342,35 +344,44 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case Some(value) => clsDefns.contains(value) if isThisBlock then Some(b) else None - ).collect { case Some(b) => b } + .collect: + case Some(b) => b - val fnBmsMap = funDefns.map(b => + val fnBmsMap = funDefns + .map: b => b -> BlockMemberSymbol(b.nme + "$" + thirdPassFresh(), b.trees) - ).toMap + .toMap - val clsBmsMap = toConvert.map(b => + val clsBmsMap = toConvert + .map: b => b -> BlockMemberSymbol(b.nme + "$" + thirdPassFresh(), b.trees) - ).toMap + .toMap val bmsMap = (fnBmsMap ++ clsBmsMap).toMap - val clsMap = clsBmsMap.map: + val clsMap = clsBmsMap + .map: case b1 -> b2 => b1.asCls match case Some(value) => val newSym = ClassSymbol(value.tree, Tree.Ident(b2.nme)) newSym.defn = value.defn S(value -> newSym) case None => None - .collect{ case Some(x) => x }.toMap + .collect: + case Some(x) => x + .toMap - val modMap = clsBmsMap.map: + val modMap = clsBmsMap + .map: case b1 -> b2 => b1.asMod match case Some(value) => val newSym = ModuleSymbol(value.tree, Tree.Ident(b2.nme)) newSym.defn = value.defn S(value -> newSym) case None => None - .collect{ case Some(x) => x }.toMap + .collect: + case Some(x) => x + .toMap val newBlk = defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) @@ -417,10 +428,10 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): // .assignFieldN(state.res, tailIdent, state.res.tail.next) .ret(SimpleCall(handleBlockImplPath, state.res :: h.lhs.asPath :: Nil)))) - val handlers = h.handlers.map(handler => + val handlers = h.handlers.map: handler => val lam = Value.Lam(PlainParamList(Param(FldFlags.empty, handler.resumeSym, N) :: Nil), translateBlock(handler.body, functionHandlerCtx)) val tmp = freshTmp() - FunDefn(handler.sym, handler.params, Return(SimpleCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false))) + FunDefn(handler.sym, handler.params, Return(SimpleCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false)) // TODO: it seems that our current syntax didn't know how to call super, calling it with empty param list now val clsDefn = ClsLikeDefn(h.cls, syntax.Cls, S(h.par), handlers, Nil, Nil, @@ -447,19 +458,19 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case CallPlaceholder(res, uid, canRet, c, rest) => trivial = false blockBuilder - .assign(res, c) - .ifthen( - res.asPath, - Case.Cls(effectSigSym, effectSigPath), - ReturnCont(res, uid) - ) - .chain(ResumptionPoint(res, uid, _)) - .staticif(canRet, _.ifthen( - res.asPath, - Case.Cls(retClsSym, retClsPath), - blockBuilder.ret(if handlerCtx.isHandleFree then res.asPath.value else res.asPath) - )) - .rest(rest) + .assign(res, c) + .ifthen( + res.asPath, + Case.Cls(effectSigSym, effectSigPath), + ReturnCont(res, uid) + ) + .chain(ResumptionPoint(res, uid, _)) + .staticif(canRet, _.ifthen( + res.asPath, + Case.Cls(retClsSym, retClsPath), + blockBuilder.ret(if handlerCtx.isHandleFree then res.asPath.value else res.asPath) + )) + .rest(rest) case b => b val actualBlock = prepareBlock(b) if trivial then return N @@ -531,18 +542,18 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): b.map(genNormalBody(_, clsSym)) match case CallPlaceholder(res, uid, canRet, c, rest) => blockBuilder - .assign(res, c) - .ifthen( - res.asPath, - Case.Cls(effectSigSym, effectSigPath), - handlerCtx.linkAndHandle(LinkState(res.asPath, clsSym.asPath, uid)) - ) - .staticif(canRet && !handlerCtx.isTopLevel, _.ifthen( - res.asPath, - Case.Cls(retClsSym, retClsPath), - blockBuilder.ret(if handlerCtx.isHandleFree then res.asPath.value else res.asPath) - )) - .rest(rest) + .assign(res, c) + .ifthen( + res.asPath, + Case.Cls(effectSigSym, effectSigPath), + handlerCtx.linkAndHandle(LinkState(res.asPath, clsSym.asPath, uid)) + ) + .staticif(canRet && !handlerCtx.isTopLevel, _.ifthen( + res.asPath, + Case.Cls(retClsSym, retClsPath), + blockBuilder.ret(if handlerCtx.isHandleFree then res.asPath.value else res.asPath) + )) + .rest(rest) case b => b def translateTopLevel(b: Block): Block = diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index f942b6d530..7afff945c7 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -308,7 +308,6 @@ fun sum(x) = sum(10000) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded -:handler :expect 2 fun foo(h) = h.perform() @@ -323,7 +322,6 @@ handle h = Eff with foo(h) //│ = 2 -:handler :expect 3 fun foo(h) = h.perform() @@ -338,7 +336,6 @@ handle h = Eff with foo(h) //│ = 3 -:handler :expect 123 fun foo(h) = h.perform() @@ -365,7 +362,6 @@ handle h = Eff with foo(h) //│ = 123 -:handler :expect 123 fun foo(h) = h.perform() From b89eb90d56a26256b2845c138454611ae755ebb2 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Tue, 14 Jan 2025 19:29:05 +0800 Subject: [PATCH 072/114] revert whitespace change --- hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index b8999de3f8..f17e290e49 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -47,7 +47,7 @@ abstract class Symbol(using State) extends Located: def asClsLike: Opt[ClassSymbol | ModuleSymbol | PatternSymbol] = (asCls: Opt[ClassSymbol | ModuleSymbol | PatternSymbol]) orElse asMod orElse asPat def asTpe: Opt[TypeSymbol] = asCls orElse asAls - + override def equals(x: Any): Bool = x match case that: Symbol => uid === that.uid case _ => false From 2b3e83e9a2fa81e6bae56ebcd7931722cc52be77 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 16 Jan 2025 11:52:35 +0800 Subject: [PATCH 073/114] Eliminate many tail calls to Lowering#term to reduce stack usage --- .../test/scala/hkmc2/JSBackendDiffMaker.scala | 6 +- .../src/main/scala/hkmc2/MLsCompiler.scala | 2 +- .../main/scala/hkmc2/codegen/Lowering.scala | 132 ++++++++++-------- .../src/test/mlscript/codegen/SetIn.mls | 27 ++-- 4 files changed, 88 insertions(+), 79 deletions(-) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala index 9a9ad17f64..1dd18bf467 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -61,10 +61,9 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: case d => outerRaise(d) given Elaborator.Ctx = curCtx val low = ltl.givenIn: - new codegen.Lowering + new codegen.Lowering(lowerHandlers = handler.isSet) with codegen.LoweringSelSanityChecks(instrument = false) with codegen.LoweringTraceLog(instrument = false) - with codegen.LoweringHandler(handler.isSet) val jsb = new JSBuilder with JSBuilderArgNumSanityChecks(instrument = false) val le = low.program(blk) @@ -77,10 +76,9 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: if js.isSet && !showingJSYieldedCompileError then given Elaborator.Ctx = curCtx val low = ltl.givenIn: - new codegen.Lowering + new codegen.Lowering(lowerHandlers = handler.isSet) with codegen.LoweringSelSanityChecks(noSanityCheck.isUnset) with codegen.LoweringTraceLog(traceJS.isSet) - with codegen.LoweringHandler(handler.isSet) val jsb = new JSBuilder with JSBuilderArgNumSanityChecks(noSanityCheck.isUnset) val le = low.program(blk) diff --git a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala index 5e07decb64..7edfa7485c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala @@ -69,7 +69,7 @@ class MLsCompiler(preludeFile: os.Path): val (blk, newCtx) = elab.importFrom(mainParse.resultBlk) val low = ltl.givenIn: - codegen.Lowering() + codegen.Lowering(lowerHandlers = false) val jsb = codegen.js.JSBuilder() val le = low.program(blk) val baseScp: utils.Scope = diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index bdb271e5f3..a1bcdfada2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1,18 +1,20 @@ package hkmc2 package codegen +import scala.annotation.tailrec + import mlscript.utils.*, shorthands.* import utils.* import hkmc2.Message.MessageContext +import semantics.* import hkmc2.{semantics => sem} -import hkmc2.semantics.{Term => st} -import semantics.Elaborator.State +import semantics.{Term => st} +import semantics.Term.{Throw => _, *} +import semantics.Elaborator.{State, Ctx} import syntax.{Literal, Tree} -import semantics.* -import semantics.Term.{Throw => _, *} abstract class TailOp extends (Result => Block) @@ -46,11 +48,16 @@ end Subst import Subst.subst -class Lowering(using TL, Raise, Elaborator.State): +class Lowering(lowerHandlers: Bool)(using TL, Raise, State, Ctx): def returnedTerm(t: st)(using Subst): Block = term(t)(Ret) - def term(t: st, inStmtPos: Bool = false)(k: Result => Block)(using Subst): Block = + // * Used to work around Scala's @tailrec annotation for those few calls that are not in tail position. + final def term_nonTail(t: st, inStmtPos: Bool = false)(k: Result => Block)(using Subst): Block = + term(t: st, inStmtPos: Bool)(k) + + @tailrec + final def term(t: st, inStmtPos: Bool = false)(k: Result => Block)(using Subst): Block = tl.log(s"Lowering.term ${t.showDbg.truncate(100, "[...]")}${ if inStmtPos then " (in stmt)" else ""}${ t.symbol.fold("")(" " + _)}") @@ -70,9 +77,9 @@ class Lowering(using TL, Raise, Elaborator.State): case st.Tup(fs) => fs.foldRight[Ls[Arg] => Block](args => k(Value.Arr(args.reverse))){ case (a: Fld, acc) => - args => subTerm(a.term)(r => acc(Arg(false, r) :: args)) + args => subTerm_nonTail(a.term)(r => acc(Arg(false, r) :: args)) case (s: Spd, acc) => - args => subTerm(s.term)(r => acc(Arg(true, r) :: args)) + args => subTerm_nonTail(s.term)(r => acc(Arg(true, r) :: args)) }(Nil) case st.Ref(sym) => sym match @@ -109,7 +116,7 @@ class Lowering(using TL, Raise, Elaborator.State): def rec(as: Ls[Bool -> st], asr: Ls[Arg]): Block = as match case Nil => k(Call(fr, asr.reverse)(isMlsFun)) case (spd, a) :: as => - subTerm(a): ar => + subTerm_nonTail(a): ar => rec(as, Arg(spd, ar) :: asr) rec(as, Nil) case _ => @@ -126,9 +133,32 @@ class Lowering(using TL, Raise, Elaborator.State): subTerm(prefix): p => conclude(Select(p, nme)(sel.sym)) case _ => subTerm(f)(conclude) + + case st.Blk((h @ Handle(lhs, rhs, cls, defs)) :: stmts, res) => + if !lowerHandlers then + raise(ErrorReport( + msg"Effect handlers are not enabled" -> + h.toLoc :: Nil, + source = Diagnostic.Source.Compilation)) + return End("error") + val handlers = defs.map { + case HandlerTermDefinition(resumeSym, td) => td.body match + case None => + raise(ErrorReport(msg"Handler function definitions cannot be empty" -> td.toLoc :: Nil)) + N + case Some(bod) => + val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme)) + S(Handler(td.sym, resumeSym, paramLists, bodyBlock)) + }.collect{ case Some(v) => v } + val resSym = TempSymbol(S(t)) + subTerm(rhs): par => + HandleBlock(lhs, resSym, par, cls, handlers, + term_nonTail(st.Blk(stmts, res))(HandleBlockReturn(_)), + k(Value.Ref(resSym))) + case st.Blk(Nil, res) => term(res)(k) case st.Blk((t: sem.Term) :: stats, res) => - subTerm(t, inStmtPos = true)(r => term(st.Blk(stats, res))(k)) + subTerm(t, inStmtPos = true)(r => term_nonTail(st.Blk(stats, res))(k)) case st.Blk((d: Declaration) :: stats, res) => d match case td: TermDefinition => @@ -140,15 +170,15 @@ class Lowering(using TL, Raise, Elaborator.State): td.k match case knd: syntax.Val => assert(td.params.isEmpty) - subTerm(bod)(r => + subTerm_nonTail(bod)(r => // Assign(td.sym, r, // term(st.Blk(stats, res))(k))) Define(ValDefn(td.owner, knd, td.sym, r), - term(st.Blk(stats, res))(k))) + term_nonTail(st.Blk(stats, res))(k))) case syntax.Fun => val (paramLists, bodyBlock) = setupFunctionOrByNameDef(td.params, bod, S(td.sym.nme)) Define(FunDefn(td.sym, paramLists, bodyBlock), - term(st.Blk(stats, res))(k)) + term_nonTail(st.Blk(stats, res))(k)) case cls: ClassLikeDef => reportAnnotations(cls, cls.annotations) val bodBlk = cls.body.blk @@ -171,13 +201,13 @@ class Lowering(using TL, Raise, Elaborator.State): privateFlds, publicFlds, End(), - term(Blk(rest2, bodBlk.res))(ImplctRet) + term_nonTail(Blk(rest2, bodBlk.res))(ImplctRet) // * This is just a minor improvement to get `constructor() {}` instead of `constructor() { null }` .mapTail: case Return(Value.Lit(syntax.Tree.UnitLit(true)), true) => End() case t => t ), - term(st.Blk(stats, res))(k)) + term_nonTail(st.Blk(stats, res))(k)) case _ => // TODO handle term(st.Blk(stats, res))(k) @@ -186,7 +216,7 @@ class Lowering(using TL, Raise, Elaborator.State): term(st.Blk(stats, res))(k) case st.Blk((DefineVar(sym, rhs)) :: stats, res) => subTerm(rhs): r => - Assign(sym, r, term(st.Blk(stats, res))(k)) + Assign(sym, r, term_nonTail(st.Blk(stats, res))(k)) case Assgn(lhs, rhs) => lhs match case Ref(sym: LocalSymbol) => @@ -194,11 +224,11 @@ class Lowering(using TL, Raise, Elaborator.State): Assign(sym, r, k(Value.Lit(syntax.Tree.UnitLit(true)))) case sel @ SynthSel(prefix, nme) => subTerm(prefix): p => - subTerm(rhs): r => + subTerm_nonTail(rhs): r => AssignField(p, nme, r, k(Value.Lit(syntax.Tree.UnitLit(true))))(sel.sym) case sel @ Sel(prefix, nme) => subTerm(prefix): p => - subTerm(rhs): r => + subTerm_nonTail(rhs): r => AssignField(p, nme, r, k(Value.Lit(syntax.Tree.UnitLit(true))))(sel.sym) case st.Blk((imp @ Import(sym, path)) :: stats, res) => @@ -272,7 +302,7 @@ class Lowering(using TL, Raise, Elaborator.State): def go(split: Split, topLevel: Bool)(using Subst): Block = split match case Split.Let(sym, trm, tl) => - term(trm): r => + term_nonTail(trm): r => Assign(sym, r, go(tl, topLevel)) case Split.Cons(Branch(scrut, pat, tail), restSplit) => subTerm(scrut): sr => @@ -285,7 +315,7 @@ class Lowering(using TL, Raise, Elaborator.State): pat match case Pattern.Lit(lit) => mkMatch(Case.Lit(lit) -> go(tail, topLevel = false)) case Pattern.ClassLike(cls, trm, args0, _refined) => - subTerm(trm): st => + subTerm_nonTail(trm): st => val args = args0.getOrElse(Nil) val clsParams = cls match case cls: ClassSymbol => cls.tree.clsParams @@ -300,9 +330,9 @@ class Lowering(using TL, Raise, Elaborator.State): mkMatch(mkArgs(clsParams.zip(args))) case Pattern.Tuple(len, inf) => mkMatch(Case.Tup(len, inf) -> go(tail, topLevel = false)) case Split.Else(els) => - if k.isInstanceOf[TailOp] && isIf then term(els)(k) + if k.isInstanceOf[TailOp] && isIf then term_nonTail(els)(k) else - term(els): r => + term_nonTail(els): r => Assign(l, r, if isWhile && !topLevel then Continue(lbl) else End() @@ -335,35 +365,31 @@ class Lowering(using TL, Raise, Elaborator.State): def rec(as: Ls[st], asr: Ls[Path]): Block = as match case Nil => k(Instantiate(sr, asr.reverse)) case a :: as => - subTerm(a): ar => + subTerm_nonTail(a): ar => rec(as, ar :: asr) rec(as, Nil) case Try(sub, finallyDo) => val l = new TempSymbol(S(sub)) TryBlock( - term(sub)(p => Assign(l, p, End())), - term(finallyDo)(_ => End()), + subTerm_nonTail(sub)(p => Assign(l, p, End())), + subTerm_nonTail(finallyDo)(_ => End()), k(Value.Ref(l)) ) - - case Handle(lhs, rhs, clsSym, defs) => - raise(ErrorReport( - msg"Effect handlers are not enabled" -> - t.toLoc :: Nil, - source = Diagnostic.Source.Compilation)) - End("error") + + case Handle(lhs, rhs, clsSym, defs) => die // FIXME // * BbML-specific cases: t.Cls#field and mutable operations case sp @ SelProj(prefix, _, proj) => setupSelection(prefix, proj, sp.sym)(k) case Region(reg, body) => - Assign(reg, Instantiate(Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Region"))(N), Nil), term(body)(k)) + Assign(reg, Instantiate(Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Region"))(N), Nil), + term_nonTail(body)(k)) case RegRef(reg, value) => def rec(as: Ls[st], asr: Ls[Path]): Block = as match case Nil => k(Instantiate(Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Ref"))(N), asr.reverse)) case a :: as => - subTerm(a): ar => + subTerm_nonTail(a): ar => rec(as, ar :: asr) rec(reg :: value :: Nil, Nil) case Deref(ref) => @@ -371,7 +397,7 @@ class Lowering(using TL, Raise, Elaborator.State): k(Select(r, Tree.Ident("value"))(N)) case SetRef(lhs, rhs) => subTerm(lhs): ref => - subTerm(rhs): value => + subTerm_nonTail(rhs): value => AssignField(ref, Tree.Ident("value"), value, k(value))(N) case Annotated(prefix, receiver) => @@ -385,7 +411,10 @@ class Lowering(using TL, Raise, Elaborator.State): // case _ => // subTerm(t)(k) - def subTerm(t: st, inStmtPos: Bool = false)(k: Path => Block)(using Subst): Block = + def subTerm_nonTail(t: st, inStmtPos: Bool = false)(k: Path => Block)(using Subst): Block = + subTerm(t: st, inStmtPos: Bool)(k) + + inline def subTerm(t: st, inStmtPos: Bool = false)(k: Path => Block)(using Subst): Block = term(t, inStmtPos = inStmtPos): case v: Value => k(v) case p: Path => k(p) @@ -399,7 +428,9 @@ class Lowering(using TL, Raise, Elaborator.State): // subTerm(t)(r => codegen.Assign(resSym, r, codegen.End()))(using Subst.empty) def topLevel(t: st): Block = - term(t)(ImplctRet)(using Subst.empty) + val res = term(t)(ImplctRet)(using Subst.empty) + if lowerHandlers then HandlerLowering().translateTopLevel(res) + else res def program(main: st): Program = def go(acc: Ls[Local -> Str], trm: st): Program = @@ -434,7 +465,7 @@ class Lowering(using TL, Raise, Elaborator.State): trait LoweringSelSanityChecks - (instrument: Bool)(using TL, Raise, Elaborator.State) + (instrument: Bool)(using TL, Raise, State) extends Lowering: override def setupSelection(prefix: st, nme: Tree.Ident, sym: Opt[FieldSymbol])(k: Result => Block)(using Subst): Block = @@ -460,7 +491,7 @@ trait LoweringSelSanityChecks trait LoweringTraceLog - (instrument: Bool)(using TL, Raise, Elaborator.State) + (instrument: Bool)(using TL, Raise, State) extends Lowering: private def selFromGlobalThis(path: Str*): Path = @@ -540,26 +571,3 @@ trait LoweringTraceLog ) -trait LoweringHandler - (instrument: Bool)(using TL, Raise, Elaborator.State, Elaborator.Ctx) - extends Lowering: - override def term(t: st, inStmtPos: Bool)(k: Result => Block)(using Subst): Block = - if !instrument then return super.term(t, inStmtPos = inStmtPos)(k) - t match - case st.Blk(Handle(lhs, rhs, cls, defs) :: stmts, res) => - val handlers = defs.map { - case HandlerTermDefinition(resumeSym, td) => td.body match - case None => - raise(ErrorReport(msg"Handler function definitions cannot be empty" -> td.toLoc :: Nil)) - N - case Some(bod) => - val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme)) - S(Handler(td.sym, resumeSym, paramLists, bodyBlock)) - }.collect{ case Some(v) => v } - val resSym = TempSymbol(S(t)) - subTerm(rhs): par => - HandleBlock(lhs, resSym, par, cls, handlers, term(st.Blk(stmts, res))(HandleBlockReturn(_)), k(Value.Ref(resSym))) - case _ => super.term(t, inStmtPos = inStmtPos)(k) - override def topLevel(t: st): Block = - if !instrument then return super.topLevel(t) - HandlerLowering().translateTopLevel(super.topLevel(t)) diff --git a/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls b/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls index 67c5f7e12f..477c0508e8 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls @@ -33,12 +33,13 @@ x :sjs set x += 1 in log(x) //│ JS (unsanitized): -//│ let old, tmp, tmp1; +//│ let old, tmp, tmp1, tmp2; //│ old = this.x; //│ try { //│ tmp1 = this.x + 1; //│ this.x = tmp1; -//│ tmp = this.log(this.x); +//│ tmp2 = this.log(this.x); +//│ tmp = tmp2; //│ } finally { //│ this.x = old; //│ } @@ -79,7 +80,7 @@ fun example() = example() //│ JS (unsanitized): //│ function example() { -//│ let x, get_x, old, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; +//│ let x, get_x, old, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; //│ x = 0; //│ get_x = () => { //│ return x; @@ -91,13 +92,14 @@ example() //│ tmp2 = globalThis.log(x); //│ tmp3 = (tmp2 , globalThis.log); //│ tmp4 = get_x() ?? null; -//│ tmp = tmp3(tmp4) ?? null; +//│ tmp5 = tmp3(tmp4) ?? null; +//│ tmp = tmp5; //│ } finally { //│ x = old; //│ } -//│ tmp5 = globalThis.log(x); -//│ tmp6 = get_x() ?? null; -//│ return globalThis.log(tmp6); +//│ tmp6 = globalThis.log(x); +//│ tmp7 = get_x() ?? null; +//│ return globalThis.log(tmp7); //│ } //│ this.example() //│ > 1 @@ -117,7 +119,7 @@ fun example() = example() //│ JS (unsanitized): //│ function example() { -//│ let x, get_x, y, old, tmp, tmp1, tmp2, tmp3, tmp4, tmp5; +//│ let x, get_x, y, old, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; //│ x = 0; //│ get_x = () => { //│ return x; @@ -127,14 +129,15 @@ example() //│ tmp1 = x + 1; //│ x = tmp1; //│ tmp2 = globalThis.log(x); -//│ tmp = (tmp2 , x); +//│ tmp3 = (tmp2 , x); +//│ tmp = tmp3; //│ } finally { //│ x = old; //│ } //│ y = tmp; -//│ tmp3 = globalThis.log(x); -//│ tmp4 = get_x() ?? null; -//│ tmp5 = globalThis.log(tmp4); +//│ tmp4 = globalThis.log(x); +//│ tmp5 = get_x() ?? null; +//│ tmp6 = globalThis.log(tmp5); //│ return y; //│ } //│ this.example() From e901993ddecf16384bb850f6917fe4092d1b8c0f Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 16 Jan 2025 12:02:15 +0800 Subject: [PATCH 074/114] Make Handle a statement, as it should be --- .../main/scala/hkmc2/codegen/Lowering.scala | 2 -- .../scala/hkmc2/semantics/Elaborator.scala | 18 +++++++++--------- .../src/main/scala/hkmc2/semantics/Term.scala | 4 +++- .../main/scala/hkmc2/syntax/ParseRule.scala | 2 +- .../src/main/scala/hkmc2/syntax/Tree.scala | 6 +++--- .../src/test/mlscript/parser/Handler.mls | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index a1bcdfada2..bb08bf6f6a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -377,8 +377,6 @@ class Lowering(lowerHandlers: Bool)(using TL, Raise, State, Ctx): k(Value.Ref(l)) ) - case Handle(lhs, rhs, clsSym, defs) => die // FIXME - // * BbML-specific cases: t.Cls#field and mutable operations case sp @ SelProj(prefix, _, proj) => setupSelection(prefix, proj, sp.sym)(k) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index c9748b4cb7..43ed05fab2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -206,16 +206,16 @@ extends Importer: term(bod), ), Term.Assgn(lt, sym.ref(id)))) case _ => ??? // TODO error - case Handle(id, cls, blk, S(bod)) => - term(Block(Handle(id, cls, blk, N) :: bod :: Nil)) - case Handle(id: Ident, cls: Ident, Block(sts), N) => + case Hndl(id, cls, blk, S(bod)) => + term(Block(Hndl(id, cls, blk, N) :: bod :: Nil)) + case Hndl(id: Ident, cls: Ident, Block(sts), N) => raise(ErrorReport( msg"Expected a body for handle bindings in expression position" -> tree.toLoc :: Nil)) - block(Handle(id, cls, Block(sts), N) :: Nil)._1 + block(Hndl(id, cls, Block(sts), N) :: Nil)._1 - case h: Handle => + case h: Hndl => raise(ErrorReport( msg"Unsupported handle binding shape" -> h.toLoc :: Nil)) @@ -653,7 +653,7 @@ extends Importer: case (tree @ LetLike(`let`, lhs, S(rhs), N)) :: sts => raise(ErrorReport(msg"Unsupported let binding shape" -> tree.toLoc :: Nil)) go(sts, Nil, Term.Error :: acc) - case (hd @ Handle(id: Ident, cls: Ident, Block(sts_), N)) :: sts => + case (hd @ Hndl(id: Ident, cls: Ident, Block(sts_), N)) :: sts => reportUnusedAnnotations val res: Term.Blk = ctx.nest(N).givenIn: val sym = fieldOrVarSym(HandlerBind, id) @@ -684,13 +684,13 @@ extends Importer: raise(ErrorReport(msg"Only function definitions are allowed in handler blocks" -> st.toLoc :: Nil)) None }.collect { case Some(x) => x } - - val newAcc = Term.Handle(sym, term(cls), derivedClsSym, tds) :: acc + + val newAcc = Handle(sym, term(cls), derivedClsSym, tds) :: acc val newCtx = ctx + (id.name -> sym) val body = block(sts)(using newCtx)._1 Term.Blk(newAcc.reverse, body) (res, ctx) - case (tree @ Handle(_, _, _, N)) :: sts => + case (tree @ Hndl(_, _, _, N)) :: sts => raise(ErrorReport(msg"Unsupported handle binding shape" -> tree.toLoc :: Nil)) go(sts, Nil, Term.Error :: acc) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index ea9eadec6b..8837368560 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -39,7 +39,6 @@ enum Term extends Statement: case Ret(result: Term) case Throw(result: Term) case Try(body: Term, finallyDo: Term) - case Handle(lhs: LocalSymbol, rhs: Term, derivedClsSym: ClassSymbol, defs: Ls[HandlerTermDefinition]) case Annotated(annotation: Term, target: Term) lazy val symbol: Opt[Symbol] = this match @@ -245,6 +244,9 @@ case class ObjBody(blk: Term.Blk): case class Import(sym: MemberSymbol[?], file: Str) extends Statement +case class Handle(lhs: LocalSymbol, rhs: Term, derivedClsSym: ClassSymbol, defs: Ls[HandlerTermDefinition]) + extends Statement + sealed abstract class Declaration: val sym: Symbol diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala index cbb73aa0c4..4518605f93 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala @@ -227,7 +227,7 @@ class ParseRules(using State): End(None) ) ) { case (rhs, (S(defs), body)) => (rhs, defs, body) } - ) { case (lhs, (rhs, defs, body))=> Handle(lhs, rhs, defs, body) } + ) { case (lhs, (rhs, defs, body))=> Hndl(lhs, rhs, defs, body) } , Kw(`new`): ParseRule("`new` keyword")( diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 821312b8b1..f3d519e2d7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -52,7 +52,7 @@ enum Tree extends AutoLocated: case Block(stmts: Ls[Tree])(using State) extends Tree with semantics.BlockImpl case OpBlock(items: Ls[Tree -> Tree]) case LetLike(kw: Keyword.letLike, lhs: Tree, rhs: Opt[Tree], body: Opt[Tree]) - case Handle(lhs: Tree, cls: Tree, defs: Tree, body: Opt[Tree]) + case Hndl(lhs: Tree, cls: Tree, defs: Tree, body: Opt[Tree]) case Def(lhs: Tree, rhs: Tree) case TermDef(k: TermDefKind, head: Tree, rhs: Opt[Tree]) extends Tree with TermDefImpl case TypeDef(k: TypeDefKind, head: Tree, extension: Opt[Tree], body: Opt[Tree])(using State) extends Tree with TypeDefImpl @@ -87,7 +87,7 @@ enum Tree extends AutoLocated: case OpBlock(items) => items.flatMap: case (op, body) => op :: body :: Nil case LetLike(kw, lhs, rhs, body) => lhs :: Nil ++ rhs ++ body - case Handle(lhs, rhs, defs, body) => body match + case Hndl(lhs, rhs, defs, body) => body match case Some(value) => lhs :: rhs :: defs :: value :: Nil case None => lhs :: rhs :: defs :: Nil case TypeDef(k, head, extension, body) => @@ -148,7 +148,7 @@ enum Tree extends AutoLocated: case Region(name, body) => "region" case RegRef(reg, value) => "region reference" case Effectful(eff, body) => "effectful" - case Handle(_, _, _, _) => "handle" + case Hndl(_, _, _, _) => "handle" case Def(lhs, rhs) => "defining assignment" case Spread(_, _, _) => "spread" case Annotated(_, _) => "annotated" diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index d2d38757f3..cbddb6aac7 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -24,7 +24,7 @@ handle h = Eff with fun g(a)(r) = r(1) //│ Parsed: //│ TermDef(Fun,App(Ident(foo),Tup(List(Ident(x)))),Some(IntLit(1))) -//│ Handle(Ident(h),Ident(Eff),Block(List(TermDef(Fun,App(App(Ident(f),Tup(List())),Tup(List(Ident(r)))),Some(App(Ident(r),Tup(List(IntLit(0)))))), TermDef(Fun,App(App(Ident(g),Tup(List(Ident(a)))),Tup(List(Ident(r)))),Some(App(Ident(r),Tup(List(IntLit(1)))))))),None) +//│ Hndl(Ident(h),Ident(Eff),Block(List(TermDef(Fun,App(App(Ident(f),Tup(List())),Tup(List(Ident(r)))),Some(App(Ident(r),Tup(List(IntLit(0)))))), TermDef(Fun,App(App(Ident(g),Tup(List(Ident(a)))),Tup(List(Ident(r)))),Some(App(Ident(r),Tup(List(IntLit(1)))))))),None) :e From e8c690f680fdaf1ed19415558869a2ef963018df Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 16 Jan 2025 16:34:30 +0800 Subject: [PATCH 075/114] preserve tail-calls --- .../hkmc2/codegen/StackSafeTransform.scala | 74 ++-- .../src/test/mlscript-compile/Predef.mjs | 1 + .../src/test/mlscript-compile/Predef.mls | 3 +- .../src/test/mlscript/handlers/Effects.mls | 14 +- .../mlscript/handlers/RecursiveHandlers.mls | 46 +- .../test/mlscript/handlers/StackSafety.mls | 409 ++++++++++++++++++ .../src/test/mlscript/parser/Handler.mls | 6 +- 7 files changed, 492 insertions(+), 61 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 454ea2e695..4e5365b5d3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -17,10 +17,12 @@ class StackSafeTransform(depthLimit: Int)(using State): def asArg = Arg(false, p) private val STACK_DEPTH_IDENT: Tree.Ident = Tree.Ident("__stackDepth") + private val STACK_OFFSET_IDENT: Tree.Ident = Tree.Ident("__stackOffset") private val STACK_HANDLER_IDENT: Tree.Ident = Tree.Ident("__stackHandler") private val stackDelayClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__StackDelay")).selN(Tree.Ident("class")) private val stackDepthPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(STACK_DEPTH_IDENT) + private val stackOffsetPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(STACK_OFFSET_IDENT) private val stackHandlerPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(STACK_HANDLER_IDENT) private val predefPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")) @@ -51,39 +53,40 @@ class StackSafeTransform(depthLimit: Int)(using State): // Increases the stack depth, assigns the call to a value, then decreases the stack depth // then binds that value to a desired block - def extractRes(res: Result, f: Result => Block) = + def extractRes(res: Result, decrement: Bool, f: Result => Block) = res match - case c: Call if c.isMlsFun => + case Call(Value.Ref(s: BuiltinSymbol), args) => f(res) + case Call(path, args) => val tmp = TempSymbol(None, "tmp") - val depthPositive = TempSymbol(None, "depthPositive") - blockBuilder + val inc = blockBuilder .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) - .assign(tmp, res) - .assign(depthPositive, op(">", stackDepthPath, intLit(0))) - .ifthen( // only reduce stack depth if it's positive, since the stack depth is reset after the stack is unwound - depthPositive.asPath, Case.Lit(Tree.BoolLit(true)), - blockBuilder - .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("-", stackDepthPath, intLit(1))) - .end() - ) - .rest(f(tmp.asPath)) + + if decrement then + inc + .assign(tmp, res) + .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("-", stackDepthPath, intLit(1))) + .rest(f(tmp.asPath)) + else + inc.ret(res) case _ => f(res) // Rewrites anything that can contain a Call to increase the stack depth def transform(b: Block): Block = b match - case Return(res, implct) => extractRes(res, Return(_, false)) - case Assign(lhs, rhs, rest) => extractRes(rhs, Assign(lhs, _, transform(rest))) - case b @ AssignField(lhs, nme, rhs, rest) => extractRes(rhs, AssignField(lhs, nme, _, transform(rest))(b.symbol)) + case Return(c: Call, implct) => extractRes(c, false, Return(_, false)) + case Return(res, implct) => extractRes(res, true, Return(_, false)) + case Assign(lhs, rhs, rest) => extractRes(rhs, true, Assign(lhs, _, transform(rest))) + case b @ AssignField(lhs, nme, rhs, rest) => extractRes(rhs, true, AssignField(lhs, nme, _, transform(rest))(b.symbol)) case Define(defn, rest) => Define(rewriteDefn(defn), transform(rest)) case HandleBlock(lhs, res, par, cls, handlers, body, rest) => HandleBlock( lhs, res, par, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, transform(h.body))), transform(body), transform(rest) ) - case HandleBlockReturn(res) => extractRes(res, HandleBlockReturn(_)) + case HandleBlockReturn(c: Call) => extractRes(c, false, HandleBlockReturn(_)) + case HandleBlockReturn(res) => extractRes(res, true, HandleBlockReturn(_)) case _ => b.map(transform) - // TODO: this will just some simple analysis. some more in-depth analysis could be done at some other point + // TODO: this will just do some simple analysis. some more in-depth analysis could be done at some other point def isTrivial(b: Block): Boolean = b match case Match(scrut, arms, dflt, rest) => arms.foldLeft(dflt.map(isTrivial).getOrElse(true))((acc, bl) => acc && isTrivial(bl._2)) && isTrivial(rest) @@ -111,19 +114,22 @@ class StackSafeTransform(depthLimit: Int)(using State): ) def rewriteBlk(blk: Block) = + val diffSym = TempSymbol(None, "diff") val scrut1Sym = TempSymbol(None, "scrut1") val scrut2Sym = TempSymbol(None, "scrut2") val scrutSym = TempSymbol(None, "scrut") - val scrut1 = op(">=", stackDepthPath, intLit(depthLimit)) + val diff = op("-", stackDepthPath, stackOffsetPath) + val scrut1 = op(">=", diffSym.asPath, intLit(depthLimit)) val scrut2 = op("!==", stackHandlerPath, Value.Lit(Tree.UnitLit(false))) val scrutVal = op("&&", scrut1Sym.asPath, scrut2Sym.asPath) val newBody = transform(blk) blockBuilder - .assign(scrut1Sym, scrut1) // stackDepth >= depthLimit - .assign(scrut2Sym, scrut2) // stackHandler !== null - .assign(scrutSym, scrutVal) // stackDepth >= depthLimit && stackHandler !== null + .assign(diffSym, diff) // diff = stackDepth - stackOffset + .assign(scrut1Sym, scrut1) // diff >= depthLimit + .assign(scrut2Sym, scrut2) // stackHandler !== null + .assign(scrutSym, scrutVal) // diff >= depthLimit && stackHandler !== null .ifthen( scrutSym.asPath, Case.Lit(Tree.BoolLit(true)), blockBuilder.assign( // tmp = perform(undefined) @@ -140,8 +146,11 @@ class StackSafeTransform(depthLimit: Int)(using State): // symbols val resumeSym = VarSymbol(Tree.Ident("resume")) - val handlerSym = TempSymbol(None, "stackHandler"); - val resSym = TempSymbol(None, "res"); + val handlerSym = TempSymbol(None, "stackHandler") + val resSym = TempSymbol(None, "res") + val handlerRes = TempSymbol(None, "res") + val curOffsetSym = TempSymbol(None, "curOffset") + val clsSym = ClassSymbol( Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), Tree.Ident("StackDelay$") @@ -156,10 +165,21 @@ class StackSafeTransform(depthLimit: Int)(using State): stackDelayClsPath, clsSym, List(Handler( BlockMemberSymbol("perform", Nil), resumeSym, List(ParamList(ParamListFlags.empty, Nil, N)), + /* + fun perform() = + let curOffset = stackOffset + stackOffset = stackDepth + let ret = resume() + stackOffset = curOffset + ret + */ blockBuilder - .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // Q: should this be 0 or 1 to account for some overhead? - .ret(Call(Value.Ref(resumeSym), List())(true)) - )), // create a "unit" handler, i.e. fun perform(k) = k(()) + .assign(curOffsetSym, stackOffsetPath) + .assignFieldN(predefPath, STACK_OFFSET_IDENT, stackDepthPath) + .assign(handlerRes, Call(Value.Ref(resumeSym), List())(true)) + .assignFieldN(predefPath, STACK_OFFSET_IDENT, curOffsetSym.asPath) + .ret(handlerRes.asPath) + )), blockBuilder .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 .assignFieldN(predefPath, STACK_HANDLER_IDENT, handlerSym.asPath) // assign stack handler diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index e89448fb31..51032aa22a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -126,6 +126,7 @@ const Predef$class = class Predef { toString() { return "__Return(" + this.value + ")"; } }; this.__stackDepth = 0; + this.__stackOffset = 0; this.__stackHandler = null; this.__StackDelay = function __StackDelay() { return new __StackDelay.class(); }; this.__StackDelay.class = class __StackDelay { diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index ac2cc62822..fb942e7c26 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -212,7 +212,8 @@ fun __resumeHandleBlocks(handleBlock, tailHandleBlock, value) = return value // stack safety -val __stackDepth = 0 +val __stackDepth = 0 // Tracks the virtual + real stack depth +val __stackOffset = 0 // How much to offset __stackDepth by to get the true stack depth (i.e. the virtual depth) val __stackHandler = null abstract class __StackDelay() with fun perform() \ No newline at end of file diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index d3e4e06612..d1774c534b 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -148,7 +148,7 @@ if true do //│ let tmp; //│ function handleBlock$0() { //│ let h, scrut, tmp1, res; -//│ class Effect$h$1160 extends globalThis.Effect { +//│ class Effect$h$1161 extends globalThis.Effect { //│ constructor() { //│ let tmp2; //│ tmp2 = super(); @@ -158,11 +158,11 @@ if true do //│ return arg; //│ }); //│ } -//│ toString() { return "Effect$h$1160"; } +//│ toString() { return "Effect$h$1161"; } //│ } -//│ h = new Effect$h$1160(); -//│ function Cont$1185(pc1) { return new Cont$1185.class(pc1); } -//│ Cont$1185.class = class Cont$1185 extends globalThis.Predef.__Cont.class { +//│ h = new Effect$h$1161(); +//│ function Cont$1186(pc1) { return new Cont$1186.class(pc1); } +//│ Cont$1186.class = class Cont$1186 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp2; //│ tmp2 = super(null, null); @@ -201,7 +201,7 @@ if true do //│ break; //│ } //│ } -//│ toString() { return "Cont$1185(" + this.pc + ")"; } +//│ toString() { return "Cont$1186(" + this.pc + ")"; } //│ }; //│ function f$0() { //│ return 3; @@ -210,7 +210,7 @@ if true do //│ if (scrut) { //│ res = f$0(); //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$1185(1); +//│ res.tail.next = new Cont$1186(1); //│ return globalThis.Predef.__handleBlockImpl(res, h); //│ } //│ tmp1 = res; diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 78234b71a9..400ceb7bbe 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -127,7 +127,7 @@ str //│ if (scrut) { //│ function handleBlock$0() { //│ let h1, tmp2; -//│ class Effect$h1$885 extends globalThis.Effect { +//│ class Effect$h1$886 extends globalThis.Effect { //│ constructor() { //│ let tmp3; //│ tmp3 = super(); @@ -135,8 +135,8 @@ str //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h1, (k) => { //│ let tmp3, tmp4, tmp5, res; -//│ function Cont$1049(pc1) { return new Cont$1049.class(pc1); } -//│ Cont$1049.class = class Cont$1049 extends globalThis.Predef.__Cont.class { +//│ function Cont$1050(pc1) { return new Cont$1050.class(pc1); } +//│ Cont$1050.class = class Cont$1050 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp6; //│ tmp6 = super(null, null); @@ -167,13 +167,13 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$1049(" + this.pc + ")"; } +//│ toString() { return "Cont$1050(" + this.pc + ")"; } //│ }; //│ tmp3 = globalThis.str + "A"; //│ globalThis.str = tmp3; //│ res = k(arg) ?? null; //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$1049.class(10); +//│ res.tail.next = new Cont$1050.class(10); //│ res.tail = res.tail.next; //│ return res; //│ } @@ -183,11 +183,11 @@ str //│ return null; //│ }); //│ } -//│ toString() { return "Effect$h1$885"; } +//│ toString() { return "Effect$h1$886"; } //│ } -//│ h1 = new Effect$h1$885(); -//│ function Cont$1007(pc1) { return new Cont$1007.class(pc1); } -//│ Cont$1007.class = class Cont$1007 extends globalThis.Predef.__Cont.class { +//│ h1 = new Effect$h1$886(); +//│ function Cont$1008(pc1) { return new Cont$1008.class(pc1); } +//│ Cont$1008.class = class Cont$1008 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp3; //│ tmp3 = super(null, null); @@ -219,11 +219,11 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$1007(" + this.pc + ")"; } +//│ toString() { return "Cont$1008(" + this.pc + ")"; } //│ }; //│ function handleBlock$1$0() { //│ let h2, tmp3, res, res1; -//│ class Effect$h2$895 extends globalThis.Effect { +//│ class Effect$h2$896 extends globalThis.Effect { //│ constructor() { //│ let tmp4; //│ tmp4 = super(); @@ -231,8 +231,8 @@ str //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h2, (k) => { //│ let tmp4, tmp5, tmp6, tmp7, tmp8, res2; -//│ function Cont$968(pc1) { return new Cont$968.class(pc1); } -//│ Cont$968.class = class Cont$968 extends globalThis.Predef.__Cont.class { +//│ function Cont$969(pc1) { return new Cont$969.class(pc1); } +//│ Cont$969.class = class Cont$969 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp9; //│ tmp9 = super(null, null); @@ -265,14 +265,14 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$968(" + this.pc + ")"; } +//│ toString() { return "Cont$969(" + this.pc + ")"; } //│ }; //│ tmp4 = globalThis.str + "B"; //│ tmp5 = globalThis.str + tmp4; //│ globalThis.str = tmp5; //│ res2 = k(arg) ?? null; //│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$968.class(5); +//│ res2.tail.next = new Cont$969.class(5); //│ res2.tail = res2.tail.next; //│ return res2; //│ } @@ -283,11 +283,11 @@ str //│ return null; //│ }); //│ } -//│ toString() { return "Effect$h2$895"; } +//│ toString() { return "Effect$h2$896"; } //│ } -//│ h2 = new Effect$h2$895(); -//│ function Cont$942(pc1) { return new Cont$942.class(pc1); } -//│ Cont$942.class = class Cont$942 extends globalThis.Predef.__Cont.class { +//│ h2 = new Effect$h2$896(); +//│ function Cont$943(pc1) { return new Cont$943.class(pc1); } +//│ Cont$943.class = class Cont$943 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp4; //│ tmp4 = super(null, null); @@ -325,24 +325,24 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$942(" + this.pc + ")"; } +//│ toString() { return "Cont$943(" + this.pc + ")"; } //│ }; //│ res1 = h2.perform(null) ?? null; //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$942(3); +//│ res1.tail.next = new Cont$943(3); //│ return globalThis.Predef.__handleBlockImpl(res1, h2); //│ } //│ tmp3 = res1; //│ res = h1.perform(null) ?? null; //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$942(2); +//│ res.tail.next = new Cont$943(2); //│ return globalThis.Predef.__handleBlockImpl(res, h2); //│ } //│ return res; //│ } //│ tmp2 = handleBlock$1$0(); //│ if (tmp2 instanceof globalThis.Predef.__EffectSig.class) { -//│ tmp2.tail.next = new Cont$1007(7); +//│ tmp2.tail.next = new Cont$1008(7); //│ return globalThis.Predef.__handleBlockImpl(tmp2, h1); //│ } //│ if (tmp2 instanceof globalThis.Predef.__Return.class) { diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 9dc3fe8259..1e48f750ba 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -1,5 +1,6 @@ :js +// sanity check :expect 5050 fun sum(n) = if n == 0 then 0 @@ -8,6 +9,204 @@ fun sum(n) = sum(100) //│ = 5050 +// preserve tail calls +:stackSafe 5 +:handler +:expect 0 +:sjs +fun hi(n) = + if n == 0 then 0 + else hi(n - 1) +hi(0) +//│ JS (unsanitized): +//│ let res; +//│ function hi(n) { +//│ let scrut, tmp, diff, scrut1, scrut2, scrut3, tmp1, res1; +//│ function Cont$243(pc1) { return new Cont$243.class(pc1); } +//│ Cont$243.class = class Cont$243 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp2; +//│ tmp2 = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 6) { +//│ res1 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 7) { +//│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; +//│ scrut1 = diff >= 5; +//│ scrut2 = globalThis.Predef.__stackHandler !== undefined; +//│ scrut3 = scrut1 && scrut2; +//│ if (scrut3) { +//│ res1 = globalThis.Predef.__stackHandler.perform(); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = this; +//│ this.pc = 6; +//│ return res1; +//│ } +//│ this.pc = 6; +//│ continue contLoop; +//│ } +//│ this.pc = 9; +//│ continue contLoop; +//│ } else if (this.pc === 9) { +//│ scrut = n == 0; +//│ if (scrut) { +//│ return 0; +//│ } else { +//│ tmp = n - 1; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ return globalThis.hi(tmp); +//│ } +//│ this.pc = 8; +//│ continue contLoop; +//│ } else if (this.pc === 8) { +//│ break contLoop; +//│ } else if (this.pc === 6) { +//│ tmp1 = res1; +//│ this.pc = 9; +//│ continue contLoop; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$243(" + this.pc + ")"; } +//│ }; +//│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; +//│ scrut1 = diff >= 5; +//│ scrut2 = globalThis.Predef.__stackHandler !== undefined; +//│ scrut3 = scrut1 && scrut2; +//│ if (scrut3) { +//│ res1 = globalThis.Predef.__stackHandler.perform(); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = new Cont$243.class(6); +//│ res1.tail = res1.tail.next; +//│ return res1; +//│ } +//│ tmp1 = res1; +//│ } +//│ scrut = n == 0; +//│ if (scrut) { +//│ return 0; +//│ } else { +//│ tmp = n - 1; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ return globalThis.hi(tmp); +//│ } +//│ } +//│ function handleBlock$0() { +//│ let stackHandler, res1; +//│ class StackDelay$ extends globalThis.Predef.__StackDelay.class { +//│ constructor() { +//│ let tmp; +//│ tmp = super(); +//│ } +//│ perform() { +//│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { +//│ let res2, curOffset, res3; +//│ function Cont$218(pc1) { return new Cont$218.class(pc1); } +//│ Cont$218.class = class Cont$218 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp; +//│ tmp = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 3) { +//│ res3 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 4) { +//│ curOffset = globalThis.Predef.__stackOffset; +//│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; +//│ res3 = resume(); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = this; +//│ this.pc = 3; +//│ return res3; +//│ } +//│ this.pc = 3; +//│ continue contLoop; +//│ } else if (this.pc === 3) { +//│ res2 = res3; +//│ globalThis.Predef.__stackOffset = curOffset; +//│ return res2; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$218(" + this.pc + ")"; } +//│ }; +//│ curOffset = globalThis.Predef.__stackOffset; +//│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; +//│ res3 = resume(); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = new Cont$218.class(3); +//│ res3.tail = res3.tail.next; +//│ return res3; +//│ } +//│ res2 = res3; +//│ globalThis.Predef.__stackOffset = curOffset; +//│ return res2; +//│ }); +//│ } +//│ toString() { return "StackDelay$"; } +//│ } +//│ stackHandler = new StackDelay$(); +//│ function Cont$194(pc1) { return new Cont$194.class(pc1); } +//│ Cont$194.class = class Cont$194 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp; +//│ tmp = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 1) { +//│ res1 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 2) { +//│ globalThis.Predef.__stackDepth = 0; +//│ globalThis.Predef.__stackHandler = stackHandler; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ res1 = globalThis.hi(0); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = this; +//│ this.pc = 1; +//│ return res1; +//│ } +//│ this.pc = 1; +//│ continue contLoop; +//│ } else if (this.pc === 1) { +//│ return res1; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$194(" + this.pc + ")"; } +//│ }; +//│ globalThis.Predef.__stackDepth = 0; +//│ globalThis.Predef.__stackHandler = stackHandler; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ res1 = globalThis.hi(0); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = new Cont$194(1); +//│ return globalThis.Predef.__handleBlockImpl(res1, stackHandler); +//│ } +//│ return res1; +//│ } +//│ res = handleBlock$0(); +//│ if (res instanceof this.Predef.__EffectSig.class) { +//│ throw new this.Error("Unhandled effects"); +//│ } +//│ this.Predef.__stackDepth = 0; +//│ this.Predef.__stackHandler = undefined; +//│ res +//│ = 0 + +:sjs :stackSafe 1000 :handler :expect 50005000 @@ -16,8 +215,218 @@ fun sum(n) = else n + sum(n - 1) sum(10000) +//│ JS (unsanitized): +//│ let res; +//│ function sum(n) { +//│ let scrut, tmp, tmp1, diff, scrut1, scrut2, scrut3, tmp2, tmp3, res1, res2; +//│ function Cont$488(pc1) { return new Cont$488.class(pc1); } +//│ Cont$488.class = class Cont$488 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp4; +//│ tmp4 = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 7) { +//│ res2 = value$; +//│ } else if (this.pc === 6) { +//│ res1 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 8) { +//│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; +//│ scrut1 = diff >= 1000; +//│ scrut2 = globalThis.Predef.__stackHandler !== undefined; +//│ scrut3 = scrut1 && scrut2; +//│ if (scrut3) { +//│ res1 = globalThis.Predef.__stackHandler.perform(); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = this; +//│ this.pc = 6; +//│ return res1; +//│ } +//│ this.pc = 6; +//│ continue contLoop; +//│ } +//│ this.pc = 10; +//│ continue contLoop; +//│ } else if (this.pc === 10) { +//│ scrut = n == 0; +//│ if (scrut) { +//│ return 0; +//│ } else { +//│ tmp = n - 1; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ res2 = globalThis.sum(tmp); +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = this; +//│ this.pc = 7; +//│ return res2; +//│ } +//│ this.pc = 7; +//│ continue contLoop; +//│ } +//│ this.pc = 9; +//│ continue contLoop; +//│ } else if (this.pc === 9) { +//│ break contLoop; +//│ } else if (this.pc === 7) { +//│ tmp2 = res2; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ tmp1 = tmp2; +//│ return n + tmp1; +//│ } else if (this.pc === 6) { +//│ tmp3 = res1; +//│ this.pc = 10; +//│ continue contLoop; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$488(" + this.pc + ")"; } +//│ }; +//│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; +//│ scrut1 = diff >= 1000; +//│ scrut2 = globalThis.Predef.__stackHandler !== undefined; +//│ scrut3 = scrut1 && scrut2; +//│ if (scrut3) { +//│ res1 = globalThis.Predef.__stackHandler.perform(); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = new Cont$488.class(6); +//│ res1.tail = res1.tail.next; +//│ return res1; +//│ } +//│ tmp3 = res1; +//│ } +//│ scrut = n == 0; +//│ if (scrut) { +//│ return 0; +//│ } else { +//│ tmp = n - 1; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ res2 = globalThis.sum(tmp); +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = new Cont$488.class(7); +//│ res2.tail = res2.tail.next; +//│ return res2; +//│ } +//│ tmp2 = res2; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ tmp1 = tmp2; +//│ return n + tmp1; +//│ } +//│ } +//│ function handleBlock$0() { +//│ let stackHandler, res1; +//│ class StackDelay$ extends globalThis.Predef.__StackDelay.class { +//│ constructor() { +//│ let tmp; +//│ tmp = super(); +//│ } +//│ perform() { +//│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { +//│ let res2, curOffset, res3; +//│ function Cont$462(pc1) { return new Cont$462.class(pc1); } +//│ Cont$462.class = class Cont$462 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp; +//│ tmp = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 3) { +//│ res3 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 4) { +//│ curOffset = globalThis.Predef.__stackOffset; +//│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; +//│ res3 = resume(); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = this; +//│ this.pc = 3; +//│ return res3; +//│ } +//│ this.pc = 3; +//│ continue contLoop; +//│ } else if (this.pc === 3) { +//│ res2 = res3; +//│ globalThis.Predef.__stackOffset = curOffset; +//│ return res2; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$462(" + this.pc + ")"; } +//│ }; +//│ curOffset = globalThis.Predef.__stackOffset; +//│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; +//│ res3 = resume(); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = new Cont$462.class(3); +//│ res3.tail = res3.tail.next; +//│ return res3; +//│ } +//│ res2 = res3; +//│ globalThis.Predef.__stackOffset = curOffset; +//│ return res2; +//│ }); +//│ } +//│ toString() { return "StackDelay$"; } +//│ } +//│ stackHandler = new StackDelay$(); +//│ function Cont$438(pc1) { return new Cont$438.class(pc1); } +//│ Cont$438.class = class Cont$438 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp; +//│ tmp = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 1) { +//│ res1 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 2) { +//│ globalThis.Predef.__stackDepth = 0; +//│ globalThis.Predef.__stackHandler = stackHandler; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ res1 = globalThis.sum(10000); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = this; +//│ this.pc = 1; +//│ return res1; +//│ } +//│ this.pc = 1; +//│ continue contLoop; +//│ } else if (this.pc === 1) { +//│ return res1; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$438(" + this.pc + ")"; } +//│ }; +//│ globalThis.Predef.__stackDepth = 0; +//│ globalThis.Predef.__stackHandler = stackHandler; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ res1 = globalThis.sum(10000); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = new Cont$438(1); +//│ return globalThis.Predef.__handleBlockImpl(res1, stackHandler); +//│ } +//│ return res1; +//│ } +//│ res = handleBlock$0(); +//│ if (res instanceof this.Predef.__EffectSig.class) { +//│ throw new this.Error("Unhandled effects"); +//│ } +//│ this.Predef.__stackDepth = 0; +//│ this.Predef.__stackHandler = undefined; +//│ res //│ = 50005000 +// stack-overflows without :re fun sum(n) = if n == 0 then 0 diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index 3afdf71d12..8daad0fe12 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$156),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } } +//│ Elab: { { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$157),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } } :e ( @@ -73,7 +73,7 @@ handle h = Eff with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$185),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$185),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } +//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$186),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$186),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } :e handle h = Eff with @@ -127,4 +127,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.125: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$262),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$262),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } +//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$263),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$263),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } From 0c5d205a12a0b22b75b891254ccb5985742f7a3c Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 16 Jan 2025 18:11:28 +0800 Subject: [PATCH 076/114] deal with lambdas, logic fixes --- .../main/scala/hkmc2/codegen/Lowering.scala | 2 +- .../hkmc2/codegen/StackSafeTransform.scala | 73 +++++++---- .../test/mlscript/handlers/StackSafety.mls | 124 ++++++++++++++---- 3 files changed, 144 insertions(+), 55 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index f5a6862808..16f464503e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -432,7 +432,7 @@ class Lowering(lowerHandlers: Bool, stackLimit: Option[Int])(using TL, Raise, St case Some(lim) => StackSafeTransform(lim).transformTopLevel(res) if lowerHandlers then HandlerLowering().translateTopLevel(stackSafe) - else res + else stackSafe def program(main: st): Program = def go(acc: Ls[Local -> Str], trm: st): Program = diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 4e5365b5d3..159c53588a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -53,38 +53,56 @@ class StackSafeTransform(depthLimit: Int)(using State): // Increases the stack depth, assigns the call to a value, then decreases the stack depth // then binds that value to a desired block - def extractRes(res: Result, decrement: Bool, f: Result => Block) = + def extractRes(res: Result, isTailCall: Bool, f: Result => Block) = res match case Call(Value.Ref(s: BuiltinSymbol), args) => f(res) case Call(path, args) => - val tmp = TempSymbol(None, "tmp") - val inc = blockBuilder - .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) - - if decrement then - inc + if isTailCall then + blockBuilder + .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) + .ret(res) + else + val tmp = TempSymbol(None, "tmp") + val prevDepth = TempSymbol(None, "prevDepth") + blockBuilder + .assign(prevDepth, stackDepthPath) + .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) .assign(tmp, res) - .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("-", stackDepthPath, intLit(1))) + .assignFieldN(predefPath, STACK_DEPTH_IDENT, prevDepth.asPath) .rest(f(tmp.asPath)) - else - inc.ret(res) case _ => f(res) // Rewrites anything that can contain a Call to increase the stack depth - def transform(b: Block): Block = b match - case Return(c: Call, implct) => extractRes(c, false, Return(_, false)) - case Return(res, implct) => extractRes(res, true, Return(_, false)) - case Assign(lhs, rhs, rest) => extractRes(rhs, true, Assign(lhs, _, transform(rest))) - case b @ AssignField(lhs, nme, rhs, rest) => extractRes(rhs, true, AssignField(lhs, nme, _, transform(rest))(b.symbol)) - case Define(defn, rest) => Define(rewriteDefn(defn), transform(rest)) - case HandleBlock(lhs, res, par, cls, handlers, body, rest) => - HandleBlock( - lhs, res, par, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, transform(h.body))), - transform(body), transform(rest) - ) - case HandleBlockReturn(c: Call) => extractRes(c, false, HandleBlockReturn(_)) - case HandleBlockReturn(res) => extractRes(res, true, HandleBlockReturn(_)) - case _ => b.map(transform) + def transform(b: Block): Block = + // 1. rewrite lambdas + def firstPass(b: Block): Block = b.map(firstPass) match + case HandleBlock(lhs, res, par, cls, handlers, body, rest) => + HandleBlock( + lhs, res, par, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, firstPass(h.body))), + firstPass(body), firstPass(rest) + ) + case b => b.mapValue { + case Value.Lam(params, body) => Value.Lam(params, rewriteBlk(body)) + case v => v + } + + // 2. rewrite calls and definitions + def secondPass(b: Block): Block = b match + case Return(c: Call, implct) => extractRes(c, true, Return(_, false)) + case Return(res, implct) => extractRes(res, false, Return(_, false)) + case Assign(lhs, rhs, rest) => extractRes(rhs, false, Assign(lhs, _, secondPass(rest))) + case b @ AssignField(lhs, nme, rhs, rest) => extractRes(rhs, false, AssignField(lhs, nme, _, secondPass(rest))(b.symbol)) + case Define(defn, rest) => Define(rewriteDefn(defn), secondPass(rest)) + case HandleBlock(lhs, res, par, cls, handlers, body, rest) => + HandleBlock( + lhs, res, par, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, secondPass(h.body))), + secondPass(body), secondPass(rest) + ) + case HandleBlockReturn(c: Call) => extractRes(c, true, HandleBlockReturn(_)) + case HandleBlockReturn(res) => extractRes(res, false, HandleBlockReturn(_)) + case _ => b.map(secondPass) + + secondPass(firstPass(b)) // TODO: this will just do some simple analysis. some more in-depth analysis could be done at some other point def isTrivial(b: Block): Boolean = b match @@ -141,7 +159,12 @@ class StackSafeTransform(depthLimit: Int)(using State): def transformTopLevel(b: Block) = def replaceReturns(b: Block): Block = b match - case Return(res, false) => HandleBlockReturn(res) + case Return(res, _) => HandleBlockReturn(res) + case HandleBlock(lhs, res, par, cls, handlers, body, rest) => + HandleBlock( + lhs, res, par, cls, handlers, + replaceReturns(body), replaceReturns(rest) + ) case _ => b.map(replaceReturns) // symbols diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 1e48f750ba..74c8396e18 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -10,6 +10,7 @@ sum(100) //│ = 5050 // preserve tail calls +// MUST see "return globalThis.hi(tmp)" in the output :stackSafe 5 :handler :expect 0 @@ -22,8 +23,8 @@ hi(0) //│ let res; //│ function hi(n) { //│ let scrut, tmp, diff, scrut1, scrut2, scrut3, tmp1, res1; -//│ function Cont$243(pc1) { return new Cont$243.class(pc1); } -//│ Cont$243.class = class Cont$243 extends globalThis.Predef.__Cont.class { +//│ function Cont$241(pc1) { return new Cont$241.class(pc1); } +//│ Cont$241.class = class Cont$241 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp2; //│ tmp2 = super(null, null); @@ -72,7 +73,7 @@ hi(0) //│ break; //│ } //│ } -//│ toString() { return "Cont$243(" + this.pc + ")"; } +//│ toString() { return "Cont$241(" + this.pc + ")"; } //│ }; //│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; //│ scrut1 = diff >= 5; @@ -81,7 +82,7 @@ hi(0) //│ if (scrut3) { //│ res1 = globalThis.Predef.__stackHandler.perform(); //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$243.class(6); +//│ res1.tail.next = new Cont$241.class(6); //│ res1.tail = res1.tail.next; //│ return res1; //│ } @@ -106,8 +107,8 @@ hi(0) //│ perform() { //│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { //│ let res2, curOffset, res3; -//│ function Cont$218(pc1) { return new Cont$218.class(pc1); } -//│ Cont$218.class = class Cont$218 extends globalThis.Predef.__Cont.class { +//│ function Cont$216(pc1) { return new Cont$216.class(pc1); } +//│ Cont$216.class = class Cont$216 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp; //│ tmp = super(null, null); @@ -137,13 +138,13 @@ hi(0) //│ break; //│ } //│ } -//│ toString() { return "Cont$218(" + this.pc + ")"; } +//│ toString() { return "Cont$216(" + this.pc + ")"; } //│ }; //│ curOffset = globalThis.Predef.__stackOffset; //│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; //│ res3 = resume(); //│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$218.class(3); +//│ res3.tail.next = new Cont$216.class(3); //│ res3.tail = res3.tail.next; //│ return res3; //│ } @@ -155,8 +156,8 @@ hi(0) //│ toString() { return "StackDelay$"; } //│ } //│ stackHandler = new StackDelay$(); -//│ function Cont$194(pc1) { return new Cont$194.class(pc1); } -//│ Cont$194.class = class Cont$194 extends globalThis.Predef.__Cont.class { +//│ function Cont$192(pc1) { return new Cont$192.class(pc1); } +//│ Cont$192.class = class Cont$192 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp; //│ tmp = super(null, null); @@ -185,14 +186,14 @@ hi(0) //│ break; //│ } //│ } -//│ toString() { return "Cont$194(" + this.pc + ")"; } +//│ toString() { return "Cont$192(" + this.pc + ")"; } //│ }; //│ globalThis.Predef.__stackDepth = 0; //│ globalThis.Predef.__stackHandler = stackHandler; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; //│ res1 = globalThis.hi(0); //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$194(1); +//│ res1.tail.next = new Cont$192(1); //│ return globalThis.Predef.__handleBlockImpl(res1, stackHandler); //│ } //│ return res1; @@ -218,9 +219,9 @@ sum(10000) //│ JS (unsanitized): //│ let res; //│ function sum(n) { -//│ let scrut, tmp, tmp1, diff, scrut1, scrut2, scrut3, tmp2, tmp3, res1, res2; -//│ function Cont$488(pc1) { return new Cont$488.class(pc1); } -//│ Cont$488.class = class Cont$488 extends globalThis.Predef.__Cont.class { +//│ let scrut, tmp, tmp1, diff, scrut1, scrut2, scrut3, tmp2, prevDepth, tmp3, res1, res2; +//│ function Cont$484(pc1) { return new Cont$484.class(pc1); } +//│ Cont$484.class = class Cont$484 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp4; //│ tmp4 = super(null, null); @@ -256,6 +257,7 @@ sum(10000) //│ return 0; //│ } else { //│ tmp = n - 1; +//│ prevDepth = globalThis.Predef.__stackDepth; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; //│ res2 = globalThis.sum(tmp); //│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { @@ -272,7 +274,7 @@ sum(10000) //│ break contLoop; //│ } else if (this.pc === 7) { //│ tmp2 = res2; -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ globalThis.Predef.__stackDepth = prevDepth; //│ tmp1 = tmp2; //│ return n + tmp1; //│ } else if (this.pc === 6) { @@ -283,7 +285,7 @@ sum(10000) //│ break; //│ } //│ } -//│ toString() { return "Cont$488(" + this.pc + ")"; } +//│ toString() { return "Cont$484(" + this.pc + ")"; } //│ }; //│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; //│ scrut1 = diff >= 1000; @@ -292,7 +294,7 @@ sum(10000) //│ if (scrut3) { //│ res1 = globalThis.Predef.__stackHandler.perform(); //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$488.class(6); +//│ res1.tail.next = new Cont$484.class(6); //│ res1.tail = res1.tail.next; //│ return res1; //│ } @@ -303,15 +305,16 @@ sum(10000) //│ return 0; //│ } else { //│ tmp = n - 1; +//│ prevDepth = globalThis.Predef.__stackDepth; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; //│ res2 = globalThis.sum(tmp); //│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$488.class(7); +//│ res2.tail.next = new Cont$484.class(7); //│ res2.tail = res2.tail.next; //│ return res2; //│ } //│ tmp2 = res2; -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth - 1; +//│ globalThis.Predef.__stackDepth = prevDepth; //│ tmp1 = tmp2; //│ return n + tmp1; //│ } @@ -326,8 +329,8 @@ sum(10000) //│ perform() { //│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { //│ let res2, curOffset, res3; -//│ function Cont$462(pc1) { return new Cont$462.class(pc1); } -//│ Cont$462.class = class Cont$462 extends globalThis.Predef.__Cont.class { +//│ function Cont$458(pc1) { return new Cont$458.class(pc1); } +//│ Cont$458.class = class Cont$458 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp; //│ tmp = super(null, null); @@ -357,13 +360,13 @@ sum(10000) //│ break; //│ } //│ } -//│ toString() { return "Cont$462(" + this.pc + ")"; } +//│ toString() { return "Cont$458(" + this.pc + ")"; } //│ }; //│ curOffset = globalThis.Predef.__stackOffset; //│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; //│ res3 = resume(); //│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$462.class(3); +//│ res3.tail.next = new Cont$458.class(3); //│ res3.tail = res3.tail.next; //│ return res3; //│ } @@ -375,8 +378,8 @@ sum(10000) //│ toString() { return "StackDelay$"; } //│ } //│ stackHandler = new StackDelay$(); -//│ function Cont$438(pc1) { return new Cont$438.class(pc1); } -//│ Cont$438.class = class Cont$438 extends globalThis.Predef.__Cont.class { +//│ function Cont$434(pc1) { return new Cont$434.class(pc1); } +//│ Cont$434.class = class Cont$434 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp; //│ tmp = super(null, null); @@ -405,14 +408,14 @@ sum(10000) //│ break; //│ } //│ } -//│ toString() { return "Cont$438(" + this.pc + ")"; } +//│ toString() { return "Cont$434(" + this.pc + ")"; } //│ }; //│ globalThis.Predef.__stackDepth = 0; //│ globalThis.Predef.__stackHandler = stackHandler; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; //│ res1 = globalThis.sum(10000); //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$438(1); +//│ res1.tail.next = new Cont$434(1); //│ return globalThis.Predef.__handleBlockImpl(res1, stackHandler); //│ } //│ return res1; @@ -426,7 +429,7 @@ sum(10000) //│ res //│ = 50005000 -// stack-overflows without +// stack-overflows without :stackSafe :re fun sum(n) = if n == 0 then 0 @@ -434,3 +437,66 @@ fun sum(n) = n + sum(n - 1) sum(10000) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + +:handler +:stackSafe 100 +mut val ctr = 0 +fun dummy(x) = x +fun foo(f) = + if ctr > 10000 then 0 + else + set ctr += 1 + dummy(f(f)) +foo(foo) +//│ = 0 +//│ ctr = 10001 + +:stackSafe 1000 +:handler +:expect 50005000 +val foo = + val f = n => + if n <= 0 then 0 + else n + f(n-1) + f(10000) +foo +//│ = 50005000 +//│ foo = 50005000 + +:re +fun foo() = + val 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 +:handler +:stackSafe 100 +:expect 50005000 +fun foo(h) = h.perform +handle h = Eff with + fun perform(resume) = + val f = n => + if n <= 0 then 0 + else n + f(n-1) + resume(f(10000)) +foo(h) +//│ = 50005000 + +:re +:handler +fun foo(h) = h.perform(2) +handle h = Eff with + fun perform(a)(resume) = + val f = n => + if n <= 0 then 0 + else n + f(n-1) + resume(f(10000)) +foo(h) +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded From 5d54505fff60429d096a58f09c4f5121ae67b3d6 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 16 Jan 2025 21:24:10 +0800 Subject: [PATCH 077/114] fix some cases, don't optimise trivial fns --- .../hkmc2/codegen/StackSafeTransform.scala | 84 ++++++++++-------- .../test/mlscript/handlers/StackSafety.mls | 88 ++++++++++++++++++- 2 files changed, 133 insertions(+), 39 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 159c53588a..e436a7e2d1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -56,7 +56,7 @@ class StackSafeTransform(depthLimit: Int)(using State): def extractRes(res: Result, isTailCall: Bool, f: Result => Block) = res match case Call(Value.Ref(s: BuiltinSymbol), args) => f(res) - case Call(path, args) => + case _: Call | _: Instantiate => if isTailCall then blockBuilder .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) @@ -104,23 +104,29 @@ class StackSafeTransform(depthLimit: Int)(using State): secondPass(firstPass(b)) - // TODO: this will just do some simple analysis. some more in-depth analysis could be done at some other point - def isTrivial(b: Block): Boolean = b match - case Match(scrut, arms, dflt, rest) => - arms.foldLeft(dflt.map(isTrivial).getOrElse(true))((acc, bl) => acc && isTrivial(bl._2)) && isTrivial(rest) - case Return(res, implct) => true - case Throw(exc) => ??? - case Label(label, body, rest) => ??? - case Break(label) => ??? - case Continue(label) => ??? - case Begin(sub, rest) => ??? - case TryBlock(sub, finallyDo, rest) => ??? - case Assign(lhs, rhs, rest) => ??? - case AssignField(lhs, nme, rhs, rest) => ??? - case Define(defn, rest) => ??? - case HandleBlock(lhs, res, par, cls, handlers, body, rest) => ??? - case HandleBlockReturn(res) => ??? - case End(msg) => ??? + def isTrivial(b: Block): Boolean = + def resTrivial(r: Result) = r match + case Call(Value.Ref(_: BuiltinSymbol), _) => true + case _: Call => false + case _: Instantiate => false + case _ => true + + b match + case Match(scrut, arms, dflt, rest) => + arms.foldLeft(dflt.map(isTrivial).getOrElse(true))((acc, bl) => acc && isTrivial(bl._2)) && isTrivial(rest) + case Return(res, implct) => resTrivial(res) + case Throw(exc) => resTrivial(exc) + case Label(label, body, rest) => isTrivial(body) && isTrivial(rest) + case Break(label) => true + case Continue(label) => true + case Begin(sub, rest) => isTrivial(sub) && isTrivial(rest) + case TryBlock(sub, finallyDo, rest) => isTrivial(sub) && isTrivial(finallyDo) && isTrivial(rest) + case Assign(lhs, rhs, rest) => resTrivial(rhs) && isTrivial(rest) + case AssignField(lhs, nme, rhs, rest) => resTrivial(rhs) && isTrivial(rest) + case Define(defn, rest) => isTrivial(rest) + case HandleBlock(lhs, res, par, cls, handlers, body, rest) => isTrivial(body) && isTrivial(rest) + case HandleBlockReturn(res) => true + case End(msg) => true def rewriteDefn(defn: Defn) = defn match case d: FunDefn => rewriteFn(d) @@ -132,28 +138,30 @@ class StackSafeTransform(depthLimit: Int)(using State): ) def rewriteBlk(blk: Block) = - val diffSym = TempSymbol(None, "diff") - val scrut1Sym = TempSymbol(None, "scrut1") - val scrut2Sym = TempSymbol(None, "scrut2") - val scrutSym = TempSymbol(None, "scrut") - val diff = op("-", stackDepthPath, stackOffsetPath) - val scrut1 = op(">=", diffSym.asPath, intLit(depthLimit)) - val scrut2 = op("!==", stackHandlerPath, Value.Lit(Tree.UnitLit(false))) - val scrutVal = op("&&", scrut1Sym.asPath, scrut2Sym.asPath) - val newBody = transform(blk) - blockBuilder - .assign(diffSym, diff) // diff = stackDepth - stackOffset - .assign(scrut1Sym, scrut1) // diff >= depthLimit - .assign(scrut2Sym, scrut2) // stackHandler !== null - .assign(scrutSym, scrutVal) // diff >= depthLimit && stackHandler !== null - .ifthen( - scrutSym.asPath, Case.Lit(Tree.BoolLit(true)), - blockBuilder.assign( // tmp = perform(undefined) - TempSymbol(None, "tmp"), - Call(Select(stackHandlerPath, Tree.Ident("perform"))(N), Nil)(true)).end()) - .rest(newBody) + if isTrivial(blk) then + newBody + else + val diffSym = TempSymbol(None, "diff") + val scrut1Sym = TempSymbol(None, "scrut1") + val scrut2Sym = TempSymbol(None, "scrut2") + val scrutSym = TempSymbol(None, "scrut") + val diff = op("-", stackDepthPath, stackOffsetPath) + val scrut1 = op(">=", diffSym.asPath, intLit(depthLimit)) + val scrut2 = op("!==", stackHandlerPath, Value.Lit(Tree.UnitLit(false))) + val scrutVal = op("&&", scrut1Sym.asPath, scrut2Sym.asPath) + blockBuilder + .assign(diffSym, diff) // diff = stackDepth - stackOffset + .assign(scrut1Sym, scrut1) // diff >= depthLimit + .assign(scrut2Sym, scrut2) // stackHandler !== null + .assign(scrutSym, scrutVal) // diff >= depthLimit && stackHandler !== null + .ifthen( + scrutSym.asPath, Case.Lit(Tree.BoolLit(true)), + blockBuilder.assign( // tmp = perform(undefined) + TempSymbol(None, "tmp"), + Call(Select(stackHandlerPath, Tree.Ident("perform"))(N), Nil)(true)).end()) + .rest(newBody) def rewriteFn(defn: FunDefn) = FunDefn(defn.sym, defn.params, rewriteBlk(defn.body)) diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 74c8396e18..e0646d8616 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -219,7 +219,7 @@ sum(10000) //│ JS (unsanitized): //│ let res; //│ function sum(n) { -//│ let scrut, tmp, tmp1, diff, scrut1, scrut2, scrut3, tmp2, prevDepth, tmp3, res1, res2; +//│ let scrut, tmp, tmp1, tmp2, prevDepth, diff, scrut1, scrut2, scrut3, tmp3, res1, res2; //│ function Cont$484(pc1) { return new Cont$484.class(pc1); } //│ Cont$484.class = class Cont$484 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { @@ -500,3 +500,89 @@ handle h = Eff with resume(f(10000)) foo(h) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + +:handler +:stackSafe +:sjs +fun max(a, b) = if a < b then b else a +//│ JS (unsanitized): +//│ let res; +//│ function max(a, b) { +//│ let scrut; +//│ scrut = a < b; +//│ if (scrut) { +//│ return b; +//│ } else { +//│ return a; +//│ } +//│ } +//│ function handleBlock$0() { +//│ let stackHandler; +//│ class StackDelay$ extends globalThis.Predef.__StackDelay.class { +//│ constructor() { +//│ let tmp; +//│ tmp = super(); +//│ } +//│ perform() { +//│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { +//│ let res1, curOffset, res2; +//│ function Cont$1488(pc1) { return new Cont$1488.class(pc1); } +//│ Cont$1488.class = class Cont$1488 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp; +//│ tmp = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 1) { +//│ res2 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 2) { +//│ curOffset = globalThis.Predef.__stackOffset; +//│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; +//│ res2 = resume(); +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = this; +//│ this.pc = 1; +//│ return res2; +//│ } +//│ this.pc = 1; +//│ continue contLoop; +//│ } else if (this.pc === 1) { +//│ res1 = res2; +//│ globalThis.Predef.__stackOffset = curOffset; +//│ return res1; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$1488(" + this.pc + ")"; } +//│ }; +//│ curOffset = globalThis.Predef.__stackOffset; +//│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; +//│ res2 = resume(); +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = new Cont$1488.class(1); +//│ res2.tail = res2.tail.next; +//│ return res2; +//│ } +//│ res1 = res2; +//│ globalThis.Predef.__stackOffset = curOffset; +//│ return res1; +//│ }); +//│ } +//│ toString() { return "StackDelay$"; } +//│ } +//│ stackHandler = new StackDelay$(); +//│ globalThis.Predef.__stackDepth = 0; +//│ globalThis.Predef.__stackHandler = stackHandler; +//│ return null; +//│ } +//│ res = handleBlock$0(); +//│ if (res instanceof this.Predef.__EffectSig.class) { +//│ throw new this.Error("Unhandled effects"); +//│ } +//│ this.Predef.__stackDepth = 0; +//│ this.Predef.__stackHandler = undefined; +//│ res From a6cdfc9e372af1a2f8d983e94894226e4e71fc8b Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 16 Jan 2025 21:30:07 +0800 Subject: [PATCH 078/114] initial merge --- .github/workflows/nix.yml | 2 +- flake.nix | 2 +- .../src/test/scala/hkmc2/DiffTestRunner.scala | 2 +- .../src/test/scala/hkmc2/LlirDiffMaker.scala | 73 +++ .../main/scala/hkmc2/codegen/Printer.scala | 15 +- .../main/scala/hkmc2/codegen/cpp/Ast.scala | 220 ++++++++ .../scala/hkmc2/codegen/cpp/CodeGen.scala | 240 ++++++++ .../hkmc2/codegen/cpp/CompilerHost.scala | 44 ++ .../scala/hkmc2/codegen/llir/Analysis.scala | 125 +++++ .../scala/hkmc2/codegen/llir/Builder.scala | 326 +++++++++++ .../main/scala/hkmc2/codegen/llir/Fresh.scala | 28 + .../scala/hkmc2/codegen/llir/Interp.scala | 211 +++++++ .../main/scala/hkmc2/codegen/llir/Llir.scala | 214 +++++++ .../hkmc2/codegen/llir/RefResolver.scala | 56 ++ .../scala/hkmc2/codegen/llir/Validator.scala | 45 ++ .../src/test/mlscript-compile/cpp/Makefile | 27 + .../test/mlscript-compile/cpp/mlsprelude.h | 529 ++++++++++++++++++ .../src/test/mlscript/llir/BadPrograms.mls | 27 + .../src/test/mlscript/llir/Playground.mls | 442 +++++++++++++++ 19 files changed, 2624 insertions(+), 4 deletions(-) create mode 100644 hkmc2/jvm/src/test/scala/hkmc2/LlirDiffMaker.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/Ast.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/CodeGen.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/CompilerHost.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Analysis.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Fresh.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Interp.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Llir.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/llir/RefResolver.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Validator.scala create mode 100644 hkmc2/shared/src/test/mlscript-compile/cpp/Makefile create mode 100644 hkmc2/shared/src/test/mlscript-compile/cpp/mlsprelude.h create mode 100644 hkmc2/shared/src/test/mlscript/llir/BadPrograms.mls create mode 100644 hkmc2/shared/src/test/mlscript/llir/Playground.mls diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 044e4df66e..6700307686 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -18,7 +18,7 @@ jobs: - name: Install TypeScript run: npm ci - name: Run test - run: sbt -J-Xmx4096M -J-Xss4M test + run: sbt -J-Xmx4096M -J-Xss8M test - name: Check no changes run: | git update-index -q --refresh diff --git a/flake.nix b/flake.nix index 5f81da24a5..8e657face9 100644 --- a/flake.nix +++ b/flake.nix @@ -11,7 +11,7 @@ (system: let sbtOverlay = self: super: { - sbt = super.sbt.override { jre = super.jdk8_headless; }; + sbt = super.sbt.override { jre = super.jdk11_headless; }; }; pkgs = import nixpkgs { inherit system; diff --git a/hkmc2/jvm/src/test/scala/hkmc2/DiffTestRunner.scala b/hkmc2/jvm/src/test/scala/hkmc2/DiffTestRunner.scala index 6390cf3c3e..5d770db337 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/DiffTestRunner.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/DiffTestRunner.scala @@ -10,7 +10,7 @@ import mlscript.utils._, shorthands._ class MainDiffMaker(val rootPath: Str, val file: os.Path, val preludeFile: os.Path, val predefFile: os.Path, val relativeName: Str) - extends BbmlDiffMaker + extends LlirDiffMaker diff --git a/hkmc2/jvm/src/test/scala/hkmc2/LlirDiffMaker.scala b/hkmc2/jvm/src/test/scala/hkmc2/LlirDiffMaker.scala new file mode 100644 index 0000000000..c79eda3dce --- /dev/null +++ b/hkmc2/jvm/src/test/scala/hkmc2/LlirDiffMaker.scala @@ -0,0 +1,73 @@ +package hkmc2 + +import scala.collection.mutable + +import mlscript.utils.*, shorthands.* +import utils.* + +import document.* +import codegen.Block +import codegen.llir.* +import codegen.cpp.* +import hkmc2.syntax.Tree.Ident +import hkmc2.codegen.Path +import hkmc2.semantics.Term.Blk +import hkmc2.codegen.llir.Fresh +import hkmc2.utils.Scope +import hkmc2.codegen.llir.Ctx +import hkmc2.codegen.llir._ + +abstract class LlirDiffMaker extends BbmlDiffMaker: + val llir = NullaryCommand("llir") + val cpp = NullaryCommand("cpp") + val sllir = NullaryCommand("sllir") + val scpp = NullaryCommand("scpp") + val rcpp = NullaryCommand("rcpp") + val intl = NullaryCommand("intl") + val wcpp = Command[Str]("wcpp", false)(x => x.stripLeading()) + + def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) = + val p = new java.io.PrintWriter(f) + try { op(p) } finally { p.close() } + + override def processTerm(trm: Blk, inImport: Bool)(using Raise): Unit = + super.processTerm(trm, inImport) + if llir.isSet then + val low = ltl.givenIn: + codegen.Lowering(false, None) + val le = low.program(trm) + given Scope = Scope.empty + val fresh = Fresh() + val fuid = FreshInt() + val cuid = FreshInt() + val llb = LlirBuilder(tl)(fresh, fuid, cuid) + given Ctx = Ctx.empty + try + val llirProg = llb.bProg(le) + if sllir.isSet then + output("LLIR:") + output(llirProg.show()) + if cpp.isSet || scpp.isSet || rcpp.isSet || wcpp.isSet then + val cpp = codegen.cpp.CppCodeGen.codegen(llirProg) + if scpp.isSet then + output("\nCpp:") + output(cpp.toDocument.toString) + val auxPath = os.Path(rootPath) / "hkmc2"/"shared"/"src"/"test"/"mlscript-compile"/"cpp" + if wcpp.isSet then + printToFile(java.io.File((auxPath / s"${wcpp.get.get}.cpp").toString)): + p => p.println(cpp.toDocument.toString) + if rcpp.isSet then + val cppHost = CppCompilerHost(auxPath.toString, output.apply) + if !cppHost.ready then + output("\nCpp Compilation Failed: Cpp compiler or GNU Make not found") + else + output("\n") + cppHost.compileAndRun(cpp.toDocument.toString) + if intl.isSet then + val intr = codegen.llir.Interpreter(verbose = true) + output("\nInterpreted:") + output(intr.interpret(llirProg)) + catch + case e: LowLevelIRError => + output("Stopped due to an error during the Llir generation") + diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala index b86f008df9..2015624b40 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala @@ -64,7 +64,20 @@ object Printer: case ValDefn(owner, k, sym, rhs) => doc"val ${sym.nme} = ${mkDocument(rhs)}" case ClsLikeDefn(sym, k, parentSym, methods, privateFields, publicFields, preCtor, ctor) => - doc"class ${sym.nme} #{ #} " + def optFldBody(t: semantics.TermDefinition) = + t.body match + case Some(x) => doc" = ..." + case None => doc"" + val clsDefn = sym.defn.getOrElse(die) + val clsParams = clsDefn.paramsOpt.fold(Nil)(_.paramSyms) + val ctorParams = clsParams.map(p => summon[Scope].allocateName(p)) + val privFields = privateFields.map(x => doc"let ${x.id.name} = ...").mkDocument(sep = doc" # ") + val pubFields = publicFields.map(x => doc"${x.k.str} ${x.sym.nme}${optFldBody(x)}").mkDocument(sep = doc" # ") + val docPrivFlds = if privateFields.isEmpty then doc"" else doc" # ${privFields}" + val docPubFlds = if publicFields.isEmpty then doc"" else doc" # ${pubFields}" + val docBody = if publicFields.isEmpty && privateFields.isEmpty then doc"" else doc" { #{ ${docPrivFlds}${docPubFlds} #} # }" + val docCtorParams = if clsParams.isEmpty then doc"" else doc"(${ctorParams.mkString(", ")})" + doc"class ${sym.nme}${docCtorParams}${docBody}" def mkDocument(arg: Arg)(using Raise, Scope): Document = val doc = mkDocument(arg.value) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/Ast.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/Ast.scala new file mode 100644 index 0000000000..c05e4f1d3e --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/Ast.scala @@ -0,0 +1,220 @@ +package hkmc2.codegen.cpp + +import mlscript._ +import mlscript.utils._ +import mlscript.utils.shorthands._ + +import hkmc2.Message.MessageContext +import hkmc2.document._ + +import scala.language.implicitConversions + +private def raw(x: String): Document = doc"$x" +given Conversion[String, Document] = x => doc"$x" + +enum Specifier: + case Extern + case Static + case Inline + + def toDocument = + this match + case Extern => "extern" + case Static => "static" + case Inline => "inline" + + override def toString: Str = toDocument + +object Type: + def toDocuments(args: Ls[Type], sep: Document, extraTypename: Bool = false): Document = + args.map(_.toDocument(extraTypename)).mkDocument(sep) + + def toDocuments(args: Ls[(Str, Type)], sep: Document): Document = + args.map(x => doc"${x._2.toDocument()} ${x._1}").mkDocument(sep) + +enum Type: + case Prim(name: Str) + case Ptr(inner: Type) + case Ref(inner: Type) + case Array(inner: Type, size: Opt[Int]) + case FuncPtr(ret: Type, args: List[Type]) + case Struct(name: Str) + case Enum(name: Str) + case Template(name: Str, args: List[Type]) + case Var(name: Str) + case Qualifier(inner: Type, qual: Str) + + def toDocument(extraTypename: Bool = false): Document = + def aux(x: Type): Document = x match + case Prim(name) => name + case Ptr(inner) => doc"${aux(inner)}*" + case Ref(inner) => doc"${aux(inner)}&" + case Array(inner, size) => + doc"${aux(inner)}[${size.fold("")(x => x.toString)}]" + case FuncPtr(ret, args) => + doc"${aux(ret)}(${Type.toDocuments(args, sep = ", ")})" + case Struct(name) => doc"struct $name" + case Enum(name) => doc"enum $name" + case Template(name, args) => + doc"$name<${Type.toDocuments(args, sep = ", ")}>" + case Var(name) => name + case Qualifier(inner, qual) => doc"${aux(inner)} $qual" + aux(this) + + override def toString: Str = toDocument().toString + +object Stmt: + def toDocuments(decl: Ls[Decl], stmts: Ls[Stmt]): Document = + (decl.map(_.toDocument) ++ stmts.map(_.toDocument)).mkDocument(doc" # ") + +enum Stmt: + case AutoBind(lhs: Ls[Str], rhs: Expr) + case Assign(lhs: Str, rhs: Expr) + case Return(expr: Expr) + case If(cond: Expr, thenStmt: Stmt, elseStmt: Opt[Stmt]) + case While(cond: Expr, body: Stmt) + case For(init: Stmt, cond: Expr, update: Stmt, body: Stmt) + case ExprStmt(expr: Expr) + case Break + case Continue + case Block(decl: Ls[Decl], stmts: Ls[Stmt]) + case Switch(expr: Expr, cases: Ls[(Expr, Stmt)]) + case Raw(stmt: Str) + + def toDocument: Document = + def aux(x: Stmt): Document = x match + case AutoBind(lhs, rhs) => + lhs match + case Nil => rhs.toDocument + case x :: Nil => + doc"auto $x = ${rhs.toDocument};" + case _ => + doc"auto [${lhs.mkDocument(", ")}] = ${rhs.toDocument};" + case Assign(lhs, rhs) => + doc"$lhs = ${rhs.toDocument};" + case Return(expr) => + doc"return ${expr.toDocument};" + case If(cond, thenStmt, elseStmt) => + doc"if (${cond.toDocument}) ${thenStmt.toDocument}${elseStmt.fold(doc" ")(x => doc" else ${x.toDocument}")}" + case While(cond, body) => + doc"while (${cond.toDocument}) ${body.toDocument}" + case For(init, cond, update, body) => + doc"for (${init.toDocument}; ${cond.toDocument}; ${update.toDocument}) ${body.toDocument}" + case ExprStmt(expr) => + doc"${expr.toDocument};" + case Break => "break;" + case Continue => "continue;" + case Block(decl, stmts) => + doc"{ #{ # ${Stmt.toDocuments(decl, stmts)} #} # }" + case Switch(expr, cases) => + val docCases = cases.map { + case (cond, stmt) => doc"case ${cond.toDocument}: ${stmt.toDocument}" + }.mkDocument(doc" # ") + doc"switch (${expr.toDocument}) { #{ # ${docCases} #} # }" + case Raw(stmt) => stmt + aux(this) + +object Expr: + def toDocuments(args: Ls[Expr], sep: Document): Document = + args.map(_.toDocument).mkDocument(sep) + +enum Expr: + case Var(name: Str) + case IntLit(value: BigInt) + case DoubleLit(value: Double) + case StrLit(value: Str) + case CharLit(value: Char) + case Call(func: Expr, args: Ls[Expr]) + case Member(expr: Expr, member: Str) + case Index(expr: Expr, index: Expr) + case Unary(op: Str, expr: Expr) + case Binary(op: Str, lhs: Expr, rhs: Expr) + case Initializer(exprs: Ls[Expr]) + case Constructor(name: Str, init: Expr) + + def toDocument: Document = + def aux(x: Expr): Document = x match + case Var(name) => name + case IntLit(value) => value.toString + case DoubleLit(value) => value.toString + case StrLit(value) => s"\"$value\"" // need more reliable escape utils + case CharLit(value) => value.toInt.toString + case Call(func, args) => + doc"${func.toDocument}(${Expr.toDocuments(args, sep = ", ")})" + case Member(expr, member) => + doc"${expr.toDocument}->$member" + case Index(expr, index) => + doc"${expr.toDocument}[${index.toDocument}]" + case Unary(op, expr) => + doc"($op${expr.toDocument})" + case Binary(op, lhs, rhs) => + doc"(${lhs.toDocument} $op ${rhs.toDocument})" + case Initializer(exprs) => + doc"{${Expr.toDocuments(exprs, sep = ", ")}}" + case Constructor(name, init) => + doc"$name(${init.toDocument})" + aux(this) + +case class CompilationUnit(includes: Ls[Str], decls: Ls[Decl], defs: Ls[Def]): + def toDocument: Document = + (includes.map(raw) ++ decls.map(_.toDocument) ++ defs.map(_.toDocument)).mkDocument(doc" # ") + def toDocumentWithoutHidden: Document = + val hiddenNames: Set[Str] = Set() + defs.filterNot { + case Def.StructDef(name, _, _, _) => hiddenNames.contains(name.stripPrefix("_mls_")) + case _ => false + }.map(_.toDocument).mkDocument(doc" # ") + +enum Decl: + case StructDecl(name: Str) + case EnumDecl(name: Str) + case FuncDecl(ret: Type, name: Str, args: Ls[Type]) + case VarDecl(name: Str, typ: Type) + + def toDocument: Document = + def aux(x: Decl): Document = x match + case StructDecl(name) => doc"struct $name;" + case EnumDecl(name) => doc"enum $name;" + case FuncDecl(ret, name, args) => + doc"${ret.toDocument()} $name(${Type.toDocuments(args, sep = ", ")});" + case VarDecl(name, typ) => + doc"${typ.toDocument()} $name;" + aux(this) + +enum Def: + case StructDef(name: Str, fields: Ls[(Str, Type)], inherit: Opt[Ls[Str]], methods: Ls[Def] = Ls.empty) + case EnumDef(name: Str, fields: Ls[(Str, Opt[Int])]) + case FuncDef(specret: Type, name: Str, args: Ls[(Str, Type)], body: Stmt.Block, or: Bool = false, virt: Bool = false) + case VarDef(typ: Type, name: Str, init: Opt[Expr]) + case RawDef(raw: Str) + + def toDocument: Document = + def aux(x: Def): Document = x match + case StructDef(name, fields, inherit, defs) => + val docFirst = doc"struct $name${inherit.fold(doc"")(x => doc": public ${x.mkDocument(doc", ")}")} {" + val docFields = fields.map { + case (name, typ) => doc"${typ.toDocument()} $name;" + }.mkDocument(doc" # ") + val docDefs = defs.map(_.toDocument).mkDocument(doc" # ") + val docLast = "};" + doc"$docFirst #{ # $docFields # $docDefs #} # $docLast" + case EnumDef(name, fields) => + val docFirst = doc"enum $name {" + val docFields = fields.map { + case (name, value) => value.fold(s"$name")(x => s"$name = $x") + }.mkDocument(doc" # ") + val docLast = "};" + doc"$docFirst #{ # $docFields #} # $docLast" + case FuncDef(specret, name, args, body, or, virt) => + val docVirt = (if virt then doc"virtual " else doc"") + val docSpecRet = specret.toDocument() + val docArgs = Type.toDocuments(args, sep = ", ") + val docOverride = if or then doc" override" else doc"" + val docBody = body.toDocument + doc"$docVirt$docSpecRet $name($docArgs)$docOverride ${body.toDocument}" + case VarDef(typ, name, init) => + val docTyp = typ.toDocument() + val docInit = init.fold(raw(""))(x => doc" = ${x.toDocument}") + doc"$docTyp $name$docInit;" + case RawDef(x) => x + aux(this) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/CodeGen.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/CodeGen.scala new file mode 100644 index 0000000000..80f83ae7c3 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/CodeGen.scala @@ -0,0 +1,240 @@ +package hkmc2.codegen.cpp + +import mlscript.utils._ +import mlscript.utils.shorthands._ +import scala.collection.mutable.ListBuffer + +import hkmc2.codegen.llir.{Expr => IExpr, _} +import hkmc2.codegen.cpp._ + +object CppCodeGen: + def mapName(name: Name): Str = "_mls_" + name.str.replace('$', '_').replace('\'', '_') + def mapName(name: Str): Str = "_mls_" + name.replace('$', '_').replace('\'', '_') + val freshName = Fresh(div = '_'); + val mlsValType = Type.Prim("_mlsValue") + val mlsUnitValue = Expr.Call(Expr.Var("_mlsValue::create<_mls_Unit>"), Ls()); + val mlsRetValue = "_mls_retval" + val mlsRetValueDecl = Decl.VarDecl(mlsRetValue, mlsValType) + val mlsMainName = "_mlsMain" + val mlsPrelude = "#include \"mlsprelude.h\"" + val mlsPreludeImpl = "#include \"mlsprelude.cpp\"" + val mlsInternalClass = Set("True", "False", "Boolean", "Callable") + val mlsObject = "_mlsObject" + val mlsBuiltin = "builtin" + val mlsEntryPoint = s"int main() { return _mlsLargeStack(_mlsMainWrapper); }"; + def mlsIntLit(x: BigInt) = Expr.Call(Expr.Var("_mlsValue::fromIntLit"), Ls(Expr.IntLit(x))) + def mlsStrLit(x: Str) = Expr.Call(Expr.Var("_mlsValue::fromStrLit"), Ls(Expr.StrLit(x))) + def mlsCharLit(x: Char) = Expr.Call(Expr.Var("_mlsValue::fromIntLit"), Ls(Expr.CharLit(x))) + def mlsNewValue(cls: Str, args: Ls[Expr]) = Expr.Call(Expr.Var(s"_mlsValue::create<$cls>"), args) + def mlsIsValueOf(cls: Str, scrut: Expr) = Expr.Call(Expr.Var(s"_mlsValue::isValueOf<$cls>"), Ls(scrut)) + def mlsIsBoolLit(scrut: Expr, lit: hkmc2.syntax.Tree.BoolLit) = Expr.Call(Expr.Var("_mlsValue::isIntLit"), Ls(scrut, Expr.IntLit(if lit.value then 1 else 0))) + def mlsIsIntLit(scrut: Expr, lit: hkmc2.syntax.Tree.IntLit) = Expr.Call(Expr.Var("_mlsValue::isIntLit"), Ls(scrut, Expr.IntLit(lit.value))) + def mlsDebugPrint(x: Expr) = Expr.Call(Expr.Var("_mlsValue::print"), Ls(x)) + def mlsTupleValue(init: Expr) = Expr.Constructor("_mlsValue::tuple", init) + def mlsAs(name: Str, cls: Str) = Expr.Var(s"_mlsValue::as<$cls>($name)") + def mlsAsUnchecked(name: Str, cls: Str) = Expr.Var(s"_mlsValue::cast<$cls>($name)") + def mlsObjectNameMethod(name: Str) = s"constexpr static inline const char *typeName = \"${name}\";" + def mlsTypeTag() = s"constexpr static inline uint32_t typeTag = nextTypeTag();" + def mlsTypeTag(n: Int) = s"constexpr static inline uint32_t typeTag = $n;" + def mlsCommonCreateMethod(cls: Str, fields: Ls[Str], id: Int) = + val parameters = fields.map{x => s"_mlsValue $x"}.mkString(", ") + val fieldsAssignment = fields.map{x => s"_mlsVal->$x = $x; "}.mkString + s"static _mlsValue create($parameters) { auto _mlsVal = new (std::align_val_t(_mlsAlignment)) $cls; _mlsVal->refCount = 1; _mlsVal->tag = typeTag; $fieldsAssignment return _mlsValue(_mlsVal); }" + def mlsCommonPrintMethod(fields: Ls[Str]) = + if fields.isEmpty then s"virtual void print() const override { std::printf(\"%s\", typeName); }" + else + val fieldsPrint = fields.map{x => s"this->$x.print(); "}.mkString("std::printf(\", \"); ") + s"virtual void print() const override { std::printf(\"%s\", typeName); std::printf(\"(\"); $fieldsPrint std::printf(\")\"); }" + def mlsCommonDestructorMethod(cls: Str, fields: Ls[Str]) = + val fieldsDeletion = fields.map{x => s"_mlsValue::destroy(this->$x); "}.mkString + s"virtual void destroy() override { $fieldsDeletion operator delete (this, std::align_val_t(_mlsAlignment)); }" + def mlsThrowNonExhaustiveMatch = Stmt.Raw("_mlsNonExhaustiveMatch();"); + def mlsCall(fn: Str, args: Ls[Expr]) = Expr.Call(Expr.Var("_mlsCall"), Expr.Var(fn) :: args) + def mlsMethodCall(cls: ClassRef, method: Str, args: Ls[Expr]) = + Expr.Call(Expr.Member(Expr.Call(Expr.Var(s"_mlsMethodCall<${cls.name |> mapName}>"), Ls(args.head)), method), args.tail) + def mlsFnWrapperName(fn: Str) = s"_mlsFn_$fn" + def mlsFnCreateMethod(fn: Str) = s"static _mlsValue create() { static _mlsFn_$fn mlsFn alignas(_mlsAlignment); mlsFn.refCount = stickyRefCount; mlsFn.tag = typeTag; return _mlsValue(&mlsFn); }" + def mlsNeverValue(n: Int) = if (n <= 1) then Expr.Call(Expr.Var(s"_mlsValue::never"), Ls()) else Expr.Call(Expr.Var(s"_mlsValue::never<$n>"), Ls()) + + case class Ctx( + defnCtx: Set[Str], + ) + + def codegenClassInfo(using ctx: Ctx)(cls: ClassInfo): (Opt[Def], Decl) = + val fields = cls.fields.map{x => (x |> mapName, mlsValType)} + val parents = if cls.parents.nonEmpty then cls.parents.toList.map(mapName) else mlsObject :: Nil + val decl = Decl.StructDecl(cls.name |> mapName) + if mlsInternalClass.contains(cls.name) then return (None, decl) + val theDef = Def.StructDef( + cls.name |> mapName, fields, + if parents.nonEmpty then Some(parents) else None, + Ls(Def.RawDef(mlsObjectNameMethod(cls.name)), + Def.RawDef(mlsTypeTag()), + Def.RawDef(mlsCommonPrintMethod(cls.fields.map(mapName))), + Def.RawDef(mlsCommonDestructorMethod(cls.name |> mapName, cls.fields.map(mapName))), + Def.RawDef(mlsCommonCreateMethod(cls.name |> mapName, cls.fields.map(mapName), cls.id))) + ++ cls.methods.map{case (name, defn) => { + val (theDef, decl) = codegenDefn(using Ctx(ctx.defnCtx + cls.name))(defn) + theDef match + case x @ Def.FuncDef(_, name, _, _, _, _) => x.copy(virt = true) + case _ => theDef + }} + ) + (S(theDef), decl) + + def toExpr(texpr: TrivialExpr, reifyUnit: Bool = false)(using ctx: Ctx): Opt[Expr] = texpr match + case IExpr.Ref(name) => S(Expr.Var(name |> mapName)) + case IExpr.Literal(hkmc2.syntax.Tree.BoolLit(x)) => S(mlsIntLit(if x then 1 else 0)) + case IExpr.Literal(hkmc2.syntax.Tree.IntLit(x)) => S(mlsIntLit(x)) + case IExpr.Literal(hkmc2.syntax.Tree.DecLit(x)) => S(mlsIntLit(x.toBigInt)) + case IExpr.Literal(hkmc2.syntax.Tree.StrLit(x)) => S(mlsStrLit(x)) + case IExpr.Literal(hkmc2.syntax.Tree.UnitLit(_)) => if reifyUnit then S(mlsUnitValue) else None + + def toExpr(texpr: TrivialExpr)(using ctx: Ctx): Expr = texpr match + case IExpr.Ref(name) => Expr.Var(name |> mapName) + case IExpr.Literal(hkmc2.syntax.Tree.BoolLit(x)) => mlsIntLit(if x then 1 else 0) + case IExpr.Literal(hkmc2.syntax.Tree.IntLit(x)) => mlsIntLit(x) + case IExpr.Literal(hkmc2.syntax.Tree.DecLit(x)) => mlsIntLit(x.toBigInt) + case IExpr.Literal(hkmc2.syntax.Tree.StrLit(x)) => mlsStrLit(x) + case IExpr.Literal(hkmc2.syntax.Tree.UnitLit(_)) => mlsUnitValue + + + def wrapMultiValues(exprs: Ls[TrivialExpr])(using ctx: Ctx): Expr = exprs match + case x :: Nil => toExpr(x, reifyUnit = true).get + case _ => + val init = Expr.Initializer(exprs.map{x => toExpr(x)}) + mlsTupleValue(init) + + def codegenCaseWithIfs(scrut: TrivialExpr, cases: Ls[(Pat, Node)], default: Opt[Node], storeInto: Str)(using decls: Ls[Decl], stmts: Ls[Stmt])(using ctx: Ctx): (Ls[Decl], Ls[Stmt]) = + val scrut2 = toExpr(scrut) + val init: Stmt = + default.fold(mlsThrowNonExhaustiveMatch)(x => { + val (decls2, stmts2) = codegen(x, storeInto)(using Ls.empty, Ls.empty[Stmt]) + Stmt.Block(decls2, stmts2) + }) + val stmt = cases.foldRight(S(init)) { + case ((Pat.Class(cls), arm), nextarm) => + val (decls2, stmts2) = codegen(arm, storeInto)(using Ls.empty, Ls.empty[Stmt]) + val stmt = Stmt.If(mlsIsValueOf(cls.name |> mapName, scrut2), Stmt.Block(decls2, stmts2), nextarm) + S(stmt) + case ((Pat.Lit(i @ hkmc2.syntax.Tree.IntLit(_)), arm), nextarm) => + val (decls2, stmts2) = codegen(arm, storeInto)(using Ls.empty, Ls.empty[Stmt]) + val stmt = Stmt.If(mlsIsIntLit(scrut2, i), Stmt.Block(decls2, stmts2), nextarm) + S(stmt) + case ((Pat.Lit(i @ hkmc2.syntax.Tree.BoolLit(_)), arm), nextarm) => + val (decls2, stmts2) = codegen(arm, storeInto)(using Ls.empty, Ls.empty[Stmt]) + val stmt = Stmt.If(mlsIsBoolLit(scrut2, i), Stmt.Block(decls2, stmts2), nextarm) + S(stmt) + case _ => ??? + } + (decls, stmt.fold(stmts)(x => stmts :+ x)) + + def codegenJumpWithCall(func: FuncRef, args: Ls[TrivialExpr], storeInto: Opt[Str])(using decls: Ls[Decl], stmts: Ls[Stmt])(using ctx: Ctx): (Ls[Decl], Ls[Stmt]) = + val call = Expr.Call(Expr.Var(func.name |> mapName), args.map(toExpr)) + val stmts2 = stmts ++ Ls(storeInto.fold(Stmt.Return(call))(x => Stmt.Assign(x, call))) + (decls, stmts2) + + def codegenOps(op: Str, args: Ls[TrivialExpr])(using ctx: Ctx) = op match + case "+" => Expr.Binary("+", toExpr(args(0)), toExpr(args(1))) + case "-" => Expr.Binary("-", toExpr(args(0)), toExpr(args(1))) + case "*" => Expr.Binary("*", toExpr(args(0)), toExpr(args(1))) + case "/" => Expr.Binary("/", toExpr(args(0)), toExpr(args(1))) + case "%" => Expr.Binary("%", toExpr(args(0)), toExpr(args(1))) + case "==" => Expr.Binary("==", toExpr(args(0)), toExpr(args(1))) + case "!=" => Expr.Binary("!=", toExpr(args(0)), toExpr(args(1))) + case "<" => Expr.Binary("<", toExpr(args(0)), toExpr(args(1))) + case "<=" => Expr.Binary("<=", toExpr(args(0)), toExpr(args(1))) + case ">" => Expr.Binary(">", toExpr(args(0)), toExpr(args(1))) + case ">=" => Expr.Binary(">=", toExpr(args(0)), toExpr(args(1))) + case "&&" => Expr.Binary("&&", toExpr(args(0)), toExpr(args(1))) + case "||" => Expr.Binary("||", toExpr(args(0)), toExpr(args(1))) + case "!" => Expr.Unary("!", toExpr(args(0))) + case _ => TODO("codegenOps") + + + def codegen(expr: IExpr)(using ctx: Ctx): Expr = expr match + case x @ (IExpr.Ref(_) | IExpr.Literal(_)) => toExpr(x, reifyUnit = true).get + case IExpr.CtorApp(cls, args) => mlsNewValue(cls.name |> mapName, args.map(toExpr)) + case IExpr.Select(name, cls, field) => Expr.Member(mlsAsUnchecked(name |> mapName, cls.name |> mapName), field |> mapName) + case IExpr.BasicOp(name, args) => codegenOps(name, args) + case IExpr.AssignField(assignee, cls, field, value) => TODO("codegen assign field") + + def codegenBuiltin(names: Ls[Name], builtin: Str, args: Ls[TrivialExpr])(using ctx: Ctx): Ls[Stmt] = builtin match + case "error" => Ls(Stmt.Raw("throw std::runtime_error(\"Error\");"), Stmt.AutoBind(names.map(mapName), mlsNeverValue(names.size))) + case _ => Ls(Stmt.AutoBind(names.map(mapName), Expr.Call(Expr.Var("_mls_builtin_" + builtin), args.map(toExpr)))) + + def codegen(body: Node, storeInto: Str)(using decls: Ls[Decl], stmts: Ls[Stmt])(using ctx: Ctx): (Ls[Decl], Ls[Stmt]) = body match + case Node.Result(res) => + val expr = wrapMultiValues(res) + val stmts2 = stmts ++ Ls(Stmt.Assign(storeInto, expr)) + (decls, stmts2) + case Node.Jump(defn, args) => + codegenJumpWithCall(defn, args, S(storeInto)) + case Node.Panic(msg) => (decls, stmts :+ Stmt.Raw(s"throw std::runtime_error(\"$msg\");")) + case Node.LetExpr(name, expr, body) => + val stmts2 = stmts ++ Ls(Stmt.AutoBind(Ls(name |> mapName), codegen(expr))) + codegen(body, storeInto)(using decls, stmts2) + case Node.LetMethodCall(names, cls, method, IExpr.Ref(Name("builtin")) :: args, body) => + val stmts2 = stmts ++ codegenBuiltin(names, args.head.toString.replace("\"", ""), args.tail) + codegen(body, storeInto)(using decls, stmts2) + case Node.LetMethodCall(names, cls, method, args, body) => + val call = mlsMethodCall(cls, method.str |> mapName, args.map(toExpr)) + val stmts2 = stmts ++ Ls(Stmt.AutoBind(names.map(mapName), call)) + codegen(body, storeInto)(using decls, stmts2) + case Node.LetCall(names, defn, args, body) => + val call = Expr.Call(Expr.Var(defn.name |> mapName), args.map(toExpr)) + val stmts2 = stmts ++ Ls(Stmt.AutoBind(names.map(mapName), call)) + codegen(body, storeInto)(using decls, stmts2) + case Node.Case(scrut, cases, default) => + codegenCaseWithIfs(scrut, cases, default, storeInto) + + def codegenDefn(using ctx: Ctx)(defn: Func): (Def, Decl) = defn match + case Func(id, name, params, resultNum, body) => + val decls = Ls(mlsRetValueDecl) + val stmts = Ls.empty[Stmt] + val (decls2, stmts2) = codegen(body, mlsRetValue)(using decls, stmts) + val stmtsWithReturn = stmts2 :+ Stmt.Return(Expr.Var(mlsRetValue)) + val theDef = Def.FuncDef(mlsValType, name |> mapName, params.map(x => (x |> mapName, mlsValType)), Stmt.Block(decls2, stmtsWithReturn)) + val decl = Decl.FuncDecl(mlsValType, name |> mapName, params.map(x => mlsValType)) + (theDef, decl) + + def codegenTopNode(node: Node)(using ctx: Ctx): (Def, Decl) = + val decls = Ls(mlsRetValueDecl) + val stmts = Ls.empty[Stmt] + val (decls2, stmts2) = codegen(node, mlsRetValue)(using decls, stmts) + val stmtsWithReturn = stmts2 :+ Stmt.Return(Expr.Var(mlsRetValue)) + val theDef = Def.FuncDef(mlsValType, mlsMainName, Ls(), Stmt.Block(decls2, stmtsWithReturn)) + val decl = Decl.FuncDecl(mlsValType, mlsMainName, Ls()) + (theDef, decl) + + // Topological sort of classes based on inheritance relationships + def sortClasses(prog: Program): Ls[ClassInfo] = + var depgraph = prog.classes.map(x => (x.name, x.parents)).toMap + var degree = depgraph.view.mapValues(_.size).toMap + def removeNode(node: Str) = + degree -= node + depgraph -= node + depgraph = depgraph.view.mapValues(_.filter(_ != node)).toMap + degree = depgraph.view.mapValues(_.size).toMap + val sorted = ListBuffer.empty[ClassInfo] + var work = degree.filter(_._2 == 0).keys.toSet + while work.nonEmpty do + val node = work.head + work -= node + sorted.addOne(prog.classes.find(_.name == node).get) + removeNode(node) + val next = degree.filter(_._2 == 0).keys + work ++= next + if depgraph.nonEmpty then + val cycle = depgraph.keys.mkString(", ") + throw new Exception(s"Cycle detected in class hierarchy: $cycle") + sorted.toList + + def codegen(prog: Program): CompilationUnit = + val sortedClasses = sortClasses(prog) + val defnCtx = prog.defs.map(_.name) + val (defs, decls) = sortedClasses.map(codegenClassInfo(using Ctx(defnCtx))).unzip + val (defs2, decls2) = prog.defs.map(codegenDefn(using Ctx(defnCtx))).unzip + val (defMain, declMain) = codegenTopNode(prog.main)(using Ctx(defnCtx)) + CompilationUnit(Ls(mlsPrelude), decls ++ decls2 :+ declMain, defs.flatten ++ defs2 :+ defMain :+ Def.RawDef(mlsEntryPoint)) + diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/CompilerHost.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/CompilerHost.scala new file mode 100644 index 0000000000..867ffe4e65 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/CompilerHost.scala @@ -0,0 +1,44 @@ +package hkmc2.codegen.cpp + +import mlscript._ +import mlscript.utils.shorthands._ +import scala.collection.mutable.ListBuffer + +final class CppCompilerHost(val auxPath: Str, val output: Str => Unit): + import scala.sys.process._ + private def ifAnyCppCompilerExists(): Boolean = + Seq("g++", "--version").! == 0 || Seq("clang++", "--version").! == 0 + + private def isMakeExists(): Boolean = + import scala.sys.process._ + Seq("make", "--version").! == 0 + + val ready = ifAnyCppCompilerExists() && isMakeExists() + + def compileAndRun(src: Str): Unit = + if !ready then + return + val srcPath = os.temp(contents = src, suffix = ".cpp") + val binPath = os.temp(suffix = ".mls.out") + var stdout = ListBuffer[Str]() + var stderr = ListBuffer[Str]() + val buildLogger = ProcessLogger(stdout :+= _, stderr :+= _) + val buildResult = Seq("make", "-B", "-C", auxPath, "auto", s"SRC=$srcPath", s"DST=$binPath") ! buildLogger + if buildResult != 0 then + output("Compilation failed: ") + for line <- stdout do output(line) + for line <- stderr do output(line) + return + + stdout.clear() + stderr.clear() + val runCmd = Seq(binPath.toString) + val runResult = runCmd ! buildLogger + if runResult != 0 then + output("Execution failed: ") + for line <- stdout do output(line) + for line <- stderr do output(line) + return + + output("Execution succeeded: ") + for line <- stdout do output(line) \ No newline at end of file diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Analysis.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Analysis.scala new file mode 100644 index 0000000000..042295874a --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Analysis.scala @@ -0,0 +1,125 @@ +package hkmc2.codegen.llir + +import mlscript._ +import hkmc2.codegen._ +import hkmc2.codegen.llir.{ Program => LlirProgram, Node, Func } +import mlscript.utils._ +import mlscript.utils.shorthands._ +import hkmc2.semantics.BuiltinSymbol +import hkmc2.syntax.Tree.UnitLit +import hkmc2.{Raise, raise, Diagnostic, ErrorReport, Message} +import hkmc2.Message.MessageContext +import hkmc2.semantics.InnerSymbol +import hkmc2.codegen.llir.FuncRef.fromName +import scala.collection.mutable.ListBuffer + +import scala.annotation.tailrec +import scala.collection.immutable.* +import scala.collection.mutable.{HashMap => MutHMap} +import scala.collection.mutable.{HashSet => MutHSet, Set => MutSet} + +class UsefulnessAnalysis(verbose: Bool = false): + import Expr._ + import Node._ + + def log(x: Any) = if verbose then println(x) + + val uses = MutHMap[(Name, Int), Int]() + val defs = MutHMap[Name, Int]() + + private def addDef(x: Name) = + defs.update(x, defs.getOrElse(x, 0) + 1) + + private def addUse(x: Name) = + val def_count = defs.get(x) match + case None => throw Exception(s"Use of undefined variable $x") + case Some(value) => value + val key = (x, defs(x)) + uses.update(key, uses.getOrElse(key, 0) + 1) + + private def f(x: TrivialExpr): Unit = x match + case Ref(name) => addUse(name) + case _ => () + + private def f(x: Expr): Unit = x match + case Ref(name) => addUse(name) + case Literal(lit) => + case CtorApp(name, args) => args.foreach(f) + case Select(name, cls, field) => addUse(name) + case BasicOp(name, args) => args.foreach(f) + case AssignField(assignee, _, _, value) => + addUse(assignee) + f(value) + + private def f(x: Node): Unit = x match + case Result(res) => res.foreach(f) + case Jump(defn, args) => args.foreach(f) + case Case(scrut, cases, default) => + scrut match + case Ref(name) => addUse(name) + case _ => () + cases.foreach { case (cls, body) => f(body) }; default.foreach(f) + case LetMethodCall(names, cls, method, args, body) => addUse(method); args.foreach(f); names.foreach(addDef); f(body) + case LetExpr(name, expr, body) => f(expr); addDef(name); f(body) + case LetCall(names, defn, args, body) => args.foreach(f); names.foreach(addDef); f(body) + + def run(x: Func) = + x.params.foreach(addDef) + f(x.body) + uses.toMap + +class FreeVarAnalysis(extended_scope: Bool = true, verbose: Bool = false): + import Expr._ + import Node._ + + private val visited = MutHSet[Str]() + private def f(using defined: Set[Str])(defn: Func, fv: Set[Str]): Set[Str] = + val defined2 = defn.params.foldLeft(defined)((acc, param) => acc + param.str) + f(using defined2)(defn.body, fv) + private def f(using defined: Set[Str])(expr: Expr, fv: Set[Str]): Set[Str] = expr match + case Ref(name) => if defined.contains(name.str) then fv else fv + name.str + case Literal(lit) => fv + case CtorApp(name, args) => args.foldLeft(fv)((acc, arg) => f(using defined)(arg.toExpr, acc)) + case Select(name, cls, field) => if defined.contains(name.str) then fv else fv + name.str + case BasicOp(name, args) => args.foldLeft(fv)((acc, arg) => f(using defined)(arg.toExpr, acc)) + case AssignField(assignee, _, _, value) => f(using defined)( + value.toExpr, + if defined.contains(assignee.str) then fv + assignee.str else fv + ) + private def f(using defined: Set[Str])(node: Node, fv: Set[Str]): Set[Str] = node match + case Result(res) => res.foldLeft(fv)((acc, arg) => f(using defined)(arg.toExpr, acc)) + case Jump(defnref, args) => + var fv2 = args.foldLeft(fv)((acc, arg) => f(using defined)(arg.toExpr, acc)) + if extended_scope && !visited.contains(defnref.name) then + val defn = defnref.expectFn + visited.add(defn.name) + val defined2 = defn.params.foldLeft(defined)((acc, param) => acc + param.str) + fv2 = f(using defined2)(defn, fv2) + fv2 + case Case(scrut, cases, default) => + val fv2 = scrut match + case Ref(name) => if defined.contains(name.str) then fv else fv + name.str + case _ => fv + val fv3 = cases.foldLeft(fv2) { + case (acc, (cls, body)) => f(using defined)(body, acc) + } + fv3 + case LetMethodCall(resultNames, cls, method, args, body) => + var fv2 = args.foldLeft(fv)((acc, arg) => f(using defined)(arg.toExpr, acc)) + val defined2 = resultNames.foldLeft(defined)((acc, name) => acc + name.str) + f(using defined2)(body, fv2) + case LetExpr(name, expr, body) => + val fv2 = f(using defined)(expr, fv) + val defined2 = defined + name.str + f(using defined2)(body, fv2) + case LetCall(resultNames, defnref, args, body) => + var fv2 = args.foldLeft(fv)((acc, arg) => f(using defined)(arg.toExpr, acc)) + val defined2 = resultNames.foldLeft(defined)((acc, name) => acc + name.str) + if extended_scope && !visited.contains(defnref.name) then + val defn = defnref.expectFn + visited.add(defn.name) + val defined2 = defn.params.foldLeft(defined)((acc, param) => acc + param.str) + fv2 = f(using defined2)(defn, fv2) + f(using defined2)(body, fv2) + def run(node: Node) = f(using Set.empty)(node, Set.empty) + def run_with(node: Node, defined: Set[Str]) = f(using defined)(node, Set.empty) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala new file mode 100644 index 0000000000..06caaffced --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala @@ -0,0 +1,326 @@ +package hkmc2 +package codegen +package llir + +import scala.collection.mutable.ListBuffer + +import mlscript.utils.* +import mlscript.utils.shorthands.* +import hkmc2.document.* +import hkmc2.utils.Scope +import hkmc2.utils.TraceLogger +import hkmc2.Message.MessageContext + +import hkmc2.syntax.Tree +import hkmc2.semantics.* +import hkmc2.codegen.llir.{ Program => LlirProgram, Node, Func } +import FuncRef.fromName +import hkmc2.codegen.Program + + +def err(msg: Message)(using Raise): Unit = + raise(ErrorReport(msg -> N :: Nil, + source = Diagnostic.Source.Compilation)) + +def errStop(msg: Message)(using Raise) = + err(msg) + throw LowLevelIRError("stopped") + +final case class Ctx( + def_acc: ListBuffer[Func], + class_acc: ListBuffer[ClassInfo], + symbol_ctx: Map[Str, Name] = Map.empty, + fn_ctx: Map[Local, Name] = Map.empty, // is a known function + class_ctx: Map[Local, Name] = Map.empty, + block_ctx: Map[Local, Name] = Map.empty, + is_top_level: Bool = true, +): + def addFuncName(n: Local, m: Name) = copy(fn_ctx = fn_ctx + (n -> m)) + def findFuncName(n: Local)(using Raise) = fn_ctx.get(n) match + case None => + errStop(msg"Function name not found: ${n.toString()}") + Name("error") + case Some(value) => value + def addClassName(n: Local, m: Name) = copy(class_ctx = class_ctx + (n -> m)) + def findClassName(n: Local)(using Raise) = class_ctx.get(n) match + case None => + errStop(msg"Class name not found: ${n.toString()}") + Name("error") + case Some(value) => value + def addName(n: Str, m: Name) = copy(symbol_ctx = symbol_ctx + (n -> m)) + def findName(n: Str)(using Raise): Name = symbol_ctx.get(n) match + case None => + errStop(msg"Name not found: $n") + Name("error") + case Some(value) => value + def reset = + def_acc.clear() + class_acc.clear() + def nonTopLevel = copy(is_top_level = false) + +object Ctx: + val empty = Ctx(ListBuffer.empty, ListBuffer.empty) + + +final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: FreshInt): + import tl.{trace, log} + def er = Expr.Ref + def nr = Node.Result + def nme(x: Str) = Name(x) + def sr(x: Str) = er(Name(x)) + def sr(x: Name) = er(x) + def nsr(xs: Ls[Name]) = xs.map(er(_)) + + private def allocIfNew(l: Local)(using Raise, Scope): String = + trace[Str](s"allocIfNew begin: $l", x => s"allocIfNew end: $x"): + if summon[Scope].lookup(l).isDefined then + getVar_!(l) + else + summon[Scope].allocateName(l) + + private def getVar_!(l: Local)(using Raise, Scope): String = + trace[Str](s"getVar_! begin", x => s"getVar_! end: $x"): + l match + case ts: semantics.TermSymbol => + ts.owner match + case S(owner) => ts.id.name + case N => + ts.id.name + case ts: semantics.BlockMemberSymbol => // this means it's a locally-defined member + ts.nme + case ts: semantics.InnerSymbol => + summon[Scope].findThis_!(ts) + case _ => summon[Scope].lookup_!(l) + + private def bBind(name: Opt[Str], e: Result, body: Block)(k: TrivialExpr => Ctx ?=> Node)(using ctx: Ctx)(using Raise, Scope): Node = + trace[Node](s"bBind begin: $name", x => s"bBind end: ${x.show}"): + bResult(e): + case r: Expr.Ref => + given Ctx = ctx.addName(name.getOrElse(fresh.make.str), r.name) + log(s"bBind ref: $name -> $r") + bBlock(body)(k) + case l: Expr.Literal => + val v = fresh.make + given Ctx = ctx.addName(name.getOrElse(fresh.make.str), v) + log(s"bBind lit: $name -> $v") + Node.LetExpr(v, l, bBlock(body)(k)) + + private def bArgs(e: Ls[Arg])(k: Ls[TrivialExpr] => Ctx ?=> Node)(using ctx: Ctx)(using Raise, Scope): Node = + trace[Node](s"bArgs begin", x => s"bArgs end: ${x.show}"): + e match + case Nil => k(Nil) + case Arg(spread, x) :: xs => bPath(x): + case r: TrivialExpr => bArgs(xs): + case rs: Ls[TrivialExpr] => k(r :: rs) + + private def bPaths(e: Ls[Path])(k: Ls[TrivialExpr] => Ctx ?=> Node)(using ctx: Ctx)(using Raise, Scope): Node = + trace[Node](s"bArgs begin", x => s"bArgs end: ${x.show}"): + e match + case Nil => k(Nil) + case x :: xs => bPath(x): + case r: TrivialExpr => bPaths(xs): + case rs: Ls[TrivialExpr] => k(r :: rs) + + private def bFunDef(e: FunDefn)(using ctx: Ctx)(using Raise, Scope): Func = + trace[Func](s"bFunDef begin: ${e.sym}", x => s"bFunDef end: ${x.show}"): + val FunDefn(sym, params, body) = e + if !ctx.is_top_level then + errStop(msg"Non top-level definition ${sym.nme} not supported") + else if params.length != 1 then + errStop(msg"Curried function or zero arguments function not supported: ${params.length.toString}") + else + val paramsList = params.head.params.map(x => x -> summon[Scope].allocateName(x.sym)) + val ctx2 = paramsList.foldLeft(ctx)((acc, x) => acc.addName(getVar_!(x._1.sym), x._2 |> nme)) + val ctx3 = ctx2.nonTopLevel + val pl = paramsList.map(_._2).map(nme) + Func( + fnUid.make, + sym.nme, + params = pl, + resultNum = 1, + body = bBlock(body)(x => Node.Result(Ls(x)))(using ctx3) + ) + + private def bClsLikeDef(e: ClsLikeDefn)(using ctx: Ctx)(using Raise, Scope): ClassInfo = + trace[ClassInfo](s"bClsLikeDef begin", x => s"bClsLikeDef end: ${x.show}"): + val ClsLikeDefn(sym, kind, parentSym, methods, privateFields, publicFields, preCtor, ctor) = e + if !ctx.is_top_level then + errStop(msg"Non top-level definition ${sym.nme} not supported") + else + val clsDefn = sym.defn.getOrElse(die) + val clsParams = clsDefn.paramsOpt.fold(Nil)(_.paramSyms) + val clsFields = publicFields.map(_.sym) + ClassInfo( + clsUid.make, + sym.nme, + clsParams.map(_.nme) ++ clsFields.map(_.nme), + ) + + private def bValue(v: Value)(k: TrivialExpr => Ctx ?=> Node)(using ctx: Ctx)(using Raise, Scope) : Node = + trace[Node](s"bValue begin", x => s"bValue end: ${x.show}"): + v match + case Value.Ref(l) => k(ctx.findName(getVar_!(l)) |> sr) + case Value.This(sym) => errStop(msg"Unsupported value: This"); Node.Result(Ls()) + case Value.Lit(lit) => k(Expr.Literal(lit)) + case Value.Lam(params, body) => errStop(msg"Unsupported value: Lam"); Node.Result(Ls()) + case Value.Arr(elems) => errStop(msg"Unsupported value: Arr"); Node.Result(Ls()) + + private def getClassOfMem(p: FieldSymbol)(using ctx: Ctx)(using Raise, Scope): Local = + trace[Local](s"bMemSym begin", x => s"bMemSym end: $x"): + p match + case ts: TermSymbol => ts.owner.get + case ms: MemberSymbol[?] => ms.defn.get.sym + + private def bPath(p: Path)(k: TrivialExpr => Ctx ?=> Node)(using ctx: Ctx)(using Raise, Scope) : Node = + trace[Node](s"bPath begin", x => s"bPath end: ${x.show}"): + p match + case Select(Value.Ref(_: TopLevelSymbol), name) if name.name.head.isUpper => + val v = fresh.make + Node.LetExpr(v, Expr.CtorApp(ClassRef.fromName(name.name), Ls()), k(v |> sr)) + // field selection + case s @ Select(qual, name) => + log(s"bPath Select: $qual.$name with ${s.symbol}") + s.symbol match + case None => + errStop(msg"Unsupported selection by users") + case Some(value) => + bPath(qual): + case q: Expr.Ref => + val v = fresh.make + val cls = ClassRef.fromName(ctx.findClassName(getClassOfMem(s.symbol.get))) + val field = name.name + Node.LetExpr(v, Expr.Select(q.name, cls, field), k(v |> sr)) + case q: Expr.Literal => + errStop(msg"Unsupported select on literal") + Node.Result(Ls()) + case x: Value => bValue(x)(k) + + private def bResult(r: Result)(k: TrivialExpr => Ctx ?=> Node)(using ctx: Ctx)(using Raise, Scope) : Node = + trace[Node](s"bResult begin", x => s"bResult end: ${x.show}"): + r match + case Call(Value.Ref(sym: BuiltinSymbol), args) => + bArgs(args): + case args: Ls[TrivialExpr] => + val v = fresh.make + Node.LetExpr(v, Expr.BasicOp(sym.nme, args), k(v |> sr)) + case Call(Select(Value.Ref(_: TopLevelSymbol), name), args) if name.name.head.isUpper => + bArgs(args): + case args: Ls[TrivialExpr] => + val v = fresh.make + Node.LetExpr(v, Expr.CtorApp(ClassRef.fromName(name.name), args), k(v |> sr)) + case Call(Select(Value.Ref(_: TopLevelSymbol), name), args) => + bArgs(args): + case args: Ls[TrivialExpr] => + val v = fresh.make + Node.LetCall(Ls(v), FuncRef.fromName(name.name), args, k(v |> sr)) + case Call(Select(Value.Ref(_: BuiltinSymbol), name), args) => + bArgs(args): + case args: Ls[TrivialExpr] => + val v = fresh.make + Node.LetExpr(v, Expr.CtorApp(ClassRef.fromName(name.name), args), k(v |> sr)) + case Call(Value.Ref(name), args) if ctx.fn_ctx.contains(name) => + bArgs(args): + case args: Ls[TrivialExpr] => + val v = fresh.make + val fn = ctx.fn_ctx.get(name).get + Node.LetCall(Ls(v), FuncRef.fromName(fn), args, k(v |> sr)) + case Call(fn, args) => + bPath(fn): + case f: TrivialExpr => + bArgs(args): + case args: Ls[TrivialExpr] => + val v = fresh.make + Node.LetMethodCall(Ls(v), ClassRef(R("Callable")), Name("apply" + args.length), f :: args, k(v |> sr)) + case Instantiate( + Select(Select(Value.Ref(_: TopLevelSymbol), name), Tree.Ident("class")), args) => + bPaths(args): + case args: Ls[TrivialExpr] => + val v = fresh.make + Node.LetExpr(v, Expr.CtorApp(ClassRef.fromName(name.name), args), k(v |> sr)) + case Instantiate(cls, args) => + errStop(msg"Unsupported kind of Instantiate") + Node.Result(Ls()) + case x: Path => bPath(x)(k) + + private def bBlock(blk: Block)(k: TrivialExpr => Ctx ?=> Node)(using ctx: Ctx)(using Raise, Scope) : Node = + trace[Node](s"bBlock begin", x => s"bBlock end: ${x.show}"): + blk match + case Match(scrut, arms, dflt, rest) => + bPath(scrut): + case e: TrivialExpr => + val jp = fresh.make("j") + val fvset = (rest.freeVars -- rest.definedVars).map(allocIfNew) + val fvs1 = fvset.toList + val new_ctx = fvs1.foldLeft(ctx)((acc, x) => acc.addName(x, fresh.make)) + val fvs = fvs1.map(new_ctx.findName(_)) + def cont(x: TrivialExpr)(using ctx: Ctx) = Node.Jump( + FuncRef.fromName(jp), + fvs1.map(x => ctx.findName(x)).map(sr) + ) + val casesList: Ls[(Pat, Node)] = arms.map: + case (Case.Lit(lit), body) => + (Pat.Lit(lit), bBlock(body)(cont)(using ctx)) + case (Case.Cls(cls, _), body) => + (Pat.Class(ClassRef.fromName(cls.nme)), bBlock(body)(cont)(using ctx)) + case (Case.Tup(len, inf), body) => + (Pat.Class(ClassRef.fromName("Tuple" + len.toString())), bBlock(body)(cont)(using ctx)) + val defaultCase = dflt.map(bBlock(_)(cont)) + val jpdef = Func( + fnUid.make, + jp.str, + params = fvs, + resultNum = 1, + bBlock(rest)(k)(using new_ctx), + ) + summon[Ctx].def_acc += jpdef + Node.Case(e, casesList, defaultCase) + case Return(res, implct) => bResult(res)(x => Node.Result(Ls(x))) + case Throw(Instantiate(Select(Value.Ref(_), ident), Ls(Value.Lit(Tree.StrLit(e))))) if ident.name == "Error" => + Node.Panic(e) + case Label(label, body, rest) => ??? + case Break(label) => TODO("Break not supported") + case Continue(label) => TODO("Continue not supported") + case Begin(sub, rest) => + // re-associate rest blocks to correctly handle the continuation + sub match + case _: BlockTail => + val definedVars = sub.definedVars + definedVars.foreach(allocIfNew) + bBlock(sub): + x => bBlock(rest)(k) + case Assign(lhs, rhs, rest2) => + bBlock(Assign(lhs, rhs, Begin(rest2, rest)))(k) + case Begin(sub, rest2) => + bBlock(Begin(sub, Begin(rest2, rest)))(k) + case Define(defn, rest2) => + bBlock(Define(defn, Begin(rest2, rest)))(k) + case Match(scrut, arms, dflt, rest2) => + bBlock(Match(scrut, arms, dflt, Begin(rest2, rest)))(k) + case _ => TODO(s"Other non-tail sub components of Begin not supported $sub") + case TryBlock(sub, finallyDo, rest) => TODO("TryBlock not supported") + case Assign(lhs, rhs, rest) => + val name = allocIfNew(lhs) + bBind(S(name), rhs, rest)(k) + case AssignField(lhs, nme, rhs, rest) => TODO("AssignField not supported") + case Define(fd @ FunDefn(sym, params, body), rest) => + val f = bFunDef(fd) + ctx.def_acc += f + val new_ctx = ctx.addFuncName(sym, Name(f.name)) + bBlock(rest)(k)(using new_ctx) + case Define(cd @ ClsLikeDefn(sym, kind, parentSym, methods, privateFields, publicFields, preCtor, ctor), rest) => + val c = bClsLikeDef(cd) + ctx.class_acc += c + val new_ctx = ctx.addClassName(sym, Name(c.name)) + bBlock(rest)(k)(using new_ctx) + case End(msg) => k(Expr.Literal(Tree.UnitLit(false))) + case _: Block => + val docBlock = blk.showAsTree + errStop(msg"Unsupported block: $docBlock") + Node.Result(Ls()) + + def bProg(e: Program)(using Raise, Scope): LlirProgram = + val ctx = Ctx.empty + given Ctx = ctx + ctx.reset + val entry = bBlock(e.main)(x => Node.Result(Ls(x))) + LlirProgram(ctx.class_acc.toSet, ctx.def_acc.toSet, entry) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Fresh.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Fresh.scala new file mode 100644 index 0000000000..0c5688eab6 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Fresh.scala @@ -0,0 +1,28 @@ +package hkmc2.codegen.llir + +import collection.mutable.{HashMap => MutHMap} +import mlscript.utils.shorthands._ + +final class Fresh(div : Char = '$'): + private val counter = MutHMap[Str, Int]() + private def gensym(s: Str) = { + val n = s.lastIndexOf(div) + val (ts, suffix) = s.splitAt(if n == -1 then s.length() else n) + var x = if suffix.stripPrefix(div.toString).forall(_.isDigit) then ts else s + val count = counter.getOrElse(x, 0) + val tmp = s"$x$div$count" + counter.update(x, count + 1) + Name(tmp) + } + + def make(s: Str) = gensym(s) + def make = gensym("x") + +final class FreshInt: + private var counter = 0 + def make: Int = { + val tmp = counter + counter += 1 + tmp + } + diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Interp.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Interp.scala new file mode 100644 index 0000000000..4f5416d1f3 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Interp.scala @@ -0,0 +1,211 @@ +package hkmc2.codegen.llir + +import mlscript.* +import mlscript.utils.* +import scala.collection.immutable.* +import scala.collection.mutable.ListBuffer +import shorthands.* +import scala.util.boundary, boundary.break + +import hkmc2.codegen.llir.* +import hkmc2.syntax.Tree + +enum Stuck: + case StuckExpr(expr: Expr, msg: Str) + case StuckNode(node: Node, msg: Str) + + override def toString: String = + this match + case StuckExpr(expr, msg) => s"StuckExpr(${expr.show}, $msg)" + case StuckNode(node, msg) => s"StuckNode(${node.show}, $msg)" + +final case class InterpreterError(message: String) extends Exception(message) + +class Interpreter(verbose: Bool): + private def log(x: Any) = if verbose then println(x) + import Stuck._ + + private case class Configuration( + var context: Ctx + ) + + private type Result[T] = Either[Stuck, T] + + private enum Value: + case Class(cls: ClassInfo, var fields: Ls[Value]) + case Literal(lit: hkmc2.syntax.Literal) + + override def toString: String = + import hkmc2.syntax.Tree.* + this match + case Class(cls, fields) => s"${cls.name}(${fields.mkString(",")})" + case Literal(IntLit(lit)) => lit.toString + case Literal(BoolLit(lit)) => lit.toString + case Literal(DecLit(lit)) => lit.toString + case Literal(StrLit(lit)) => lit.toString + case Literal(UnitLit(undefinedOrNull)) => if undefinedOrNull then "undefined" else "null" + + private final case class Ctx( + bindingCtx: Map[Str, Value], + classCtx: Map[Str, ClassInfo], + funcCtx: Map[Str, Func], + ) + + import Node._ + import Expr._ + + private def getTrue(using ctx: Ctx) = Value.Literal(hkmc2.syntax.Tree.BoolLit(true)) + private def getFalse(using ctx: Ctx) = Value.Literal(hkmc2.syntax.Tree.BoolLit(false)) + + private def eval(op: Str, x1: Value, x2: Value)(using ctx: Ctx): Opt[Value] = + import Value.{Literal => Li} + import hkmc2.syntax.Tree.* + (op, x1, x2) match + case ("+", Li(IntLit(x)), Li(IntLit(y))) => S(Li(IntLit(x + y))) + case ("-", Li(IntLit(x)), Li(IntLit(y))) => S(Li(IntLit(x - y))) + case ("*", Li(IntLit(x)), Li(IntLit(y))) => S(Li(IntLit(x * y))) + case ("/", Li(IntLit(x)), Li(IntLit(y))) => S(Li(IntLit(x / y))) + case ("==", Li(IntLit(x)), Li(IntLit(y))) => S(if x == y then getTrue else getFalse) + case ("!=", Li(IntLit(x)), Li(IntLit(y))) => S(if x != y then getTrue else getFalse) + case ("<=", Li(IntLit(x)), Li(IntLit(y))) => S(if x <= y then getTrue else getFalse) + case (">=", Li(IntLit(x)), Li(IntLit(y))) => S(if x >= y then getTrue else getFalse) + case (">", Li(IntLit(x)), Li(IntLit(y))) => S(if x > y then getTrue else getFalse) + case ("<", Li(IntLit(x)), Li(IntLit(y))) => S(if x < y then getTrue else getFalse) + case _ => N + + private def evalArgs(using ctx: Ctx)(exprs: Ls[TrivialExpr]): Result[Ls[Value]] = + var values = ListBuffer.empty[Value] + var stuck: Opt[Stuck] = None + exprs foreach { expr => + stuck match + case None => eval(expr) match + case L(x) => stuck = Some(x) + case R(x) => values += x + case _ => () + } + stuck.toLeft(values.toList) + + private def eval(expr: TrivialExpr)(using ctx: Ctx): Result[Value] = expr match + case e @ Ref(name) => ctx.bindingCtx.get(name.str).toRight(StuckExpr(e, s"undefined variable $name")) + case Literal(lit) => R(Value.Literal(lit)) + + private def eval(expr: Expr)(using ctx: Ctx): Result[Value] = expr match + case Ref(Name(x)) => ctx.bindingCtx.get(x).toRight(StuckExpr(expr, s"undefined variable $x")) + case Literal(x) => R(Value.Literal(x)) + case CtorApp(cls, args) => + for + xs <- evalArgs(args) + cls <- ctx.classCtx.get(cls.name).toRight(StuckExpr(expr, s"undefined class ${cls.name}")) + yield Value.Class(cls, xs) + case Select(name, cls, field) => + ctx.bindingCtx.get(name.str).toRight(StuckExpr(expr, s"undefined variable $name")).flatMap { + case Value.Class(cls2, xs) if cls.name == cls2.name => + xs.zip(cls2.fields).find{_._2 == field} match + case Some((x, _)) => R(x) + case None => L(StuckExpr(expr, s"unable to find selected field $field")) + case Value.Class(cls2, xs) => L(StuckExpr(expr, s"unexpected class $cls2")) + case x => L(StuckExpr(expr, s"unexpected value $x")) + } + case BasicOp(name, args) => boundary: + evalArgs(args).flatMap( + xs => + name match + case "+" | "-" | "*" | "/" | "==" | "!=" | "<=" | ">=" | "<" | ">" => + if xs.length < 2 then break: + L(StuckExpr(expr, s"not enough arguments for basic operation $name")) + else eval(name, xs.head, xs.tail.head).toRight(StuckExpr(expr, s"unable to evaluate basic operation")) + case _ => L(StuckExpr(expr, s"unexpected basic operation $name"))) + case AssignField(assignee, cls, field, value) => + for + x <- eval(Ref(assignee): TrivialExpr) + y <- eval(value) + res <- x match + case obj @ Value.Class(cls2, xs) if cls.name == cls2.name => + xs.zip(cls2.fields).find{_._2 == field} match + case Some((_, _)) => + obj.fields = xs.map(x => if x == obj then y else x) + // Ideally, we should return a unit value here, but here we return the assignee value for simplicity. + R(obj) + case None => L(StuckExpr(expr, s"unable to find selected field $field")) + case Value.Class(cls2, xs) => L(StuckExpr(expr, s"unexpected class $cls2")) + case x => L(StuckExpr(expr, s"unexpected value $x")) + yield res + + private def eval(node: Node)(using ctx: Ctx): Result[Ls[Value]] = node match + case Result(res) => evalArgs(res) + case Jump(func, args) => + for + xs <- evalArgs(args) + func <- ctx.funcCtx.get(func.name).toRight(StuckNode(node, s"undefined function ${func.name}")) + ctx1 = ctx.copy(bindingCtx = ctx.bindingCtx ++ func.params.map{_.str}.zip(xs)) + res <- eval(func.body)(using ctx1) + yield res + case Case(scrut, cases, default) => + eval(scrut) flatMap { + case Value.Class(cls, fields) => + cases.find { + case (Pat.Class(cls2), _) => cls.name == cls2.name + case _ => false + } match { + case Some((_, x)) => eval(x) + case None => + default match + case S(x) => eval(x) + case N => L(StuckNode(node, s"can not find the matched case, ${cls.name} expected")) + } + case Value.Literal(lit) => + cases.find { + case (Pat.Lit(lit2), _) => lit == lit2 + case _ => false + } match { + case Some((_, x)) => eval(x) + case None => + default match + case S(x) => eval(x) + case N => L(StuckNode(node, s"can not find the matched case, $lit expected")) + } + } + case LetExpr(name, expr, body) => + for + x <- eval(expr) + ctx1 = ctx.copy(bindingCtx = ctx.bindingCtx + (name.str -> x)) + res <- eval(body)(using ctx1) + yield res + case LetMethodCall(names, cls, method, args, body) => + for + ys <- evalArgs(args).flatMap { + case Value.Class(cls2, xs) :: args => + cls2.methods.get(method.str).toRight(StuckNode(node, s"undefined method ${method.str}")).flatMap { method => + val ctx1 = ctx.copy(bindingCtx = ctx.bindingCtx ++ cls2.fields.zip(xs) ++ method.params.map{_.str}.zip(args)) + eval(method.body)(using ctx1) + } + case _ => L(StuckNode(node, s"not enough arguments for method call, or the first argument is not a class")) + } + ctx2 = ctx.copy(bindingCtx = ctx.bindingCtx ++ names.map{_.str}.zip(ys)) + res <- eval(body)(using ctx2) + yield res + case LetCall(names, func, args, body) => + for + xs <- evalArgs(args) + func <- ctx.funcCtx.get(func.name).toRight(StuckNode(node, s"undefined function ${func.name}")) + ctx1 = ctx.copy(bindingCtx = ctx.bindingCtx ++ func.params.map{_.str}.zip(xs)) + ys <- eval(func.body)(using ctx1) + ctx2 = ctx.copy(bindingCtx = ctx.bindingCtx ++ names.map{_.str}.zip(ys)) + res <- eval(body)(using ctx2) + yield res + case Panic(msg) => L(StuckNode(node, msg)) + + private def f(prog: Program): Ls[Value] = + val Program(classes, defs, main) = prog + given Ctx = Ctx( + bindingCtx = Map.empty, + classCtx = classes.map{cls => cls.name -> cls}.toMap, + funcCtx = defs.map{func => func.name -> func}.toMap, + ) + eval(main) match + case R(x) => x + case L(x) => throw InterpreterError("Stuck evaluation: " + x.toString) + + def interpret(prog: Program): Str = + val node = f(prog) + node.map(_.toString).mkString(",") diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Llir.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Llir.scala new file mode 100644 index 0000000000..72be21209b --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Llir.scala @@ -0,0 +1,214 @@ +package hkmc2.codegen.llir + +import mlscript._ +import mlscript.utils._ +import mlscript.utils.shorthands._ + +import hkmc2.syntax._ +import hkmc2.Message.MessageContext +import hkmc2.document._ + +import util.Sorting +import collection.immutable.SortedSet +import language.implicitConversions +import collection.mutable.{Map as MutMap, Set as MutSet, HashMap, ListBuffer} + +private def raw(x: String): Document = doc"$x" + +final case class LowLevelIRError(message: String) extends Exception(message) + +case class Program( + classes: Set[ClassInfo], + defs: Set[Func], + main: Node, +): + override def toString: String = + val t1 = classes.toArray + val t2 = defs.toArray + Sorting.quickSort(t1) + Sorting.quickSort(t2) + s"Program({${t1.mkString(",\n")}}, {\n${t2.mkString("\n")}\n},\n$main)" + + def show(hiddenNames: Set[Str] = Set.empty) = toDocument(hiddenNames).toString + def toDocument(hiddenNames: Set[Str] = Set.empty) : Document = + val t1 = classes.toArray + val t2 = defs.toArray + Sorting.quickSort(t1) + Sorting.quickSort(t2) + given Conversion[String, Document] = raw + val docClasses = t1.filter(x => !hiddenNames.contains(x.name)).map(_.toDocument).toList.mkDocument(doc" # ") + val docDefs = t2.map(_.toDocument).toList.mkDocument(doc" # ") + val docMain = main.toDocument + doc" #{ $docClasses\n$docDefs\n$docMain #} " + +implicit object ClassInfoOrdering extends Ordering[ClassInfo] { + def compare(a: ClassInfo, b: ClassInfo) = a.id.compare(b.id) +} + +case class ClassInfo( + id: Int, + name: Str, + fields: Ls[Str], +): + var parents: Set[Str] = Set.empty + var methods: Map[Str, Func] = Map.empty + override def hashCode: Int = id + override def toString: String = + s"ClassInfo($id, $name, [${fields mkString ","}], parents: ${parents mkString ","}, methods:\n${methods mkString ",\n"})" + + def show = toDocument.toString + def toDocument: Document = + given Conversion[String, Document] = raw + val ext = if parents.isEmpty then "" else " extends " + parents.mkString(", ") + if methods.isEmpty then + doc"class $name(${fields.mkString(",")})$ext" + else + val docFirst = doc"class $name (${fields.mkString(",")})$ext {" + val docMethods = methods.map { (_, func) => func.toDocument }.toList.mkDocument(doc" # ") + val docLast = doc"}" + doc"$docFirst #{ # $docMethods # #} $docLast" + +case class Name(str: Str): + def trySubst(map: Map[Str, Name]) = map.getOrElse(str, this) + override def toString: String = str + +object FuncRef: + def fromName(name: Str) = FuncRef(Right(name)) + def fromName(name: Name) = FuncRef(Right(name.str)) + def fromFunc(func: Func) = FuncRef(Left(func)) + +class FuncRef(var func: Either[Func, Str]): + def name: String = func.fold(_.name, x => x) + def expectFn: Func = func.fold(identity, x => throw Exception(s"Expected a def, but got $x")) + def getFunc: Opt[Func] = func.left.toOption + override def equals(o: Any): Bool = o match { + case o: FuncRef => o.name == this.name + case _ => false + } + +object ClassRef: + def fromName(name: Str) = ClassRef(Right(name)) + def fromName(name: Name) = ClassRef(Right(name.str)) + def fromClass(cls: ClassInfo) = ClassRef(Left(cls)) + +class ClassRef(var cls: Either[ClassInfo, Str]): + def name: String = cls.fold(_.name, x => x) + def expectCls: ClassInfo = cls.fold(identity, x => throw Exception(s"Expected a class, but got $x")) + def getClass: Opt[ClassInfo] = cls.left.toOption + override def equals(o: Any): Bool = o match { + case o: ClassRef => o.name == this.name + case _ => false + } + +implicit object FuncOrdering extends Ordering[Func] { + def compare(a: Func, b: Func) = a.id.compare(b.id) +} + +case class Func( + id: Int, + name: Str, + params: Ls[Name], + resultNum: Int, + body: Node +): + var recBoundary: Opt[Int] = None + override def hashCode: Int = id + + override def toString: String = + val ps = params.map(_.toString).mkString("[", ",", "]") + s"Def($id, $name, $ps, \n$resultNum, \n$body\n)" + + def show = toDocument + def toDocument: Document = + given Conversion[String, Document] = raw + val docFirst = doc"def $name(${params.map(_.toString).mkString(",")}) =" + val docBody = body.toDocument + doc"$docFirst #{ # $docBody #} " + +sealed trait TrivialExpr: + import Expr._ + override def toString: String + def show: String + def toDocument: Document + def toExpr: Expr = this match { case x: Expr => x } + +private def showArguments(args: Ls[TrivialExpr]) = args map (_.show) mkString "," + +enum Expr: + case Ref(name: Name) extends Expr, TrivialExpr + case Literal(lit: hkmc2.syntax.Literal) extends Expr, TrivialExpr + case CtorApp(cls: ClassRef, args: Ls[TrivialExpr]) + case Select(name: Name, cls: ClassRef, field: Str) + case BasicOp(name: Str, args: Ls[TrivialExpr]) + case AssignField(assignee: Name, cls: ClassRef, field: Str, value: TrivialExpr) + + override def toString: String = show + + def show: String = toDocument.toString + + def toDocument: Document = + given Conversion[String, Document] = raw + this match + case Ref(s) => s.toString + case Literal(Tree.BoolLit(lit)) => s"$lit" + case Literal(Tree.IntLit(lit)) => s"$lit" + case Literal(Tree.DecLit(lit)) => s"$lit" + case Literal(Tree.StrLit(lit)) => s"$lit" + case Literal(Tree.UnitLit(undefinedOrNull)) => if undefinedOrNull then "undefined" else "null" + case CtorApp(cls, args) => + doc"${cls.name}(${args.map(_.toString).mkString(",")})" + case Select(s, cls, fld) => + doc"${s.toString}.<${cls.name}:$fld>" + case BasicOp(name: Str, args) => + doc"$name(${args.map(_.toString).mkString(",")})" + case AssignField(assignee, clsInfo, fieldName, value) => + doc"${assignee.toString}.${fieldName} := ${value.toString}" + +enum Pat: + case Lit(lit: hkmc2.syntax.Literal) + case Class(cls: ClassRef) + + override def toString: String = this match + case Lit(lit) => s"$lit" + case Class(cls) => s"${cls.name}" + +enum Node: + // Terminal forms: + case Result(res: Ls[TrivialExpr]) + case Jump(func: FuncRef, args: Ls[TrivialExpr]) + case Case(scrutinee: TrivialExpr, cases: Ls[(Pat, Node)], default: Opt[Node]) + case Panic(msg: Str) + // Intermediate forms: + case LetExpr(name: Name, expr: Expr, body: Node) + case LetMethodCall(names: Ls[Name], cls: ClassRef, method: Name, args: Ls[TrivialExpr], body: Node) + case LetCall(names: Ls[Name], func: FuncRef, args: Ls[TrivialExpr], body: Node) + + override def toString: String = show + + def show: String = toDocument.toString + + def toDocument: Document = + given Conversion[String, Document] = raw + this match + case Result(res) => (res |> showArguments) + case Jump(jp, args) => + doc"jump ${jp.name}(${args |> showArguments})" + case Case(x, cases, default) => + val docFirst = doc"case ${x.toString} of" + val docCases = cases.map { + case (pat, node) => doc"${pat.toString} => #{ # ${node.toDocument} #} " + }.mkDocument(doc" # ") + default match + case N => doc"$docFirst #{ # $docCases #} " + case S(dc) => + val docDeft = doc"_ => #{ # ${dc.toDocument} #} " + doc"$docFirst #{ # $docCases # $docDeft #} " + case Panic(msg) => + doc"panic ${s"\"$msg\""}" + case LetExpr(x, expr, body) => + doc"let ${x.toString} = ${expr.toString} in # ${body.toDocument}" + case LetMethodCall(xs, cls, method, args, body) => + doc"let ${xs.map(_.toString).mkString(",")} = ${cls.name}.${method.toString}(${args.map(_.toString).mkString(",")}) in # ${body.toDocument}" + case LetCall(xs, func, args, body) => + doc"let* (${xs.map(_.toString).mkString(",")}) = ${func.name}(${args.map(_.toString).mkString(",")}) in # ${body.toDocument}" + diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/RefResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/RefResolver.scala new file mode 100644 index 0000000000..5b6da3eab9 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/RefResolver.scala @@ -0,0 +1,56 @@ +package hkmc2.codegen.llir + +import mlscript.utils.shorthands._ + +import Node._ + +// Resolves the definition references by turning them from Right(name) to Left(Func). +private final class RefResolver(defs: Map[Str, Func], classes: Map[Str, ClassInfo], allowInlineJp: Bool): + private def f(x: Expr): Unit = x match + case Expr.Ref(name) => + case Expr.Literal(lit) => + case Expr.CtorApp(cls, args) => classes.get(cls.name) match + case None => throw LowLevelIRError(f"unknown class ${cls.name} in ${classes.keySet.mkString(",")}") + case Some(value) => cls.cls = Left(value) + case Expr.Select(name, cls, field) => classes.get(cls.name) match + case None => throw LowLevelIRError(f"unknown class ${cls.name} in ${classes.keySet.mkString(",")}") + case Some(value) => cls.cls = Left(value) + case Expr.BasicOp(name, args) => + case Expr.AssignField(name, cls, field, value) => classes.get(cls.name) match + case None => throw LowLevelIRError(f"unknown class ${cls.name} in ${classes.keySet.mkString(",")}") + case Some(value) => cls.cls = Left(value) + + private def f(x: Pat): Unit = x match + case Pat.Lit(lit) => + case Pat.Class(cls) => classes.get(cls.name) match + case None => throw LowLevelIRError(f"unknown class ${cls.name} in ${classes.keySet.mkString(",")}") + case Some(value) => cls.cls = Left(value) + + private def f(x: Node): Unit = x match + case Result(res) => + case Case(scrut, cases, default) => cases foreach { (_, body) => f(body) }; default foreach f + case LetExpr(name, expr, body) => f(expr); f(body) + case LetMethodCall(names, cls, method, args, body) => f(body) + case LetCall(resultNames, defnref, args, body) => + defs.get(defnref.name) match + case Some(defn) => defnref.func = Left(defn) + case None => throw LowLevelIRError(f"unknown function ${defnref.name} in ${defs.keySet.mkString(",")}") + f(body) + case Jump(defnref, _) => + // maybe not promoted yet + defs.get(defnref.name) match + case Some(defn) => defnref.func = Left(defn) + case None => + if !allowInlineJp then + throw LowLevelIRError(f"unknown function ${defnref.name} in ${defs.keySet.mkString(",")}") + case Panic(_) => + def run(node: Node) = f(node) + def run(node: Func) = f(node.body) + +def resolveRef(entry: Node, defs: Set[Func], classes: Set[ClassInfo], allowInlineJp: Bool = false): Unit = + val defsMap = defs.map(x => x.name -> x).toMap + val classesMap = classes.map(x => x.name -> x).toMap + val rl = RefResolver(defsMap, classesMap, allowInlineJp) + rl.run(entry) + defs.foreach(rl.run(_)) + diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Validator.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Validator.scala new file mode 100644 index 0000000000..a660f9f078 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Validator.scala @@ -0,0 +1,45 @@ +package hkmc2.codegen.llir + +import hkmc2.utils._ + +private final class FuncRefInSet(defs: Set[Func], classes: Set[ClassInfo]): + import Node._ + import Expr._ + + private def f(x: Expr): Unit = x match + case Ref(name) => + case Literal(lit) => + case CtorApp(name, args) => + case Select(name, ref, field) => ref.getClass match { + case Some(real_class) => if !classes.exists(_ eq real_class) then throw LowLevelIRError("ref is not in the set") + case _ => + } + case BasicOp(name, args) => + case AssignField(assignee, ref, _, value) => ref.getClass match { + case Some(real_class) => if !classes.exists(_ eq real_class) then throw LowLevelIRError("ref is not in the set") + case _ => + } + + private def f(x: Node): Unit = x match + case Result(res) => + case Jump(func, args) => + case Case(x, cases, default) => cases foreach { (_, body) => f(body) }; default foreach f + case Panic(_) => + case LetExpr(name, expr, body) => f(body) + case LetMethodCall(names, cls, method, args, body) => f(body) + case LetCall(res, ref, args, body) => + ref.getFunc match { + case Some(real_func) => if !defs.exists(_ eq real_func) then throw LowLevelIRError("ref is not in the set") + case _ => + } + f(body) + def run(node: Node) = f(node) + def run(func: Func) = f(func.body) + +def validateRefInSet(entry: Node, defs: Set[Func], classes: Set[ClassInfo]): Unit = + val funcRefInSet = FuncRefInSet(defs, classes) + defs.foreach(funcRefInSet.run(_)) + +def validate(entry: Node, defs: Set[Func], classes: Set[ClassInfo]): Unit = + validateRefInSet(entry, defs, classes) + diff --git a/hkmc2/shared/src/test/mlscript-compile/cpp/Makefile b/hkmc2/shared/src/test/mlscript-compile/cpp/Makefile new file mode 100644 index 0000000000..45aae4802c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/cpp/Makefile @@ -0,0 +1,27 @@ +CXX := g++ +CFLAGS += -O3 -Wall -Wextra -std=c++20 -I. -Wno-inconsistent-missing-override -I/opt/homebrew/include +LDFLAGS += -L/opt/homebrew/lib +LDLIBS := -lmimalloc -lgmp +SRC := +INCLUDES := mlsprelude.h +DST := +DEFAULT_TARGET := mls +TARGET := $(or $(DST),$(DEFAULT_TARGET)) + +.PHONY: pre all run clean auto + +all: $(TARGET) + +run: $(TARGET) + ./$(TARGET) + +pre: $(SRC) + sed -i '' 's#^//│ ##g' $(SRC) + +clean: + rm -r $(TARGET) $(TARGET).dSYM + +auto: $(TARGET) + +$(TARGET): $(SRC) $(INCLUDES) + $(CXX) $(CFLAGS) $(LDFLAGS) $(SRC) $(LDLIBS) -o $(TARGET) diff --git a/hkmc2/shared/src/test/mlscript-compile/cpp/mlsprelude.h b/hkmc2/shared/src/test/mlscript-compile/cpp/mlsprelude.h new file mode 100644 index 0000000000..fed52549bb --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/cpp/mlsprelude.h @@ -0,0 +1,529 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr std::size_t _mlsAlignment = 8; + +template class tuple_type { + template > struct impl; + template struct impl> { + template using wrap = T; + using type = std::tuple...>; + }; + +public: + using type = typename impl<>::type; +}; +template struct counter { + using tag = counter; + + struct generator { + friend consteval auto is_defined(tag) { return true; } + }; + friend consteval auto is_defined(tag); + + template + static consteval auto exists(auto) { + return true; + } + + static consteval auto exists(...) { return generator(), false; } +}; + +template +consteval auto nextTypeTag() { + if constexpr (not counter::exists(Id)) + return Id; + else + return nextTypeTag(); +} + +struct _mlsObject { + uint32_t refCount; + uint32_t tag; + constexpr static inline uint32_t stickyRefCount = + std::numeric_limits::max(); + + void incRef() { + if (refCount != stickyRefCount) + ++refCount; + } + bool decRef() { + if (refCount != stickyRefCount && --refCount == 0) + return true; + return false; + } + + virtual void print() const = 0; + virtual void destroy() = 0; +}; + +class _mlsValue { + using uintptr_t = std::uintptr_t; + using uint64_t = std::uint64_t; + + void *value alignas(_mlsAlignment); + + bool isInt63() const { return (reinterpret_cast(value) & 1) == 1; } + + bool isPtr() const { return (reinterpret_cast(value) & 1) == 0; } + + uint64_t asInt63() const { return reinterpret_cast(value) >> 1; } + + uintptr_t asRawInt() const { return reinterpret_cast(value); } + + static _mlsValue fromRawInt(uintptr_t i) { + return _mlsValue(reinterpret_cast(i)); + } + + static _mlsValue fromInt63(uint64_t i) { + return _mlsValue(reinterpret_cast((i << 1) | 1)); + } + + void *asPtr() const { + assert(!isInt63()); + return value; + } + + _mlsObject *asObject() const { + assert(isPtr()); + return static_cast<_mlsObject *>(value); + } + + bool eqInt63(const _mlsValue &other) const { + return asRawInt() == other.asRawInt(); + } + + _mlsValue addInt63(const _mlsValue &other) const { + return fromRawInt(asRawInt() + other.asRawInt() - 1); + } + + _mlsValue subInt63(const _mlsValue &other) const { + return fromRawInt(asRawInt() - other.asRawInt() + 1); + } + + _mlsValue mulInt63(const _mlsValue &other) const { + return fromInt63(asInt63() * other.asInt63()); + } + + _mlsValue divInt63(const _mlsValue &other) const { + return fromInt63(asInt63() / other.asInt63()); + } + + _mlsValue gtInt63(const _mlsValue &other) const { + return _mlsValue::fromBoolLit(asInt63() > other.asInt63()); + } + + _mlsValue ltInt63(const _mlsValue &other) const { + return _mlsValue::fromBoolLit(asInt63() < other.asInt63()); + } + + _mlsValue geInt63(const _mlsValue &other) const { + return _mlsValue::fromBoolLit(asInt63() >= other.asInt63()); + } + + _mlsValue leInt63(const _mlsValue &other) const { + return _mlsValue::fromBoolLit(asInt63() <= other.asInt63()); + } + +public: + explicit _mlsValue() : value(nullptr) {} + explicit _mlsValue(void *value) : value(value) {} + _mlsValue(const _mlsValue &other) : value(other.value) { + if (isPtr()) + asObject()->incRef(); + } + + _mlsValue &operator=(const _mlsValue &other) { + if (value != nullptr && isPtr()) + asObject()->decRef(); + value = other.value; + if (isPtr()) + asObject()->incRef(); + return *this; + } + + ~_mlsValue() { + if (isPtr()) + if (asObject()->decRef()) { + asObject()->destroy(); + value = nullptr; + } + } + + uint64_t asInt() const { + assert(isInt63()); + return asInt63(); + } + + static _mlsValue fromIntLit(uint64_t i) { return fromInt63(i); } + + static _mlsValue fromBoolLit(bool b) { return fromInt63(b); } + + template static tuple_type<_mlsValue, N> never() { + __builtin_unreachable(); + } + static _mlsValue never() { __builtin_unreachable(); } + + template static _mlsValue create(U... args) { + return _mlsValue(T::create(args...)); + } + + static void destroy(_mlsValue &v) { v.~_mlsValue(); } + + template static bool isValueOf(const _mlsValue &v) { + return v.asObject()->tag == T::typeTag; + } + + static bool isIntLit(const _mlsValue &v, uint64_t n) { + return v.asInt63() == n; + } + + static bool isIntLit(const _mlsValue &v) { return v.isInt63(); } + + template static T *as(const _mlsValue &v) { + return dynamic_cast(v.asObject()); + } + + template static T *cast(_mlsValue &v) { + return static_cast(v.asObject()); + } + + // Operators + + _mlsValue operator==(const _mlsValue &other) const { + if (isInt63() && other.isInt63()) + return _mlsValue::fromBoolLit(eqInt63(other)); + assert(false); + } + + _mlsValue operator+(const _mlsValue &other) const { + if (isInt63() && other.isInt63()) + return addInt63(other); + assert(false); + } + + _mlsValue operator-(const _mlsValue &other) const { + if (isInt63() && other.isInt63()) + return subInt63(other); + assert(false); + } + + _mlsValue operator*(const _mlsValue &other) const { + if (isInt63() && other.isInt63()) + return mulInt63(other); + assert(false); + } + + _mlsValue operator/(const _mlsValue &other) const { + if (isInt63() && other.isInt63()) + return divInt63(other); + assert(false); + } + + _mlsValue operator>(const _mlsValue &other) const { + if (isInt63() && other.isInt63()) + return gtInt63(other); + assert(false); + } + + _mlsValue operator<(const _mlsValue &other) const { + if (isInt63() && other.isInt63()) + return ltInt63(other); + assert(false); + } + + _mlsValue operator>=(const _mlsValue &other) const { + if (isInt63() && other.isInt63()) + return geInt63(other); + assert(false); + } + + _mlsValue operator<=(const _mlsValue &other) const { + if (isInt63() && other.isInt63()) + return leInt63(other); + assert(false); + } + + // Auxiliary functions + + void print() const { + if (isInt63()) + std::printf("%" PRIu64, asInt63()); + else if (isPtr() && asObject()) + asObject()->print(); + } +}; + +struct _mls_Callable : public _mlsObject { + virtual _mlsValue _mls_apply0() { throw std::runtime_error("Not implemented"); } + virtual _mlsValue _mls_apply1(_mlsValue) { + throw std::runtime_error("Not implemented"); + } + virtual _mlsValue _mls_apply2(_mlsValue, _mlsValue) { + throw std::runtime_error("Not implemented"); + } + virtual _mlsValue _mls_apply3(_mlsValue, _mlsValue, _mlsValue) { + throw std::runtime_error("Not implemented"); + } + virtual _mlsValue _mls_apply4(_mlsValue, _mlsValue, _mlsValue, _mlsValue) { + throw std::runtime_error("Not implemented"); + } + virtual void destroy() override {} +}; + +inline static _mls_Callable *_mlsToCallable(_mlsValue fn) { + auto *ptr = _mlsValue::as<_mls_Callable>(fn); + if (!ptr) + throw std::runtime_error("Not a callable object"); + return ptr; +} + +template +inline static _mlsValue _mlsCall(_mlsValue f, U... args) { + static_assert(sizeof...(U) <= 4, "Too many arguments"); + if constexpr (sizeof...(U) == 0) + return _mlsToCallable(f)->_mls_apply0(); + else if constexpr (sizeof...(U) == 1) + return _mlsToCallable(f)->_mls_apply1(args...); + else if constexpr (sizeof...(U) == 2) + return _mlsToCallable(f)->_mls_apply2(args...); + else if constexpr (sizeof...(U) == 3) + return _mlsToCallable(f)->_mls_apply3(args...); + else if constexpr (sizeof...(U) == 4) + return _mlsToCallable(f)->_mls_apply4(args...); +} + +template +inline static T *_mlsMethodCall(_mlsValue self) { + auto *ptr = _mlsValue::as(self); + if (!ptr) + throw std::runtime_error("unable to convert object for method calls"); + return ptr; +} + +inline int _mlsLargeStack(void *(*fn)(void *)) { + pthread_t thread; + pthread_attr_t attr; + + size_t stacksize = 512 * 1024 * 1024; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, stacksize); + + int rc = pthread_create(&thread, &attr, fn, nullptr); + if (rc) { + printf("ERROR: return code from pthread_create() is %d\n", rc); + return 1; + } + pthread_join(thread, NULL); + return 0; +} + +_mlsValue _mlsMain(); + +inline void *_mlsMainWrapper(void *) { + _mlsValue res = _mlsMain(); + res.print(); + return nullptr; +} + +struct _mls_Unit final : public _mlsObject { + constexpr static inline const char *typeName = "Unit"; + constexpr static inline uint32_t typeTag = nextTypeTag(); + virtual void print() const override { std::printf(typeName); } + static _mlsValue create() { + static _mls_Unit mlsUnit alignas(_mlsAlignment); + mlsUnit.refCount = stickyRefCount; + mlsUnit.tag = typeTag; + return _mlsValue(&mlsUnit); + } + virtual void destroy() override {} +}; + +#include + +struct _mls_ZInt final : public _mlsObject { + boost::multiprecision::mpz_int z; + constexpr static inline const char *typeName = "Z"; + constexpr static inline uint32_t typeTag = nextTypeTag(); + virtual void print() const override { + std::printf(typeName); + std::printf("("); + std::printf("%s", z.str().c_str()); + std::printf(")"); + } + virtual void destroy() override { + z.~number(); + operator delete(this, std::align_val_t(_mlsAlignment)); + } + static _mlsValue create() { + auto _mlsVal = new (std::align_val_t(_mlsAlignment)) _mls_ZInt; + _mlsVal->refCount = 1; + _mlsVal->tag = typeTag; + return _mlsValue(_mlsVal); + } + static _mlsValue create(_mlsValue z) { + auto _mlsVal = new (std::align_val_t(_mlsAlignment)) _mls_ZInt; + _mlsVal->z = z.asInt(); + _mlsVal->refCount = 1; + _mlsVal->tag = typeTag; + return _mlsValue(_mlsVal); + } + _mlsValue operator+(const _mls_ZInt &other) const { + auto _mlsVal = _mlsValue::create<_mls_ZInt>(); + _mlsValue::cast<_mls_ZInt>(_mlsVal)->z = z + other.z; + return _mlsVal; + } + + _mlsValue operator-(const _mls_ZInt &other) const { + auto _mlsVal = _mlsValue::create<_mls_ZInt>(); + _mlsValue::cast<_mls_ZInt>(_mlsVal)->z = z - other.z; + return _mlsVal; + } + + _mlsValue operator*(const _mls_ZInt &other) const { + auto _mlsVal = _mlsValue::create<_mls_ZInt>(); + _mlsValue::cast<_mls_ZInt>(_mlsVal)->z = z * other.z; + return _mlsVal; + } + + _mlsValue operator/(const _mls_ZInt &other) const { + auto _mlsVal = _mlsValue::create<_mls_ZInt>(); + _mlsValue::cast<_mls_ZInt>(_mlsVal)->z = z / other.z; + return _mlsVal; + } + + _mlsValue operator%(const _mls_ZInt &other) const { + auto _mlsVal = _mlsValue::create<_mls_ZInt>(); + _mlsValue::cast<_mls_ZInt>(_mlsVal)->z = z % other.z; + return _mlsVal; + } + + _mlsValue operator==(const _mls_ZInt &other) const { + return _mlsValue::fromBoolLit(z == other.z); + } + + _mlsValue operator>(const _mls_ZInt &other) const { + return _mlsValue::fromBoolLit(z > other.z); + } + + _mlsValue operator<(const _mls_ZInt &other) const { + return _mlsValue::fromBoolLit(z < other.z); + } + + _mlsValue operator>=(const _mls_ZInt &other) const { + return _mlsValue::fromBoolLit(z >= other.z); + } + + _mlsValue operator<=(const _mls_ZInt &other) const { + return _mlsValue::fromBoolLit(z <= other.z); + } + + _mlsValue toInt() const { + return _mlsValue::fromIntLit(z.convert_to()); + } + + static _mlsValue fromInt(uint64_t i) { + return _mlsValue::create<_mls_ZInt>(_mlsValue::fromIntLit(i)); + } +}; + +__attribute__((noinline)) inline void _mlsNonExhaustiveMatch() { + throw std::runtime_error("Non-exhaustive match"); +} + +inline _mlsValue _mls_builtin_z_add(_mlsValue a, _mlsValue b) { + assert(_mlsValue::isValueOf<_mls_ZInt>(a)); + assert(_mlsValue::isValueOf<_mls_ZInt>(b)); + return *_mlsValue::cast<_mls_ZInt>(a) + *_mlsValue::cast<_mls_ZInt>(b); +} + +inline _mlsValue _mls_builtin_z_sub(_mlsValue a, _mlsValue b) { + assert(_mlsValue::isValueOf<_mls_ZInt>(a)); + assert(_mlsValue::isValueOf<_mls_ZInt>(b)); + return *_mlsValue::cast<_mls_ZInt>(a) - *_mlsValue::cast<_mls_ZInt>(b); +} + +inline _mlsValue _mls_builtin_z_mul(_mlsValue a, _mlsValue b) { + assert(_mlsValue::isValueOf<_mls_ZInt>(a)); + assert(_mlsValue::isValueOf<_mls_ZInt>(b)); + return *_mlsValue::cast<_mls_ZInt>(a) * *_mlsValue::cast<_mls_ZInt>(b); +} + +inline _mlsValue _mls_builtin_z_div(_mlsValue a, _mlsValue b) { + assert(_mlsValue::isValueOf<_mls_ZInt>(a)); + assert(_mlsValue::isValueOf<_mls_ZInt>(b)); + return *_mlsValue::cast<_mls_ZInt>(a) / *_mlsValue::cast<_mls_ZInt>(b); +} + +inline _mlsValue _mls_builtin_z_mod(_mlsValue a, _mlsValue b) { + assert(_mlsValue::isValueOf<_mls_ZInt>(a)); + assert(_mlsValue::isValueOf<_mls_ZInt>(b)); + return *_mlsValue::cast<_mls_ZInt>(a) % *_mlsValue::cast<_mls_ZInt>(b); +} + +inline _mlsValue _mls_builtin_z_equal(_mlsValue a, _mlsValue b) { + assert(_mlsValue::isValueOf<_mls_ZInt>(a)); + assert(_mlsValue::isValueOf<_mls_ZInt>(b)); + return *_mlsValue::cast<_mls_ZInt>(a) == *_mlsValue::cast<_mls_ZInt>(b); +} + +inline _mlsValue _mls_builtin_z_gt(_mlsValue a, _mlsValue b) { + assert(_mlsValue::isValueOf<_mls_ZInt>(a)); + assert(_mlsValue::isValueOf<_mls_ZInt>(b)); + return *_mlsValue::cast<_mls_ZInt>(a) > *_mlsValue::cast<_mls_ZInt>(b); +} + +inline _mlsValue _mls_builtin_z_lt(_mlsValue a, _mlsValue b) { + assert(_mlsValue::isValueOf<_mls_ZInt>(a)); + assert(_mlsValue::isValueOf<_mls_ZInt>(b)); + return *_mlsValue::cast<_mls_ZInt>(a) < *_mlsValue::cast<_mls_ZInt>(b); +} + +inline _mlsValue _mls_builtin_z_geq(_mlsValue a, _mlsValue b) { + assert(_mlsValue::isValueOf<_mls_ZInt>(a)); + assert(_mlsValue::isValueOf<_mls_ZInt>(b)); + return *_mlsValue::cast<_mls_ZInt>(a) >= *_mlsValue::cast<_mls_ZInt>(b); +} + +inline _mlsValue _mls_builtin_z_leq(_mlsValue a, _mlsValue b) { + assert(_mlsValue::isValueOf<_mls_ZInt>(a)); + assert(_mlsValue::isValueOf<_mls_ZInt>(b)); + return *_mlsValue::cast<_mls_ZInt>(a) <= *_mlsValue::cast<_mls_ZInt>(b); +} + +inline _mlsValue _mls_builtin_z_to_int(_mlsValue a) { + assert(_mlsValue::isValueOf<_mls_ZInt>(a)); + return _mlsValue::cast<_mls_ZInt>(a)->toInt(); +} + +inline _mlsValue _mls_builtin_z_of_int(_mlsValue a) { + assert(_mlsValue::isIntLit(a)); + return _mlsValue::create<_mls_ZInt>(a); +} + +inline _mlsValue _mls_builtin_print(_mlsValue a) { + a.print(); + return _mlsValue::create<_mls_Unit>(); +} + +inline _mlsValue _mls_builtin_println(_mlsValue a) { + a.print(); + std::puts(""); + return _mlsValue::create<_mls_Unit>(); +} + +inline _mlsValue _mls_builtin_debug(_mlsValue a) { + a.print(); + std::puts(""); + return a; +} diff --git a/hkmc2/shared/src/test/mlscript/llir/BadPrograms.mls b/hkmc2/shared/src/test/mlscript/llir/BadPrograms.mls new file mode 100644 index 0000000000..ec2a852a72 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/llir/BadPrograms.mls @@ -0,0 +1,27 @@ + +:global +:llir +:cpp + +:ge +fun oops(a) = + class A with + fun m = a + let x = 1 +//│ ═══[COMPILATION ERROR] Non top-level definition A not supported +//│ Stopped due to an error during the Llir generation + +:ge +let x = "oops" +x.m +//│ ═══[COMPILATION ERROR] Unsupported selection by users +//│ Stopped due to an error during the Llir generation + +:ge +fun foo(a) = + let x + if a > 0 do + x = 1 + x + 1 +//│ ═══[COMPILATION ERROR] Name not found: x +//│ Stopped due to an error during the Llir generation diff --git a/hkmc2/shared/src/test/mlscript/llir/Playground.mls b/hkmc2/shared/src/test/mlscript/llir/Playground.mls new file mode 100644 index 0000000000..a0bce1b917 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/llir/Playground.mls @@ -0,0 +1,442 @@ +:js +:llir + + +:sllir +abstract class Option[out T]: Some[T] | None +class Some[out T](x: T) extends Option[T] +object None extends Option +fun fromSome(s) = if s is Some(x) then x +class Lazy[out A](init: () -> A) with + mut val cache: Option[A] = None +fun lazy(x) = Lazy(x) +//│ LLIR: +//│ class Option() +//│ class Some(x) +//│ class None() +//│ class Lazy(init,cache) +//│ def fromSome(s) = +//│ case s of +//│ Some => +//│ let x$0 = s. in +//│ x$0 +//│ _ => +//│ panic "match error" +//│ def j$0() = +//│ null +//│ def lazy(x1) = +//│ let x$1 = Lazy(x1) in +//│ x$1 +//│ undefined + +:sllir +fun testCtor1() = None +fun testCtor2() = new None +//│ LLIR: +//│ +//│ def testCtor1() = +//│ let x$0 = None() in +//│ x$0 +//│ def testCtor2() = +//│ let x$1 = None() in +//│ x$1 +//│ undefined + +:sllir +:intl +abstract class Option[out T]: Some[T] | None +class Some[out T](x: T) extends Option[T] +object None extends Option +fun fromSome(s) = if s is Some(x) then x +abstract class Nat: S[Nat] | O +class S(s: Nat) extends Nat +object O extends Nat +fun aaa() = + let m = 1 + let n = 2 + let p = 3 + let q = 4 + m + n - p + q +fun bbb() = + let x = aaa() + x * 100 + 4 +fun not(x) = + if x then false else true +fun foo(x) = + if x then None + else Some(foo(not(x))) +fun main() = + let x = foo(false) + if x is + None then aaa() + Some(b1) then bbb() +main() +//│ = 404 +//│ LLIR: +//│ class Option() +//│ class Some(x) +//│ class None() +//│ class Nat() +//│ class S(s) +//│ class O() +//│ def fromSome(s) = +//│ case s of +//│ Some => +//│ let x$0 = s. in +//│ x$0 +//│ _ => +//│ panic "match error" +//│ def j$0() = +//│ null +//│ def aaa() = +//│ let x$1 = 1 in +//│ let x$2 = 2 in +//│ let x$3 = 3 in +//│ let x$4 = 4 in +//│ let x$5 = +(x$1,x$2) in +//│ let x$6 = -(x$5,x$3) in +//│ let x$7 = +(x$6,x$4) in +//│ x$7 +//│ def bbb() = +//│ let* (x$8) = aaa() in +//│ let x$9 = *(x$8,100) in +//│ let x$10 = +(x$9,4) in +//│ x$10 +//│ def not(x2) = +//│ case x2 of +//│ BoolLit(true) => +//│ false +//│ _ => +//│ true +//│ def j$1() = +//│ null +//│ def foo(x3) = +//│ case x3 of +//│ BoolLit(true) => +//│ let x$11 = None() in +//│ x$11 +//│ _ => +//│ let* (x$12) = not(x3) in +//│ let* (x$13) = foo(x$12) in +//│ let x$14 = Some(x$13) in +//│ x$14 +//│ def j$2() = +//│ null +//│ def main() = +//│ let* (x$15) = foo(false) in +//│ case x$15 of +//│ None => +//│ let* (x$16) = aaa() in +//│ x$16 +//│ _ => +//│ case x$15 of +//│ Some => +//│ let x$17 = x$15. in +//│ let* (x$18) = bbb() in +//│ x$18 +//│ _ => +//│ panic "match error" +//│ def j$4() = +//│ jump j$3() +//│ def j$3() = +//│ null +//│ let* (x$19) = main() in +//│ x$19 +//│ +//│ Interpreted: +//│ 404 + +:sllir +:intl +fun f1() = + let x = 1 + let x = 2 + x +f1() +//│ = 2 +//│ LLIR: +//│ +//│ def f1() = +//│ let x$0 = 1 in +//│ let x$1 = 2 in +//│ x$1 +//│ let* (x$2) = f1() in +//│ x$2 +//│ +//│ Interpreted: +//│ 2 + +:sllir +:intl +fun f2() = + let x = 0 + if x == 1 then 2 else 3 +f2() +//│ = 3 +//│ LLIR: +//│ +//│ def f2() = +//│ let x$0 = 0 in +//│ let x$1 = ==(x$0,1) in +//│ case x$1 of +//│ BoolLit(true) => +//│ 2 +//│ _ => +//│ 3 +//│ def j$0() = +//│ null +//│ let* (x$2) = f2() in +//│ x$2 +//│ +//│ Interpreted: +//│ 3 + + +:sllir +fun f3() = + let x1 = 0 + let x2 = 1 + if true then x1 else x2 +f3() +//│ = 0 +//│ LLIR: +//│ +//│ def f3() = +//│ let x$0 = 0 in +//│ let x$1 = 1 in +//│ let x$2 = true in +//│ case x$2 of +//│ BoolLit(true) => +//│ x$0 +//│ _ => +//│ x$1 +//│ def j$0() = +//│ null +//│ let* (x$3) = f3() in +//│ x$3 + + +:sllir +:intl +fun f4() = + let x = 0 + let x = if x == 1 then 2 else 3 + x +f4() +//│ = 3 +//│ LLIR: +//│ +//│ def f4() = +//│ let x$0 = 0 in +//│ let x$1 = ==(x$0,1) in +//│ case x$1 of +//│ BoolLit(true) => +//│ let x$3 = 2 in +//│ jump j$0(x$3) +//│ _ => +//│ let x$4 = 3 in +//│ jump j$0(x$4) +//│ def j$0(x$2) = +//│ x$2 +//│ let* (x$5) = f4() in +//│ x$5 +//│ +//│ Interpreted: +//│ 3 + +:sllir +:intl +fun f5() = + let x = 0 + let x = if x == 1 then 2 else 3 + let x = if x == 2 then 4 else 5 + x +f5() +//│ = 5 +//│ LLIR: +//│ +//│ def f5() = +//│ let x$0 = 0 in +//│ let x$1 = ==(x$0,1) in +//│ case x$1 of +//│ BoolLit(true) => +//│ let x$3 = 2 in +//│ jump j$0(x$3) +//│ _ => +//│ let x$4 = 3 in +//│ jump j$0(x$4) +//│ def j$0(x$2) = +//│ let x$5 = ==(x$2,2) in +//│ case x$5 of +//│ BoolLit(true) => +//│ let x$7 = 4 in +//│ jump j$1(x$7) +//│ _ => +//│ let x$8 = 5 in +//│ jump j$1(x$8) +//│ def j$1(x$6) = +//│ x$6 +//│ let* (x$9) = f5() in +//│ x$9 +//│ +//│ Interpreted: +//│ 5 + +:sllir +:scpp +fun test() = + if true do test() +//│ LLIR: +//│ +//│ def test() = +//│ let x$0 = true in +//│ case x$0 of +//│ BoolLit(true) => +//│ let* (x$1) = test() in +//│ x$1 +//│ _ => +//│ undefined +//│ def j$0() = +//│ null +//│ undefined +//│ +//│ Cpp: +//│ #include "mlsprelude.h" +//│ _mlsValue _mls_j_0(); +//│ _mlsValue _mls_test(); +//│ _mlsValue _mlsMain(); +//│ _mlsValue _mls_j_0() { +//│ _mlsValue _mls_retval; +//│ _mls_retval = _mlsValue::create<_mls_Unit>(); +//│ return _mls_retval; +//│ } +//│ _mlsValue _mls_test() { +//│ _mlsValue _mls_retval; +//│ auto _mls_x_0 = _mlsValue::fromIntLit(1); +//│ if (_mlsValue::isIntLit(_mls_x_0, 1)) { +//│ auto _mls_x_1 = _mls_test(); +//│ _mls_retval = _mls_x_1; +//│ } else { +//│ _mls_retval = _mlsValue::create<_mls_Unit>(); +//│ } +//│ return _mls_retval; +//│ } +//│ _mlsValue _mlsMain() { +//│ _mlsValue _mls_retval; +//│ _mls_retval = _mlsValue::create<_mls_Unit>(); +//│ return _mls_retval; +//│ } +//│ int main() { return _mlsLargeStack(_mlsMainWrapper); } + +:sllir +:scpp +fun test() = + (if true then test()) + 1 +//│ LLIR: +//│ +//│ def test() = +//│ let x$0 = true in +//│ case x$0 of +//│ BoolLit(true) => +//│ let* (x$2) = test() in +//│ jump j$0(x$2) +//│ _ => +//│ panic "match error" +//│ def j$0(x$1) = +//│ let x$3 = +(x$1,1) in +//│ x$3 +//│ undefined +//│ +//│ Cpp: +//│ #include "mlsprelude.h" +//│ _mlsValue _mls_j_0(_mlsValue); +//│ _mlsValue _mls_test(); +//│ _mlsValue _mlsMain(); +//│ _mlsValue _mls_j_0(_mlsValue _mls_x_1) { +//│ _mlsValue _mls_retval; +//│ auto _mls_x_3 = (_mls_x_1 + _mlsValue::fromIntLit(1)); +//│ _mls_retval = _mls_x_3; +//│ return _mls_retval; +//│ } +//│ _mlsValue _mls_test() { +//│ _mlsValue _mls_retval; +//│ auto _mls_x_0 = _mlsValue::fromIntLit(1); +//│ if (_mlsValue::isIntLit(_mls_x_0, 1)) { +//│ auto _mls_x_2 = _mls_test(); +//│ _mls_retval = _mls_j_0(_mls_x_2); +//│ } else { +//│ throw std::runtime_error("match error"); +//│ } +//│ return _mls_retval; +//│ } +//│ _mlsValue _mlsMain() { +//│ _mlsValue _mls_retval; +//│ _mls_retval = _mlsValue::create<_mls_Unit>(); +//│ return _mls_retval; +//│ } +//│ int main() { return _mlsLargeStack(_mlsMainWrapper); } + + +:sllir +:intl +:scpp +fun f() = + let x = 10 + if true do + set x += 1 + x +f() +//│ = 11 +//│ LLIR: +//│ +//│ def f() = +//│ let x$0 = 10 in +//│ let x$1 = true in +//│ case x$1 of +//│ BoolLit(true) => +//│ let x$3 = +(x$0,1) in +//│ let x$4 = undefined in +//│ jump j$0(x$3) +//│ _ => +//│ let x$5 = undefined in +//│ jump j$0(x$0) +//│ def j$0(x$2) = +//│ x$2 +//│ let* (x$6) = f() in +//│ x$6 +//│ +//│ Cpp: +//│ #include "mlsprelude.h" +//│ _mlsValue _mls_j_0(_mlsValue); +//│ _mlsValue _mls_f(); +//│ _mlsValue _mlsMain(); +//│ _mlsValue _mls_j_0(_mlsValue _mls_x_2) { +//│ _mlsValue _mls_retval; +//│ _mls_retval = _mls_x_2; +//│ return _mls_retval; +//│ } +//│ _mlsValue _mls_f() { +//│ _mlsValue _mls_retval; +//│ auto _mls_x_0 = _mlsValue::fromIntLit(10); +//│ auto _mls_x_1 = _mlsValue::fromIntLit(1); +//│ if (_mlsValue::isIntLit(_mls_x_1, 1)) { +//│ auto _mls_x_3 = (_mls_x_0 + _mlsValue::fromIntLit(1)); +//│ auto _mls_x_4 = _mlsValue::create<_mls_Unit>(); +//│ _mls_retval = _mls_j_0(_mls_x_3); +//│ } else { +//│ auto _mls_x_5 = _mlsValue::create<_mls_Unit>(); +//│ _mls_retval = _mls_j_0(_mls_x_0); +//│ } +//│ return _mls_retval; +//│ } +//│ _mlsValue _mlsMain() { +//│ _mlsValue _mls_retval; +//│ auto _mls_x_6 = _mls_f(); +//│ _mls_retval = _mls_x_6; +//│ return _mls_retval; +//│ } +//│ int main() { return _mlsLargeStack(_mlsMainWrapper); } +//│ +//│ Interpreted: +//│ 11 + From 8a1af99ebfdab67ac8eec195a9dc3c5817895e74 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 16 Jan 2025 21:43:53 +0800 Subject: [PATCH 079/114] fix merge --- .../src/test/scala/hkmc2/LlirDiffMaker.scala | 3 ++ .../src/test/mlscript/bbml/bbBorrowing.mls | 1 - .../src/test/mlscript/codegen/IfThenElse.mls | 38 +++++++++---------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/LlirDiffMaker.scala b/hkmc2/jvm/src/test/scala/hkmc2/LlirDiffMaker.scala index c79eda3dce..5fb2839157 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/LlirDiffMaker.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/LlirDiffMaker.scala @@ -16,6 +16,7 @@ import hkmc2.codegen.llir.Fresh import hkmc2.utils.Scope import hkmc2.codegen.llir.Ctx import hkmc2.codegen.llir._ +import hkmc2.semantics.Elaborator abstract class LlirDiffMaker extends BbmlDiffMaker: val llir = NullaryCommand("llir") @@ -30,6 +31,8 @@ abstract class LlirDiffMaker extends BbmlDiffMaker: val p = new java.io.PrintWriter(f) try { op(p) } finally { p.close() } + given Elaborator.Ctx = curCtx + override def processTerm(trm: Blk, inImport: Bool)(using Raise): Unit = super.processTerm(trm, inImport) if llir.isSet then diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbBorrowing.mls b/hkmc2/shared/src/test/mlscript/bbml/bbBorrowing.mls index aac41b556f..69f3fbcbaf 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbBorrowing.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbBorrowing.mls @@ -194,7 +194,6 @@ letreg of r0 => // * Can leak the iterator fun iterate_unsafe: [A, Rg, Br, Res] -> MutVec[A, Rg, Br] -> (Iter[A, Br] ->{Br} Res) ->{Br} Res -//│ Type: ⊤ // * Eg: letreg of r => let b = mkVec(r) diff --git a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls index da169b32e2..77e1848be7 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls @@ -42,25 +42,6 @@ let f = x => log((if x then "ok" else "ko") + "!") :sjs let f = x => log((if x and x then "ok" else "ko") + "!") -//│ JS (unsanitized): -//│ let tmp; -//│ tmp = (x) => { -//│ let tmp1, tmp2; -//│ if (x) { -//│ if (x) { -//│ tmp1 = "ok"; -//│ } else { -//│ tmp1 = "ko"; -//│ } -//│ } else { -//│ tmp1 = "ko"; -//│ } -//│ tmp2 = tmp1 + "!"; -//│ return this.log(tmp2); -//│ }; -//│ this.f = tmp; -//│ null -//│ f = [Function: tmp] // --- TODO: What we want --- // this.f = (x) => { // let tmp, tmp1, flag; @@ -81,6 +62,25 @@ let f = x => log((if x and x then "ok" else "ko") + "!") // }; // undefined // f = [Function (anonymous)] +//│ JS (unsanitized): +//│ let tmp; +//│ tmp = (x) => { +//│ let tmp1, tmp2; +//│ if (x) { +//│ if (x) { +//│ tmp1 = "ok"; +//│ } else { +//│ tmp1 = "ko"; +//│ } +//│ } else { +//│ tmp1 = "ko"; +//│ } +//│ tmp2 = tmp1 + "!"; +//│ return this.log(tmp2); +//│ }; +//│ this.f = tmp; +//│ null +//│ f = [Function: tmp] f(true) //│ > ok! From edbbe95fb0a9e6782c5043e68ad6580abd8be38a Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 16 Jan 2025 21:46:27 +0800 Subject: [PATCH 080/114] Clean up --- hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala index af9e8d5655..57333dbab3 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -14,7 +14,6 @@ import utils.Scope import hkmc2.syntax.Tree.Ident import hkmc2.codegen.Path import hkmc2.Diagnostic.Source -import hkmc2.syntax.Keyword.`then` abstract class JSBackendDiffMaker extends MLsDiffMaker: From 944f499bd8dd7cd720c2059c641ecef7437c7f87 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Fri, 17 Jan 2025 03:11:08 +0800 Subject: [PATCH 081/114] merge from ir-handler-transform --- .../src/main/scala/hkmc2/codegen/Block.scala | 34 +++++++--- .../scala/hkmc2/codegen/HandlerLowering.scala | 62 +++++-------------- .../hkmc2/codegen/StackSafeTransform.scala | 26 +------- .../main/scala/hkmc2/utils/SymbolSubst.scala | 2 +- 4 files changed, 45 insertions(+), 79 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 311d90f862..1269aeb116 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -12,7 +12,6 @@ import hkmc2.semantics.{Term => st} import syntax.{Literal, Tree} import semantics.* import semantics.Term.* -import scala.reflect.ClassTag import sem.Elaborator.State @@ -105,20 +104,16 @@ sealed abstract class Block extends Product with AutoLocated: ) case Value.Arr(elems) => Value.Arr(elems.map(a => Arg(a.spread, pathMap(a.value)))) - def _resMap(r: Result) = r match + def resMap(r: Result) = r match case p: Path => pathMap(p) case c @ Call(fun, args) => Call(pathMap(fun), args.map(a => Arg(a.spread, pathMap(a.value))))(c.isMlsFun) case Instantiate(cls, args) => Instantiate(pathMap(cls), args.map(pathMap)) - def resMap[T <: Result : ClassTag](l: T): T = _resMap(l) match - case r : T => r - case _ => l - this match case Return(res, implct) => Return(resMap(res), implct) case Throw(exc: Path) => Throw(pathMap(exc)) case Assign(l, r, rst) => Assign(l.subst, resMap(r), rst.mapSyms) - case blk @ AssignField(l, n, r, rst) => AssignField(pathMap(l), n, _resMap(r), rst.mapSyms)(blk.symbol) + case blk @ AssignField(l, n, r, rst) => AssignField(pathMap(l), n, resMap(r), rst.mapSyms)(blk.symbol) case Match(scrut, arms, dflt, rst) => val newArms = arms.map((cse, blk) => val newCse = cse match @@ -394,7 +389,9 @@ case class Call(fun: Path, args: Ls[Arg])(val isMlsFun: Bool) extends Result case class Instantiate(cls: Path, args: Ls[Path]) extends Result -sealed abstract class Path extends Result +sealed abstract class Path extends Result: + def selN(id: Tree.Ident) = Select(this, id)(N) + def asArg = Arg(false, this) case class Select(qual: Path, name: Tree.Ident)(val symbol: Opt[FieldSymbol]) extends Path with ProductWithExtraInfo: def extraInfo: Str = symbol.mkString @@ -408,3 +405,24 @@ enum Value extends Path: case class Arg(spread: Bool, value: Path) +extension (k: Block => Block) + + def chain(other: Block => Block): Block => Block = b => k(other(b)) + def rest(b: Block): Block = k(b) + def transform(f: (Block => Block) => (Block => Block)) = f(k) + + def assign(l: Local, r: Result) = k.chain(Assign(l, r, _)) + def assignFieldN(lhs: Path, nme: Tree.Ident, rhs: Result) = k.chain(AssignField(lhs, nme, rhs, _)(N)) + def break(l: Local): Block = k.rest(Break(l)) + def continue(l: Local): Block = k.rest(Continue(l)) + def define(defn: Defn) = k.chain(Define(defn, _)) + def end = k.rest(End()) + def ifthen(scrut: Path, cse: Case, trm: Block): Block => Block = k.chain(Match(scrut, cse -> trm :: Nil, N, _)) + def label(label: Local, body: Block) = k.chain(Label(label, body, _)) + def ret(r: Result) = k.rest(Return(r, false)) + def staticif(b: Boolean, f: (Block => Block) => (Block => Block)) = if b then k.transform(f) else k + +def blockBuilder: Block => Block = identity + +extension (l: Local) + def asPath: Path = Value.Ref(l) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 8c830f20c1..a91d21185d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -1,55 +1,29 @@ package hkmc2 package codegen -import mlscript.utils.*, shorthands.* -import utils.* +import scala.annotation.tailrec +import mlscript.utils.*, shorthands.* +import hkmc2.utils.* +import hkmc2.utils.SymbolSubst import hkmc2.Message.MessageContext -import semantics.Elaborator.State - import syntax.{Literal, Tree, ParamBind} import semantics.* -import scala.annotation.tailrec -import hkmc2.semantics.Elaborator.ctx - -import hkmc2.utils.SymbolSubst +import semantics.Elaborator.ctx +import semantics.Elaborator.State object HandlerLowering: private val pcIdent: Tree.Ident = Tree.Ident("pc") private val nextIdent: Tree.Ident = Tree.Ident("next") private val tailIdent: Tree.Ident = Tree.Ident("tail") - - extension (k: Block => Block) - - def chain(other: Block => Block): Block => Block = b => k(other(b)) - def rest(b: Block): Block = k(b) - def transform(f: (Block => Block) => (Block => Block)) = f(k) - - def assign(l: Local, r: Result) = k.chain(Assign(l, r, _)) - def assignFieldN(lhs: Path, nme: Tree.Ident, rhs: Result) = k.chain(AssignField(lhs, nme, rhs, _)(N)) - def break(l: Local): Block = k.rest(Break(l)) - def continue(l: Local): Block = k.rest(Continue(l)) - def define(defn: Defn) = k.chain(Define(defn, _)) - def end() = k.rest(End()) - def ifthen(scrut: Path, cse: Case, trm: Block): Block => Block = k.chain(Match(scrut, cse -> trm :: Nil, N, _)) - def label(label: Local, body: Block) = k.chain(Label(label, body, _)) - def ret(r: Result) = k.rest(Return(r, false)) - def staticif(b: Boolean, f: (Block => Block) => (Block => Block)) = if b then k.transform(f) else k - - private def blockBuilder: Block => Block = identity extension (p: Path) - def selN(id: Tree.Ident) = Select(p, id)(N) def pc = p.selN(pcIdent) + def value = p.selN(Tree.Ident("value")) def next = p.selN(nextIdent) def tail = p.selN(tailIdent) - def value = p.selN(Tree.Ident("value")) - def asArg = Arg(false, p) - - extension (l: Local) - def asPath: Path = Value.Ref(l) private case class LinkState(res: Path, cls: Path, uid: StateId) @@ -173,7 +147,6 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): val armsParts = arms.map((cse, blkk) => (cse, go(blkk)(afterEnd = S(restId)))) val dfltParts = dflt.map(blkk => go(blkk)(afterEnd = S(restId))) - val states_ = restParts.states ::: armsParts.flatMap(_._2.states) val states = dfltParts match @@ -218,7 +191,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case Break(label) => val (start, end) = labelIds.get(label) match - case N => raise(ErrorReport( + case N => raise(InternalError( msg"Could not find label '${label.nme}'" -> label.toLoc :: Nil, source = Diagnostic.Source.Compilation)) @@ -227,7 +200,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): PartRet(StateTransition(end), Nil) case Continue(label) => val (start, end) = labelIds.get(label) match - case N => raise(ErrorReport( + case N => raise(InternalError( msg"Could not find label '${label.nme}'" -> label.toLoc :: Nil, source = Diagnostic.Source.Compilation)) @@ -275,8 +248,8 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): * 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 + * a) generate continuation class + * b) generate normal function body * 3. float out definitions */ @@ -286,13 +259,12 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): val stage2 = secondPass(stage1) if h.isTopLevel then stage2 else thirdPass(stage2) - private def firstPass(b: Block)(using HandlerCtx): Block = - b.map(firstPass) match - case b: HandleBlock => translateHandleBlock(b) - case b => b.mapValue { + private def firstPass(b: Block)(using HandlerCtx): Block = b.map(firstPass) match + case b: HandleBlock => translateHandleBlock(b) + case b => b.mapValue: case Value.Lam(params, body) => Value.Lam(params, translateBlock(body, functionHandlerCtx)) case v => v - } match { + .match case Return(c: Call, implct) if handlerCtx.isHandleFree => Return(c, implct) case b => b.mapResult: case r @ Call(Value.Ref(_: BuiltinSymbol), _) => N @@ -300,11 +272,11 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): val res = freshTmp("res") S(k => CallPlaceholder(res, freshId(), false, c, k(Value.Ref(res)))) case r => N - } match + .match case Define(f: FunDefn, rst) => Define(translateFun(f), rst) case Define(c: ClsLikeDefn, rst) => Define(translateCls(c), rst) case b => b - + private def secondPass(b: Block)(using HandlerCtx): Block = val cls = if handlerCtx.isTopLevel then N else genContClass(b) cls match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index e436a7e2d1..9210008faa 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -10,12 +10,6 @@ import hkmc2.syntax.Tree class StackSafeTransform(depthLimit: Int)(using State): - extension (l: Local) - def asPath: Path = Value.Ref(l) - extension (p: Path) - def selN(id: Tree.Ident) = Select(p, id)(N) - def asArg = Arg(false, p) - private val STACK_DEPTH_IDENT: Tree.Ident = Tree.Ident("__stackDepth") private val STACK_OFFSET_IDENT: Tree.Ident = Tree.Ident("__stackOffset") private val STACK_HANDLER_IDENT: Tree.Ident = Tree.Ident("__stackHandler") @@ -32,24 +26,6 @@ class StackSafeTransform(depthLimit: Int)(using State): Call(State.builtinOpsMap(op).asPath, List(a.asArg, b.asArg))(true) // TODO: this code is copied from HandlerLowering and is quite useful. Maybe refactor it into a utils file - extension (k: Block => Block) - - def chain(other: Block => Block): Block => Block = b => k(other(b)) - def rest(b: Block): Block = k(b) - def transform(f: (Block => Block) => (Block => Block)) = f(k) - - def assign(l: Local, r: Result) = k.chain(Assign(l, r, _)) - def assignFieldN(lhs: Path, nme: Tree.Ident, rhs: Result) = k.chain(AssignField(lhs, nme, rhs, _)(N)) - def break(l: Local): Block = k.rest(Break(l)) - def continue(l: Local): Block = k.rest(Continue(l)) - def define(defn: Defn) = k.chain(Define(defn, _)) - def end() = k.rest(End()) - def ifthen(scrut: Path, cse: Case, trm: Block): Block => Block = k.chain(Match(scrut, cse -> trm :: Nil, N, _)) - def label(label: Local, body: Block) = k.chain(Label(label, body, _)) - def ret(r: Result) = k.rest(Return(r, false)) - def staticif(b: Boolean, f: (Block => Block) => (Block => Block)) = if b then k.transform(f) else k - - private def blockBuilder: Block => Block = identity // Increases the stack depth, assigns the call to a value, then decreases the stack depth // then binds that value to a desired block @@ -160,7 +136,7 @@ class StackSafeTransform(depthLimit: Int)(using State): scrutSym.asPath, Case.Lit(Tree.BoolLit(true)), blockBuilder.assign( // tmp = perform(undefined) TempSymbol(None, "tmp"), - Call(Select(stackHandlerPath, Tree.Ident("perform"))(N), Nil)(true)).end()) + Call(Select(stackHandlerPath, Tree.Ident("perform"))(N), Nil)(true)).end) .rest(newBody) def rewriteFn(defn: FunDefn) = FunDefn(defn.sym, defn.params, rewriteBlk(defn.body)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/SymbolSubst.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/SymbolSubst.scala index f0ac4f0e5e..9ee9509d41 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/SymbolSubst.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/SymbolSubst.scala @@ -15,4 +15,4 @@ trait SymbolSubst: def mapModuleSym(s: ModuleSymbol): ModuleSymbol = s def mapTypeAliasSym(s: TypeAliasSymbol): TypeAliasSymbol = s def mapPatSym(s: PatternSymbol): PatternSymbol = s - def mapTopLevelSym(s: TopLevelSymbol): TopLevelSymbol = s \ No newline at end of file + def mapTopLevelSym(s: TopLevelSymbol): TopLevelSymbol = s From 45d3e1d364c14cb726649bdc27c40a93442a6f0c Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 22 Jan 2025 18:08:11 +0800 Subject: [PATCH 082/114] clean up and document floatOutDefns --- hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala | 7 +++++++ hkmc2/shared/src/test/mlscript-compile/Predef.mls | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index a8beaead57..5b6b0e48e7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -87,6 +87,13 @@ sealed abstract class Block extends Product with AutoLocated: case HandleBlockReturn(res) => res.freeVars case End(msg) => Set.empty + // Moves definitions in a block to the top. Only scans one definition deep, i.e. definitions inside other definitions + // are not moved out. + // + // outerOnly = true: only top-level definitions (which could be exported) are moved to the top, which is essentially + // a re-ordering of statements. + // + // outerOnly = false: definitions inside `if` and `while` statements are also moved out. def floatOutDefns(outerOnly: Bool) = def rec(b: Block, acc: List[Defn]): (Block, List[Defn]) = b match diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index d277e19677..2f9f67277a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -255,4 +255,4 @@ val __stackDepth = 0 // Tracks the virtual + real stack depth val __stackOffset = 0 // How much to offset __stackDepth by to get the true stack depth (i.e. the virtual depth) val __stackHandler = null abstract class __StackDelay() with - fun perform()// Private definitions for algebraic effects \ No newline at end of file + fun perform()// Private definitions for algebraic effects From d0bac71ad7206ae28744e9f9a115804f2ab6e3e7 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 22 Jan 2025 22:07:41 +0800 Subject: [PATCH 083/114] Merge from hkmc2 --- .../src/main/scala/hkmc2/MLsCompiler.scala | 7 +- .../src/main/scala/hkmc2/bbml/bbML.scala | 13 +- .../src/main/scala/hkmc2/codegen/Block.scala | 67 ++-- .../hkmc2/codegen/BlockTransformer.scala | 100 +++--- .../scala/hkmc2/codegen/HandlerLowering.scala | 65 ++-- .../main/scala/hkmc2/codegen/Lowering.scala | 39 ++- .../main/scala/hkmc2/codegen/Printer.scala | 11 +- .../hkmc2/codegen/StackSafeTransform.scala | 11 +- .../scala/hkmc2/codegen/js/JSBuilder.scala | 126 ++++---- .../scala/hkmc2/codegen/llir/Builder.scala | 99 +++--- .../scala/hkmc2/semantics/BlockImpl.scala | 2 +- .../scala/hkmc2/semantics/Elaborator.scala | 157 ++++++---- .../main/scala/hkmc2/semantics/Scope.scala | 19 -- .../main/scala/hkmc2/semantics/Symbol.scala | 18 +- .../src/main/scala/hkmc2/semantics/Term.scala | 90 ++++-- .../src/main/scala/hkmc2/syntax/Tree.scala | 22 +- .../src/main/scala/hkmc2/utils/ReplHost.scala | 4 +- .../src/main/scala/hkmc2/utils/Scope.scala | 13 +- .../src/main/scala/hkmc2/utils/utils.scala | 9 + .../src/main/scala/utils/TraceLogger.scala | 5 +- .../src/test/mlscript-compile/Example.mjs | 7 +- .../src/test/mlscript-compile/Option.mjs | 7 +- .../src/test/mlscript-compile/Predef.mjs | 23 +- .../src/test/mlscript-compile/Stack.mjs | 13 +- .../shared/src/test/mlscript-compile/Str.mjs | 7 +- .../test/mlscript-compile/apps/Accounting.mjs | 27 +- .../src/test/mlscript-compile/apps/CSV.mjs | 11 +- .../mlscript/backlog/MultilineExpressions.mls | 22 +- .../src/test/mlscript/backlog/Records.mls | 9 +- .../src/test/mlscript/backlog/ToTriage.mls | 84 ++++- .../src/test/mlscript/basics/BadClasses.mls | 3 +- .../src/test/mlscript/basics/BadDefs.mls | 42 ++- .../src/test/mlscript/basics/Classes.mls | 5 +- .../src/test/mlscript/basics/Declare.mls | 36 +++ .../mlscript/basics/MemberProjections.mls | 6 +- .../test/mlscript/basics/MiscArrayTests.mls | 4 +- .../test/mlscript/basics/MultiParamLists.mls | 46 +-- .../src/test/mlscript/basics/MutVal.mls | 3 + hkmc2/shared/src/test/mlscript/basics/New.mls | 6 +- .../src/test/mlscript/basics/OpBlocks.mls | 2 +- .../src/test/mlscript/basics/Overloading.mls | 25 ++ .../src/test/mlscript/basics/PredefTest.mls | 2 +- .../src/test/mlscript/bbml/bbBasics.mls | 34 +- .../src/test/mlscript/bbml/bbBounds.mls | 2 +- .../shared/src/test/mlscript/bbml/bbCheck.mls | 4 +- .../src/test/mlscript/bbml/bbCodeGen.mls | 86 +++--- .../src/test/mlscript/bbml/bbErrors.mls | 2 +- .../src/test/mlscript/bbml/bbExtrude.mls | 2 +- .../src/test/mlscript/bbml/bbGetters.mls | 27 +- .../src/test/mlscript/bbml/bbOption.mls | 2 +- .../src/test/mlscript/bbml/bbPrelude.mls | 74 +++-- hkmc2/shared/src/test/mlscript/bbml/bbRec.mls | 22 +- .../src/test/mlscript/bbml/bbUntyped.mls | 13 + .../src/test/mlscript/codegen/Arrays.mls | 6 + .../src/test/mlscript/codegen/BadInit.mls | 8 +- .../src/test/mlscript/codegen/BadOpen.mls | 2 +- .../src/test/mlscript/codegen/BasicTerms.mls | 32 +- .../test/mlscript/codegen/BlockPrinter.mls | 12 +- .../src/test/mlscript/codegen/BuiltinOps.mls | 6 +- .../src/test/mlscript/codegen/CaseOfCase.mls | 21 +- .../test/mlscript/codegen/CaseShorthand.mls | 12 +- .../test/mlscript/codegen/ClassInClass.mls | 15 +- .../src/test/mlscript/codegen/ClassInFun.mls | 19 +- .../test/mlscript/codegen/ClassMatching.mls | 96 +++--- .../src/test/mlscript/codegen/Comma.mls | 6 +- .../src/test/mlscript/codegen/ConsoleLog.mls | 18 +- .../test/mlscript/codegen/DelayedLetInit.mls | 52 ++-- hkmc2/shared/src/test/mlscript/codegen/Do.mls | 10 +- .../src/test/mlscript/codegen/EarlyReturn.mls | 7 +- .../test/mlscript/codegen/FieldSymbols.mls | 16 +- .../src/test/mlscript/codegen/FunInClass.mls | 71 +++-- .../src/test/mlscript/codegen/Functions.mls | 14 +- .../test/mlscript/codegen/FunctionsThis.mls | 9 +- .../src/test/mlscript/codegen/FunnyOpen.mls | 8 +- .../src/test/mlscript/codegen/Getters.mls | 168 +++++----- .../src/test/mlscript/codegen/GlobalThis.mls | 13 +- .../src/test/mlscript/codegen/Hygiene.mls | 65 ++-- .../src/test/mlscript/codegen/IfThenElse.mls | 48 +-- .../test/mlscript/codegen/ImportExample.mls | 4 +- .../src/test/mlscript/codegen/ImportMLs.mls | 6 +- .../src/test/mlscript/codegen/ImportMLsJS.mls | 2 +- .../src/test/mlscript/codegen/ImportedOps.mls | 13 +- .../src/test/mlscript/codegen/Lambdas.mls | 2 +- .../test/mlscript/codegen/ModuleMethods.mls | 9 +- .../src/test/mlscript/codegen/Modules.mls | 47 ++- .../test/mlscript/codegen/NestedClasses.mls | 13 + .../test/mlscript/codegen/OpenWildcard.mls | 10 +- .../src/test/mlscript/codegen/OptMatch.mls | 25 +- .../test/mlscript/codegen/ParamClasses.mls | 58 ++-- .../src/test/mlscript/codegen/PartialApps.mls | 26 +- .../test/mlscript/codegen/PlainClasses.mls | 93 +++--- .../src/test/mlscript/codegen/PredefJS.mls | 1 + .../src/test/mlscript/codegen/Primes.mls | 14 +- .../shared/src/test/mlscript/codegen/Pwd.mls | 10 +- .../src/test/mlscript/codegen/ReboundLet.mls | 8 +- .../shared/src/test/mlscript/codegen/Repl.mls | 18 +- .../test/mlscript/codegen/SanityChecks.mls | 138 +++++---- .../src/test/mlscript/codegen/SetIn.mls | 98 +++--- .../src/test/mlscript/codegen/Spreads.mls | 4 +- .../shared/src/test/mlscript/codegen/This.mls | 38 ++- .../mlscript/codegen/ThisCallVariations.mls | 32 +- .../src/test/mlscript/codegen/ThisCalls.mls | 4 +- .../src/test/mlscript/codegen/Throw.mls | 9 +- .../src/test/mlscript/codegen/TraceLog.mls | 9 +- .../test/mlscript/codegen/TraceLogIndent.mls | 4 +- .../src/test/mlscript/codegen/While.mls | 74 +++-- .../src/test/mlscript/decls/Prelude.mls | 2 + .../src/test/mlscript/handlers/Effects.mls | 67 ++-- .../test/mlscript/handlers/EffectsHygiene.mls | 37 +++ .../mlscript/handlers/EffectsInClasses.mls | 52 +++- .../handlers/HandlerBlockBindings.mls | 4 +- .../mlscript/handlers/HandlersScratch.mls | 7 + .../mlscript/handlers/RecursiveHandlers.mls | 214 ++++++------- .../mlscript/handlers/ReturnInHandler.mls | 2 +- .../test/mlscript/handlers/StackSafety.mls | 291 +++++++++--------- .../src/test/mlscript/interop/Number.mls | 1 + .../src/test/mlscript/llir/BadPrograms.mls | 13 +- .../src/test/mlscript/llir/BasicCpp.mls | 46 +++ .../src/test/mlscript/llir/LlirScratch.mls | 8 + .../src/test/mlscript/meta/ImporterTest.mls | 11 + .../shared/src/test/mlscript/nofib/lambda.mls | 2 +- hkmc2/shared/src/test/mlscript/nofib/rsa.mls | 1 - .../src/test/mlscript/parser/Extends.mls | 32 +- .../src/test/mlscript/parser/Handler.mls | 6 +- .../src/test/mlscript/parser/PrefixOps.mls | 2 +- .../src/test/mlscript/rp/MatchResult.mls | 7 +- .../test/mlscript/syntax/KeywordStutters.mls | 4 +- .../syntax/annotations/Declarations.mls | 4 +- .../syntax/annotations/Unsupported.mls | 20 +- .../mlscript/ucs/future/SymbolicClass.mls | 8 +- .../ucs/normalization/SimplePairMatches.mls | 21 +- .../mlscript/ucs/papers/OperatorSplit.mls | 14 +- .../mlscript/ucs/patterns/NamePattern.mls | 2 +- .../test/mlscript/ucs/patterns/RestTuple.mls | 23 +- .../mlscript/ucs/syntax/NestedOpSplits.mls | 2 +- .../src/test/scala/hkmc2/DiffMaker.scala | 10 +- .../test/scala/hkmc2/JSBackendDiffMaker.scala | 31 +- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 9 +- 138 files changed, 2305 insertions(+), 1686 deletions(-) delete mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/Scope.scala create mode 100644 hkmc2/shared/src/test/mlscript/basics/Declare.mls create mode 100644 hkmc2/shared/src/test/mlscript/basics/Overloading.mls create mode 100644 hkmc2/shared/src/test/mlscript/bbml/bbUntyped.mls create mode 100644 hkmc2/shared/src/test/mlscript/codegen/NestedClasses.mls create mode 100644 hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls create mode 100644 hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls create mode 100644 hkmc2/shared/src/test/mlscript/llir/BasicCpp.mls create mode 100644 hkmc2/shared/src/test/mlscript/llir/LlirScratch.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala index a756e0d12a..53a487db11 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala @@ -71,7 +71,8 @@ class MLsCompiler(preludeFile: os.Path): newCtx.nest(N).givenIn: - val (blk, newCtx) = elab.importFrom(mainParse.resultBlk) + val parsed = mainParse.resultBlk + val (blk, newCtx) = elab.importFrom(parsed) val low = ltl.givenIn: codegen.Lowering(lowerHandlers = false, None) // TODO: properly hook up stack limit val jsb = codegen.js.JSBuilder() @@ -79,8 +80,10 @@ class MLsCompiler(preludeFile: os.Path): val baseScp: utils.Scope = utils.Scope.empty val nestedScp = baseScp.nest + val nme = file.baseName + val exportedSymbol = parsed.definedSymbols.find(_._1 === nme).map(_._2) val je = nestedScp.givenIn: - jsb.program(le, S(file.baseName), wd) + jsb.program(le, exportedSymbol, wd) val jsStr = je.stripBreaks.mkString(100) val out = file / os.up / (file.baseName + ".mjs") os.write.over(out, jsStr) diff --git a/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala b/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala index 987dca51ff..1ffd51c5c4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala @@ -69,7 +69,7 @@ object BbCtx: def init(raise: Raise)(using Elaborator.State, Elaborator.Ctx): BbCtx = new BbCtx(raise, summon, None, 1, HashMap.empty, Bot, N) - val builtinOps = Set("+", "-", "*", "/", "<", ">", "<=", ">=", "==", "!=", "&&", "||") + val builtinOps = Elaborator.binaryOps ++ Elaborator.unaryOps ++ Elaborator.aliasOps.keySet end BbCtx @@ -84,8 +84,8 @@ class BBTyper(using elState: Elaborator.State, tl: TL): private def freshVar(sym: Symbol, hint: Str = "")(using ctx: BbCtx): InfVar = InfVar(ctx.lvl, infVarState.nextUid, new VarState(), false)(sym, hint) private def freshWildcard(sym: Symbol)(using ctx: BbCtx) = - val in = freshVar(sym, "-") - val out = freshVar(sym, "+") + val in = freshVar(sym, "") + val out = freshVar(sym, "") // in.state.upperBounds ::= out // * Not needed for soundness; complicates inferred types Wildcard(in, out) private def freshReg(sym: Symbol)(using ctx: BbCtx) = @@ -138,8 +138,8 @@ class BBTyper(using elState: Elaborator.State, tl: TL): // log(s"Type application: ${cls.nme} with ${targs}") cls.symbol.flatMap(_.asTpe) match case S(tpeSym) => - if tpeSym.nme === "Any" then Top - else if tpeSym.nme === "Nothing" then Bot + if tpeSym.nme === "Any" then Top // FIXME hygiene + else if tpeSym.nme === "Nothing" then Bot // FIXME hygiene else val defn = tpeSym.defn.get if targs.length != defn.tparams.length then @@ -425,6 +425,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL): trace[(GeneralType, Type)](s"${ctx.lvl}. Typing ${t.showDbg}", res => s": (${res._1.showDbg}, ${res._2.showDbg})"): given CCtx = CCtx.init(t, N) t match + case Term.Annotated(Annot.Untyped, _) => (Bot, Bot) case sel @ Term.SynthSel(Ref(_: TopLevelSymbol), nme) if sel.symbol.isDefined => typeCheck(Ref(sel.symbol.get)(sel.nme, 666)) // FIXME 666 @@ -507,8 +508,6 @@ class BBTyper(using elState: Elaborator.State, tl: TL): case _ => (error(msg"${field.name} is not a valid member in class ${clsSym.nme}" -> t.toLoc :: Nil), Bot) case N => (error(msg"Not a valid class: ${cls.describe}" -> cls.toLoc :: Nil), Bot) - case Term.App(lhs: Term.SynthSel, Term.Tup(Nil)) if lhs.sym.exists(_.isGetter) => - typeCheck(lhs) // * Getter access will be elaborated to applications. But they cannot be typed as normal applications. case t @ Term.App(lhs, Term.Tup(rhs)) => val (funTy, lhsEff) = typeCheck(lhs) app((funTy, lhsEff), rhs, t) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 5b6b0e48e7..0915d3cd5a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -36,7 +36,9 @@ sealed abstract class Block extends Product with AutoLocated: case End(_) => Set.empty case Break(_) => Set.empty case Continue(_) => Set.empty - case Define(defn, rst) => rst.definedVars + case Define(defn, rst) => + val rest = rst.definedVars + if defn.isOwned then rest else rest + defn.sym case HandleBlock(lhs, res, par, cls, hdr, bod, rst) => bod.definedVars ++ rst.definedVars + lhs case HandleBlockReturn(_) => Set.empty case TryBlock(sub, fin, rst) => sub.definedVars ++ fin.definedVars ++ rst.definedVars @@ -86,7 +88,17 @@ sealed abstract class Block extends Product with AutoLocated: (bod.freeVars - lhs) ++ rst.freeVars ++ hdr.flatMap(_.freeVars) case HandleBlockReturn(res) => res.freeVars case End(msg) => Set.empty - + + lazy val subBlocks: Ls[Block] = this match + case Match(_, arms, dflt, rest) => arms.map(_._2) ++ dflt.toList :+ rest + case Begin(sub, rest) => sub :: rest :: Nil + case TryBlock(sub, finallyDo, rest) => sub :: finallyDo :: rest :: Nil + case Assign(_, rhs, rest) => rest :: Nil + case AssignField(_, _, rhs, rest) => rest :: Nil + case Define(_, rest) => rest :: Nil + case HandleBlock(_, _, _, _, handlers, body, rest) => handlers.map(_.body) :+ body :+ rest + case _: Return | _: Throw | _: Label | _: Break | _: Continue | _: End | _: HandleBlockReturn => Nil + // Moves definitions in a block to the top. Only scans one definition deep, i.e. definitions inside other definitions // are not moved out. // @@ -163,7 +175,7 @@ sealed abstract class Block extends Product with AutoLocated: case HandleBlockReturn(res) => (b, acc) case End(msg) => (b, acc) rec(this, Nil) - + end Block sealed abstract class BlockTail extends Block @@ -198,43 +210,62 @@ case class AssignField(lhs: Path, nme: Tree.Ident, rhs: Result, rest: Block)(val case class Define(defn: Defn, rest: Block) extends Block with ProductWithTail -case class HandleBlock(lhs: Local, res: Local, par: Path, cls: ClassSymbol, handlers: Ls[Handler], body: Block, rest: Block) extends Block with ProductWithTail +case class HandleBlock( + lhs: Local, + res: Local, + par: Path, + cls: ClassSymbol, + handlers: Ls[Handler], + body: Block, + rest: Block +) extends Block with ProductWithTail + case class HandleBlockReturn(res: Result) extends BlockTail sealed abstract class Defn: - val sym: MemberSymbol[?] + val innerSym: Opt[MemberSymbol[?]] + val sym: BlockMemberSymbol + def isOwned: Bool = owner.isDefined + def owner: Opt[InnerSymbol] lazy val freeVars: Set[Local] = this match - case FunDefn(sym, params, body) => body.freeVars -- params.flatMap(_.paramSyms) - sym + case FunDefn(own, sym, params, body) => body.freeVars -- params.flatMap(_.paramSyms) - sym case ValDefn(owner, k, sym, rhs) => rhs.freeVars - case ClsLikeDefn(sym, k, parentSym, methods, privateFields, publicFields, preCtor, ctor) => + case ClsLikeDefn(own, isym, sym, k, paramsOpt, parentSym, methods, privateFields, publicFields, preCtor, ctor) => preCtor.freeVars ++ ctor.freeVars ++ methods.flatMap(_.freeVars) -- privateFields -- publicFields.map(_.sym) final case class FunDefn( + owner: Opt[InnerSymbol], sym: BlockMemberSymbol, params: Ls[ParamList], body: Block, -) extends Defn +) extends Defn: + val innerSym = N final case class ValDefn( owner: Opt[InnerSymbol], k: syntax.Val, sym: BlockMemberSymbol, rhs: Path, -) extends Defn +) extends Defn: + val innerSym = N final case class ClsLikeDefn( - sym: MemberSymbol[? <: ClassLikeDef], - k: syntax.ClsLikeKind, - parentPath: Opt[Path], - methods: Ls[FunDefn], - privateFields: Ls[TermSymbol], - publicFields: Ls[TermDefinition], - preCtor: Block, - ctor: Block, -) extends Defn + owner: Opt[InnerSymbol], + isym: MemberSymbol[? <: ClassLikeDef], + sym: BlockMemberSymbol, + k: syntax.ClsLikeKind, + paramsOpt: Opt[ParamList], + parentPath: Opt[Path], + methods: Ls[FunDefn], + privateFields: Ls[TermSymbol], + publicFields: Ls[TermDefinition], + preCtor: Block, + ctor: Block, +) extends Defn: + val innerSym = S(isym) final case class Handler( sym: BlockMemberSymbol, diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index 8e6a8b4a64..72c501efc9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -7,11 +7,12 @@ import hkmc2.utils.* import semantics.* import os.write.over + // Default implementation: nothing is transformed class BlockTransformer(subst: SymbolSubst): - + given SymbolSubst = subst - + def applyBlock(b: Block): Block = b match case _: End => b case Break(lbl) => @@ -31,15 +32,15 @@ class BlockTransformer(subst: SymbolSubst): if res2 is res then b else HandleBlockReturn(res2) case Match(scrut, arms, dflt, rst) => val scrut2 = applyPath(scrut) - val arms2 = arms.map: arm => + val arms2 = arms.mapConserve: arm => val cse2 = applyCase(arm._1) val blk2 = applyBlock(arm._2) if (cse2 is arm._1) && (blk2 is arm._2) then arm else (cse2, blk2) - val dflt2 = dflt.map(applyBlock) + val dflt2 = dflt.mapConserve(applyBlock) val rst2 = applyBlock(rst) if (scrut2 is scrut) && - (arms2 zip arms).forall(_ is _) && - (dflt2 zip dflt).forall(_ is _) && (rst2 is rst) + (arms2 is arms) && + (dflt2 is dflt) && (rst2 is rst) then b else Match(scrut2, arms2, dflt2, rst2) case Label(lbl, bod, rst) => val lbl2 = applyLocal(lbl) @@ -64,8 +65,8 @@ class BlockTransformer(subst: SymbolSubst): applyResult2(r): r2 => val l2 = applyPath(l) val rst2 = applyBlock(rst) - val sym = b.symbol.map(_.subst) - if (l2 is l) && (r2 is r) && (rst2 is rst) && (sym zip b.symbol).forall(_ is _) + val sym = b.symbol.mapConserve(_.subst) + if (l2 is l) && (r2 is r) && (rst2 is rst) && (sym is b.symbol) then b else AssignField(l2, n, r2, rst2)(sym) case Define(defn, rst) => val defn2 = applyDefn(defn) @@ -76,11 +77,11 @@ class BlockTransformer(subst: SymbolSubst): val res2 = applyLocal(res) val par2 = applyPath(par) val cls2 = cls.subst - val hdr2 = hdr.map(applyHandler) + val hdr2 = hdr.mapConserve(applyHandler) val bod2 = applyBlock(bod) val rst2 = applyBlock(rst) if (l2 is l) && (res2 is res) && (par2 is par) && (cls2 is cls) && - (hdr2 zip hdr).forall(_ is _) && (bod2 is bod) && (rst2 is rst) + (hdr2 is hdr) && (bod2 is bod) && (rst2 is rst) then b else HandleBlock(l2, res2, par2, cls2, hdr2, bod2, rst2) def applyResult2(r: Result)(k: Result => Block): Block = k(applyResult(r)) @@ -88,19 +89,19 @@ class BlockTransformer(subst: SymbolSubst): def applyResult(r: Result): Result = r match case r @ Call(fun, args) => val fun2 = applyPath(fun) - val args2 = args.map(applyArg) - if (fun2 is fun) && (args2 zip args).forall(_ is _) then r else Call(fun2, args2)(r.isMlsFun) + val args2 = args.mapConserve(applyArg) + if (fun2 is fun) && (args2 is args) then r else Call(fun2, args2)(r.isMlsFun) case Instantiate(cls, args) => val cls2 = applyPath(cls) - val args2 = args.map(applyPath) - if (cls2 is cls) && (args2 zip args).forall(_ is _) then r else Instantiate(cls2, args2) + val args2 = args.mapConserve(applyPath) + if (cls2 is cls) && (args2 is args) then r else Instantiate(cls2, args2) case p: Path => applyPath(p) def applyPath(p: Path): Path = p match case p @ Select(qual, name) => val qual2 = applyPath(qual) - val sym2 = p.symbol.map(_.subst) - if (qual2 is qual) && (sym2 zip p.symbol).forall(_ is _) then p else Select(qual2, name)(sym2) + val sym2 = p.symbol.mapConserve(_.subst) + if (qual2 is qual) && (sym2 is p.symbol) then p else Select(qual2, name)(sym2) case v: Value => applyValue(v) def applyValue(v: Value): Value = v match @@ -113,40 +114,46 @@ class BlockTransformer(subst: SymbolSubst): case Value.Lit(lit) => v case v @ Value.Lam(params, body) => applyLam(v) case Value.Arr(elems) => - val elems2 = elems.map(applyArg) - if (elems2 zip elems).forall(_ is _) then v else Value.Arr(elems2) + val elems2 = elems.mapConserve(applyArg) + if (elems2 is elems) then v else Value.Arr(elems2) def applyLocal(sym: Local): Local = sym.subst - + def applyFunDefn(fun: FunDefn): FunDefn = + val own2 = fun.owner.mapConserve(_.subst) val sym2 = fun.sym.subst - val params2 = fun.params.map(applyParamList) + val params2 = fun.params.mapConserve(applyParamList) val body2 = applyBlock(fun.body) - if (sym2 is fun.sym) && (params2 zip fun.params).forall(_ is _) && (body2 is fun.body) - then fun else FunDefn(sym2, params2, body2) + if (own2 is fun.owner) && (sym2 is fun.sym) && (params2 is fun.params) && (body2 is fun.body) + then fun else FunDefn(own2, sym2, params2, body2) def applyDefn(defn: Defn): Defn = defn match - case defn @ FunDefn(sym, params, body) => applyFunDefn(defn) + case defn: FunDefn => applyFunDefn(defn) case ValDefn(owner, k, sym, rhs) => - val owner2 = owner.map(_.subst) + val owner2 = owner.mapConserve(_.subst) val sym2 = sym.subst val rhs2 = applyPath(rhs) - if (owner2 zip owner).forall(_ is _) && (sym2 is sym) && (rhs2 is rhs) + if (owner2 is owner) && (sym2 is sym) && (rhs2 is rhs) then defn else ValDefn(owner2, k, sym2, rhs2) - case ClsLikeDefn(sym, k, parentPath, methods, privateFields, publicFields, preCtor, ctor) => + case ClsLikeDefn(own, isym, sym, k, paramsOpt, parentPath, methods, privateFields, publicFields, preCtor, ctor) => + val own2 = own.mapConserve(_.subst) + val isym2 = isym.subst val sym2 = sym.subst - val parentPath2 = parentPath.map(applyPath) - val methods2 = methods.map(applyFunDefn) - val privateFields2 = privateFields.map(_.subst) - val publicFields2 = publicFields.map(applyTermDefinition) + val paramsOpt2 = paramsOpt.mapConserve(applyParamList) + val parentPath2 = parentPath.mapConserve(applyPath) + val methods2 = methods.mapConserve(applyFunDefn) + val privateFields2 = privateFields.mapConserve(_.subst) + val publicFields2 = publicFields.mapConserve(applyTermDefinition) val preCtor2 = applyBlock(preCtor) val ctor2 = applyBlock(ctor) - if (sym2 is sym) && (parentPath2 zip parentPath).forall(_ is _) && - (methods2 zip methods).forall(_ is _) && - (privateFields2 zip privateFields).forall(_ is _) && - (publicFields2 zip publicFields).forall(_ is _) && + if (own2 is own) && (isym2 is isym) && (sym2 is sym) && + (paramsOpt2 is paramsOpt) && + (parentPath2 is parentPath) && + (methods2 is methods) && + (privateFields2 is privateFields) && + (publicFields2 is publicFields) && (preCtor2 is preCtor) && (ctor2 is ctor) - then defn else ClsLikeDefn(sym2, k, parentPath2, methods2, privateFields2, publicFields2, preCtor2, ctor2) + then defn else ClsLikeDefn(own, isym2, sym2, k, paramsOpt, parentPath2, methods2, privateFields2, publicFields2, preCtor2, ctor2) def applyArg(arg: Arg): Arg = val val2 = applyPath(arg.value) @@ -156,9 +163,9 @@ class BlockTransformer(subst: SymbolSubst): def applyParam(p: Param): Param = val sym2 = p.sym.subst if sym2 is p.sym then p else Param(p.flags, sym2, p.sign) - val params2 = pl.params.map(applyParam) - val rest2 = pl.restParam.map(applyParam) - if (params2 zip pl.params).forall(_ is _) && (rest2 zip pl.restParam).forall(_ is _) + val params2 = pl.params.mapConserve(applyParam) + val rest2 = pl.restParam.mapConserve(applyParam) + if (params2 is pl.params) && (rest2 is pl.restParam) then pl else ParamList(pl.flags, params2, rest2) def applyCase(cse: Case): Case = cse match @@ -172,10 +179,10 @@ class BlockTransformer(subst: SymbolSubst): def applyHandler(hdr: Handler): Handler = val sym2 = hdr.sym.subst val resumeSym2 = hdr.resumeSym.subst - val params2 = hdr.params.map(applyParamList) + val params2 = hdr.params.mapConserve(applyParamList) val body2 = applyBlock(hdr.body) if (sym2 is hdr.sym) && (resumeSym2 is hdr.resumeSym) && - (params2 zip hdr.params).forall(_ is _) && (body2 is hdr.body) + (params2 is hdr.params) && (body2 is hdr.body) then hdr else Handler(sym2, resumeSym2, params2, body2) def applyLam(lam: Value.Lam): Value.Lam = @@ -184,12 +191,12 @@ class BlockTransformer(subst: SymbolSubst): if (params2 is lam.params) && (body2 is lam.body) then lam else Value.Lam(params2, body2) def applyTermDefinition(td: TermDefinition): TermDefinition = - val owner2 = td.owner.map(_.subst) + val owner2 = td.owner.mapConserve(_.subst) val sym2 = td.sym.subst - val params2 = td.params.map(applyParamList) + val params2 = td.params.mapConserve(applyParamList) val resSym2 = td.resSym.subst - if (owner2 zip td.owner).forall(_ is _) && (sym2 is td.sym) && - (params2 zip td.params).forall(_ is _) && (resSym2 is td.resSym) + if (owner2 is td.owner) && (sym2 is td.sym) && + (params2 is td.params) && (resSym2 is td.resSym) then td else TermDefinition(owner2, td.k, sym2, params2, td.sign, td.body, resSym2, td.flags, td.annotations) class BlockTransformerShallow(subst: SymbolSubst) extends BlockTransformer(subst): @@ -207,9 +214,10 @@ class BlockTransformerShallow(subst: SymbolSubst) extends BlockTransformer(subst val res2 = applyLocal(res) val par2 = applyPath(par) val cls2 = cls.subst - val hdr2 = hdr.map(applyHandler) + val hdr2 = hdr.mapConserve(applyHandler) val rst2 = applyBlock(rst) if (l2 is l) && (res2 is res) && (par2 is par) && (cls2 is cls) && - (hdr2 zip hdr).forall(_ is _) && (rst2 is rst) + (hdr2 is hdr) && (rst2 is rst) then b else HandleBlock(l2, res2, par2, cls2, hdr2, bod, rst2) case _ => super.applyBlock(b) + diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index f08ec9a939..260dcda6a5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -315,12 +315,12 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case c: ClsLikeDefn => translateCls(c) case _: ValDefn => super.applyDefn(defn) transformer.applyBlock(b) - + private def secondPass(b: Block)(using HandlerCtx): Block = val cls = if handlerCtx.isTopLevel then N else genContClass(b) cls match case None => genNormalBody(b, BlockMemberSymbol("", Nil)) - case Some(cls) => Define(cls, genNormalBody(b, BlockMemberSymbol(cls.sym.nme, Nil))) + case Some(cls) => Define(cls, genNormalBody(b, cls.sym)) private val thirdPassFresh = FreshId() // moves definitions to the top level of the block @@ -329,11 +329,11 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): // we move all function defns to the top level of the handler block val (blk, defns) = b.floatOutDefns(false) val clsDefns = defns.collect: - case ClsLikeDefn(sym, k, parentPath, methods, privateFields, publicFields, preCtor, ctor) => sym + case ClsLikeDefn(own, isym, sym, k, paramsOpt, parentPath, methods, privateFields, publicFields, preCtor, ctor) => sym val funDefns = defns.collect: - case FunDefn(sym, params, body) => sym - + case FunDefn(own, sym, params, body) => sym + def getBms = var l: List[BlockMemberSymbol] = Nil val subst = new SymbolSubst: @@ -342,7 +342,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): b BlockTransformer(subst).applyBlock(b) l - + val toConvert = getBms .map: b => val clsDefn = b.asCls @@ -357,19 +357,19 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): else None .collect: case Some(b) => b - + val fnBmsMap = funDefns .map: b => b -> BlockMemberSymbol(b.nme + "$" + thirdPassFresh(), b.trees) .toMap - + val clsBmsMap = toConvert .map: b => b -> BlockMemberSymbol(b.nme + "$" + thirdPassFresh(), b.trees) .toMap - + val bmsMap = (fnBmsMap ++ clsBmsMap).toMap - + val clsMap = clsBmsMap .map: case b1 -> b2 => b1.asCls match @@ -395,7 +395,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): .toMap val newBlk = defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) - + val subst = new SymbolSubst: override def mapBlockMemberSym(b: BlockMemberSymbol) = bmsMap.get(b) match case None => b.asCls match @@ -409,11 +409,11 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): override def mapClsSym(s: ClassSymbol): ClassSymbol = clsMap.get(s).getOrElse(s) override def mapModuleSym(s: ModuleSymbol): ModuleSymbol = modMap.get(s).getOrElse(s) override def mapTermSym(s: TermSymbol): TermSymbol = TermSymbol(s.k, s.owner.map(_.subst(using this)), s.id) - + BlockTransformer(subst).applyBlock(newBlk) private def translateFun(f: FunDefn): FunDefn = - FunDefn(f.sym, f.params, translateBlock(f.body, functionHandlerCtx)) + FunDefn(f.owner, f.sym, f.params, translateBlock(f.body, functionHandlerCtx)) private def translateCls(cls: ClsLikeDefn): ClsLikeDefn = cls.copy(methods = cls.methods.map(translateFun), ctor = translateBlock(cls.ctor, ctorCtx(cls.sym.asPath))) @@ -425,6 +425,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): val lblLoop = freshTmp("handlerLoop") val tmp = freshTmp("retCont") def prepareBody(b: Block): Block = + val transform = new BlockTransformerShallow(SymbolSubst()): override def applyBlock(b: Block): Block = b match @@ -435,6 +436,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): Return(res, false) case _ => super.applyBlock(b) transform.applyBlock(b) + val handlerBody = translateBlock(prepareBody(h.body), HandlerCtx(false, false, N, state => blockBuilder .assignFieldN(state.res.tail, nextIdent, Instantiate(state.cls, Value.Lit(Tree.IntLit(state.uid)) :: Nil)) .ret(SimpleCall(handleBlockImplPath, state.res :: h.lhs.asPath :: Nil)))) @@ -444,31 +446,44 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): PlainParamList(Param(FldFlags.empty, handler.resumeSym, N) :: Nil), translateBlock(handler.body, functionHandlerCtx)) val tmp = freshTmp() - FunDefn(handler.sym, handler.params, Return(SimpleCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false)) + FunDefn( + S(h.cls), + handler.sym, handler.params, Return(SimpleCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false)) // TODO: it seems that our current syntax didn't know how to call super, calling it with empty param list now - val clsDefn = ClsLikeDefn(h.cls, syntax.Cls, S(h.par), handlers, Nil, Nil, + val clsDefn = ClsLikeDefn( + N, // no owner + h.cls, + BlockMemberSymbol(h.cls.id.name, Nil), + syntax.Cls, + h.cls.defn.get.paramsOpt, + S(h.par), handlers, Nil, Nil, Assign(freshTmp(), SimpleCall(Value.Ref(State.builtinOpsMap("super")), Nil), End()), End()) val body = blockBuilder .define(clsDefn) - .assign(h.lhs, Instantiate(Value.Ref(BlockMemberSymbol(h.cls.id.name, Nil)), Nil)) + .assign(h.lhs, Instantiate(Value.Ref(clsDefn.sym), Nil)) .rest(handlerBody) - val defn = FunDefn(sym, PlainParamList(Nil) :: Nil, body) + val defn = FunDefn( + N, // no owner + sym, PlainParamList(Nil) :: Nil, body) + val result = Define(defn, ResultPlaceholder(h.res, freshId(), true, Call(sym.asPath, Nil)(true), h.rest)) result private def genContClass(b: Block)(using HandlerCtx): Opt[ClsLikeDefn] = - val sym = ClassSymbol( + val clsSym = ClassSymbol( Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), Tree.Ident("Cont$" + State.suid.nextUid) ) + val pcVar = VarSymbol(Tree.Ident("pc")) - sym.defn = S(ClassDef( + clsSym.defn = S(ClassDef( N, syntax.Cls, - sym, + clsSym, + BlockMemberSymbol(clsSym.nme, Nil), Nil, S(PlainParamList(Param(FldFlags.empty, pcVar, N) :: Nil)), ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))), @@ -504,14 +519,14 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): val parts = partitionBlock(actualBlock) val loopLbl = freshTmp("contLoop") - val pcSymbol = TermSymbol(ParamBind, S(sym), pcIdent) + val pcSymbol = TermSymbol(ParamBind, S(clsSym), pcIdent) def transformPart(blk: Block): Block = val transform = new BlockTransformerShallow(SymbolSubst()): override def applyBlock(b: Block): Block = b match case ReturnCont(res, uid) => blockBuilder - .assignFieldN(res.asPath.tail, nextIdent, sym.asPath) + .assignFieldN(res.asPath.tail, nextIdent, clsSym.asPath) .assign(pcSymbol, Value.Lit(Tree.IntLit(uid))) .ret(res.asPath) case StateTransition(uid) => @@ -557,14 +572,18 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): val resumeSym = BlockMemberSymbol("resume", List()) val resumeFnDef = FunDefn( + S(clsSym), // owner resumeSym, List(PlainParamList(List(Param(FldFlags.empty, resumedVal, N)))), resumeBody ) S(ClsLikeDefn( - sym, + N, // no owner + clsSym, + BlockMemberSymbol(clsSym.nme, Nil), syntax.Cls, + clsSym.defn.get.paramsOpt, S(contClsPath), resumeFnDef :: Nil, Nil, diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index aa657ba2eb..4c1d9e2f5f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -61,7 +61,7 @@ class Lowering(lowerHandlers: Bool, stackLimit: Option[Int])(using TL, Raise, St final def term(t: st, inStmtPos: Bool = false)(k: Result => Block)(using Subst): Block = tl.log(s"Lowering.term ${t.showDbg.truncate(100, "[...]")}${ if inStmtPos then " (in stmt)" else ""}${ - t.symbol.fold("")(" " + _)}") + t.symbol.fold("")(" – symbol " + _)}") def warnStmt = if inStmtPos then raise(WarningReport: msg"Pure expression in statement position" -> t.toLoc :: Nil) @@ -82,7 +82,7 @@ class Lowering(lowerHandlers: Bool, stackLimit: Option[Int])(using TL, Raise, St case (s: Spd, acc) => args => subTerm_nonTail(s.term)(r => acc(Arg(true, r) :: args)) }(Nil) - case st.Ref(sym) => + case ref @ st.Ref(sym) => sym match case sym: BuiltinSymbol => warnStmt @@ -118,6 +118,8 @@ class Lowering(lowerHandlers: Bool, stackLimit: Option[Int])(using TL, Raise, St return k(Value.Lam(paramLists.head, bodyBlock)) case bs: BlockMemberSymbol => bs.defn match + case S(d) if d.isDeclare.isDefined => + return term(Sel(State.globalThisSymbol.ref(), ref.tree)(S(bs)))(k) case S(td: TermDefinition) if td.k is syntax.Fun => // * Local functions with no parameter lists are getters // * and are lowered to functions with an empty parameter list @@ -223,7 +225,7 @@ class Lowering(lowerHandlers: Bool, stackLimit: Option[Int])(using TL, Raise, St case st.Blk((d: Declaration) :: stats, res) => d match case td: TermDefinition => - reportAnnotations(td, td.annotations) + reportAnnotations(td, td.extraAnnotations) td.body match case N => // abstract declarations have no lowering term(st.Blk(stats, res))(k) @@ -238,10 +240,13 @@ class Lowering(lowerHandlers: Bool, stackLimit: Option[Int])(using TL, Raise, St term_nonTail(st.Blk(stats, res))(k))) case syntax.Fun => val (paramLists, bodyBlock) = setupFunctionOrByNameDef(td.params, bod, S(td.sym.nme)) - Define(FunDefn(td.sym, paramLists, bodyBlock), + Define(FunDefn(td.owner, td.sym, paramLists, bodyBlock), term_nonTail(st.Blk(stats, res))(k)) + case cls: ClassLikeDef if cls.sym.defn.exists(_.isDeclare.isDefined) => + // * Declarations have no lowering + term(st.Blk(stats, res))(k) case cls: ClassLikeDef => - reportAnnotations(cls, cls.annotations) + reportAnnotations(cls, cls.extraAnnotations) val bodBlk = cls.body.blk val (mtds, rest1) = bodBlk.stats.partitionMap: case td: TermDefinition if td.k is syntax.Fun => L(td) @@ -253,11 +258,11 @@ class Lowering(lowerHandlers: Bool, stackLimit: Option[Int])(using TL, Raise, St case s => R(s) val publicFlds = rest2.collect: case td @ TermDefinition(k = (_: syntax.Val)) => td - Define(ClsLikeDefn(cls.sym, syntax.Cls, N, + Define(ClsLikeDefn(cls.owner, cls.sym, cls.bsym, cls.kind, cls.paramsOpt, N, mtds.flatMap: td => td.body.map: bod => val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme)) - FunDefn(td.sym, paramLists, bodyBlock) + FunDefn(td.owner, td.sym, paramLists, bodyBlock) , privateFlds, publicFlds, @@ -272,15 +277,18 @@ class Lowering(lowerHandlers: Bool, stackLimit: Option[Int])(using TL, Raise, St case _ => // TODO handle term(st.Blk(stats, res))(k) - case st.Blk((decl @ LetDecl(sym, annotations)) :: stats, res) => + case st.Blk((decl @ LetDecl(sym, annotations)) :: (stats @ ((_: DefineVar) :: _)), res) => reportAnnotations(decl, annotations) term(st.Blk(stats, res))(k) - case st.Blk((DefineVar(sym, rhs)) :: stats, res) => + case st.Blk((decl @ LetDecl(sym, annotations)) :: stats, res) => + reportAnnotations(decl, annotations) + term(st.Blk(DefineVar(sym, Term.Lit(Tree.UnitLit(false))) :: stats, res))(k) + case st.Blk(DefineVar(sym, rhs) :: stats, res) => subTerm(rhs): r => Assign(sym, r, term_nonTail(st.Blk(stats, res))(k)) case Assgn(lhs, rhs) => lhs match - case Ref(sym: LocalSymbol) => + case Ref(sym) => subTerm(rhs): r => Assign(sym, r, k(Value.Lit(syntax.Tree.UnitLit(true)))) case sel @ SynthSel(prefix, nme) => @@ -458,16 +466,19 @@ class Lowering(lowerHandlers: Bool, stackLimit: Option[Int])(using TL, Raise, St subTerm(lhs): ref => subTerm_nonTail(rhs): value => AssignField(ref, Tree.Ident("value"), value, k(value))(N) + case Neg(_) => raise(ErrorReport( msg"Unexpected type annotations ${t.show}" -> t.toLoc :: Nil, source = Diagnostic.Source.Compilation)) End("error") - case Annotated(prefix, receiver) => + case Annotated(Annot.Untyped, receiver) => + term(receiver)(k) + case Annotated(ann, receiver) => raise(WarningReport( - msg"This annotation has no effect." -> prefix.toLoc :: - msg"Annotations are not supported on ${receiver.describe} terms." -> receiver.toLoc :: Nil)) + msg"This annotation has no effect." -> ann.toLoc :: + msg"Such annotations are not supported on ${receiver.describe} terms." -> receiver.toLoc :: Nil)) term(receiver)(k) case Error => End("error") @@ -522,7 +533,7 @@ class Lowering(lowerHandlers: Bool, stackLimit: Option[Int])(using TL, Raise, St (using Subst): (List[ParamList], Block) = (paramLists, returnedTerm(bodyTerm)) - def reportAnnotations(target: Statement, annotations: Ls[Term]): Unit = if annotations.nonEmpty then + def reportAnnotations(target: Statement, annotations: Ls[Annot]): Unit = if annotations.nonEmpty then raise(WarningReport( (msg"This annotation has no effect." -> annotations.foldLeft[Opt[Loc]](N): case (acc, term) => acc match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala index 0189ea8bbb..3015675670 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala @@ -57,19 +57,18 @@ object Printer: case End(msg) => doc"end ${msg}" def mkDocument(defn: Defn)(using Raise, Scope): Document = defn match - case FunDefn(sym, params, body) => - val docParams = doc"${params.map(_.params.map(x => summon[Scope].allocateName(x.sym)).mkString("(", ", ", ")")).mkString}" + case FunDefn(own, sym, params, body) => + val docParams = doc"${own.fold("")(_.toString+"::")}${params.map(_.params.map(x => summon[Scope].allocateName(x.sym)).mkString("(", ", ", ")")).mkString}" val docBody = mkDocument(body) doc"fun ${sym.nme}${docParams} { #{ # ${docBody} #} # }" case ValDefn(owner, k, sym, rhs) => doc"val ${sym.nme} = ${mkDocument(rhs)}" - case ClsLikeDefn(sym, k, parentSym, methods, privateFields, publicFields, preCtor, ctor) => + case ClsLikeDefn(own, _, sym, k, paramsOpt, parentSym, methods, privateFields, publicFields, preCtor, ctor) => def optFldBody(t: semantics.TermDefinition) = t.body match case Some(x) => doc" = ..." case None => doc"" - val clsDefn = sym.defn.getOrElse(die) - val clsParams = clsDefn.paramsOpt.fold(Nil)(_.paramSyms) + val clsParams = paramsOpt.fold(Nil)(_.paramSyms) val ctorParams = clsParams.map(p => summon[Scope].allocateName(p)) val privFields = privateFields.map(x => doc"let ${x.id.name} = ...").mkDocument(sep = doc" # ") val pubFields = publicFields.map(x => doc"${x.k.str} ${x.sym.nme}${optFldBody(x)}").mkDocument(sep = doc" # ") @@ -77,7 +76,7 @@ object Printer: val docPubFlds = if publicFields.isEmpty then doc"" else doc" # ${pubFields}" val docBody = if publicFields.isEmpty && privateFields.isEmpty then doc"" else doc" { #{ ${docPrivFlds}${docPubFlds} #} # }" val docCtorParams = if clsParams.isEmpty then doc"" else doc"(${ctorParams.mkString(", ")})" - doc"class ${sym.nme}${docCtorParams}${docBody}" + doc"class ${own.fold("")(_.toString+"::")}${sym.nme}${docCtorParams}${docBody}" def mkDocument(arg: Arg)(using Raise, Scope): Document = val doc = mkDocument(arg.value) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index dba473648b..85a2857304 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -120,10 +120,11 @@ class StackSafeTransform(depthLimit: Int)(using State): defn match case d: FunDefn => rewriteFn(d) case _: ValDefn => defn - case ClsLikeDefn(sym, k, parentPath, methods, privateFields, publicFields, preCtor, ctor) => + case ClsLikeDefn(owner, isym, sym, k, paramsOpt, + parentPath, methods, privateFields, publicFields, preCtor, ctor) => ClsLikeDefn( - sym, k, parentPath, methods.map(rewriteFn), privateFields, publicFields, - rewriteBlk(preCtor), rewriteBlk(ctor) // TODO: do we need to rewrite the preCtor? + owner, isym, sym, k, paramsOpt, parentPath, methods.map(rewriteFn), privateFields, + publicFields, rewriteBlk(preCtor), rewriteBlk(ctor) ) def rewriteBlk(blk: Block) = @@ -152,7 +153,7 @@ class StackSafeTransform(depthLimit: Int)(using State): Call(Select(stackHandlerPath, Tree.Ident("perform"))(N), Nil)(true)).end) .rest(newBody) - def rewriteFn(defn: FunDefn) = FunDefn(defn.sym, defn.params, rewriteBlk(defn.body)) + def rewriteFn(defn: FunDefn) = FunDefn(defn.owner, defn.sym, defn.params, rewriteBlk(defn.body)) def transformTopLevel(b: Block) = def replaceReturns(b: Block): Block = @@ -178,7 +179,7 @@ class StackSafeTransform(depthLimit: Int)(using State): Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), Tree.Ident("StackDelay$") ) - clsSym.defn = S(ClassDef(N, syntax.Cls, clsSym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))), Nil)) + clsSym.defn = S(ClassDef(N, syntax.Cls, clsSym, BlockMemberSymbol(clsSym.nme, Nil), Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))), Nil)) val (blk, defns) = b.floatOutDefns(true) 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 74faed59b4..f3734d3050 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -6,17 +6,12 @@ import mlscript.utils.*, shorthands.* import utils.* import document.* -import Scope.scope -import hkmc2.syntax.ImmutVal -import hkmc2.semantics.Elaborator -import hkmc2.syntax.Tree -import hkmc2.semantics.TopLevelSymbol -import hkmc2.semantics.InnerSymbol -import hkmc2.semantics.ParamList -import hkmc2.codegen.Value.Lam -import hkmc2.semantics.BlockMemberSymbol -import hkmc2.semantics.BuiltinSymbol import hkmc2.Message.MessageContext +import hkmc2.syntax.{Tree, ImmutVal} +import hkmc2.semantics.* +import hkmc2.codegen.Value.Lam + +import Scope.scope // TODO factor some logic for other codegen backends @@ -59,11 +54,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: then "#" + ts.id.name else ts.id.name }" - case N => - ts.id.name - case ts: semantics.BlockMemberSymbol => // this means it's a locally-defined member - ts.nme - // ts.trmTree + case N => summon[Scope].lookup_!(ts) case ts: semantics.InnerSymbol => summon[Scope].findThis_!(ts) case _ => summon[Scope].lookup_!(l) @@ -131,6 +122,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: case Value.Arr(es) => doc"[ #{ # ${es.map(argument).mkDocument(doc", # ")} #} # ]" def returningTerm(t: Block)(using Raise, Scope): Document = t match + case _: (HandleBlockReturn | HandleBlock) => die case Assign(l, r, rst) => doc" # ${getVar(l)} = ${result(r)};${returningTerm(rst)}" case AssignField(p, n, r, rst) => @@ -138,40 +130,32 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: case Define(defn, rst) => def mkThis(sym: InnerSymbol): Document = result(Value.This(sym)) - // println(defn.sym) val resJS = defn match - // case FunDefn(k: syntax.Val, sym, pss, bod) => case ValDefn(own, k: syntax.Val, sym, p) => - // scope.nest.givenIn: val sym = defn.sym - // assert(pss.isEmpty) - // val result = returningTerm(bod) - // doc"const ${sym.nme} = ${result}" own match case N => - doc"const ${sym.nme} = ${result(p)};${returningTerm(rst)}" + doc"${getVar(sym)} = ${result(p)};${returningTerm(rst)}" case S(owner) => - // doc"const ${sym.nme} = ${result(p)}; # ${mkThis(owner)}.${sym.nme} = ${sym.nme}" doc"${mkThis(owner)}.${sym.nme} = ${result(p)};${returningTerm(rst)}" - case _ => + case defn: (FunDefn | ClsLikeDefn) => + val outerScope = scope val (thisProxy, res) = scope.nestRebindThis( // * Either this is an InnerSymbol or this is a Fun, // * and we need to rebind `this` to None to shadow it. - S(defn.sym).collectFirst{ case s: InnerSymbol => s }): + defn.innerSym.collectFirst{ case s: InnerSymbol => s }): defn match - case FunDefn(sym, Nil, body) => - // doc"function ${sym.nme}() ${ braced(this.body(body)) }" + case FunDefn(own, sym, Nil, body) => lastWords("cannot generate function with no parameter list") - case FunDefn(sym, ps :: pss, bod) => + case FunDefn(own, sym, ps :: pss, bod) => val result = pss.foldRight(bod): case (ps, block) => Return(Lam(ps, block), false) val (params, bodyDoc) = setupFunction(some(sym.nme), ps, result) - doc"function ${sym.nme}($params) ${ braced(bodyDoc) }" - case ClsLikeDefn(sym, syntax.Cls, par, mtds, privFlds, _pubFlds, preCtor, ctor) => + doc"${getVar(sym)} = function ${sym.nme}($params) ${ braced(bodyDoc) };" + case ClsLikeDefn(ownr, isym, sym, kind, paramsOpt, par, mtds, privFlds, _pubFlds, preCtor, ctor) => // * Note: `_pubFlds` is not used because in JS, fields are not declared - val clsDefn = sym.defn.getOrElse(die) - val clsParams = clsDefn.paramsOpt.fold(Nil)(_.paramSyms) + val clsParams = paramsOpt.fold(Nil)(_.paramSyms) val ctorParams = clsParams.map(p => p -> scope.allocateName(p)) val preCtorCode = ctorParams.foldLeft(body(preCtor)): case (acc, (sym, nme)) => @@ -183,20 +167,20 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: ctorParams.unzip._2.mkDocument(", ") }) ${ braced(ctorCode) }${ mtds.map: - case td @ FunDefn(_, ps :: pss, bod) => + case td @ FunDefn(_, _, ps :: pss, bod) => val result = pss.foldRight(bod): case (ps, block) => Return(Lam(ps, block), false) val (params, bodyDoc) = setupFunction(some(td.sym.nme), ps, result) doc" # ${td.sym.nme}($params) ${ braced(bodyDoc) }" - case td @ FunDefn(_, Nil, bod) => + case td @ FunDefn(_, _, Nil, bod) => doc" # get ${td.sym.nme}() ${ braced(body(bod)) }" .mkDocument(" ") }${ if mtds.exists(_.sym.nme == "toString") then doc"" else doc""" # toString() { return "${sym.nme}${ - if clsDefn.paramsOpt.isEmpty then doc""""""" + if paramsOpt.isEmpty then doc""""""" else doc"""(" + ${ ctorParams.headOption.fold("")("this." + _._1.name) }${ @@ -205,23 +189,25 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: } + ")"""" }; }""" } #} # }" - if (clsDefn.kind is syntax.Mod) || (clsDefn.kind is syntax.Obj) || (clsDefn.kind is syntax.Pat) then - val clsTmp = summon[Scope].allocateName(new semantics.TempSymbol(N, sym.nme+"$"+"class")) - clsDefn.owner match + if (kind is syntax.Mod) || (kind is syntax.Obj) || (kind is syntax.Pat) then + val clsTmp = outerScope.allocateName(new semantics.TempSymbol(N, sym.nme+"$class")) + ownr match case S(owner) => - assert((clsDefn.kind is syntax.Pat) || clsDefn.paramsOpt.isEmpty) + assert((kind is syntax.Pat) || paramsOpt.isEmpty) // doc"${mkThis(owner)}.${sym.nme} = new ${clsJS}" doc"const $clsTmp = ${clsJS}; # ${mkThis(owner)}.${sym.nme} = new ${clsTmp }; # ${mkThis(owner)}.${sym.nme}.class = $clsTmp;" - case N => doc"const $clsTmp = ${clsJS}; const ${sym.nme} = new ${clsTmp - }; # ${sym.nme}.class = $clsTmp;" + case N => + val v = getVar(sym) + doc"const $clsTmp = ${clsJS}; ${v} = new ${clsTmp + }; # ${v}.class = $clsTmp;" else - val fun = clsDefn.paramsOpt match + val fun = paramsOpt match case S(params) => val (ps, bod) = setupFunction(some(sym.nme), params, End()) S(doc"function ${sym.nme}($ps) { return new ${sym.nme}.class($ps); }") case N => N - clsDefn.owner match + ownr match case S(owner) => val ths = mkThis(owner) fun match @@ -231,8 +217,8 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: doc"${ths}.${sym.nme} = ${clsJS};" case N => fun match - case S(f) => doc"${f} # ${sym.nme}.class = ${clsJS};" - case N => clsJS + case S(f) => doc"${getVar(sym)} = ${f}; # ${getVar(sym)}.class = ${clsJS};" + case N => doc"${getVar(sym)} = ${clsJS};" thisProxy match case S(proxy) if !scope.thisProxyDefined => scope.thisProxyDefined = true @@ -294,36 +280,52 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: // case _ => ??? - def program(p: Program, exprt: Opt[Str], wd: os.Path)(using Raise, Scope): Document = - val compilingFile: Bool = exprt.isDefined // * If there's an export, it means we're not in the worksheet + def avoidNames(p: Program)(using Scope): Unit = + def go(blk: Block): Unit = blk match + case Define(defn, rest) => + if !defn.isOwned then scope.allocateName(semantics.TempSymbol(N, defn.sym.nme)) + go(rest) + case _ => blk.subBlocks.foreach(go) + go(p.main) + + def program(p: Program, exprt: Opt[BlockMemberSymbol], wd: os.Path)(using Raise, Scope): Document = + avoidNames(p) p.imports.foreach: i => i._1 -> scope.allocateName(i._1) val imps = p.imports.map: i => - if compilingFile - then - val path = i._2 - val relPath = if path.startsWith("/") - then "./" + os.Path(path).relativeTo(wd).toString - else path - doc"""import ${getVar(i._1)} from "${relPath}";""" - else - val v = doc"this.${getVar(i._1)}" - doc"""$v = await import("${i._2.toString - }"); # if ($v.default !== undefined) $v = $v.default;""" + val path = i._2 + val relPath = if path.startsWith("/") + then "./" + os.Path(path).relativeTo(wd).toString + else path + doc"""import ${getVar(i._1)} from "${relPath}";""" imps.mkDocument(doc" # ") :/: block(p.main).stripBreaks :: ( exprt match - case S(e) => doc"\nexport default ${e};\n" + case S(sym) => doc"\nlet ${sym.nme} = ${scope.lookup_!(sym)}; export default ${sym.nme};\n" case N => doc"" ) - def block(t: Block)(using Raise, Scope): Document = + def worksheet(p: Program)(using Raise, Scope): (Document, Document) = + avoidNames(p) + p.imports.foreach: i => + i._1 -> scope.allocateName(i._1) + val imps = p.imports.map: i => + val v = doc"this.${getVar(i._1)}" + doc"""$v = await import("${i._2.toString + }"); # if ($v.default !== undefined) $v = $v.default;""" + blockPreamble(p.main) -> (imps.mkDocument(doc" # ") :/: returningTerm(p.main).stripBreaks) + + def blockPreamble(t: Block)(using Raise, Scope): Document = + // TODO document: mutable var assnts require the lookup val vars = t.definedVars.toSeq.filter(scope.lookup(_).isEmpty).sortBy(_.uid).iterator.map(l => l -> scope.allocateName(l)) - if vars.isEmpty then returningTerm(t) else + if vars.isEmpty then doc"" else doc" # let " :: vars.map: (_, nme) => nme .toList.mkDocument(", ") - :: doc";" :: returningTerm(t) + :: doc";" + + def block(t: Block)(using Raise, Scope): Document = + blockPreamble(t) :: returningTerm(t) def body(t: Block)(using Raise, Scope): Document = scope.nest givenIn: block(t) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala index 06caaffced..ffef9832af 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala @@ -6,15 +6,13 @@ import scala.collection.mutable.ListBuffer import mlscript.utils.* import mlscript.utils.shorthands.* +import hkmc2.utils.* import hkmc2.document.* -import hkmc2.utils.Scope -import hkmc2.utils.TraceLogger import hkmc2.Message.MessageContext import hkmc2.syntax.Tree import hkmc2.semantics.* import hkmc2.codegen.llir.{ Program => LlirProgram, Node, Func } -import FuncRef.fromName import hkmc2.codegen.Program @@ -26,6 +24,7 @@ def errStop(msg: Message)(using Raise) = err(msg) throw LowLevelIRError("stopped") + final case class Ctx( def_acc: ListBuffer[Func], class_acc: ListBuffer[ClassInfo], @@ -44,26 +43,22 @@ final case class Ctx( def addClassName(n: Local, m: Name) = copy(class_ctx = class_ctx + (n -> m)) def findClassName(n: Local)(using Raise) = class_ctx.get(n) match case None => - errStop(msg"Class name not found: ${n.toString()}") - Name("error") + errStop(msg"Class not found: ${n.toString}") case Some(value) => value def addName(n: Str, m: Name) = copy(symbol_ctx = symbol_ctx + (n -> m)) def findName(n: Str)(using Raise): Name = symbol_ctx.get(n) match case None => errStop(msg"Name not found: $n") - Name("error") case Some(value) => value - def reset = - def_acc.clear() - class_acc.clear() def nonTopLevel = copy(is_top_level = false) object Ctx: - val empty = Ctx(ListBuffer.empty, ListBuffer.empty) + def empty = Ctx(ListBuffer.empty, ListBuffer.empty) final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: FreshInt): - import tl.{trace, log} + import tl.{trace, log, logs} + def er = Expr.Ref def nr = Node.Result def nme(x: Str) = Name(x) @@ -123,7 +118,7 @@ final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: private def bFunDef(e: FunDefn)(using ctx: Ctx)(using Raise, Scope): Func = trace[Func](s"bFunDef begin: ${e.sym}", x => s"bFunDef end: ${x.show}"): - val FunDefn(sym, params, body) = e + val FunDefn(_own, sym, params, body) = e if !ctx.is_top_level then errStop(msg"Non top-level definition ${sym.nme} not supported") else if params.length != 1 then @@ -140,25 +135,29 @@ final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: resultNum = 1, body = bBlock(body)(x => Node.Result(Ls(x)))(using ctx3) ) - + private def bClsLikeDef(e: ClsLikeDefn)(using ctx: Ctx)(using Raise, Scope): ClassInfo = trace[ClassInfo](s"bClsLikeDef begin", x => s"bClsLikeDef end: ${x.show}"): - val ClsLikeDefn(sym, kind, parentSym, methods, privateFields, publicFields, preCtor, ctor) = e + val ClsLikeDefn( + _own, _isym, sym, kind, paramsOpt, parentSym, methods, privateFields, publicFields, preCtor, ctor) = e if !ctx.is_top_level then errStop(msg"Non top-level definition ${sym.nme} not supported") else val clsDefn = sym.defn.getOrElse(die) - val clsParams = clsDefn.paramsOpt.fold(Nil)(_.paramSyms) + val clsParams = paramsOpt.fold(Nil)(_.paramSyms) val clsFields = publicFields.map(_.sym) ClassInfo( clsUid.make, sym.nme, clsParams.map(_.nme) ++ clsFields.map(_.nme), ) - + private def bValue(v: Value)(k: TrivialExpr => Ctx ?=> Node)(using ctx: Ctx)(using Raise, Scope) : Node = - trace[Node](s"bValue begin", x => s"bValue end: ${x.show}"): + trace[Node](s"bValue { $v } begin", x => s"bValue end: ${x.show}"): v match + case Value.Ref(sym) if sym.nme.isCapitalized => + val v = fresh.make + Node.LetExpr(v, Expr.CtorApp(ClassRef.fromName(sym.nme), Ls()), k(v |> sr)) case Value.Ref(l) => k(ctx.findName(getVar_!(l)) |> sr) case Value.This(sym) => errStop(msg"Unsupported value: This"); Node.Result(Ls()) case Value.Lit(lit) => k(Expr.Literal(lit)) @@ -166,18 +165,14 @@ final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: case Value.Arr(elems) => errStop(msg"Unsupported value: Arr"); Node.Result(Ls()) private def getClassOfMem(p: FieldSymbol)(using ctx: Ctx)(using Raise, Scope): Local = - trace[Local](s"bMemSym begin", x => s"bMemSym end: $x"): + trace[Local](s"bMemSym { $p } begin", x => s"bMemSym end: $x"): p match case ts: TermSymbol => ts.owner.get case ms: MemberSymbol[?] => ms.defn.get.sym private def bPath(p: Path)(k: TrivialExpr => Ctx ?=> Node)(using ctx: Ctx)(using Raise, Scope) : Node = - trace[Node](s"bPath begin", x => s"bPath end: ${x.show}"): + trace[Node](s"bPath { $p } begin", x => s"bPath end: ${x.show}"): p match - case Select(Value.Ref(_: TopLevelSymbol), name) if name.name.head.isUpper => - val v = fresh.make - Node.LetExpr(v, Expr.CtorApp(ClassRef.fromName(name.name), Ls()), k(v |> sr)) - // field selection case s @ Select(qual, name) => log(s"bPath Select: $qual.$name with ${s.symbol}") s.symbol match @@ -187,7 +182,7 @@ final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: bPath(qual): case q: Expr.Ref => val v = fresh.make - val cls = ClassRef.fromName(ctx.findClassName(getClassOfMem(s.symbol.get))) + val cls = ClassRef.fromName(getClassOfMem(s.symbol.get).nme) val field = name.name Node.LetExpr(v, Expr.Select(q.name, cls, field), k(v |> sr)) case q: Expr.Literal => @@ -203,27 +198,16 @@ final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: case args: Ls[TrivialExpr] => val v = fresh.make Node.LetExpr(v, Expr.BasicOp(sym.nme, args), k(v |> sr)) - case Call(Select(Value.Ref(_: TopLevelSymbol), name), args) if name.name.head.isUpper => + case Call(Value.Ref(sym), args) if sym.nme.head.isUpper => bArgs(args): case args: Ls[TrivialExpr] => val v = fresh.make - Node.LetExpr(v, Expr.CtorApp(ClassRef.fromName(name.name), args), k(v |> sr)) - case Call(Select(Value.Ref(_: TopLevelSymbol), name), args) => + Node.LetExpr(v, Expr.CtorApp(ClassRef.fromName(sym.nme), args), k(v |> sr)) + case Call(Value.Ref(sym), args) => bArgs(args): case args: Ls[TrivialExpr] => val v = fresh.make - Node.LetCall(Ls(v), FuncRef.fromName(name.name), args, k(v |> sr)) - case Call(Select(Value.Ref(_: BuiltinSymbol), name), args) => - bArgs(args): - case args: Ls[TrivialExpr] => - val v = fresh.make - Node.LetExpr(v, Expr.CtorApp(ClassRef.fromName(name.name), args), k(v |> sr)) - case Call(Value.Ref(name), args) if ctx.fn_ctx.contains(name) => - bArgs(args): - case args: Ls[TrivialExpr] => - val v = fresh.make - val fn = ctx.fn_ctx.get(name).get - Node.LetCall(Ls(v), FuncRef.fromName(fn), args, k(v |> sr)) + Node.LetCall(Ls(v), FuncRef.fromName(sym.nme), args, k(v |> sr)) case Call(fn, args) => bPath(fn): case f: TrivialExpr => @@ -232,11 +216,11 @@ final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: val v = fresh.make Node.LetMethodCall(Ls(v), ClassRef(R("Callable")), Name("apply" + args.length), f :: args, k(v |> sr)) case Instantiate( - Select(Select(Value.Ref(_: TopLevelSymbol), name), Tree.Ident("class")), args) => + Select(Value.Ref(sym), Tree.Ident("class")), args) => bPaths(args): case args: Ls[TrivialExpr] => val v = fresh.make - Node.LetExpr(v, Expr.CtorApp(ClassRef.fromName(name.name), args), k(v |> sr)) + Node.LetExpr(v, Expr.CtorApp(ClassRef.fromName(sym.nme), args), k(v |> sr)) case Instantiate(cls, args) => errStop(msg"Unsupported kind of Instantiate") Node.Result(Ls()) @@ -302,25 +286,36 @@ final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: val name = allocIfNew(lhs) bBind(S(name), rhs, rest)(k) case AssignField(lhs, nme, rhs, rest) => TODO("AssignField not supported") - case Define(fd @ FunDefn(sym, params, body), rest) => + case Define(fd @ FunDefn(_own, sym, params, body), rest) => val f = bFunDef(fd) ctx.def_acc += f val new_ctx = ctx.addFuncName(sym, Name(f.name)) bBlock(rest)(k)(using new_ctx) - case Define(cd @ ClsLikeDefn(sym, kind, parentSym, methods, privateFields, publicFields, preCtor, ctor), rest) => - val c = bClsLikeDef(cd) - ctx.class_acc += c - val new_ctx = ctx.addClassName(sym, Name(c.name)) - bBlock(rest)(k)(using new_ctx) + case Define(_: ClsLikeDefn, rest) => bBlock(rest)(k) case End(msg) => k(Expr.Literal(Tree.UnitLit(false))) case _: Block => val docBlock = blk.showAsTree errStop(msg"Unsupported block: $docBlock") Node.Result(Ls()) - + + def registerClasses(b: Block)(using ctx: Ctx)(using Raise, Scope): Ctx = + b match + case Define(cd @ ClsLikeDefn(_own, isym, sym, kind, _paramsOpt, parentSym, methods, privateFields, publicFields, preCtor, ctor), rest) => + val c = bClsLikeDef(cd) + ctx.class_acc += c + val new_ctx = ctx.addClassName(sym, Name(c.name)).addClassName(isym, Name(c.name)) + log(s"Define class: ${sym.nme} -> ${new_ctx}") + registerClasses(rest)(using new_ctx) + case _ => + b.subBlocks.foldLeft(ctx)((ctx, rest) => registerClasses(rest)(using ctx)) + def bProg(e: Program)(using Raise, Scope): LlirProgram = - val ctx = Ctx.empty - given Ctx = ctx - ctx.reset - val entry = bBlock(e.main)(x => Node.Result(Ls(x))) + var ctx = Ctx.empty + + // * Classes may be defined after other things such as functions, + // * especially now that the elaborator moves all functions to the top of the block. + ctx = registerClasses(e.main)(using ctx) + + val entry = bBlock(e.main)(x => Node.Result(Ls(x)))(using ctx) LlirProgram(ctx.class_acc.toSet, ctx.def_acc.toSet, entry) + diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala index 144dd6bf37..2405975042 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala @@ -27,7 +27,7 @@ trait BlockImpl(using Elaborator.State): .groupMap(_._1)(_._2).flatMap: case (nme, snmes_tds) => val (symNmes, tds) = snmes_tds.partitionMap(identity) - val sym = BlockMemberSymbol(nme, tds) + val sym = new BlockMemberSymbol(nme, tds) nme -> sym :: symNmes.map(_ -> sym) .toArray.sortBy(_._1) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 46a0e6c01e..7848491da4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -18,16 +18,16 @@ import Keyword.{`let`, `set`} object Elaborator: - private val binaryOps = Set( + val binaryOps = Set( ",", "+", "-", "*", "/", "%", "==", "!=", "<", "<=", ">", ">=", "===", "!==", "&&", "||") - private val unaryOps = Set("-", "+", "!", "~") - private val anyOps = Set("super") - private val builtins = binaryOps ++ unaryOps ++ anyOps - private val aliasOps = Map( + val unaryOps = Set("-", "+", "!", "~") + val anyOps = Set("super") + val builtins = binaryOps ++ unaryOps ++ anyOps + val aliasOps = Map( ";" -> ",", "+." -> "+", "-." -> "-", @@ -68,11 +68,16 @@ object Elaborator: // * Method `getBuiltin` is used to look up built-in symbols in the context of builtin symbols. def getBuiltin(nme: Str): Opt[Ctx.Elem] = parent.filter(_.parent.nonEmpty).fold(env.get(nme))(_.getBuiltin(nme)) + + // TODO only store this in the top-level context! object Builtins: private def assumeBuiltin(nme: Str): Symbol = getBuiltin(nme) .getOrElse(throw new NoSuchElementException(s"builtin $nme not in ${parent.map(_.env.keySet)}")) .symbol.getOrElse(throw new NoSuchElementException(s"builtin symbol $nme")) + private def assumeBuiltinTpe(nme: Str): TypeSymbol = + assumeBuiltin(nme).asTpe.getOrElse(throw new NoSuchElementException( + s"builtin type symbol $nme")) private def assumeBuiltinCls(nme: Str): ClassSymbol = assumeBuiltin(nme).asCls.getOrElse(throw new NoSuchElementException( s"builtin class symbol $nme")) @@ -82,6 +87,8 @@ object Elaborator: val Int = assumeBuiltinCls("Int") val Num = assumeBuiltinCls("Num") val Str = assumeBuiltinCls("Str") + val untyped = assumeBuiltinTpe("untyped") + // println(s"Builtins: $Int, $Num, $Str, $untyped") val Predef = assumeBuiltinMod("Predef") def getBuiltinOp(op: Str): Opt[Str] = if getBuiltin(op).isDefined then builtinBinOps.get(op) else N @@ -94,13 +101,13 @@ object Elaborator: final case class RefElem(val sym: Symbol) extends Elem: val nme = sym.nme def ref(id: Tree.Ident)(using Elaborator.State): Term = - require(id.name == nme) - Term.Ref(sym)(id, 666) // TODO 666 + // * Note: due to symbolic ops, we may have `id.name =/= nme`; + // * e.g., we can have `id.name = "|>"` and `nme = "pipe"`. + Term.Ref(sym)(id, 666) // FIXME: 666 is a temporary placeholder def symbol = S(sym) final case class SelElem(val base: Elem, val nme: Str, val symOpt: Opt[FieldSymbol]) extends Elem: def ref(id: Tree.Ident)(using Elaborator.State): Term = - // * Note: due to symbolic ops, we may have `id.name =/= nme`; - // * e.g., we can have `id.name = "|>"` and `nme = "pipe"`. + // * Same remark as in RefElem#ref Term.SynthSel(base.ref(Ident(base.nme)), new Tree.Ident(nme).withLocOf(id))(symOpt) def symbol = symOpt @@ -130,9 +137,11 @@ object Elaborator: "globalThis" -> globalThisSymbol, )) def dbg: Bool = false - def dbgUid(uid: Uid[Symbol]): Str = if dbg then s"‹$uid›" else "" + def dbgUid(uid: Uid[Symbol]): Str = + if dbg then s"‹$uid›" else "" + // ^ we do not display the uid by default to avoid polluting diff-test outputs transparent inline def State(using state: State): State = state - + end Elaborator @@ -144,7 +153,7 @@ class Elaborator(val tl: TraceLogger, val wd: os.Path) extends Importer: import tl.* - def mkLetBinding(sym: LocalSymbol, rhs: Term, annotations: Ls[Term]): Ls[Statement] = + def mkLetBinding(sym: LocalSymbol, rhs: Term, annotations: Ls[Annot]): Ls[Statement] = LetDecl(sym, annotations) :: DefineVar(sym, rhs) :: Nil def resolveField(srcTree: Tree, base: Opt[Symbol], nme: Ident): Opt[FieldSymbol] = @@ -180,6 +189,20 @@ extends Importer: else Term.SynthSel(trm, Ident("class"))(mem.clsTree.orElse(mem.modTree).map(_.symbol)) case _ => trm + def annot(tree: Tree): Ctxl[Opt[Annot]] = tree match + case Keywrd(kw @ (Keyword.`abstract` | Keyword.`declare`)) => S(Annot.Modifier(kw)) + case _ => term(tree) match + case Term.Error => N + case trm => + trm.symbol match + case S(sym) => + sym.asTpe match + case S(ctx.Builtins.untyped) => + return S(Annot.Untyped) + case _ => () + case _ => () + S(Annot.Trm(trm)) + def term(tree: Tree, inAppPrefix: Bool = false): Ctxl[Term] = trace[Term](s"Elab term ${tree.showDbg}", r => s"~> $r"): tree.desugared match @@ -240,7 +263,7 @@ extends Importer: Term.Error case id @ Ident(name) => ctx.get(name) match - case S(sym) => sym.ref(id) + case S(elem) => elem.ref(id) case N => state.builtinOpsMap.get(name) match case S(bi) => bi.ref(id) @@ -532,13 +555,12 @@ extends Importer: case Under() => raise(ErrorReport(msg"Illegal position for '_' placeholder." -> tree.toLoc :: Nil)) Term.Error - case Annotated(lhs, rhs) => - val annotation = lhs match - case App(_: (Ident | SynthSel | Sel), _) | _: (Ident | SynthSel | Sel) => term(lhs) - case _ => - raise(ErrorReport(msg"Illegal annotation shape." -> lhs.toLoc :: Nil)) - Term.Error - Term.Annotated(annotation, term(rhs)) + case Annotated(lhs, rhs) => + annot(lhs).fold(term(rhs))(ann => + Term.Annotated(ann, term(rhs))) + case Keywrd(kw) => + raise(ErrorReport(msg"Unexpected keyword '${kw.name}' in this position." -> tree.toLoc :: Nil)) + Term.Error // case _ => // ??? @@ -593,8 +615,10 @@ extends Importer: newSignatureTrees += name -> sig // TODO extract this into a separate method + // * @param funs: + // * While elaborating a block, we move all function definitions to the top (similar to JS function semantics) @tailrec - def go(sts: Ls[Tree], annotations: Ls[Term], acc: Ls[Statement]): Ctxl[(Term.Blk, Ctx)] = + def go(sts: Ls[Tree], funs: Ls[TermDefinition], annotations: Ls[Annot], acc: Ls[Statement]): Ctxl[(Term.Blk, Ctx)] = /** Call this function when the following term cannot be annotated. */ def reportUnusedAnnotations: Unit = if annotations.nonEmpty then raise: WarningReport: @@ -610,7 +634,7 @@ extends Importer: case Nil => reportUnusedAnnotations val res = unit - (Term.Blk(acc.reverse, res), ctx) + (Term.Blk(funs reverse_::: acc.reverse, res), ctx) case Open(bod) :: sts => reportUnusedAnnotations bod match @@ -623,7 +647,7 @@ extends Importer: raise(ErrorReport(msg"Illegal 'open' statement shape." -> bod.toLoc :: Nil)) N match - case N => go(sts, annotations, acc) + case N => go(sts, funs, annotations, acc) case S((base, importedTrees)) => base match case baseId: Ident => @@ -647,13 +671,13 @@ extends Importer: raise(ErrorReport(msg"Illegal 'open' statement element." -> t.toLoc :: Nil)) Nil (ctx elem_++ importedNames).givenIn: - go(sts, Nil, acc) + go(sts, funs, Nil, acc) case N => raise(ErrorReport(msg"Name not found: ${baseId.name}" -> baseId.toLoc :: Nil)) - go(sts, Nil, acc) + go(sts, funs, Nil, acc) case _ => raise(ErrorReport(msg"Illegal 'open' statement base." -> base.toLoc :: Nil)) - go(sts, Nil, acc) + go(sts, funs, Nil, acc) case (m @ Modified(Keyword.`import`, absLoc, arg)) :: sts => reportUnusedAnnotations val (newCtx, newAcc) = arg match @@ -667,7 +691,7 @@ extends Importer: arg.toLoc :: Nil)) (ctx, acc) newCtx.givenIn: - go(sts, Nil, newAcc) + go(sts, funs, Nil, newAcc) case (hd @ LetLike(`let`, Apps(id: Ident, tups), rhso, N)) :: sts if id.name.headOption.exists(_.isLower) => val sym = @@ -683,10 +707,10 @@ extends Importer: raise(ErrorReport(msg"Expected a right-hand side for let bindings with parameters" -> hd.toLoc :: Nil)) LetDecl(sym, annotations) :: acc (ctx + (id.name -> sym)) givenIn: - go(sts, Nil, newAcc) + go(sts, funs, Nil, newAcc) case (tree @ LetLike(`let`, lhs, S(rhs), N)) :: sts => raise(ErrorReport(msg"Unsupported let binding shape" -> tree.toLoc :: Nil)) - go(sts, Nil, Term.Error :: acc) + go(sts, funs, Nil, Term.Error :: acc) case (hd @ Hndl(id: Ident, cls: Ident, Block(sts_), N)) :: sts => reportUnusedAnnotations val res: Term.Blk = ctx.nest(N).givenIn: @@ -695,15 +719,18 @@ extends Importer: // TODO: shouldn't need uid here val derivedClsSym = ClassSymbol(Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), Tree.Ident(s"${cls.name}$$${id.name}$$${State.suid.nextUid}")) - derivedClsSym.defn = S(ClassDef(N, syntax.Cls, derivedClsSym, Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))), List())) - + derivedClsSym.defn = S(ClassDef( + N, syntax.Cls, derivedClsSym, + BlockMemberSymbol(derivedClsSym.name, Nil), + Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))), List())) + val elabed = ctx.nest(S(derivedClsSym)).givenIn: block(sts_)._1 elabed.res match case Term.Lit(UnitLit(true)) => case trm => raise(WarningReport(msg"Terms in handler block do nothing" -> trm.toLoc :: Nil)) - + val tds = elabed.stats.map { case td @ TermDefinition(owner, Fun, sym, params, sign, body, resSym, flags, annotations) => params.reverse match @@ -719,15 +746,15 @@ extends Importer: None }.collect { case Some(x) => x } - val newAcc = Handle(sym, term(cls), derivedClsSym, tds) :: acc + val newAcc = funs ::: Handle(sym, term(cls), derivedClsSym, tds) :: acc val newCtx = ctx + (id.name -> sym) val body = block(sts)(using newCtx)._1 - Term.Blk(newAcc.reverse, body) + Term.Blk(newAcc.reverse, body) // <<<<<<<<<<<<<<<<<<<<<<<<<<< FIXME scope problem (not calling `go`) (res, ctx) case (tree @ Hndl(_, _, _, N)) :: sts => raise(ErrorReport(msg"Unsupported handle binding shape" -> tree.toLoc :: Nil)) - go(sts, Nil, Term.Error :: acc) - + go(sts, funs, Nil, Term.Error :: acc) + case Def(lhs, rhs) :: sts => reportUnusedAnnotations lhs match @@ -736,17 +763,17 @@ extends Importer: ctx.get(id.name) match case S(elem) => elem.symbol match - case S(sym: LocalSymbol) => go(sts, Nil, DefineVar(sym, r) :: acc) + case S(sym: LocalSymbol) => go(sts, funs, Nil, DefineVar(sym, r) :: acc) case N => // TODO lookup in members? inherited/refined stuff? raise(ErrorReport(msg"Name not found: ${id.name}" -> id.toLoc :: Nil)) - go(sts, Nil, Term.Error :: acc) + go(sts, funs, Nil, Term.Error :: acc) case App(base, args) => - go(Def(base, InfixApp(args, Keyword.`=>`, rhs)) :: sts, Nil, acc) + go(Def(base, InfixApp(args, Keyword.`=>`, rhs)) :: sts, funs, Nil, acc) case _ => raise(ErrorReport(msg"Unrecognized definitional assignment left-hand side: ${lhs.describe}" -> lhs.toLoc :: Nil)) // TODO BE - go(sts, Nil, Term.Error :: acc) + go(sts, funs, Nil, Term.Error :: acc) case (td @ TermDef(k, nme, rhs)) :: sts => log(s"Processing term definition $nme") td.symbName match @@ -805,11 +832,13 @@ extends Importer: case _ => () tdf - go(sts, Nil, tdf :: acc) + tdf.k match + case Fun => go(sts, tdf :: funs, Nil, acc) + case _ => go(sts, funs, Nil, tdf :: acc) case L(d) => reportUnusedAnnotations raise(d) - go(sts, Nil, acc) + go(sts, funs, Nil, acc) case (td @ TypeDef(k, head, extension, body)) :: sts => assert((k is Als) || (k is Cls) || (k is Mod) || (k is Obj) || (k is Pat), k) td.symbName match @@ -819,7 +848,8 @@ extends Importer: case R(id) => id case L(d) => raise(d) - new Ident("") // TODO improve + return go(sts, funs, Nil, acc) + val sym = members.getOrElse(nme.name, lastWords(s"Symbol not found: ${nme.name}")) var newCtx = ctx.nest(S(td.symbol).collectFirst{ case s: InnerSymbol => s }) val tps = td.typeParams match @@ -891,10 +921,11 @@ extends Importer: patSym.split = split log(s"pattern body is ${td.extension}") val translate = new ucs.Translator(this) - val bod = translate(patSym.patternParams, - Nil, // ps.map(_.params).getOrElse(Nil), // TODO: remove pattern parameters - td.extension.getOrElse(die)) - val pd = PatternDef(owner, patSym, tps, ps, ObjBody(Term.Blk(bod, Term.Lit(UnitLit(true)))), annotations) + val bod = translate( + patSym.patternParams, + Nil, // ps.map(_.params).getOrElse(Nil), // TODO[Luyu]: remove pattern parameters + td.extension.getOrElse(die)) + val pd = PatternDef(owner, patSym, sym, tps, ps, ObjBody(Term.Blk(bod, Term.Lit(UnitLit(true)))), annotations) patSym.defn = S(pd) pd case k: (Mod.type | Obj.type) => @@ -908,7 +939,7 @@ extends Importer: // case S(t) => block(t :: Nil) case S(t) => ??? case N => (new Term.Blk(Nil, Term.Lit(UnitLit(true))), ctx) - ModuleDef(owner, clsSym, tps, ps, k, ObjBody(bod), annotations) + ModuleDef(owner, clsSym, sym, tps, ps, k, ObjBody(bod), annotations) clsSym.defn = S(cd) cd case Cls => @@ -922,32 +953,24 @@ extends Importer: // case S(t) => block(t :: Nil) case S(t) => ??? case N => (new Term.Blk(Nil, Term.Lit(UnitLit(true))), ctx) - ClassDef(owner, Cls, clsSym, tps, ps, ObjBody(bod), annotations) + ClassDef(owner, Cls, clsSym, sym, tps, ps, ObjBody(bod), annotations) clsSym.defn = S(cd) cd - go(sts, Nil, defn :: acc) - - case Modified(Keyword.`abstract`, absLoc, body) :: sts => - ??? - // TODO: pass abstract to `go` - go(body :: sts, annotations, acc) - case Modified(Keyword.`declare`, absLoc, body) :: sts => - // TODO: pass declare to `go` - go(body :: sts, annotations, acc) + sym.defn = S(defn) + go(sts, funs, Nil, defn :: acc) case Annotated(annotation, target) :: sts => - go(target :: sts, annotations :+ term(annotation), acc) - case (result: Tree) :: Nil => - reportUnusedAnnotations - val res = term(result) - (Term.Blk(acc.reverse, res), ctx) + go(target :: sts, funs, annotations ++ annot(annotation), acc) case (st: Tree) :: sts => - reportUnusedAnnotations - val res = term(st) // TODO reject plain term statements? Currently, `(1, 2)` is allowed to elaborate (tho it should be rejected in type checking later) - go(sts, Nil, res :: acc) + // TODO reject plain term statements? Currently, `(1, 2)` is allowed to elaborate (tho it should be rejected in type checking later) + val res = annotations.foldLeft(term(st)): + case (acc, ann) => Term.Annotated(ann, acc) + sts match + case Nil => (Term.Blk(funs reverse_::: acc.reverse, res), ctx) + case _ => go(sts, funs, Nil, res :: acc) end go c.withMembers(members, c.outer).givenIn: - go(blk.desugStmts, Nil, Nil) + go(blk.desugStmts, Nil, Nil, Nil) def fieldOrVarSym(k: TermDefKind, id: Ident)(using Ctx): LocalSymbol & NamedSymbol = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Scope.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Scope.scala deleted file mode 100644 index fc5889814c..0000000000 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Scope.scala +++ /dev/null @@ -1,19 +0,0 @@ -package hkmc2 -package semantics - -import scala.collection.mutable - -import mlscript.utils.*, shorthands.* -import syntax.* - - -// TODO move to codegen - -type Scoped[A] = Scope ?=> A - -class Scope(val name: Str, enclosing: Opt[Scope]): - private val symbolTable = mutable.HashMap.empty[Str, Symbol] - -// class Scope(val name: Str, enclosing: Opt[Scope], symbolTable: Map[Str, Symbol]): -// () - diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 5ed4b946e5..93c851dffe 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -146,9 +146,6 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[Tree])(using State) override def toString: Str = s"member:$nme${State.dbgUid(uid)}" - override val isGetter: Bool = // TODO: this should be checked based on a special syntax for getter - trmImplTree.exists(t => t.k === Fun && t.paramLists.isEmpty) - def subst(using sub: SymbolSubst): BlockMemberSymbol = sub.mapBlockMemberSym(this) end BlockMemberSymbol @@ -157,7 +154,6 @@ end BlockMemberSymbol sealed abstract class MemberSymbol[Defn <: Definition](using State) extends Symbol: def nme: Str var defn: Opt[Defn] = N - val isGetter: Bool = false def subst(using SymbolSubst): MemberSymbol[Defn] @@ -201,6 +197,7 @@ sealed trait ClassLikeSymbol extends Symbol: /** This is the symbol associated to specific definitions. * One overloaded `BlockMemberSymbol` may correspond to multiple `InnerSymbol`s * A `Ref(_: InnerSymbol)` represents a `this`-like reference to the current object. */ + // TODO prevent from appearing in Ref sealed trait InnerSymbol extends Symbol: def subst(using SymbolSubst): InnerSymbol @@ -212,7 +209,7 @@ class ClassSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) override def toString: Str = s"class:$nme${State.dbgUid(uid)}" /** Compute the arity. */ def arity: Int = tree.paramLists.headOption.fold(0)(_.fields.length) - + override def subst(using sub: SymbolSubst): ClassSymbol = sub.mapClsSym(this) class ModuleSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) @@ -221,14 +218,14 @@ class ModuleSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) def nme = id.name def toLoc: Option[Loc] = id.toLoc // TODO track source tree of module here override def toString: Str = s"module:${id.name}${State.dbgUid(uid)}" - + override def subst(using sub: SymbolSubst): ModuleSymbol = sub.mapModuleSym(this) class TypeAliasSymbol(val id: Tree.Ident)(using State) extends MemberSymbol[TypeDef]: def nme = id.name def toLoc: Option[Loc] = id.toLoc // TODO track source tree of type alias here - override def toString: Str = s"module:${id.name}${State.dbgUid(uid)}" - + override def toString: Str = s"type:${id.name}${State.dbgUid(uid)}" + def subst(using sub: SymbolSubst): TypeAliasSymbol = sub.mapTypeAliasSym(this) class PatternSymbol(val id: Tree.Ident, val params: Opt[Tree.Tup], val body: Tree)(using State) @@ -245,7 +242,7 @@ class PatternSymbol(val id: Tree.Ident, val params: Opt[Tree.Tup], val body: Tre * `T` in `pattern Nullable(pattern T) = null | T`. */ var patternParams: Ls[Param] = Nil - + override def subst(using sub: SymbolSubst): PatternSymbol = sub.mapPatSym(this) class TopLevelSymbol(blockNme: Str)(using State) @@ -253,5 +250,6 @@ class TopLevelSymbol(blockNme: Str)(using State) def nme = blockNme def toLoc: Option[Loc] = N override def toString: Str = s"globalThis:$blockNme${State.dbgUid(uid)}" - + def subst(using sub: SymbolSubst): TopLevelSymbol = sub.mapTopLevelSym(this) + diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 2f32144aa0..5936a7f139 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -8,6 +8,23 @@ import scala.collection.mutable.Buffer final case class QuantVar(sym: VarSymbol, ub: Opt[Term], lb: Opt[Term]) +enum Annot extends AutoLocated: + case Untyped + case Modifier(mod: Keyword) + case Trm(trm: Term) + + def symbol: Opt[Symbol] = this match + case Trm(trm) => trm.symbol + case _ => N + + def subTerms: Ls[Term] = this match + case Trm(trm) => trm :: Nil + case _: Modifier | Untyped => Nil + + def children: Ls[Located] = this match + case Trm(trm) => trm :: Nil + case _: Modifier | Untyped => Nil + enum Term extends Statement: case Error case Lit(lit: Literal) @@ -39,7 +56,7 @@ enum Term extends Statement: case Ret(result: Term) case Throw(result: Term) case Try(body: Term, finallyDo: Term) - case Annotated(annotation: Term, target: Term) + case Annotated(annot: Annot, target: Term) lazy val symbol: Opt[Symbol] = this match case Ref(sym) => S(sym) @@ -132,7 +149,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case Try(body, finallyDo) => body :: finallyDo :: Nil case Handle(lhs, rhs, derivedClsSym, defs) => rhs :: defs.flatMap(_.td.subTerms) case Neg(e) => e :: Nil - case Annotated(annotation, target) => annotation :: target :: Nil + case Annotated(ann, target) => ann.subTerms ::: target :: Nil protected def children: Ls[Located] = this match case t: Lit => t.lit.asTree :: Nil @@ -170,7 +187,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case Forall(tvs, outer, body) => s"forall ${tvs.mkString(", ")}${outer.map(v => s", outer $v").mkString}: ${body.toString}" case WildcardTy(in, out) => s"in ${in.map(_.toString).getOrElse("⊥")} out ${out.map(_.toString).getOrElse("⊤")}" case Sel(pre, nme) => s"${pre.showDbg}.${nme.name}" - case SynthSel(pre, nme) => s"${pre.showDbg}(.)${nme.name}" + case SynthSel(pre, nme) => s"(${pre.showDbg}.)${nme.name}" case IfLike(kw, body) => s"${kw.name} { ${body.showDbg} }" case Lam(params, body) => s"λ${params.showDbg}. ${body.showDbg}" case Blk(stats, res) => @@ -205,10 +222,10 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: cls.tparams.map(_.showDbg).mkStringOr(", ", "[", "]")}${ cls.paramsOpt.fold("")(_.toString)} ${cls.body}" case Import(sym, file) => s"import ${sym} from ${file}" - case Annotated(annotation, target) => s"@${annotation.showDbg} ${target.showDbg}" + case Annotated(ann, target) => s"@${ann} ${target.showDbg}" case Throw(res) => s"throw ${res.showDbg}" -final case class LetDecl(sym: LocalSymbol, annotations: Ls[Term]) extends Statement +final case class LetDecl(sym: LocalSymbol, annotations: Ls[Annot]) extends Statement final case class DefineVar(sym: LocalSymbol, rhs: Term) extends Statement @@ -230,8 +247,11 @@ final case class TermDefinition( body: Opt[Term], resSym: FlowSymbol, flags: TermDefFlags, - annotations: Ls[Term], -) extends Companion + annotations: Ls[Annot], +) extends CompanionValue: + def extraAnnotations: Ls[Annot] = annotations.filter: + case Annot.Modifier(Keyword.`declare` | Keyword.`abstract`) => false + case _ => true final case class HandlerTermDefinition( resumeSym: LocalSymbol & NamedSymbol, @@ -252,42 +272,51 @@ case class Handle(lhs: LocalSymbol, rhs: Term, derivedClsSym: ClassSymbol, defs: sealed abstract class Declaration: val sym: Symbol -sealed abstract class Definition extends Declaration with Statement +sealed abstract class Definition extends Declaration with Statement: + val annotations: Ls[Annot] + def isDeclare: Opt[Annot.Modifier] = annotations.collectFirst: + case mod @ Annot.Modifier(Keyword.`declare`) => mod -sealed trait Companion extends Definition +sealed trait CompanionValue extends Definition sealed abstract class TypeLikeDef extends Definition: val tparams: Ls[TyParam] - val annotations: Ls[Term] + val annotations: Ls[Annot] sealed abstract class ClassLikeDef extends TypeLikeDef: val owner: Opt[InnerSymbol] val sym: MemberSymbol[? <: ClassLikeDef] + val bsym: BlockMemberSymbol val paramsOpt: Opt[ParamList] val tparams: Ls[TyParam] val kind: ClsLikeKind val body: ObjBody - val annotations: Ls[Term] + val annotations: Ls[Annot] + def extraAnnotations: Ls[Annot] = annotations.filter: + case Annot.Modifier(Keyword.`declare` | Keyword.`abstract`) => false + case _ => true case class ModuleDef( owner: Opt[InnerSymbol], sym: ModuleSymbol, + bsym: BlockMemberSymbol, tparams: Ls[TyParam], paramsOpt: Opt[ParamList], kind: ClsLikeKind, body: ObjBody, - annotations: Ls[Term], -) extends ClassLikeDef with Companion + annotations: Ls[Annot], +) extends ClassLikeDef with CompanionValue case class PatternDef( owner: Opt[InnerSymbol], sym: PatternSymbol, + bsym: BlockMemberSymbol, tparams: Ls[TyParam], paramsOpt: Opt[ParamList], body: ObjBody, - annotations: Ls[Term], + annotations: Ls[Annot], ) extends ClassLikeDef: self => val kind: ClsLikeKind = Pat @@ -299,15 +328,26 @@ sealed abstract class ClassDef extends ClassLikeDef: val tparams: Ls[TyParam] val paramsOpt: Opt[ParamList] val body: ObjBody - val companion: Opt[Companion] - val annotations: Ls[Term] + val companion: Opt[CompanionValue] + val annotations: Ls[Annot] object ClassDef: - def apply(owner: Opt[InnerSymbol], kind: ClsLikeKind, sym: InnerSymbol, tparams: Ls[TyParam], paramsOpt: Opt[ParamList], body: ObjBody, annotations: Ls[Term]): ClassDef = + def apply( + owner: Opt[InnerSymbol], + kind: ClsLikeKind, + sym: InnerSymbol, + bsym: BlockMemberSymbol, + tparams: Ls[TyParam], + paramsOpt: Opt[ParamList], + body: ObjBody, + annotations: Ls[Annot], + ): ClassDef = paramsOpt match case S(params) => Parameterized(owner, kind, sym.asInstanceOf// TODO: improve + , bsym , tparams, params, body, N, annotations) case N => Plain(owner, kind, sym.asInstanceOf// TODO: improve + , bsym , tparams, body, N, annotations) def unapply(cls: ClassDef): Opt[(ClassSymbol, Ls[TyParam], Opt[ParamList], ObjBody)] = @@ -315,19 +355,21 @@ object ClassDef: case class Parameterized( owner: Opt[InnerSymbol], - kind: ClsLikeKind, sym: ClassSymbol, + kind: ClsLikeKind, + sym: ClassSymbol, bsym: BlockMemberSymbol, tparams: Ls[TyParam], params: ParamList, body: ObjBody, companion: Opt[ModuleDef], - annotations: Ls[Term] + annotations: Ls[Annot] ) extends ClassDef: val paramsOpt: Opt[ParamList] = S(params) case class Plain( owner: Opt[InnerSymbol], - kind: ClsLikeKind, sym: ClassSymbol, + kind: ClsLikeKind, + sym: ClassSymbol, bsym: BlockMemberSymbol, tparams: Ls[TyParam], - body: ObjBody, companion: Opt[Companion], - annotations: Ls[Term] + body: ObjBody, companion: Opt[CompanionValue], + annotations: Ls[Annot] ) extends ClassDef: val paramsOpt: Opt[ParamList] = N @@ -338,8 +380,8 @@ case class TypeDef( sym: TypeAliasSymbol, tparams: Ls[TyParam], rhs: Opt[Term], - companion: Opt[Companion], - annotations: Ls[Term], + companion: Opt[CompanionValue], + annotations: Ls[Annot], ) extends TypeLikeDef diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 5b7071109b..f08c3990c6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -43,10 +43,11 @@ enum Tree extends AutoLocated: case Error() case Under() case Ident(name: Str) + case Keywrd(kw: Keyword) case IntLit(value: BigInt) extends Tree with Literal case DecLit(value: BigDecimal) extends Tree with Literal case StrLit(value: Str) extends Tree with Literal - case UnitLit(undefinedOrNull: Bool) extends Tree with Literal + case UnitLit(isNullNotUndefined: Bool) extends Tree with Literal case BoolLit(value: Bool) extends Tree with Literal case Bra(k: BracketKind, inner: Tree) case Block(stmts: Ls[Tree])(using State) extends Tree with semantics.BlockImpl @@ -116,6 +117,8 @@ enum Tree extends AutoLocated: case Def(lhs, rhs) => lhs :: rhs :: Nil case Spread(_, _, body) => body.toList case Annotated(annotation, target) => annotation :: target :: Nil + case MemberProj(cls, name) => cls :: Nil + case Keywrd(kw) => Nil def describe: Str = this match case Empty() => "empty" @@ -156,6 +159,8 @@ enum Tree extends AutoLocated: case Spread(_, _, _) => "spread" case Annotated(_, _) => "annotated" case Open(_) => "open" + case MemberProj(_, _) => "member projection" + case Keywrd(kw) => s"'${kw.name}' keyword" def deparenthesized: Tree = this match case Bra(BracketKind.Round, inner) => inner.deparenthesized @@ -170,18 +175,16 @@ enum Tree extends AutoLocated: LetLike(kw, Ident("_").withLocOf(und), r, b) case Modified(Keyword.`declare`, modLoc, s) => - // TODO handle `declare` modifier! - s + Annotated(Keywrd(Keyword.`declare`), s) // TODO properly attach location case Modified(Keyword.`abstract`, modLoc, s) => - // TODO handle `declare` modifier! - s + Annotated(Keywrd(Keyword.`abstract`), s) // TODO properly attach location case Modified(Keyword.`mut`, modLoc, TermDef(ImmutVal, anme, rhs)) => - TermDef(MutVal, anme, rhs).desugared + TermDef(MutVal, anme, rhs).withLocOf(this).desugared case LetLike(letLike, App(f @ Ident(nme), Tup((id: Ident) :: r :: Nil)), N, bodo) if nme.endsWith("=") => - LetLike(letLike, id, S(App(Ident(nme.init), Tup(id :: r :: Nil))), bodo).desugared + LetLike(letLike, id, S(App(Ident(nme.init), Tup(id :: r :: Nil))), bodo).withLocOf(this).desugared case _ => this - + /** S(true) means eager spread, S(false) means lazy spread, N means no spread. */ def asParam: Opt[(Opt[Bool], Ident, Opt[Tree])] = this match case und: Under => S(N, new Ident("_").withLocOf(und), N) @@ -219,6 +222,7 @@ object Apps: case t => S(t, Nil) object PossiblyAnnotated: + def apply(t: Tree, anns: Ls[Tree]): Tree = anns.foldRight(t)(Annotated(_, _)) def unapply(t: Tree): Opt[(Ls[Tree], Tree)] = t match case Annotated(q, PossiblyAnnotated(qs, target)) => S(q :: qs, target) case other => S((Nil, other)) @@ -335,7 +339,7 @@ trait TypeOrTermDef: end TypeOrTermDef -trait TypeDefImpl(using semantics.Elaborator.State) extends TypeOrTermDef: +trait TypeDefImpl(using State) extends TypeOrTermDef: this: TypeDef => lazy val symbol = k match diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/ReplHost.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/ReplHost.scala index c64201eb45..324afb2dd7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/ReplHost.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/ReplHost.scala @@ -101,10 +101,10 @@ class ReplHost(rootPath: Str)(using TL) { stdin.write(if code.endsWith("\n") then code else code + "\n") stdin.flush() - def query(code: Str, showStackTrace: Bool): (ReplHost.Reply, Str) = + def query(prelude: Str, code: Str, showStackTrace: Bool): (ReplHost.Reply, Str) = // Wrap the code with `try`-`catch` block. val wrapped = - s"try { $code } catch (e) { console.log('\\u200B' + ${if showStackTrace then "e.stack" else "e"} + '\\u200B'); }" + s"${prelude}try { $code } catch (e) { console.log('\\u200B' + ${if showStackTrace then "e.stack" else "e"} + '\\u200B'); }" // Send the code send(wrapped) (parseQueryResult() match diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala index 31cb97e323..16aca26c06 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala @@ -79,6 +79,7 @@ class Scope case S(outer) => (if outer.thisProxyAccessed then S(outer.thisProxy) else N, res) + // TODO more efficient! def inScope(name: Str): Bool = bindings.valuesIterator.contains(name) || parent.exists(_.inScope(name)) @@ -88,7 +89,7 @@ class Scope def lookup_!(l: Local)(using Raise): Str = lookup(l).getOrElse: - raise(InternalError(msg"Not in scope: ${l.toString}" -> l.toLoc :: Nil, + raise(InternalError(msg"Not in scope: ${l.toString} (${l.getClass.toString})" -> l.toLoc :: Nil, source = Diagnostic.Source.Compilation)) l.nme @@ -99,7 +100,7 @@ class Scope prefix + tmp.nameHints.head case _ => if l.nme.isEmpty && prefix.isEmpty then "tmp" else prefix + l.nme - val realBase = Scope.replaceTicks(base) + val realBase = Scope.replaceInvalidCharacters(base) val name = // Try just realBase. @@ -120,7 +121,13 @@ object Scope: def empty(using State): Scope = Scope(N, S(S(State.globalThisSymbol)), MutMap.empty) - def replaceTicks(str: Str): Str = str.replace('\'', '$') + def replaceInvalidCharacters(str: Str): Str = + str.iterator.map: + case c if c.isLetter || c.isDigit => c + // case '\'' => "$tick" + case '$' => "$" + case _ => "$_" + .mkString end Scope diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala index 16ba6ad680..4005922162 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala @@ -76,3 +76,12 @@ extension (t: Product) if inTailPos then ": \\\n" + args.mkString("\n") else ":\n" + args.mkString("\n").indent(" ") +extension [A](self: Opt[A]) + def mapConserve[B](f: A => A): Opt[A] = + self match + case S(v) => + val v2 = f(v) + if v2 is v then self + else S(v2) + case N => N + diff --git a/hkmc2/shared/src/main/scala/utils/TraceLogger.scala b/hkmc2/shared/src/main/scala/utils/TraceLogger.scala index f2b3da992d..bbdc8df9c9 100644 --- a/hkmc2/shared/src/main/scala/utils/TraceLogger.scala +++ b/hkmc2/shared/src/main/scala/utils/TraceLogger.scala @@ -25,7 +25,10 @@ abstract class TraceLogger: inline def log(msg: => Any): Unit = log(msg, noIndent = false) - def log(msg: => Any, noIndent: Bool = false) = + def logs(msgs: => Any*): Unit = + if doTrace then msgs.foreach(log(_)) + + def log(msg: => Any, noIndent: Bool = false): Unit = if doTrace then emitDbg(if noIndent then msg.toString else "| " * indent + msg.toString.indentNewLines("| " * indent + "> ")) diff --git a/hkmc2/shared/src/test/mlscript-compile/Example.mjs b/hkmc2/shared/src/test/mlscript-compile/Example.mjs index 99722dab26..9a837bd035 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Example.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Example.mjs @@ -1,4 +1,5 @@ import Predef from "./Predef.mjs"; +let Example1; const Example$class = class Example { constructor() {} funnySlash(f, arg) { @@ -23,7 +24,7 @@ const Example$class = class Example { } } toString() { return "Example"; } -}; const Example = new Example$class; -Example.class = Example$class; +}; Example1 = new Example$class; +Example1.class = Example$class; null -export default Example; +let Example = Example1; export default Example; diff --git a/hkmc2/shared/src/test/mlscript-compile/Option.mjs b/hkmc2/shared/src/test/mlscript-compile/Option.mjs index 95316a2f7d..e7c6929b90 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Option.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Option.mjs @@ -1,4 +1,5 @@ import Predef from "./Predef.mjs"; +let Option1; const Option$class = class Option { constructor() { this.Some = function Some(value1) { return new Some.class(value1); }; @@ -38,7 +39,7 @@ const Option$class = class Option { return Predef.pipeInto(2134, Predef.print); } toString() { return "Option"; } -}; const Option = new Option$class; -Option.class = Option$class; +}; Option1 = new Option$class; +Option1.class = Option$class; null -export default Option; +let Option = Option1; export default Option; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index c80e8dda86..fc380b8a0c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -1,6 +1,7 @@ +let Predef1; const Predef$class = class Predef { constructor() { - this.assert = console.assert; + this.assert = globalThis.console.assert; this.foldl = this.fold; this.MatchResult = function MatchResult(captures1) { return new MatchResult.class(captures1); }; this.MatchResult.class = class MatchResult { @@ -52,7 +53,7 @@ const Predef$class = class Predef { tmp2 = "\n" + tmp1; tmp3 = msg.replaceAll("\n", tmp2); tmp4 = tmp + tmp3; - return console.log(tmp4) ?? null; + return globalThis.console.log(tmp4) ?? null; } else { return null; } @@ -192,16 +193,16 @@ const Predef$class = class Predef { } print(...xs) { let tmp; - tmp = xs.map(String) ?? null; - return console.log(...tmp) ?? null; + tmp = xs.map(globalThis.String) ?? null; + return globalThis.console.log(...tmp) ?? null; } notImplemented(msg) { let tmp; tmp = "Not implemented: " + msg; - throw Error(tmp); + throw globalThis.Error(tmp); } get notImplementedError() { - throw Error("Not implemented"); + throw globalThis.Error("Not implemented"); } tuple(...xs1) { return xs1; @@ -307,7 +308,7 @@ const Predef$class = class Predef { tmp7 = "s"; } tmp8 = tmp5("Function", name, " expected ", tmp6, expected, " argument", tmp7, " but got ", got) ?? null; - throw Error(tmp8); + throw globalThis.Error(tmp8); } else { return null; } @@ -421,7 +422,7 @@ const Predef$class = class Predef { let scrut, cont, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; scrut = cur2.resumed; if (scrut === true) { - throw Error("Multiple resumption"); + throw globalThis.Error("Multiple resumption"); } else { tmp = null; } @@ -509,7 +510,7 @@ const Predef$class = class Predef { return tmp3; } toString() { return "Predef"; } -}; const Predef = new Predef$class; -Predef.class = Predef$class; +}; Predef1 = new Predef$class; +Predef1.class = Predef$class; null -export default Predef; +let Predef = Predef1; export default Predef; diff --git a/hkmc2/shared/src/test/mlscript-compile/Stack.mjs b/hkmc2/shared/src/test/mlscript-compile/Stack.mjs index da8188390b..369f9ca60a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Stack.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Stack.mjs @@ -1,4 +1,5 @@ import Predef from "./Predef.mjs"; +let Stack1; const Stack$class = class Stack { constructor() { this.Cons = function Cons(head1, tail1) { return new Cons.class(head1, tail1); }; @@ -87,9 +88,9 @@ const Stack$class = class Stack { return arr1; } zip(...xss) { - let tmp, tmp1; + let go, tmp, tmp1; const this$Stack = this; - function go(heads, tails) { + go = function go(heads, tails) { return (caseScrut) => { let param0, param1, h, t, param01, param11, h2, t2, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11; if (caseScrut instanceof this$Stack.Cons.class) { @@ -136,13 +137,13 @@ const Stack$class = class Stack { } } }; - } + }; tmp = go(this.Nil, this.Nil); tmp1 = this.fromArray(xss); return tmp(tmp1) ?? null; } toString() { return "Stack"; } -}; const Stack = new Stack$class; -Stack.class = Stack$class; +}; Stack1 = new Stack$class; +Stack1.class = Stack$class; null -export default Stack; +let Stack = Stack1; export default Stack; diff --git a/hkmc2/shared/src/test/mlscript-compile/Str.mjs b/hkmc2/shared/src/test/mlscript-compile/Str.mjs index cf22ef318d..c11e334c3f 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Str.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Str.mjs @@ -1,3 +1,4 @@ +let Str1; const Str$class = class Str { constructor() {} concat2(a, b) { @@ -10,7 +11,7 @@ const Str$class = class Str { return globalThis.String(value) ?? null; } toString() { return "Str"; } -}; const Str = new Str$class; -Str.class = Str$class; +}; Str1 = new Str$class; +Str1.class = Str$class; null -export default Str; +let Str = Str1; export default Str; diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs index 7eab1aff4a..0d152a04ef 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs @@ -1,15 +1,8 @@ import fs from "fs"; import Str from "./../Str.mjs"; import Predef from "./../Predef.mjs"; -class Num { - constructor() {} - toString() { return "Num"; } -} -class Bool { - constructor() {} - toString() { return "Bool"; } -} -class Accounting { +let Accounting1; +Accounting1 = class Accounting { constructor() { this.warnings = []; this.Project = function Project(num1) { return new Project.class(num1); }; @@ -20,12 +13,12 @@ class Accounting { toString() { return "Project(" + this.num + ")"; } }; const this$Accounting = this; - this.Line = function Line(name1, proj1, starting_balance1, isMatchable1) { return new Line.class(name1, proj1, starting_balance1, isMatchable1); }; + this.Line = function Line(name1, proj1, starting$_balance1, isMatchable1) { return new Line.class(name1, proj1, starting$_balance1, isMatchable1); }; this.Line.class = class Line { - constructor(name, proj, starting_balance, isMatchable) { + constructor(name, proj, starting$_balance, isMatchable) { this.name = name; this.proj = proj; - this.starting_balance = starting_balance; + this.starting_balance = starting$_balance; this.isMatchable = isMatchable; this.balance = this.starting_balance; } @@ -91,7 +84,7 @@ class Accounting { } snapShot(label) { let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; - tmp = String(label) ?? null; + tmp = globalThis.String(label) ?? null; tmp1 = Str.concat2("|", tmp); tmp2 = Str.concat2(tmp1, "|"); tmp3 = this$Accounting.lines.map((x) => { @@ -157,9 +150,9 @@ class Accounting { tmp = amt / 1000; return tmp.toFixed(1) ?? null; } - mkLine(nme, proj, starting_balance, matchable) { + mkLine(nme, proj, starting$_balance, matchable) { let line, tmp, tmp1; - tmp = this.Line(nme, proj, starting_balance, matchable); + tmp = this.Line(nme, proj, starting$_balance, matchable); line = tmp; tmp1 = this.lines.push(line) ?? null; return line; @@ -175,6 +168,6 @@ class Accounting { return Predef.print(tmp4); } toString() { return "Accounting"; } -} +}; null -export default Accounting; +let Accounting = Accounting1; export default Accounting; diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mjs b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mjs index 62daa861ad..01f1a12d87 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mjs @@ -1,7 +1,8 @@ import Str from "./../Str.mjs"; import Predef from "./../Predef.mjs"; -function CSV(strDelimiter1) { return new CSV.class(strDelimiter1); } -CSV.class = class CSV { +let CSV1; +CSV1 = function CSV(strDelimiter1) { return new CSV.class(strDelimiter1); }; +CSV1.class = class CSV { constructor(strDelimiter) { this.strDelimiter = strDelimiter; let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; @@ -13,7 +14,7 @@ CSV.class = class CSV { tmp4 = tmp3 + "\\r\\n]*))"; tmp5 = "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" + tmp4; tmp6 = tmp2 + tmp5; - tmp7 = new RegExp(tmp6, "gi"); + tmp7 = new globalThis.RegExp(tmp6, "gi"); this.objPattern = tmp7; } toArrays(strData) { @@ -35,7 +36,7 @@ CSV.class = class CSV { } scrut2 = arrMatches[2]; if (scrut2 === true) { - tmp2 = new RegExp("\"\"", "g"); + tmp2 = new globalThis.RegExp("\"\"", "g"); tmp3 = arrMatches[2].replace(tmp2, "\""); } else { tmp3 = arrMatches[3]; @@ -55,4 +56,4 @@ CSV.class = class CSV { toString() { return "CSV(" + this.strDelimiter + ")"; } }; null -export default CSV; +let CSV = CSV1; export default CSV; diff --git a/hkmc2/shared/src/test/mlscript/backlog/MultilineExpressions.mls b/hkmc2/shared/src/test/mlscript/backlog/MultilineExpressions.mls index 5df52a53aa..de4bd1ce06 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/MultilineExpressions.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/MultilineExpressions.mls @@ -117,10 +117,10 @@ if 1 * 2 //│ kw = keyword 'then' //│ rhs = IntLit of 0 //│ JS (unsanitized): -//│ let scrut, scrut1; -//│ scrut = 1 * 2; -//│ scrut1 = scrut + 3; -//│ if (scrut1 === true) { +//│ let scrut3, scrut4; +//│ scrut3 = 1 * 2; +//│ scrut4 = scrut3 + 3; +//│ if (scrut4 === true) { //│ 0 //│ } else { //│ throw new this.Error("match error"); @@ -159,15 +159,15 @@ if 1 + 2 //│ kw = keyword 'then' //│ rhs = IntLit of 0 //│ JS (unsanitized): -//│ let scrut, scrut1, scrut2, scrut3; -//│ scrut = 1; -//│ scrut1 = scrut + 2; -//│ scrut3 = scrut1 * 3; -//│ if (scrut3 === true) { +//│ let scrut5, scrut6, scrut7, scrut8; +//│ scrut5 = 1; +//│ scrut6 = scrut5 + 2; +//│ scrut8 = scrut6 * 3; +//│ if (scrut8 === true) { //│ 0 //│ } else { -//│ scrut2 = scrut1 + 4; -//│ if (scrut2 === true) { +//│ scrut7 = scrut6 + 4; +//│ if (scrut7 === true) { //│ 0 //│ } else { //│ throw new this.Error("match error"); diff --git a/hkmc2/shared/src/test/mlscript/backlog/Records.mls b/hkmc2/shared/src/test/mlscript/backlog/Records.mls index e9291568fe..d908c9c2f0 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/Records.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/Records.mls @@ -14,6 +14,7 @@ let rcd = //│ ╔══[ERROR] Name not found: fieldB //│ ║ l.9: fieldB: //│ ╙── ^^^^^^ +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: rcd (class hkmc2.semantics.VarSymbol) // * Or this syntax? @@ -23,11 +24,12 @@ let rcd = fieldB = "..." //│ ╔══[ERROR] Name not found: fieldA -//│ ║ l.22: fieldA = 1 +//│ ║ l.23: fieldA = 1 //│ ╙── ^^^^^^ //│ ╔══[ERROR] Name not found: fieldB -//│ ║ l.23: fieldB = +//│ ║ l.24: fieldB = //│ ╙── ^^^^^^ +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: rcd (class hkmc2.semantics.VarSymbol) // * Or this syntax? @@ -37,7 +39,8 @@ let rcd = new with fieldB = "..." //│ ╔══[PARSE ERROR] Expected expression or block after `new` keyword; found 'with' keyword instead -//│ ║ l.35: let rcd = new with +//│ ║ l.37: let rcd = new with //│ ╙── ^^^^ +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: rcd (class hkmc2.semantics.VarSymbol) diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index 56db026e9d..737f6a0f4f 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -80,8 +80,8 @@ set g.0 += 1 // FIXME :fixme set g.0 = g.0 + 1 -//│ > try { let selRes, tmp, tmp1; selRes = this.g[0]; if (selRes === undefined) { throw new this.Error("Access to required field '0' yielded 'undefined'"); } else { tmp = selRes; } tmp1 = tmp + 1; this.g.0 = tmp1; null } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^^ +//│ > let selRes, tmp1, tmp2;try { selRes = g[0]; if (selRes === undefined) { throw new this.Error("Access to required field '0' yielded 'undefined'"); } else { tmp1 = selRes; } tmp2 = tmp1 + 1; g.0 = tmp2; null } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ > ^^ //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected number // ——— ——— ——— @@ -114,28 +114,20 @@ undefined === null // ——— ——— ——— -// FIXME not sanitized? -:ssjs -Num -//│ JS: -//│ this.Num - -// ——— ——— ——— - Infinity //│ = Infinity :sjs val Infinity = 1 //│ JS (unsanitized): -//│ this.Infinity = 1; null -//│ Infinity = Infinity +//│ let Infinity1; Infinity1 = 1; null +//│ Infinity = 1 :sjs Infinity //│ JS (unsanitized): -//│ this.Infinity -//│ = Infinity +//│ Infinity1 +//│ = 1 module Test with val Infinity = 1 @@ -156,7 +148,7 @@ f of 2 of 3 //│ ╔══[PARSE ERROR] Expected end of input; found indented block instead -//│ ║ l.157: of 3 +//│ ║ l.149: of 3 //│ ╙── ^^ //│ = [Function (anonymous)] @@ -169,3 +161,65 @@ f of // ——— ——— ——— +object Oops with + if true then + fun f = 1 + () +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: member:f (class hkmc2.semantics.BlockMemberSymbol) + +// We forget to reset the owner to None in sub-blocks like this one +object Oops with + if true then + val fakeField = 1 + print(fakeField) + print(this.fakeField) // shouldn't work! +//│ > 1 +//│ > 1 + +:e +Oops.fakeField +//│ ╔══[ERROR] Object 'Oops' does not contain member 'fakeField' +//│ ║ l.180: Oops.fakeField +//│ ╙── ^^^^^^^^^^ +//│ = 1 + +// ——— ——— ——— + +fun p: Int + +:fixme // make this a proper error +p +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: member:p (class hkmc2.semantics.BlockMemberSymbol) + + +:ctx +fun (++) test: (Int, Int) -> Int +//│ Env: +//│ ++ -> RefElem(member:test) +//│ test -> RefElem(member:test) + +test(1, 2) +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: member:test (class hkmc2.semantics.BlockMemberSymbol) + +:sjs +1 ++ 1 +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: member:test (class hkmc2.semantics.BlockMemberSymbol) + +// ——— ——— ——— + +// TODO forbid declarations inside local blocks +:todo +:e +fun main() = + declare fun foo: Int + () + +// TODO forbid unimplemented signatures inside local blocks +:todo +:e +fun main() = + fun bar: Int + () + +// ——— ——— ——— + diff --git a/hkmc2/shared/src/test/mlscript/basics/BadClasses.mls b/hkmc2/shared/src/test/mlscript/basics/BadClasses.mls index 1a18a92221..2e2ed0c5a6 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadClasses.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadClasses.mls @@ -14,7 +14,8 @@ class Foo { 1 } //│ ║ l.12: class Foo { 1 } //│ ╙── ^^^^^ -// Note that these work: + +// * Note that these work: class Foo with { 1 } diff --git a/hkmc2/shared/src/test/mlscript/basics/BadDefs.mls b/hkmc2/shared/src/test/mlscript/basics/BadDefs.mls index 18c3d07af5..f702b6d954 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadDefs.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadDefs.mls @@ -12,57 +12,67 @@ x //│ = 0 +:sjs +val ++ = 0 +//│ JS (unsanitized): +//│ let $_$_1; $_$_1 = 0; null +//│ ++ = 0 + +:sjs +++ +//│ JS (unsanitized): +//│ $_$_1 +//│ = 0 + + :e -:ge val ++ y = 1 //│ ╔══[ERROR] Invalid value definition head: unexpected identifier in this position -//│ ║ l.17: val ++ y = 1 +//│ ║ l.29: val ++ y = 1 //│ ╙── ^ -//│ > try { this.++ = 1; null } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^^ -//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected token '++' -//│ > try { ++ } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^ -//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected token '}' +//│ ++ = 1 :e y //│ ╔══[ERROR] Name not found: y -//│ ║ l.29: y +//│ ║ l.36: y //│ ╙── ^ ++ +//│ = 1 :e :ge fun ++ z = 0 //│ ╔══[ERROR] Invalid function definition head: unexpected identifier in this position -//│ ║ l.39: fun ++ z = 0 +//│ ║ l.47: fun ++ z = 0 //│ ╙── ^ -//│ > try { function ++(...args) { globalThis.Predef.checkArgs("++", 0, true, args.length); return 0; } null } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^^ +//│ > let $_$_5;try { $_$_5 = function ++(...args) { globalThis.Predef.checkArgs("++", 0, true, args.length); return 0; }; null } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ > ^^ //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected token '++' :sjs +:re ++ //│ JS (unsanitized): -//│ this["++"] +//│ let tmp; tmp = $_$_5(); tmp +//│ ═══[RUNTIME ERROR] ReferenceError: $_$_5 is not defined :e fun (class Lol) foo = 0 //│ ╔══[ERROR] This type definition is not a valid symbolic name -//│ ║ l.54: fun (class Lol) foo = 0 +//│ ║ l.64: fun (class Lol) foo = 0 //│ ╙── ^^^ foo -//│ = [Function: foo] +//│ = 0 :e Lol //│ ╔══[ERROR] Name not found: Lol -//│ ║ l.63: Lol +//│ ║ l.73: Lol //│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/Classes.mls b/hkmc2/shared/src/test/mlscript/basics/Classes.mls index c649619aed..acb7f3888e 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Classes.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Classes.mls @@ -34,10 +34,7 @@ class Foo(x: Int) { log("Hello!") } //│ ║ l.32: class Foo(x: Int) { log("Hello!") } //│ ╙── ^^^^^^^^^^^^^^^^^ //│ JS (unsanitized): -//│ this. = class { constructor() {} toString() { return ""; } }; null -//│ > try { this. = class { constructor() {} toString() { return ""; } }; null } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^ -//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected token '<' +//│ null diff --git a/hkmc2/shared/src/test/mlscript/basics/Declare.mls b/hkmc2/shared/src/test/mlscript/basics/Declare.mls new file mode 100644 index 0000000000..d5a366f10f --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/basics/Declare.mls @@ -0,0 +1,36 @@ +:js + + +:sjs +declare fun foo: Int +//│ JS (unsanitized): +//│ null + +:re +:sjs +foo +//│ JS (unsanitized): +//│ this.foo +//│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' + + +declare fun p: Int + +:re +p +//│ ═══[RUNTIME ERROR] Error: Access to required field 'p' yielded 'undefined' + + +fun q: Int +fun q = 1 + +q +//│ = 1 + +declare module q with { val x } + +:re +q.x +//│ ═══[RUNTIME ERROR] Error: Access to required field 'q' yielded 'undefined' + + diff --git a/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls b/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls index aa21cfd1b3..7b85edc312 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls @@ -46,8 +46,8 @@ M.Foo:: n(foo, 2) :sjs let m = M.Foo::m //│ JS (unsanitized): -//│ this.m = (self, ...args) => { return self.m(...args); }; null -//│ m = [Function (anonymous)] +//│ let m; m = (self, ...args) => { return self.m(...args); }; null +//│ m = [Function: m] m(foo) //│ = 124 @@ -128,7 +128,7 @@ Foo::n(foo, 2) //│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; //│ ╙── add a space before ‹identifier› to make it an operator application. //│ JS (unsanitized): -//│ ((self, ...args) => { return self.n(...args) ?? null; })(this.foo, 2) +//│ ((self, ...args) => { return self.n(...args) ?? null; })(foo, 2) //│ = 125 diff --git a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls index 33c4cb9c4c..a3b4f390af 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls @@ -77,9 +77,9 @@ xs fun f = print(0) f -//│ = [Function: f] +//│ > 0 f -//│ = [Function: f] +//│ > 0 diff --git a/hkmc2/shared/src/test/mlscript/basics/MultiParamLists.mls b/hkmc2/shared/src/test/mlscript/basics/MultiParamLists.mls index 6d048b8bad..0f8ee2ea12 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MultiParamLists.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MultiParamLists.mls @@ -6,11 +6,11 @@ fun f(n1: Int): Int = n1 //│ JS (unsanitized): -//│ function f(n1) { return n1; } null +//│ let f1; f1 = function f(n1) { return n1; }; null f(42) //│ JS (unsanitized): -//│ this.f(42) +//│ f1(42) //│ = 42 // TODO compile this to @@ -19,58 +19,60 @@ f(42) fun f(n1: Int)(n2: Int): Int = (10 * n1 + n2) //│ JS (unsanitized): -//│ function f(n1) { return (n2) => { let tmp; tmp = 10 * n1; return tmp + n2; }; } null +//│ let f3; f3 = function f(n1) { return (n2) => { let tmp; tmp = 10 * n1; return tmp + n2; }; }; null // TODO compile this to // this.f$(4, 2) f(4)(2) //│ JS (unsanitized): -//│ let tmp; tmp = this.f(4); tmp(2) ?? null +//│ let tmp; tmp = f3(4); tmp(2) ?? null //│ = 42 fun f(n1: Int)(n2: Int)(n3: Int): Int = 10 * (10 * n1 + n2) + n3 //│ JS (unsanitized): -//│ function f(n1) { +//│ let f5; +//│ f5 = function f(n1) { //│ return (n2) => { //│ return (n3) => { -//│ let tmp, tmp1, tmp2; -//│ tmp = 10 * n1; -//│ tmp1 = tmp + n2; -//│ tmp2 = 10 * tmp1; -//│ return tmp2 + n3; +//│ let tmp1, tmp2, tmp3; +//│ tmp1 = 10 * n1; +//│ tmp2 = tmp1 + n2; +//│ tmp3 = 10 * tmp2; +//│ return tmp3 + n3; //│ }; //│ }; -//│ } +//│ }; //│ null f(4)(2)(0) //│ JS (unsanitized): -//│ let tmp, tmp1; tmp = this.f(4); tmp1 = tmp(2) ?? null; tmp1(0) ?? null +//│ let tmp1, tmp2; tmp1 = f5(4); tmp2 = tmp1(2) ?? null; tmp2(0) ?? null //│ = 420 fun f(n1: Int)(n2: Int)(n3: Int)(n4: Int): Int = 10 * (10 * (10 * n1 + n2) + n3) + n4 //│ JS (unsanitized): -//│ function f(n1) { +//│ let f7; +//│ f7 = function f(n1) { //│ return (n2) => { //│ return (n3) => { //│ return (n4) => { -//│ let tmp, tmp1, tmp2, tmp3, tmp4; -//│ tmp = 10 * n1; -//│ tmp1 = tmp + n2; -//│ tmp2 = 10 * tmp1; -//│ tmp3 = tmp2 + n3; -//│ tmp4 = 10 * tmp3; -//│ return tmp4 + n4; +//│ let tmp3, tmp4, tmp5, tmp6, tmp7; +//│ tmp3 = 10 * n1; +//│ tmp4 = tmp3 + n2; +//│ tmp5 = 10 * tmp4; +//│ tmp6 = tmp5 + n3; +//│ tmp7 = 10 * tmp6; +//│ return tmp7 + n4; //│ }; //│ }; //│ }; -//│ } +//│ }; //│ null f(3)(0)(3)(1) //│ JS (unsanitized): -//│ let tmp, tmp1, tmp2; tmp = this.f(3); tmp1 = tmp(0) ?? null; tmp2 = tmp1(3) ?? null; tmp2(1) ?? null +//│ let tmp3, tmp4, tmp5; tmp3 = f7(3); tmp4 = tmp3(0) ?? null; tmp5 = tmp4(3) ?? null; tmp5(1) ?? null //│ = 3031 diff --git a/hkmc2/shared/src/test/mlscript/basics/MutVal.mls b/hkmc2/shared/src/test/mlscript/basics/MutVal.mls index bbd3d0410a..3c28b56adf 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MutVal.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MutVal.mls @@ -4,7 +4,10 @@ mut val cached: Int = 1 //│ cached = 1 +:sjs set cached = 2 +//│ JS (unsanitized): +//│ cached1 = 2; null cached //│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/basics/New.mls b/hkmc2/shared/src/test/mlscript/basics/New.mls index 902325d510..6e1ae2a99a 100644 --- a/hkmc2/shared/src/test/mlscript/basics/New.mls +++ b/hkmc2/shared/src/test/mlscript/basics/New.mls @@ -13,9 +13,13 @@ new Foo //│ = Foo {} +:w new Foo Foo +//│ ╔══[WARNING] Pure expression in statement position +//│ ║ l.18: Foo +//│ ╙── ^^^ //│ = Foo {} // * Interesting... it tries to call `Foo()` and instantiate the resulting class value... @@ -29,7 +33,7 @@ new Foo () //│ ╔══[ERROR] Illegal juxtaposition right-hand side. -//│ ║ l.30: () +//│ ║ l.34: () //│ ╙── ^^ //│ = Foo {} diff --git a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls index d140173360..0d365c5c7c 100644 --- a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls +++ b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls @@ -31,7 +31,7 @@ let f = x => x * 2 -//│ f = [Function (anonymous)] +//│ f = [Function: f] 2 + 1 diff --git a/hkmc2/shared/src/test/mlscript/basics/Overloading.mls b/hkmc2/shared/src/test/mlscript/basics/Overloading.mls new file mode 100644 index 0000000000..e3bf1bb15d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/basics/Overloading.mls @@ -0,0 +1,25 @@ +:js + +:global +// :d + + +:todo +fun Foo() = 1 +module Foo with { val x = 1 } +//│ ╔══[ERROR] Multiple definitions of symbol 'Foo' +//│ ╟── defined here +//│ ║ l.8: fun Foo() = 1 +//│ ║ ^^^^^^^^^ +//│ ╟── defined here +//│ ║ l.9: module Foo with { val x = 1 } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^ + +Foo +//│ = Foo { x: 1, class: [class Foo] } + +:todo +Foo() +//│ ═══[RUNTIME ERROR] TypeError: Foo2 is not a function + + diff --git a/hkmc2/shared/src/test/mlscript/basics/PredefTest.mls b/hkmc2/shared/src/test/mlscript/basics/PredefTest.mls index 1f9f83597e..df9b82c448 100644 --- a/hkmc2/shared/src/test/mlscript/basics/PredefTest.mls +++ b/hkmc2/shared/src/test/mlscript/basics/PredefTest.mls @@ -15,7 +15,7 @@ Predef.Test === globalThis.Predef.Test :fixme new Predef.Test is Predef.Test -//│ ═══[RUNTIME ERROR] TypeError: tmp2 is not a constructor +//│ ═══[RUNTIME ERROR] TypeError: tmp7 is not a constructor (new Predef.Test) is Predef.Test //│ > Test diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbBasics.mls b/hkmc2/shared/src/test/mlscript/bbml/bbBasics.mls index 42513011b6..eae45c3181 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbBasics.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbBasics.mls @@ -159,13 +159,13 @@ let ip = new Printer(foofoo) in ip.Printer#f(42) :e let ip = new Printer(foofoo) in ip.Printer#f("42") -//│ ╔══[ERROR] Type error in string literal with expected type '-T +//│ ╔══[ERROR] Type error in string literal with expected type 'T //│ ║ l.161: let ip = new Printer(foofoo) in ip.Printer#f("42") //│ ║ ^^^^ -//│ ╟── because: cannot constrain Str <: '-T -//│ ╟── because: cannot constrain Str <: '-T -//│ ╟── because: cannot constrain Str <: ¬(¬'-T) -//│ ╟── because: cannot constrain Str <: '-T +//│ ╟── because: cannot constrain Str <: 'T +//│ ╟── because: cannot constrain Str <: 'T +//│ ╟── because: cannot constrain Str <: ¬(¬'T) +//│ ╟── because: cannot constrain Str <: 'T //│ ╙── because: cannot constrain Str <: ¬(¬{Int}) //│ Type: Str @@ -186,13 +186,13 @@ let tf = new TFun(inc) in tf.TFun#f(1) :e let tf = new TFun(inc) in tf.TFun#f("1") -//│ ╔══[ERROR] Type error in string literal with expected type '-T +//│ ╔══[ERROR] Type error in string literal with expected type 'T //│ ║ l.188: let tf = new TFun(inc) in tf.TFun#f("1") //│ ║ ^^^ -//│ ╟── because: cannot constrain Str <: '-T -//│ ╟── because: cannot constrain Str <: '-T -//│ ╟── because: cannot constrain Str <: ¬(¬'-T) -//│ ╟── because: cannot constrain Str <: '-T +//│ ╟── because: cannot constrain Str <: 'T +//│ ╟── because: cannot constrain Str <: 'T +//│ ╟── because: cannot constrain Str <: ¬(¬'T) +//│ ╟── because: cannot constrain Str <: 'T //│ ╙── because: cannot constrain Str <: ¬(¬{Int}) //│ Type: Str ∨ Int @@ -260,6 +260,18 @@ fact(1) //│ Type: Int +fun fact2 = x => fact2(1) +//│ Type: ⊤ + +fun fact2 = x => if x is + n then fact2(n - 1) +//│ Type: ⊤ + +fun fact2(x) = if x is + n then fact2(n - 1) +//│ Type: ⊤ + + fun fact2 = case 0 then 1 n then n * fact2(n - 1) @@ -296,7 +308,7 @@ throw new Error("oops") :e throw 42 //│ ╔══[ERROR] Type error in throw -//│ ║ l.297: throw 42 +//│ ║ l.309: throw 42 //│ ║ ^^ //│ ╙── because: cannot constrain Int <: Error //│ Type: ⊥ diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbBounds.mls b/hkmc2/shared/src/test/mlscript/bbml/bbBounds.mls index 772c2e3aa7..d2c0a80950 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbBounds.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbBounds.mls @@ -111,7 +111,7 @@ fun h: [C] -> ([A extends Int] -> A -> ([B extends A -> A restricts A -> A] -> B :e bazbaz: [A extends Int] -> A -> ([B extends A -> A restricts A -> A] -> B) -> A -//│ ╔══[ERROR] Cannot type non-function term SynthSel(Ref(globalThis:block#17),Ident(bazbaz)) as (A) ->{⊥} ([outer, 'B] -> 'B) ->{⊥} A +//│ ╔══[ERROR] Cannot type non-function term Ref(member:bazbaz) as (A) ->{⊥} ([outer, 'B] -> 'B) ->{⊥} A //│ ║ l.113: bazbaz: [A extends Int] -> A -> ([B extends A -> A restricts A -> A] -> B) -> A //│ ╙── ^^^^^^ //│ Type: ['A] -> ('A) ->{⊥} ('A -> 'A) ->{⊥} 'A diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbCheck.mls b/hkmc2/shared/src/test/mlscript/bbml/bbCheck.mls index a66c1ee9bf..71659a36e3 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbCheck.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbCheck.mls @@ -119,7 +119,7 @@ fun baz(z) = :e baz: Int -> (([A] -> A -> A), Int) -> Int -//│ ╔══[ERROR] Cannot type non-function term SynthSel(Ref(globalThis:block#18),Ident(baz)) as (Int) ->{⊥} ([outer, 'A] -> ('A) ->{⊥} 'A, Int) ->{⊥} Int +//│ ╔══[ERROR] Cannot type non-function term Ref(member:baz) as (Int) ->{⊥} ([outer, 'A] -> ('A) ->{⊥} 'A, Int) ->{⊥} Int //│ ║ l.121: baz: Int -> (([A] -> A -> A), Int) -> Int //│ ╙── ^^^ //│ Type: ⊥ @@ -130,7 +130,7 @@ baz(42) :e baz(42): (([A] -> A -> A), Int) -> Int -//│ ╔══[ERROR] Cannot type non-function term App(SynthSel(Ref(globalThis:block#18),Ident(baz)),Tup(List(Fld(‹›,Lit(IntLit(42)),None)))) as ([outer, 'A] -> ('A) ->{⊥} 'A, Int) ->{⊥} Int +//│ ╔══[ERROR] Cannot type non-function term App(Ref(member:baz),Tup(List(Fld(‹›,Lit(IntLit(42)),None)))) as ([outer, 'A] -> ('A) ->{⊥} 'A, Int) ->{⊥} Int //│ ║ l.132: baz(42): (([A] -> A -> A), Int) -> Int //│ ╙── ^^^^^^^ //│ Type: ⊥ diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls b/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls index 3008c89e03..2f1114c2c0 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls @@ -30,7 +30,7 @@ :sjs let x = 1 in x + 1 //│ JS (unsanitized): -//│ this.x = 1; this.x + 1 +//│ let x; x = 1; x + 1 //│ = 2 //│ Type: Int @@ -52,7 +52,7 @@ false :sjs (x => x): [T] -> T -> T //│ JS (unsanitized): -//│ (x) => { return x; } +//│ (x1) => { return x1; } //│ = [Function (anonymous)] //│ Type: ['T] -> ('T) ->{⊥} 'T @@ -60,10 +60,11 @@ false :sjs class Foo(x: Int) //│ JS (unsanitized): -//│ this.Foo = function Foo(x1) { return new Foo.class(x1); }; -//│ this.Foo.class = class Foo { -//│ constructor(x) { -//│ this.x = x; +//│ let Foo1; +//│ Foo1 = function Foo(x2) { return new Foo.class(x2); }; +//│ Foo1.class = class Foo { +//│ constructor(x1) { +//│ this.x = x1; //│ } //│ toString() { return "Foo(" + this.x + ")"; } //│ }; @@ -74,7 +75,7 @@ class Foo(x: Int) :sjs new Foo(42) //│ JS (unsanitized): -//│ new this.Foo.class(42) +//│ new Foo1.class(42) //│ = Foo { x: 42 } //│ Type: Foo @@ -82,7 +83,7 @@ new Foo(42) :sjs let foo = new Foo(42) in foo.Foo#x //│ JS (unsanitized): -//│ let tmp; tmp = new this.Foo.class(42); this.foo = tmp; this.foo.x +//│ let foo, tmp; tmp = new Foo1.class(42); foo = tmp; foo.x //│ = 42 //│ Type: Int @@ -90,14 +91,14 @@ let foo = new Foo(42) in foo.Foo#x :sjs fun inc(x) = x + 1 //│ JS (unsanitized): -//│ function inc(x) { return x + 1; } null +//│ let inc1; inc1 = function inc(x1) { return x1 + 1; }; null //│ Type: ⊤ :sjs inc(41) //│ JS (unsanitized): -//│ this.inc(41) +//│ inc1(41) //│ = 42 //│ Type: Int @@ -113,7 +114,7 @@ if 1 == 2 then 0 else 42 :sjs if 1 is Int then 1 else 0 //│ JS (unsanitized): -//│ let scrut; scrut = 1; if (globalThis.Number.isInteger(scrut)) { 1 } else { 0 } +//│ let scrut1; scrut1 = 1; if (globalThis.Number.isInteger(scrut1)) { 1 } else { 0 } //│ = 1 //│ Type: Int @@ -127,11 +128,10 @@ class Foo() let foo = new Foo() if foo is Foo then 1 else 0 //│ JS (unsanitized): -//│ let scrut, tmp; -//│ tmp = new this.Foo.class(); -//│ this.foo = tmp; -//│ scrut = this.foo; -//│ if (scrut instanceof this.Foo.class) { +//│ let foo1, tmp1; +//│ tmp1 = new Foo3.class(); +//│ foo1 = tmp1; +//│ if (foo1 instanceof Foo3.class) { //│ 1 //│ } else { //│ 0 @@ -146,20 +146,21 @@ fun pow(x) = case 0 then 1 n then x * pow(x)(n-1) //│ JS (unsanitized): -//│ function pow(x) { +//│ let pow1; +//│ pow1 = function pow(x1) { //│ return (caseScrut) => { -//│ let n, tmp, tmp1, tmp2; +//│ let n, tmp2, tmp3, tmp4; //│ if (caseScrut === 0) { //│ return 1; //│ } else { //│ n = caseScrut; -//│ tmp = globalThis.pow(x); -//│ tmp1 = n - 1; -//│ tmp2 = tmp(tmp1) ?? null; -//│ return x * tmp2; +//│ tmp2 = pow1(x1); +//│ tmp3 = n - 1; +//│ tmp4 = tmp2(tmp3) ?? null; +//│ return x1 * tmp4; //│ } //│ }; -//│ } +//│ }; //│ null //│ Type: ⊤ @@ -169,7 +170,8 @@ fun not = case true then false false then true //│ JS (unsanitized): -//│ function not() { +//│ let not1; +//│ not1 = function not() { //│ return (caseScrut) => { //│ if (caseScrut === true) { //│ return false; @@ -181,19 +183,17 @@ fun not = case //│ } //│ } //│ }; -//│ } +//│ }; //│ null //│ Type: ⊤ -:fixme // TODO fix discrepancy in codegen for such functions :expect true :sjs not of false //│ JS (unsanitized): -//│ this.not(false) -//│ ═══[RUNTIME ERROR] Expected: true, got: [Function (anonymous)] -//│ = [Function (anonymous)] +//│ let tmp2; tmp2 = not1(); tmp2(false) +//│ = true //│ Type: Bool @@ -202,35 +202,35 @@ fun fact = case 0 then 1 n then n * fact(n - 1) //│ JS (unsanitized): -//│ function fact() { +//│ let fact1; +//│ fact1 = function fact() { //│ return (caseScrut) => { -//│ let n, tmp, tmp1; +//│ let n, tmp3, tmp4, tmp5; //│ if (caseScrut === 0) { //│ return 1; //│ } else { //│ n = caseScrut; -//│ tmp = n - 1; -//│ tmp1 = globalThis.fact(tmp); -//│ return n * tmp1; +//│ tmp3 = fact1(); +//│ tmp4 = n - 1; +//│ tmp5 = tmp3(tmp4); +//│ return n * tmp5; //│ } //│ }; -//│ } +//│ }; //│ null //│ Type: ⊤ -:fixme // TODO fix discrepancy in codegen for such functions :expect 6 fact(3) -//│ ═══[RUNTIME ERROR] Expected: 6, got: [Function (anonymous)] -//│ = [Function (anonymous)] +//│ = 6 //│ Type: Int :sjs region x in 42 //│ JS (unsanitized): -//│ let x; x = new this.Region(); 42 +//│ let x1; x1 = new this.Region(); 42 //│ = 42 //│ Type: Int @@ -238,7 +238,7 @@ region x in 42 :sjs region x in x //│ JS (unsanitized): -//│ let x; x = new this.Region(); x +//│ let x2; x2 = new this.Region(); x2 //│ = Region {} //│ Type: Region[?] @@ -246,7 +246,7 @@ region x in x :sjs region x in x.ref 42 //│ JS (unsanitized): -//│ let x; x = new this.Region(); new this.Ref(x, 42) +//│ let x3; x3 = new this.Region(); new this.Ref(x3, 42) //│ = Ref { reg: Region {}, value: 42 } //│ Type: Ref[Int, ?] @@ -254,7 +254,7 @@ region x in x.ref 42 :sjs region x in let y = x.ref 42 in !y //│ JS (unsanitized): -//│ let x, tmp; x = new this.Region(); tmp = new this.Ref(x, 42); this.y = tmp; this.y.value +//│ let x4, y, tmp4; x4 = new this.Region(); tmp4 = new this.Ref(x4, 42); y = tmp4; y.value //│ = 42 //│ Type: Int @@ -262,6 +262,6 @@ region x in let y = x.ref 42 in !y :sjs region x in let y = x.ref 42 in y := 0 //│ JS (unsanitized): -//│ let x, tmp; x = new this.Region(); tmp = new this.Ref(x, 42); this.y = tmp; this.y.value = 0; 0 +//│ let x5, y1, tmp5; x5 = new this.Region(); tmp5 = new this.Ref(x5, 42); y1 = tmp5; y1.value = 0; 0 //│ = 0 //│ Type: Int diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbErrors.mls b/hkmc2/shared/src/test/mlscript/bbml/bbErrors.mls index ae7bce86ad..5ea6f59bd7 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbErrors.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbErrors.mls @@ -65,7 +65,7 @@ inc("oops") fun inc(x) = x + 1 inc : Int -//│ ╔══[ERROR] Type error in selection with expected type Int +//│ ╔══[ERROR] Type error in reference with expected type Int //│ ║ l.67: inc : Int //│ ║ ^^^ //│ ╙── because: cannot constrain 'x -> Int <: Int diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbExtrude.mls b/hkmc2/shared/src/test/mlscript/bbml/bbExtrude.mls index dfa4fc9929..cc18c0b9e9 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbExtrude.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbExtrude.mls @@ -77,7 +77,7 @@ f(g)(foo) :fixme // ??? f(g)(bar) -//│ ╔══[ERROR] Type error in selection with expected type 'A +//│ ╔══[ERROR] Type error in reference with expected type 'A //│ ║ l.79: f(g)(bar) //│ ║ ^^^ //│ ╟── because: cannot constrain C[Int] <: 'A diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls b/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls index 5936a60a8a..684b963dd2 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls @@ -75,21 +75,22 @@ fun test2() = funny //│ ═══[WARNING] Pure expression in statement position //│ JS (unsanitized): -//│ function test2() { -//│ let tmp; -//│ function funny() { +//│ let test25; +//│ test25 = function test2() { +//│ let funny, tmp1; +//│ funny = function funny() { //│ return (caseScrut) => { -//│ let n, tmp1, tmp2, tmp3; +//│ let n, tmp2, tmp3, tmp4; //│ n = caseScrut; -//│ tmp1 = funny(); -//│ tmp2 = n - 1; -//│ tmp3 = tmp1(tmp2); -//│ return tmp3 + 1; +//│ tmp2 = funny(); +//│ tmp3 = n - 1; +//│ tmp4 = tmp2(tmp3); +//│ return tmp4 + 1; //│ }; -//│ } -//│ tmp = funny(); -//│ return tmp; -//│ } +//│ }; +//│ tmp1 = funny(); +//│ return tmp1; +//│ }; //│ null //│ ═══[WARNING] Pure expression in statement position //│ ╔══[ERROR] Function definition shape not yet supported for funny @@ -105,7 +106,7 @@ fun test2() = fun test3 = log("Hi") //│ ╔══[ERROR] Function definition shape not yet supported for test3 -//│ ║ l.106: log("Hi") +//│ ║ l.107: log("Hi") //│ ╙── ^^^^^^^^^ //│ Type: ⊤ diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbOption.mls b/hkmc2/shared/src/test/mlscript/bbml/bbOption.mls index 8945623af2..1f14dd5831 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbOption.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbOption.mls @@ -7,7 +7,7 @@ class Option[out A](inspect: [E, Res] -> (() ->{E} Res, A ->{E} Res) ->{E} Res) //│ Type: ⊤ opt => opt.Option#inspect -//│ Type: (Option[out '+A]) ->{⊥} ['E, 'Res] -> (() ->{'E} 'Res, ('+A) ->{'E} 'Res) ->{'E} 'Res +//│ Type: (Option[out 'A]) ->{⊥} ['E, 'Res] -> (() ->{'E} 'Res, ('A) ->{'E} 'Res) ->{'E} 'Res opt => opt.Option#inspect( () => 42 diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls b/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls index 1e2011e495..df712b4cd3 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbPrelude.mls @@ -1,43 +1,53 @@ // :bbml -class Any -class Nothing +declare class Any +declare class Nothing -class Bool -class Int -class Num +declare class untyped -class CodeBase[T, C, S] -class Region[T] -class Ref[T, S](reg: Region[T], value: S) +declare class Bool +declare class Int +declare class Num -class Str(length: Int, concat: Str -> Str) +class + CodeBase[T, C, S] + Region[T] + Ref[T, S](reg: Region[T], value: S) -class Error(msg: Str) +@untyped +set + globalThis.CodeBase = CodeBase + globalThis.Region = Region + globalThis.Ref = Ref + +declare class Str(length: Int, concat: Str -> Str) + +declare class Error(msg: Str) declare module Predef -fun run: [T] -> CodeBase[out T, out Nothing, out Any] -> T -fun log: Str -> Any -fun error: Nothing - -fun (+): (Int, Int) -> Int -fun (-): (Int, Int) -> Int -fun (*): (Int, Int) -> Int -fun (/): (Int, Int) -> Num -fun (+.): (Num, Num) -> Num -fun (-.): (Num, Num) -> Num -fun (*.): (Num, Num) -> Num -fun (/.): (Num, Num) -> Num - -fun (<): (Int, Int) -> Bool -fun (>): (Int, Int) -> Bool -fun (<=): (Int, Int) -> Bool -fun (>=): (Int, Int) -> Bool -fun (==): [T] -> (T, T) -> Bool -fun (!=): [T] -> (T, T) -> Bool - -fun (&&): (Bool, Bool) -> Bool -fun (||): (Bool, Bool) -> Bool +declare fun run: [T] -> CodeBase[out T, out Nothing, out Any] -> T +declare fun log: Str -> Any +declare fun error: Nothing + +declare fun (+): (Int, Int) -> Int +declare fun (-): (Int, Int) -> Int +declare fun (*): (Int, Int) -> Int +declare fun (/): (Int, Int) -> Num +declare fun (+.): (Num, Num) -> Num +declare fun (-.): (Num, Num) -> Num +declare fun (*.): (Num, Num) -> Num +declare fun (/.): (Num, Num) -> Num + +declare fun (<): (Int, Int) -> Bool +declare fun (>): (Int, Int) -> Bool +declare fun (<=): (Int, Int) -> Bool +declare fun (>=): (Int, Int) -> Bool +declare fun (==): [T] -> (T, T) -> Bool +declare fun (!=): [T] -> (T, T) -> Bool + +declare fun (&&): (Bool, Bool) -> Bool +declare fun (||): (Bool, Bool) -> Bool + diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbRec.mls b/hkmc2/shared/src/test/mlscript/bbml/bbRec.mls index 262359a290..846ca5bebc 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbRec.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbRec.mls @@ -13,6 +13,20 @@ fun f x = f //│ ╙── ^ //│ Type: ⊤ +:e +f +//│ ╔══[ERROR] Variable not found: f +//│ ║ l.17: f +//│ ╙── ^ +//│ Type: ⊥ + +:e +x +//│ ╔══[ERROR] Name not found: x +//│ ║ l.24: x +//│ ╙── ^ +//│ Type: ⊥ + fun f(x) = f f //│ Type: ['f] -> ⊤ -> (⊤ -> 'f) @@ -26,7 +40,7 @@ f :todo fun f(x) = f(x.a) //│ ╔══[ERROR] Term shape not yet supported by BbML: Sel(Ref(x),Ident(a)) -//│ ║ l.27: fun f(x) = f(x.a) +//│ ║ l.41: fun f(x) = f(x.a) //│ ╙── ^^^ //│ Type: ⊤ @@ -37,7 +51,7 @@ class Foo[A](a: A) :todo proper error Foo(123) //│ ╔══[ERROR] Variable not found: Foo -//│ ║ l.38: Foo(123) +//│ ║ l.52: Foo(123) //│ ╙── ^^^ //│ Type: ⊥ @@ -48,8 +62,8 @@ new Foo(123) :todo proper error fun f(x) = f(Foo.a(x)) -//│ ╔══[ERROR] Term shape not yet supported by BbML: Sel(SynthSel(Ref(globalThis:block#5),Ident(Foo)),Ident(a)) -//│ ║ l.50: fun f(x) = f(Foo.a(x)) +//│ ╔══[ERROR] Term shape not yet supported by BbML: Sel(Ref(member:Foo),Ident(a)) +//│ ║ l.64: fun f(x) = f(Foo.a(x)) //│ ╙── ^^^^^ //│ Type: ⊤ diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbUntyped.mls b/hkmc2/shared/src/test/mlscript/bbml/bbUntyped.mls new file mode 100644 index 0000000000..13101cde37 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/bbml/bbUntyped.mls @@ -0,0 +1,13 @@ +:bbml +//│ Type: ⊤ + +//│ Type: ⊤ + + +@untyped 1 +//│ Type: ⊥ + +(@untyped 1)(2) +//│ Type: ⊥ + + diff --git a/hkmc2/shared/src/test/mlscript/codegen/Arrays.mls b/hkmc2/shared/src/test/mlscript/codegen/Arrays.mls index e43e7075af..7c4f3c4f90 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Arrays.mls @@ -11,14 +11,20 @@ let empty = [] empty.0 //│ ═══[RUNTIME ERROR] Error: Access to required field '0' yielded 'undefined' +:sjs let single = [1] +//│ JS (unsanitized): +//│ let single; single = [ 1 ]; null //│ single = [ 1 ] single.0 //│ = 1 +:sjs val single = [1] +//│ JS (unsanitized): +//│ let single2; single2 = [ 1 ]; null //│ single = [ 1 ] single.0 diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadInit.mls b/hkmc2/shared/src/test/mlscript/codegen/BadInit.mls index 4811fa65f2..bf09fb007d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadInit.mls @@ -9,12 +9,13 @@ module Bar with module Baz with val a = Bar.x //│ JS (unsanitized): +//│ let Bar1; //│ const Bar$class = class Bar { //│ constructor() { //│ this.x = 1; //│ const Baz$class = class Baz { //│ constructor() { -//│ this.a = globalThis.Bar.x; +//│ this.a = Bar1.x; //│ } //│ toString() { return "Baz"; } //│ }; @@ -22,9 +23,8 @@ module Bar with //│ this.Baz.class = Baz$class; //│ } //│ toString() { return "Bar"; } -//│ }; -//│ this.Bar = new Bar$class; -//│ this.Bar.class = Bar$class; +//│ }; Bar1 = new Bar$class; +//│ Bar1.class = Bar$class; //│ null //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'x') diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadOpen.mls b/hkmc2/shared/src/test/mlscript/codegen/BadOpen.mls index 808bb7f35b..e48c26a364 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadOpen.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadOpen.mls @@ -32,7 +32,7 @@ open Foo { y } :sjs y //│ JS (unsanitized): -//│ this.Foo.y +//│ Foo1.y val Oops = "oops" diff --git a/hkmc2/shared/src/test/mlscript/codegen/BasicTerms.mls b/hkmc2/shared/src/test/mlscript/codegen/BasicTerms.mls index 035ff60a48..8f01c0d9af 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BasicTerms.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BasicTerms.mls @@ -34,23 +34,43 @@ log("Hi") //│ JS (unsanitized): //│ this.log("Hi") ?? null -//│ ═══[RUNTIME ERROR] TypeError: this.log is not a function +//│ ═══[RUNTIME ERROR] Error: Access to required field 'log' yielded 'undefined' :fixme :lot log("Hi") 2 //│ JS (unsanitized): -//│ let tmp; tmp = this.log("Hi") ?? null; 2 +//│ let tmp1; tmp1 = this.log("Hi") ?? null; 2 //│ Lowered: //│ Program: //│ imports = Nil //│ main = Assign: +//│ lhs = $selRes +//│ rhs = Select{member:log}: +//│ qual = Ref of globalThis:globalThis +//│ name = Ident of "log" +//│ rest = Begin: \ +//│ sub = Match: +//│ scrut = Ref of $selRes +//│ arms = Ls of +//│ Tuple2: +//│ _1 = Lit of UnitLit of false +//│ _2 = Throw of Instantiate: +//│ cls = Select: +//│ qual = Ref of globalThis:globalThis +//│ name = Ident of "Error" +//│ args = Ls of +//│ Lit of StrLit of "Access to required field 'log' yielded 'undefined'" +//│ dflt = S of Assign: +//│ lhs = $tmp +//│ rhs = Ref of $selRes +//│ rest = End of "" +//│ rest = End of "" +//│ rest = Assign: \ //│ lhs = $tmp //│ rhs = Call: -//│ fun = Select{member:log}: -//│ qual = Ref of globalThis:import#Prelude -//│ name = Ident of "log" +//│ fun = Ref of $tmp //│ args = Ls of //│ Arg: //│ spread = false @@ -58,7 +78,7 @@ log("Hi") //│ rest = Return: \ //│ res = Lit of IntLit of 2 //│ implct = true -//│ ═══[RUNTIME ERROR] TypeError: this.log is not a function +//│ ═══[RUNTIME ERROR] Error: Access to required field 'log' yielded 'undefined' :re diff --git a/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls b/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls index 2a6a2937d6..c0dfca6012 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls @@ -12,7 +12,7 @@ x + 1 fun incr(n) = n + 1 fun (|>) pipe(x, f) = f(x) //│ Pretty Lowered: -//│ define fun incr(n) { return +(n, 1) } in define fun pipe(x, f) { return f(x) } in return null +//│ define fun incr(n) { return +(n, 1) } in define fun pipe(x1, f) { return f(x1) } in return null :slot let x = 1 @@ -20,9 +20,9 @@ let x = if x == 0 then 1 else 0 let x = x + 1 //│ Pretty Lowered: //│ -//│ set x = 1 in +//│ set x2 = 1 in //│ begin -//│ set scrut = ==(x, 0) in +//│ set scrut = ==(x2, 0) in //│ match scrut //│ true => //│ set tmp = 1 in @@ -30,8 +30,8 @@ let x = x + 1 //│ else //│ set tmp = 0 in //│ end; -//│ set x1 = tmp in -//│ set tmp1 = +(x, 1) in -//│ set x2 = tmp1 in +//│ set x3 = tmp in +//│ set tmp1 = +(x3, 1) in +//│ set x4 = tmp1 in //│ return null //│ x = 1 diff --git a/hkmc2/shared/src/test/mlscript/codegen/BuiltinOps.mls b/hkmc2/shared/src/test/mlscript/codegen/BuiltinOps.mls index d88b7a820f..d7a212e268 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BuiltinOps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BuiltinOps.mls @@ -74,7 +74,7 @@ id(+)(1, 2) :re id(+)(1) //│ JS (unsanitized): -//│ let tmp; tmp = Predef.id((arg1, arg2) => { return arg1 + arg2; }); tmp(1) ?? null +//│ let tmp1; tmp1 = Predef.id((arg1, arg2) => { return arg1 + arg2; }); tmp1(1) ?? null //│ ═══[RUNTIME ERROR] Error: Function expected 2 arguments but got 1 @@ -83,14 +83,14 @@ fun (+) lol(a, b) = [a, b] :sjs 1 + 2 //│ JS (unsanitized): -//│ this.lol(1, 2) +//│ lol1(1, 2) //│ = [ 1, 2 ] :sjs id(~)(2) //│ JS (unsanitized): -//│ let tmp; tmp = Predef.id((arg) => { return ~ arg; }); tmp(2) ?? null +//│ let tmp2; tmp2 = Predef.id((arg) => { return ~ arg; }); tmp2(2) ?? null //│ = -3 2 |> ~ diff --git a/hkmc2/shared/src/test/mlscript/codegen/CaseOfCase.mls b/hkmc2/shared/src/test/mlscript/codegen/CaseOfCase.mls index 751c7c2051..9ece4a90ff 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CaseOfCase.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CaseOfCase.mls @@ -18,33 +18,34 @@ fun test(x) = Some(v) then log(v) None then log("none") //│ JS (unsanitized): -//│ function test(x) { +//│ let test1; +//│ test1 = function test(x) { //│ let param0, v, scrut, param01, v1, tmp, tmp1; -//│ if (x instanceof globalThis.Some.class) { +//│ if (x instanceof Some1.class) { //│ param0 = x.value; //│ v = param0; //│ tmp = v + 1; -//│ tmp1 = globalThis.Some(tmp); +//│ tmp1 = Some1(tmp); //│ } else { -//│ if (x instanceof globalThis.None.class) { -//│ tmp1 = globalThis.None; +//│ if (x instanceof None1.class) { +//│ tmp1 = None1; //│ } else { //│ throw new globalThis.Error("match error"); //│ } //│ } //│ scrut = tmp1; -//│ if (scrut instanceof globalThis.Some.class) { +//│ if (scrut instanceof Some1.class) { //│ param01 = scrut.value; //│ v1 = param01; -//│ return globalThis.log(v1); +//│ return log1(v1); //│ } else { -//│ if (scrut instanceof globalThis.None.class) { -//│ return globalThis.log("none"); +//│ if (scrut instanceof None1.class) { +//│ return log1("none"); //│ } else { //│ throw new globalThis.Error("match error"); //│ } //│ } -//│ } +//│ }; //│ null diff --git a/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls b/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls index 720358d504..3bff29053b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls @@ -64,19 +64,19 @@ val isDefined = case Some then true None then false //│ JS (unsanitized): -//│ let tmp; -//│ tmp = (caseScrut) => { -//│ if (caseScrut instanceof this.Some.class) { +//│ let isDefined1, tmp5; +//│ tmp5 = (caseScrut) => { +//│ if (caseScrut instanceof Some1.class) { //│ return true; //│ } else { -//│ if (caseScrut instanceof this.None.class) { +//│ if (caseScrut instanceof None1.class) { //│ return false; //│ } else { //│ throw new this.Error("match error"); //│ } //│ } //│ }; -//│ this.isDefined = tmp; +//│ isDefined1 = tmp5; //│ null -//│ isDefined = [Function: tmp] +//│ isDefined = [Function: tmp5] diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls index ad2a0d3dcd..2b48603e8f 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls @@ -17,8 +17,9 @@ class Outer(a, b) with log(i.c) log(i.i1(1)) //│ JS (unsanitized): -//│ this.Outer = function Outer(a1, b1) { return new Outer.class(a1, b1); }; -//│ this.Outer.class = class Outer { +//│ let Outer1; +//│ Outer1 = function Outer(a1, b1) { return new Outer.class(a1, b1); }; +//│ Outer1.class = class Outer { //│ constructor(a, b) { //│ this.a = a; //│ this.b = b; @@ -29,10 +30,10 @@ class Outer(a, b) with //│ constructor(c) { //│ this.c = c; //│ let tmp3, tmp4, tmp5; -//│ tmp3 = globalThis.log(this$Outer.a); -//│ tmp4 = globalThis.log(this.c); +//│ tmp3 = log1(this$Outer.a); +//│ tmp4 = log1(this.c); //│ tmp5 = this.i1(this$Outer.a); -//│ globalThis.log(tmp5) +//│ log1(tmp5) //│ } //│ i1(d) { //│ return [ @@ -45,9 +46,9 @@ class Outer(a, b) with //│ }; //│ tmp = this.Inner(this.a); //│ this.i = tmp; -//│ tmp1 = globalThis.log(this.i.c); +//│ tmp1 = log1(this.i.c); //│ tmp2 = this.i.i1(1) ?? null; -//│ globalThis.log(tmp2) +//│ log1(tmp2) //│ } //│ o1(c) { //│ return this.Inner(c); diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls index b31400baf2..e9e73c1780 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls @@ -9,15 +9,17 @@ fun test(a) = class C with { val x = a } new C //│ JS (unsanitized): -//│ function test(a) { -//│ class C { +//│ let test1; +//│ test1 = function test(a) { +//│ let C; +//│ C = class C { //│ constructor() { //│ this.x = a; //│ } //│ toString() { return "C"; } -//│ } +//│ }; //│ return new C(); -//│ } +//│ }; //│ null test(12) @@ -35,9 +37,10 @@ fun test(x) = class Foo(a, b) Foo(x, x + 1) //│ JS (unsanitized): -//│ function test(x) { -//│ let tmp; -//│ function Foo(a1, b1) { return new Foo.class(a1, b1); } +//│ let test5; +//│ test5 = function test(x) { +//│ let Foo, tmp; +//│ Foo = function Foo(a1, b1) { return new Foo.class(a1, b1); }; //│ Foo.class = class Foo { //│ constructor(a, b) { //│ this.a = a; @@ -47,7 +50,7 @@ fun test(x) = //│ }; //│ tmp = x + 1; //│ return Foo(x, tmp); -//│ } +//│ }; //│ null diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls index 585ead01ef..7cdf362bb9 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls @@ -13,8 +13,8 @@ object None if Some(0) is Some(x) then x //│ JS (unsanitized): //│ let scrut, param0, x; -//│ scrut = this.Some(0); -//│ if (scrut instanceof this.Some.class) { +//│ scrut = Some1(0); +//│ if (scrut instanceof Some1.class) { //│ param0 = scrut.value; //│ x = param0; //│ x @@ -31,12 +31,11 @@ let s = Some(0) if s is Some(x) then x //│ JS (unsanitized): -//│ let scrut, param0, x; -//│ scrut = this.s; -//│ if (scrut instanceof this.Some.class) { -//│ param0 = scrut.value; -//│ x = param0; -//│ x +//│ let param01, x1; +//│ if (s instanceof Some1.class) { +//│ param01 = s.value; +//│ x1 = param01; +//│ x1 //│ } else { //│ throw new this.Error("match error"); //│ } @@ -61,12 +60,12 @@ if s is :sjs x => if x is Some(x) then x //│ JS (unsanitized): -//│ (x) => { -//│ let param0, x1; -//│ if (x instanceof this.Some.class) { -//│ param0 = x.value; -//│ x1 = param0; -//│ return x1; +//│ (x3) => { +//│ let param04, x4; +//│ if (x3 instanceof Some1.class) { +//│ param04 = x3.value; +//│ x4 = param04; +//│ return x4; //│ } else { //│ throw new this.Error("match error"); //│ } @@ -120,25 +119,26 @@ fun f(x) = if x is None then "ok" else log("oops") //│ JS (unsanitized): -//│ function f(x) { -//│ let param0, x1, scrut; -//│ if (x instanceof globalThis.Some.class) { -//│ param0 = x.value; -//│ x1 = param0; -//│ scrut = x1 > 0; -//│ if (scrut === true) { +//│ let f9; +//│ f9 = function f(x3) { +//│ let param04, x4, scrut1; +//│ if (x3 instanceof Some1.class) { +//│ param04 = x3.value; +//│ x4 = param04; +//│ scrut1 = x4 > 0; +//│ if (scrut1 === true) { //│ return 42; //│ } else { -//│ return globalThis.log("oops"); +//│ return log1("oops"); //│ } //│ } else { -//│ if (x instanceof globalThis.None.class) { +//│ if (x3 instanceof None1.class) { //│ return "ok"; //│ } else { -//│ return globalThis.log("oops"); +//│ return log1("oops"); //│ } //│ } -//│ } +//│ }; //│ null f(Some(0)) @@ -162,24 +162,25 @@ fun f(x) = if x is Some(u) then u Pair(a, b) then a + b //│ JS (unsanitized): -//│ function f(x) { -//│ let param0, param1, a, b, param01, u; -//│ if (x instanceof globalThis.Some.class) { -//│ param01 = x.value; -//│ u = param01; +//│ let f11; +//│ f11 = function f(x3) { +//│ let param04, param1, a, b, param05, u; +//│ if (x3 instanceof Some1.class) { +//│ param05 = x3.value; +//│ u = param05; //│ return u; //│ } else { -//│ if (x instanceof globalThis.Pair.class) { -//│ param0 = x.fst; -//│ param1 = x.snd; -//│ a = param0; +//│ if (x3 instanceof Pair1.class) { +//│ param04 = x3.fst; +//│ param1 = x3.snd; +//│ a = param04; //│ b = param1; //│ return a + b; //│ } else { //│ throw new globalThis.Error("match error"); //│ } //│ } -//│ } +//│ }; //│ null f(Some(123)) @@ -196,24 +197,25 @@ fun f(x) = log of if x is None then "ok" else "oops" //│ JS (unsanitized): -//│ function f(x) { -//│ let param0, tmp; -//│ if (x instanceof globalThis.Some.class) { -//│ param0 = x.value; -//│ if (param0 === 0) { -//│ tmp = "0"; +//│ let f13; +//│ f13 = function f(x3) { +//│ let param04, tmp11; +//│ if (x3 instanceof Some1.class) { +//│ param04 = x3.value; +//│ if (param04 === 0) { +//│ tmp11 = "0"; //│ } else { -//│ tmp = "oops"; +//│ tmp11 = "oops"; //│ } //│ } else { -//│ if (x instanceof globalThis.None.class) { -//│ tmp = "ok"; +//│ if (x3 instanceof None1.class) { +//│ tmp11 = "ok"; //│ } else { -//│ tmp = "oops"; +//│ tmp11 = "oops"; //│ } //│ } -//│ return globalThis.log(tmp); -//│ } +//│ return log1(tmp11); +//│ }; //│ null f(Some(0)) diff --git a/hkmc2/shared/src/test/mlscript/codegen/Comma.mls b/hkmc2/shared/src/test/mlscript/codegen/Comma.mls index e034dabd8c..ef2745fc47 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Comma.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Comma.mls @@ -7,15 +7,15 @@ fun f() = console.log("ok"), 42 //│ JS (unsanitized): -//│ function f() { let tmp; tmp = globalThis.console.log("ok") ?? null; return 42; } null +//│ let f1; f1 = function f() { let tmp; tmp = globalThis.console.log("ok") ?? null; return 42; }; null fun f() = { console.log("ok"), 42 } //│ JS (unsanitized): -//│ function f() { let tmp; tmp = globalThis.console.log("ok") ?? null; return 42; } null +//│ let f3; f3 = function f() { let tmp; tmp = globalThis.console.log("ok") ?? null; return 42; }; null fun f() = console.log("ok"), 42 //│ JS (unsanitized): -//│ function f() { return globalThis.console.log("ok") ?? null; } 42 +//│ let f5; f5 = function f() { return globalThis.console.log("ok") ?? null; }; 42 //│ = 42 diff --git a/hkmc2/shared/src/test/mlscript/codegen/ConsoleLog.mls b/hkmc2/shared/src/test/mlscript/codegen/ConsoleLog.mls index b5dfe98ba1..c9ee054fe0 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ConsoleLog.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ConsoleLog.mls @@ -22,7 +22,7 @@ console.log("b") let l = console.log l(123) //│ JS (unsanitized): -//│ this.l = this.console.log; this.l(123) ?? null +//│ let l; l = this.console.log; l(123) ?? null //│ > 123 //│ l = [Function: log] @@ -46,14 +46,14 @@ let y = x + 1 console.log("c") y * 2 //│ JS (unsanitized): -//│ let tmp, tmp1, tmp2, tmp3; -//│ tmp = this.console.log("a") ?? null; -//│ this.x = 123; -//│ tmp1 = this.console.log("b") ?? null; -//│ tmp2 = this.x + 1; -//│ this.y = tmp2; -//│ tmp3 = this.console.log("c") ?? null; -//│ this.y * 2 +//│ let x, y, tmp7, tmp8, tmp9, tmp10; +//│ tmp7 = this.console.log("a") ?? null; +//│ x = 123; +//│ tmp8 = this.console.log("b") ?? null; +//│ tmp9 = x + 1; +//│ y = tmp9; +//│ tmp10 = this.console.log("c") ?? null; +//│ y * 2 //│ > a //│ > b //│ > c diff --git a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls index cf67cadfa7..ccd55948db 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls @@ -6,31 +6,31 @@ let x //│ JS (unsanitized): -//│ null +//│ let x; x = undefined; null x = 1 //│ JS (unsanitized): -//│ this.x = 1; null +//│ x = 1; null x //│ JS (unsanitized): -//│ this.x +//│ x //│ = 1 // TODO forbid redefining a let x = 2 //│ JS (unsanitized): -//│ this.x = 2; null +//│ x = 2; null x //│ JS (unsanitized): -//│ this.x +//│ x //│ = 2 let y = 1 //│ JS (unsanitized): -//│ this.y = 1; null +//│ let y; y = 1; null //│ y = 1 :e @@ -44,30 +44,30 @@ z = 1 fun f() = 1 //│ JS (unsanitized): -//│ function f() { return 1; } null +//│ let f1; f1 = function f() { return 1; }; null f //│ JS (unsanitized): -//│ this.f +//│ f1 //│ = [Function: f] let f f(x) = x + 1 //│ JS (unsanitized): -//│ this.f = (x) => { return x + 1; }; null -//│ f = [Function (anonymous)] +//│ let f2; f2 = (x1) => { return x1 + 1; }; null +//│ f = [Function: f2] f(1) //│ JS (unsanitized): -//│ this.f(1) ?? null +//│ f2(1) ?? null //│ = 2 let foo foo = 0 //│ JS (unsanitized): -//│ this.foo = 0; null +//│ let foo; foo = 0; null //│ foo = 0 :fixme @@ -79,15 +79,14 @@ else foo = 1 //│ ║ l.76: then foo = 0 //│ ╙── ^ //│ JS (unsanitized): -//│ let scrut; +//│ let foo1, scrut; +//│ foo1 = undefined; //│ scrut = true; //│ if (scrut === true) { -//│ this.foo +//│ foo1 //│ } else { //│ throw new this.Error("match error"); //│ } -//│ = 0 -//│ foo = 0 let foo if true @@ -96,31 +95,40 @@ then else foo = 1 //│ JS (unsanitized): -//│ let scrut; scrut = true; if (scrut === true) { this.foo = 0; null } else { this.foo = 1; null } +//│ let foo2, scrut1; +//│ foo2 = undefined; +//│ scrut1 = true; +//│ if (scrut1 === true) { +//│ foo2 = 0; +//│ null +//│ } else { +//│ foo2 = 1; +//│ null +//│ } //│ foo = 0 fun f() = foo = 42 //│ JS (unsanitized): -//│ function f() { globalThis.foo = 42; return null; } null +//│ let f4; f4 = function f() { foo2 = 42; return null; }; null f() //│ JS (unsanitized): -//│ this.f() +//│ f4() foo //│ JS (unsanitized): -//│ this.foo +//│ foo2 //│ = 42 :fixme fun f() = foo = 0 //│ ╔══[PARSE ERROR] Expected end of input; found '=' keyword instead -//│ ║ l.119: fun f() = foo = 0 +//│ ║ l.127: fun f() = foo = 0 //│ ╙── ^ //│ JS (unsanitized): -//│ function f() { return globalThis.foo; } null +//│ let f6; f6 = function f() { return foo2; }; null diff --git a/hkmc2/shared/src/test/mlscript/codegen/Do.mls b/hkmc2/shared/src/test/mlscript/codegen/Do.mls index b6e121a241..59334a752e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Do.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Do.mls @@ -24,11 +24,9 @@ do let hello = 1 () -// FIXME shouldn't be accessible -:todo :re print(globalThis.hello) -//│ > 1 +//│ ═══[RUNTIME ERROR] Error: Access to required field 'hello' yielded 'undefined' :ucs desugared val f = case @@ -41,12 +39,12 @@ val f = case //│ Desugared: //│ > if //│ > caseScrut is 0 then "null" -//│ > let $doTemp = globalThis:import#Prelude#666(.)console‹member:console›.log("non-null") +//│ > let $doTemp = member:console#666.log("non-null") //│ > caseScrut is 1 then "unit" //│ > let res = "other" -//│ > let $doTemp = member:Predef#666(.)print‹member:print›(res#666) +//│ > let $doTemp = (member:Predef#666.)print‹member:print›(res#666) //│ > else res#666 -//│ f = [Function: tmp] +//│ f = [Function: tmp3] f(0) //│ = 'null' diff --git a/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls b/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls index e5a45fbbb9..a3ab1d14d2 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls @@ -21,17 +21,18 @@ fun f(x) = return 0 x + 1 //│ JS (unsanitized): -//│ function f(x) { +//│ let f3; +//│ f3 = function f(x) { //│ let scrut, tmp, tmp1; //│ scrut = x < 0; //│ if (scrut === true) { -//│ tmp = globalThis.log("whoops"); +//│ tmp = log1("whoops"); //│ return 0; //│ } else { //│ tmp1 = null; //│ } //│ return x + 1; -//│ } +//│ }; //│ null f(1) diff --git a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls index 2bdf0d8c20..c2a606605a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls @@ -13,9 +13,7 @@ Foo(42).x //│ stats = Nil //│ res = Sel: //│ prefix = App: -//│ lhs = SynthSel{member:Foo}: -//│ prefix = Ref of globalThis:block#1 -//│ nme = Ident of "Foo" +//│ lhs = Ref of member:Foo //│ rhs = Tup of Ls of //│ Fld: //│ flags = () @@ -35,7 +33,7 @@ foo.x //│ Blk: //│ stats = Nil //│ res = Sel: -//│ prefix = Ref of globalThis:block#3.foo +//│ prefix = Ref of foo //│ nme = Ident of "x" //│ = 42 @@ -47,7 +45,7 @@ foo.y //│ Blk: //│ stats = Nil //│ res = Sel: -//│ prefix = Ref of globalThis:block#3.foo +//│ prefix = Ref of foo //│ nme = Ident of "y" //│ = 42 @@ -77,9 +75,7 @@ case //│ pattern = ClassLike: //│ sym = class:Foo //│ trm = SynthSel{class:Foo}: -//│ prefix = SynthSel{member:Foo}: -//│ prefix = Ref of globalThis:block#1 -//│ nme = Ident of "Foo" +//│ prefix = Ref of member:Foo //│ nme = Ident of "class" //│ parameters = S of Ls of //│ $param0 @@ -109,9 +105,7 @@ case //│ _1 = Cls: //│ cls = class:Foo //│ path = Select{class:Foo}: -//│ qual = Select{member:Foo}: -//│ qual = Ref of globalThis:block#1 -//│ name = Ident of "Foo" +//│ qual = Ref of member:Foo //│ name = Ident of "class" //│ _2 = Assign: //│ lhs = $param0 diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls index eb3cc97dcf..33f3d560a9 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls @@ -22,13 +22,15 @@ fun test(a) = h(d) Inner(42) //│ JS (unsanitized): -//│ function test(a) { -//│ function Inner(b1) { return new Inner.class(b1); } +//│ let test3; +//│ test3 = function test(a) { +//│ let Inner; +//│ Inner = function Inner(b1) { return new Inner.class(b1); }; //│ Inner.class = class Inner { //│ constructor(b) { //│ this.b = b; //│ let tmp; -//│ tmp = globalThis.log(a); +//│ tmp = log1(a); //│ } //│ f(c) { //│ return [ @@ -38,21 +40,22 @@ fun test(a) = //│ ]; //│ } //│ g(d) { +//│ let h; //│ const this$Inner = this; -//│ function h(e) { +//│ h = function h(e) { //│ return [ //│ a, //│ this$Inner.b, //│ d, //│ e //│ ]; -//│ } +//│ }; //│ return h(d); //│ } //│ toString() { return "Inner(" + this.b + ")"; } //│ }; //│ return Inner(42); -//│ } +//│ }; //│ null let i = test(100) @@ -74,9 +77,10 @@ fun test(a) = print of [a, b] [C1(1), C2(2)] //│ JS (unsanitized): -//│ function test(a) { -//│ let tmp, tmp1; -//│ function C1(b1) { return new C1.class(b1); } +//│ let test5; +//│ test5 = function test(a) { +//│ let C1, C2, tmp1, tmp2; +//│ C1 = function C1(b1) { return new C1.class(b1); }; //│ C1.class = class C1 { //│ constructor(b) { //│ this.b = b; @@ -87,7 +91,7 @@ fun test(a) = //│ } //│ toString() { return "C1(" + this.b + ")"; } //│ }; -//│ function C2(b1) { return new C2.class(b1); } +//│ C2 = function C2(b1) { return new C2.class(b1); }; //│ C2.class = class C2 { //│ constructor(b) { //│ this.b = b; @@ -98,13 +102,13 @@ fun test(a) = //│ } //│ toString() { return "C2(" + this.b + ")"; } //│ }; -//│ tmp = C1(1); -//│ tmp1 = C2(2); +//│ tmp1 = C1(1); +//│ tmp2 = C2(2); //│ return [ -//│ tmp, -//│ tmp1 +//│ tmp1, +//│ tmp2 //│ ]; -//│ } +//│ }; //│ null test(123) @@ -123,27 +127,28 @@ class Foo(a) with foo() Foo(123) //│ JS (unsanitized): -//│ this.Foo = function Foo(a1) { return new Foo.class(a1); }; -//│ this.Foo.class = class Foo { +//│ let Foo1; +//│ Foo1 = function Foo(a1) { return new Foo.class(a1); }; +//│ Foo1.class = class Foo { //│ constructor(a) { //│ this.a = a; //│ this.foo() //│ } //│ foo() { -//│ let tmp; +//│ let bar, baz, tmp1; //│ const this$Foo = this; -//│ function bar() { +//│ bar = function bar() { //│ return this$Foo.a; -//│ } -//│ function baz() { +//│ }; +//│ baz = function baz() { //│ return this$Foo.a; -//│ } -//│ tmp = bar(); +//│ }; +//│ tmp1 = bar(); //│ return baz(); //│ } //│ toString() { return "Foo(" + this.a + ")"; } //│ }; -//│ this.Foo(123) +//│ Foo1(123) //│ = Foo { a: 123 } :sjs @@ -155,24 +160,26 @@ class Bar(x) with foo()() Bar(1) //│ JS (unsanitized): -//│ this.Bar = function Bar(x1) { return new Bar.class(x1); }; -//│ this.Bar.class = class Bar { +//│ let Bar1; +//│ Bar1 = function Bar(x1) { return new Bar.class(x1); }; +//│ Bar1.class = class Bar { //│ constructor(x) { //│ this.x = x; -//│ let tmp; -//│ tmp = this.foo(); -//│ tmp() ?? null +//│ let tmp1; +//│ tmp1 = this.foo(); +//│ tmp1() ?? null //│ } //│ foo() { //│ return () => { +//│ let bar; //│ const this$Bar = this; -//│ function bar() { +//│ bar = function bar() { //│ return this$Bar.x; -//│ } +//│ }; //│ return bar(); //│ }; //│ } //│ toString() { return "Bar(" + this.x + ")"; } //│ }; -//│ this.Bar(1) +//│ Bar1(1) //│ = Bar { x: 1 } diff --git a/hkmc2/shared/src/test/mlscript/codegen/Functions.mls b/hkmc2/shared/src/test/mlscript/codegen/Functions.mls index c5290e3419..f269fc68cb 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Functions.mls @@ -41,11 +41,19 @@ r //│ = 42 //│ r = 42 -// Note that this initializes fine +// * This fails to properly initialize, as expected fun foo() = bar() let r = foo() fun bar() = r -//│ r = 42 + +// * Similar +:re +object Outer with + fun foo() = bar() + val r = foo() + fun bar() = r +Outer.r +//│ ═══[RUNTIME ERROR] Error: Access to required field 'r' yielded 'undefined' fun outerfun(x) = @@ -78,7 +86,7 @@ outerfun(100)(200) :showRepl fun test1(x) = test2(x) fun test2(y) = y + 1 -//│ REPL> Sending: try { function test1(...args) { globalThis.Predef.checkArgs("test1", 1, true, args.length); let x = args[0]; return globalThis.test2(x); } function test2(...args) { globalThis.Predef.checkArgs("test2", 1, true, args.length); let y = args[0]; return y + 1; } null } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } +//│ REPL> Sending: let test21, test11;try { test11 = function test1(...args) { globalThis.Predef.checkArgs("test1", 1, true, args.length); let x = args[0]; return test21(x); }; test21 = function test2(...args) { globalThis.Predef.checkArgs("test2", 1, true, args.length); let y = args[0]; return y + 1; }; null } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } //│ REPL> Collected: //│ > null //│ REPL> Parsed: diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunctionsThis.mls b/hkmc2/shared/src/test/mlscript/codegen/FunctionsThis.mls index a1da1cee9d..7eb5133d8b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunctionsThis.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunctionsThis.mls @@ -9,18 +9,19 @@ val x = 2 fun foo() = x + 1 //│ JS (unsanitized): -//│ this.x = 2; function foo() { return globalThis.x + 1; } null +//│ let foo1, x1; foo1 = function foo() { return x1 + 1; }; x1 = 2; null //│ x = 2 :sjs class Test with log(foo()) //│ JS (unsanitized): -//│ this.Test = class Test { +//│ let Test1; +//│ Test1 = class Test { //│ constructor() { //│ let tmp; -//│ tmp = globalThis.foo(); -//│ globalThis.log(tmp) +//│ tmp = foo1(); +//│ log1(tmp) //│ } //│ toString() { return "Test"; } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls b/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls index e151adc67e..42b449d854 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls @@ -8,9 +8,9 @@ module Mod with :ctx open Mod //│ Env: -//│ pipe -> SelElem(SelElem(RefElem(globalThis:block#1),Mod,Some(member:Mod)),pipe,Some(member:pipe)) -//│ print -> SelElem(SelElem(RefElem(globalThis:block#1),Mod,Some(member:Mod)),print,Some(member:print)) -//│ |> -> SelElem(SelElem(RefElem(globalThis:block#1),Mod,Some(member:Mod)),pipe,Some(member:pipe)) +//│ pipe -> SelElem(RefElem(member:Mod),pipe,Some(member:pipe)) +//│ print -> SelElem(RefElem(member:Mod),print,Some(member:print)) +//│ |> -> SelElem(RefElem(member:Mod),pipe,Some(member:pipe)) pipe //│ = [Function: pipe] @@ -62,6 +62,6 @@ open Mod { print, |> } :fixme 12 |> print -//│ ═══[RUNTIME ERROR] TypeError: this.Mod.|> is not a function +//│ ═══[RUNTIME ERROR] TypeError: Mod1.|> is not a function diff --git a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls index e2dd44abef..d9d5ef835a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls @@ -4,17 +4,15 @@ :sjs fun t = 42 //│ JS (unsanitized): -//│ function t() { return 42; } null +//│ let t1; t1 = function t() { return 42; }; null -:fixme // TODO fix discrepancy in codegen for such functions :expect 42 :sjs t //│ JS (unsanitized): -//│ this.t -//│ ═══[RUNTIME ERROR] Expected: 42, got: [Function: t] -//│ = [Function: t] +//│ let tmp; tmp = t1(); tmp +//│ = 42 fun main() = @@ -39,17 +37,18 @@ fun test() = 42 whoops + whoops //│ JS (unsanitized): -//│ function test() { -//│ let tmp, tmp1; -//│ function whoops() { -//│ let tmp2; -//│ tmp2 = Predef.print("ok"); +//│ let test1; +//│ test1 = function test() { +//│ let whoops, tmp1, tmp2; +//│ whoops = function whoops() { +//│ let tmp3; +//│ tmp3 = Predef.print("ok"); //│ return 42; -//│ } -//│ tmp = whoops(); +//│ }; //│ tmp1 = whoops(); -//│ return tmp + tmp1; -//│ } +//│ tmp2 = whoops(); +//│ return tmp1 + tmp2; +//│ }; //│ null test() @@ -67,6 +66,7 @@ module T with val c = p val d = this.p //│ JS (unsanitized): +//│ let T1; //│ const T$class = class T { //│ constructor() { //│ this.a = this.t; @@ -81,9 +81,8 @@ module T with //│ return 2; //│ } //│ toString() { return "T"; } -//│ }; -//│ this.T = new T$class; -//│ this.T.class = T$class; +//│ }; T1 = new T$class; +//│ T1.class = T$class; //│ null @@ -104,22 +103,22 @@ T.d module M with fun t = 0 //│ JS (unsanitized): +//│ let M1; //│ const M$class = class M { //│ constructor() {} //│ get t() { //│ return 0; //│ } //│ toString() { return "M"; } -//│ }; -//│ this.M = new M$class; -//│ this.M.class = M$class; +//│ }; M1 = new M$class; +//│ M1.class = M$class; //│ null :sjs M.t //│ JS (unsanitized): -//│ this.M.t +//│ M1.t //│ = 0 @@ -128,7 +127,16 @@ fun test() = fun whoops = 42 whoops //│ JS (unsanitized): -//│ function test() { let tmp; function whoops() { return 42; } tmp = whoops(); return tmp; } null +//│ let test3; +//│ test3 = function test() { +//│ let whoops, tmp1; +//│ whoops = function whoops() { +//│ return 42; +//│ }; +//│ tmp1 = whoops(); +//│ return tmp1; +//│ }; +//│ null :re @@ -150,7 +158,7 @@ fun bar() = fun baz() = 42 baz //│ JS (unsanitized): -//│ function bar() { function baz() { return 42; } return baz; } null +//│ let bar1; bar1 = function bar() { let baz; baz = function baz() { return 42; }; return baz; }; null :sjs @@ -159,22 +167,24 @@ fun baz() = fun z = 2 (x, y) => x + y + w + z //│ JS (unsanitized): -//│ function baz() { -//│ function w() { +//│ let baz1; +//│ baz1 = function baz() { +//│ let w, z; +//│ w = function w() { //│ return 1; -//│ } -//│ function z() { +//│ }; +//│ z = function z() { //│ return 2; -//│ } +//│ }; //│ return (x, y) => { -//│ let tmp, tmp1, tmp2, tmp3; -//│ tmp = x + y; -//│ tmp1 = w(); -//│ tmp2 = tmp + tmp1; -//│ tmp3 = z(); -//│ return tmp2 + tmp3; +//│ let tmp1, tmp2, tmp3, tmp4; +//│ tmp1 = x + y; +//│ tmp2 = w(); +//│ tmp3 = tmp1 + tmp2; +//│ tmp4 = z(); +//│ return tmp3 + tmp4; //│ }; -//│ } +//│ }; //│ null @@ -190,21 +200,23 @@ fun a() = b + d c //│ JS (unsanitized): -//│ function a() { -//│ function b() { +//│ let a1; +//│ a1 = function a() { +//│ let b, c; +//│ b = function b() { //│ return 1; -//│ } -//│ function c() { -//│ let tmp, tmp1; -//│ function d() { +//│ }; +//│ c = function c() { +//│ let d, tmp2, tmp3; +//│ d = function d() { //│ return 2; -//│ } -//│ tmp = b(); -//│ tmp1 = d(); -//│ return tmp + tmp1; -//│ } +//│ }; +//│ tmp2 = b(); +//│ tmp3 = d(); +//│ return tmp2 + tmp3; +//│ }; //│ return c; -//│ } +//│ }; //│ null @@ -220,20 +232,22 @@ fun b() = c d //│ JS (unsanitized): -//│ function b() { -//│ function c() { +//│ let b1; +//│ b1 = function b() { +//│ let c, d; +//│ c = function c() { //│ return 1; -//│ } -//│ function d() { -//│ let tmp; -//│ function c() { +//│ }; +//│ d = function d() { +//│ let c1, tmp3; +//│ c1 = function c() { //│ return 2; -//│ } -//│ tmp = c(); -//│ return tmp; -//│ } +//│ }; +//│ tmp3 = c1(); +//│ return tmp3; +//│ }; //│ return d; -//│ } +//│ }; //│ null @@ -249,23 +263,24 @@ fun c() = e + f d //│ JS (unsanitized): -//│ function c() { -//│ let tmp; -//│ function f() { +//│ let c1; +//│ c1 = function c() { +//│ let d, f, tmp4; +//│ f = function f() { //│ return 1; -//│ } -//│ function d() { -//│ let tmp1, tmp2; -//│ function e() { +//│ }; +//│ d = function d() { +//│ let e, tmp5, tmp6; +//│ e = function e() { //│ return 1; -//│ } -//│ tmp1 = e(); -//│ tmp2 = f(); -//│ return tmp1 + tmp2; -//│ } -//│ tmp = d(); -//│ return tmp; -//│ } +//│ }; +//│ tmp5 = e(); +//│ tmp6 = f(); +//│ return tmp5 + tmp6; +//│ }; +//│ tmp4 = d(); +//│ return tmp4; +//│ }; //│ null @@ -277,8 +292,9 @@ c() class Foo(x) with fun oops = x //│ JS (unsanitized): -//│ this.Foo = function Foo(x1) { return new Foo.class(x1); }; -//│ this.Foo.class = class Foo { +//│ let Foo1; +//│ Foo1 = function Foo(x1) { return new Foo.class(x1); }; +//│ Foo1.class = class Foo { //│ constructor(x) { //│ this.x = x; //│ } @@ -309,6 +325,6 @@ foo(Foo(42)) :re foo(Foo(42))() -//│ ═══[RUNTIME ERROR] TypeError: tmp1 is not a function +//│ ═══[RUNTIME ERROR] TypeError: tmp7 is not a function diff --git a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls index 6ed62b0c3e..9b74eaad3a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls @@ -29,16 +29,17 @@ fun foo() = if false then 0 foo() //│ JS (unsanitized): -//│ function foo() { -//│ let scrut; -//│ scrut = false; -//│ if (scrut === true) { +//│ let foo1; +//│ foo1 = function foo() { +//│ let scrut1; +//│ scrut1 = false; +//│ if (scrut1 === true) { //│ return 0; //│ } else { //│ throw new globalThis.Error("match error"); //│ } -//│ } -//│ this.foo() +//│ }; +//│ foo1() //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'checkArgs') diff --git a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls index 2767cd5064..91f20bd80e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls @@ -1,51 +1,63 @@ :js +// * Note how we avoid the name clash between the internal name `Test` of the class +// * and the external name `Test1` of its singleton instance. :sjs -module Test with +object Test with val x = 12 - fun foo() = Test.x + fun foo() = + print(Test) + Test.x //│ JS (unsanitized): +//│ let Test1; //│ const Test$class = class Test { //│ constructor() { //│ this.x = 12; //│ } //│ foo() { -//│ return globalThis.Test.x; +//│ let tmp; +//│ tmp = Predef.print(Test1); +//│ return Test1.x; //│ } //│ toString() { return "Test"; } -//│ }; -//│ this.Test = new Test$class; -//│ this.Test.class = Test$class; +//│ }; Test1 = new Test$class; +//│ Test1.class = Test$class; //│ null +Test +//│ = Test { x: 12, class: [class Test] } + +print(Test) +//│ > Test + :sjs Test.foo() //│ JS (unsanitized): -//│ this.Test.foo() +//│ Test1.foo() +//│ > Test //│ = 12 :sjs val Test = "oops" //│ JS (unsanitized): -//│ this.Test = "oops"; null +//│ let Test3; Test3 = "oops"; null //│ Test = 'oops' -:fixme +:re Test.foo() -//│ ═══[RUNTIME ERROR] TypeError: this.Test.foo is not a function +//│ ═══[RUNTIME ERROR] TypeError: Test3.foo is not a function -// FIXME :sjs let x = 1 let f = () => x let x = 2 f() //│ JS (unsanitized): -//│ this.x = 1; this.f = () => { return this.x; }; this.x = 2; this.f() ?? null -//│ = 2 -//│ f = [Function (anonymous)] +//│ let x, f, x1; x = 1; f = () => { return x; }; x1 = 2; f() ?? null +//│ = 1 +//│ f = [Function: f] //│ x = 2 @@ -54,29 +66,42 @@ module Test with val x = 1 let x = 2 //│ JS (unsanitized): -//│ const Test$class = class Test { +//│ let Test5; +//│ const Test$class1 = class Test { //│ #x; //│ constructor() { //│ this.x = 1; //│ this.#x = 2; //│ } //│ toString() { return "Test"; } -//│ }; -//│ this.Test = new Test$class; -//│ this.Test.class = Test$class; +//│ }; Test5 = new Test$class1; +//│ Test5.class = Test$class1; //│ null Test.x //│ = 1 + :fixme module Test with let x = 1 let f = () => x let x = 2 log(f()) -//│ > try { const Test$class = class Test { #x; #f; #x; constructor() { let tmp; this.#x = 1; this.#f = (...args) => { globalThis.Predef.checkArgs("", 0, true, args.length); return this.#x; }; this.#x = 2; tmp = this.#f() ?? null; globalThis.log(tmp) ?? null } toString() { return "Test"; } }; this.Test = new Test$class; this.Test.class = Test$class; null } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^ +//│ > let Test7;try { const Test$class2 = class Test { #x; #f; #x; constructor() { let selRes1, tmp, tmp1; this.#x = 1; this.#f = (...args) => { globalThis.Predef.checkArgs("", 0, true, args.length); return this.#x; }; this.#x = 2; selRes1 = globalThis.log; if (selRes1 === undefined) { throw new globalThis.Error("Access to required field 'log' yielded 'undefined'"); } else { tmp = selRes1; } tmp1 = this.#f() ?? null; tmp(tmp1) ?? null } toString() { return "Test"; } }; Test7 = new Test$class2; Test7.class = Test$class2; null } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ > ^ //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Identifier '#x' has already been declared +fun foo() = + if false then + module A + A + else + module A + A + +foo() +//│ = A { class: [class A] } + + diff --git a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls index 6127b48aca..8b2513a089 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls @@ -12,8 +12,8 @@ if true then 1 else 0 :sjs let f = x => if x then log("ok") else log("ko") //│ JS (unsanitized): -//│ this.f = (x) => { if (x === true) { return this.log("ok"); } else { return this.log("ko"); } }; null -//│ f = [Function (anonymous)] +//│ let f; f = (x) => { if (x === true) { return log1("ok"); } else { return log1("ko"); } }; null +//│ f = [Function: f] f(true) //│ > ok @@ -25,7 +25,7 @@ f(false) :sjs let f = x => log((if x then "ok" else "ko") + "!") //│ JS (unsanitized): -//│ let tmp; +//│ let f1, tmp; //│ tmp = (x) => { //│ let tmp1, tmp2; //│ if (x === true) { @@ -34,14 +34,33 @@ let f = x => log((if x then "ok" else "ko") + "!") //│ tmp1 = "ko"; //│ } //│ tmp2 = tmp1 + "!"; -//│ return this.log(tmp2); +//│ return log1(tmp2); //│ }; -//│ this.f = tmp; +//│ f1 = tmp; //│ null //│ f = [Function: tmp] :sjs let f = x => log((if x and x then "ok" else "ko") + "!") +//│ JS (unsanitized): +//│ let f2, tmp1; +//│ tmp1 = (x) => { +//│ let tmp2, tmp3; +//│ if (x === true) { +//│ if (x === true) { +//│ tmp2 = "ok"; +//│ } else { +//│ tmp2 = "ko"; +//│ } +//│ } else { +//│ tmp2 = "ko"; +//│ } +//│ tmp3 = tmp2 + "!"; +//│ return log1(tmp3); +//│ }; +//│ f2 = tmp1; +//│ null +//│ f = [Function: tmp1] // --- TODO: What we want --- // this.f = (x) => { // let tmp, tmp1, flag; @@ -62,25 +81,6 @@ let f = x => log((if x and x then "ok" else "ko") + "!") // }; // undefined // f = [Function (anonymous)] -//│ JS (unsanitized): -//│ let tmp; -//│ tmp = (x) => { -//│ let tmp1, tmp2; -//│ if (x === true) { -//│ if (x === true) { -//│ tmp1 = "ok"; -//│ } else { -//│ tmp1 = "ko"; -//│ } -//│ } else { -//│ tmp1 = "ko"; -//│ } -//│ tmp2 = tmp1 + "!"; -//│ return this.log(tmp2); -//│ }; -//│ this.f = tmp; -//│ null -//│ f = [Function: tmp] f(true) //│ > ok! diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportExample.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportExample.mls index 62d7461b53..c4a3809e3a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportExample.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportExample.mls @@ -18,7 +18,7 @@ let n = 42 :sjs n / 2 //│ JS (unsanitized): -//│ this.n / 2 +//│ n / 2 //│ = 21 @@ -36,7 +36,7 @@ inc / 2 :re n / 2 //│ JS (unsanitized): -//│ Example.funnySlash(this.n, 2) +//│ Example.funnySlash(n, 2) //│ ═══[RUNTIME ERROR] TypeError: f is not a function funnySlash(inc, 123) diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls index b0919898e3..6c13c60bd7 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls @@ -43,17 +43,17 @@ Some(1) :sjs (new Some(1)) isDefined() //│ JS (unsanitized): -//│ let tmp; tmp = new Option.Some.class(1); Option.isDefined(tmp) +//│ let tmp4; tmp4 = new Option.Some.class(1); Option.isDefined(tmp4) //│ = true :re new Some(1) isDefined() -//│ ═══[RUNTIME ERROR] TypeError: tmp1 is not a constructor +//│ ═══[RUNTIME ERROR] TypeError: tmp6 is not a constructor :re new Some(1) isDefined() -//│ ═══[RUNTIME ERROR] TypeError: tmp1 is not a constructor +//│ ═══[RUNTIME ERROR] TypeError: tmp8 is not a constructor open Option { Some, None, isDefined } diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls index cd2acece04..5bb59e0286 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls @@ -60,6 +60,6 @@ new Option.Some(1) String() //│ rhs = App: //│ lhs = Ident of "String" //│ rhs = Tup of Nil -//│ ═══[RUNTIME ERROR] TypeError: tmp1 is not a constructor +//│ ═══[RUNTIME ERROR] TypeError: tmp18 is not a constructor diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls index c551c63a64..500b17e7eb 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls @@ -11,19 +11,20 @@ fun foo() = "a" ~ "b" ~ "c" foo() //│ JS (unsanitized): -//│ function foo() { +//│ let foo1; +//│ foo1 = function foo() { //│ let tmp; -//│ tmp = globalThis.M.concat("a", "b"); -//│ return globalThis.M.concat(tmp, "c"); -//│ } -//│ this.foo() +//│ tmp = M1.concat("a", "b"); +//│ return M1.concat(tmp, "c"); +//│ }; +//│ foo1() //│ = 'abc' let name = "_" //│ name = '_' let display(balance) = balance -//│ display = [Function (anonymous)] +//│ display = [Function: display] "b" ~ display("-") //│ = 'b-' diff --git a/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls b/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls index aa9cebdae6..cc3dfe1e42 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls @@ -15,7 +15,7 @@ x => :todo (acc, _) => acc //│ JS (unsanitized): -//│ (acc, _) => { return acc; } +//│ (acc, $_) => { return acc; } //│ = [Function (anonymous)] diff --git a/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls b/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls index e9852a5cf2..8cd98b2754 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls @@ -7,6 +7,7 @@ object Example with val a = 456 fun f(x) = [x, a] //│ JS (unsanitized): +//│ let Example1; //│ const Example$class = class Example { //│ constructor() { //│ this.a = 456; @@ -18,16 +19,16 @@ object Example with //│ ]; //│ } //│ toString() { return "Example"; } -//│ }; -//│ this.Example = new Example$class; -//│ this.Example.class = Example$class; +//│ }; Example1 = new Example$class; +//│ Example1.class = Example$class; //│ null let s = Example.f //│ s = [Function: f] +:re s(123) -//│ = [ 123, undefined ] +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'a') Example |>. s(123) //│ = [ 123, 456 ] diff --git a/hkmc2/shared/src/test/mlscript/codegen/Modules.mls b/hkmc2/shared/src/test/mlscript/codegen/Modules.mls index a6f870de8c..cdb2089ed8 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Modules.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Modules.mls @@ -7,31 +7,31 @@ :sjs module None //│ JS (unsanitized): +//│ let None1; //│ const None$class = class None { //│ constructor() {} //│ toString() { return "None"; } -//│ }; -//│ this.None = new None$class; -//│ this.None.class = None$class; +//│ }; None1 = new None$class; +//│ None1.class = None$class; //│ null :sjs None //│ JS (unsanitized): -//│ this.None +//│ None1 //│ = None { class: [class None] } :sjs :re None() //│ JS (unsanitized): -//│ this.None() ?? null -//│ ═══[RUNTIME ERROR] TypeError: this.None is not a function +//│ None1() ?? null +//│ ═══[RUNTIME ERROR] TypeError: None1 is not a function :sjs new None //│ JS (unsanitized): -//│ new this.None.class() +//│ new None1.class() //│ = None {} @@ -42,6 +42,7 @@ module M with val x = 1 val y = x + 1 //│ JS (unsanitized): +//│ let M1; //│ const M$class = class M { //│ constructor() { //│ let tmp; @@ -59,9 +60,8 @@ module M with //│ this.y = tmp; //│ } //│ toString() { return "M"; } -//│ }; -//│ this.M = new M$class; -//│ this.M.class = M$class; +//│ }; M1 = new M$class; +//│ M1.class = M$class; //│ null M.C @@ -88,33 +88,32 @@ M.oops //│ ═══[RUNTIME ERROR] Error: Access to required field 'oops' yielded 'undefined' +// FIXME report initialization problem or make it work +:todo +:e :sjs module M with val m: module M = M //│ JS (unsanitized): -//│ const M$class = class M { +//│ let M3; +//│ const M$class1 = class M { //│ constructor() { -//│ this.m = globalThis.M; +//│ this.m = M3; //│ } //│ toString() { return "M"; } -//│ }; -//│ this.M = new M$class; -//│ this.M.class = M$class; +//│ }; M3 = new M$class1; +//│ M3.class = M$class1; //│ null +:re // FIXME +:sjs M.m -//│ > M { -//│ > C: [class C], -//│ > D: [Function: D] { class: [class D] }, -//│ > x: 1, -//│ > y: 2, -//│ > class: [class M] -//│ = } +//│ JS (unsanitized): +//│ M3.m +//│ ═══[RUNTIME ERROR] Error: Access to required field 'm' yielded 'undefined' -:todo module M() -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed diff --git a/hkmc2/shared/src/test/mlscript/codegen/NestedClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/NestedClasses.mls new file mode 100644 index 0000000000..c8f0ed9086 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/codegen/NestedClasses.mls @@ -0,0 +1,13 @@ +:js + + +module Foo with + class C(x) + +Foo.C(123).x +//│ = 123 + +(new Foo.C(123)).x +//│ = 123 + + diff --git a/hkmc2/shared/src/test/mlscript/codegen/OpenWildcard.mls b/hkmc2/shared/src/test/mlscript/codegen/OpenWildcard.mls index 1e66663ea9..70d0aebded 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/OpenWildcard.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/OpenWildcard.mls @@ -47,20 +47,19 @@ none() //│ = None { class: [class None] } -// * `open` statements are currently unhygienic and are affected by redefinitions +// * `open` statements are hygienic and are not affected by redefinitions :sjs val Option = "Oops" //│ JS (unsanitized): -//│ this.Option = "Oops"; null +//│ let Option2; Option2 = "Oops"; null //│ Option = 'Oops' -:fixme :sjs Some(123) //│ JS (unsanitized): //│ Option.Some(123) -//│ ═══[RUNTIME ERROR] TypeError: Option.Some is not a function +//│ = Some { value: 123 } module Option with val None = 123 @@ -74,11 +73,12 @@ open Option Some //│ JS (unsanitized): //│ Option.Some +//│ = [Function: Some] { class: [class Some] } :sjs None //│ JS (unsanitized): -//│ this.Option.None +//│ Option4.None //│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls b/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls index d47cc23407..65bd360b5f 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls @@ -10,17 +10,18 @@ fun isDefined(x) = if x is Some then true None then false //│ JS (unsanitized): -//│ function isDefined(x) { -//│ if (x instanceof globalThis.Some.class) { +//│ let isDefined1; +//│ isDefined1 = function isDefined(x) { +//│ if (x instanceof Some1.class) { //│ return true; //│ } else { -//│ if (x instanceof globalThis.None.class) { +//│ if (x instanceof None1.class) { //│ return false; //│ } else { //│ throw new globalThis.Error("match error"); //│ } //│ } -//│ } +//│ }; //│ null isDefined(Some(1)) @@ -35,23 +36,23 @@ val isDefined = case Some(_) then true None then false //│ JS (unsanitized): -//│ let tmp; -//│ tmp = (caseScrut) => { +//│ let isDefined3, tmp1; +//│ tmp1 = (caseScrut) => { //│ let param0; -//│ if (caseScrut instanceof this.Some.class) { +//│ if (caseScrut instanceof Some1.class) { //│ param0 = caseScrut.value; //│ return true; //│ } else { -//│ if (caseScrut instanceof this.None.class) { +//│ if (caseScrut instanceof None1.class) { //│ return false; //│ } else { //│ throw new this.Error("match error"); //│ } //│ } //│ }; -//│ this.isDefined = tmp; +//│ isDefined3 = tmp1; //│ null -//│ isDefined = [Function: tmp] +//│ isDefined = [Function: tmp1] isDefined(Some(1)) //│ = true @@ -63,7 +64,7 @@ isDefined(None) val isDefined = x => if x is Some(_) then true None then false -//│ isDefined = [Function: tmp] +//│ isDefined = [Function: tmp3] isDefined(Some(1)) //│ = true @@ -78,7 +79,7 @@ module Foo with val isOther = x => if x is Foo.Other(_) then true None then false -//│ isOther = [Function: tmp] +//│ isOther = [Function: tmp5] fun keepIfGreaterThan(x, y) = diff --git a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls index 9ee8798740..6051433ee2 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls @@ -9,8 +9,9 @@ class Foo() //│ JS (unsanitized): -//│ this.Foo = function Foo() { return new Foo.class(); }; -//│ this.Foo.class = class Foo { +//│ let Foo1; +//│ Foo1 = function Foo() { return new Foo.class(); }; +//│ Foo1.class = class Foo { //│ constructor() {} //│ toString() { return "Foo(" + + ")"; } //│ }; @@ -18,24 +19,25 @@ class Foo() Foo //│ JS (unsanitized): -//│ this.Foo +//│ Foo1 //│ = [Function: Foo] { class: [class Foo] } Foo() //│ JS (unsanitized): -//│ this.Foo() +//│ Foo1() //│ = Foo {} Foo.class //│ JS (unsanitized): -//│ this.Foo.class +//│ Foo1.class //│ = [class Foo] class Foo(a) //│ JS (unsanitized): -//│ this.Foo = function Foo(a1) { return new Foo.class(a1); }; -//│ this.Foo.class = class Foo { +//│ let Foo3; +//│ Foo3 = function Foo(a1) { return new Foo.class(a1); }; +//│ Foo3.class = class Foo { //│ constructor(a) { //│ this.a = a; //│ } @@ -45,30 +47,31 @@ class Foo(a) Foo //│ JS (unsanitized): -//│ this.Foo +//│ Foo3 //│ = [Function: Foo] { class: [class Foo] } Foo(1) //│ JS (unsanitized): -//│ this.Foo(1) +//│ Foo3(1) //│ = Foo { a: 1 } Foo(1).a //│ JS (unsanitized): -//│ let tmp; tmp = this.Foo(1); tmp.a +//│ let tmp; tmp = Foo3(1); tmp.a //│ = 1 fun foo(y) = Foo(y) foo(27) //│ JS (unsanitized): -//│ function foo(y) { return globalThis.Foo(y); } this.foo(27) +//│ let foo1; foo1 = function foo(y) { return Foo3(y); }; foo1(27) //│ = Foo { a: 27 } class Foo(a, b) //│ JS (unsanitized): -//│ this.Foo = function Foo(a1, b1) { return new Foo.class(a1, b1); }; -//│ this.Foo.class = class Foo { +//│ let Foo5; +//│ Foo5 = function Foo(a1, b1) { return new Foo.class(a1, b1); }; +//│ Foo5.class = class Foo { //│ constructor(a, b) { //│ this.a = a; //│ this.b = b; @@ -79,47 +82,47 @@ class Foo(a, b) let foo = Foo //│ JS (unsanitized): -//│ this.foo = this.Foo; null +//│ let foo2; foo2 = Foo5; null //│ foo = [Function: Foo] { class: [class Foo] } let f = foo(1, 2) //│ JS (unsanitized): -//│ let tmp; tmp = this.foo(1, 2) ?? null; this.f = tmp; null +//│ let f, tmp1; tmp1 = foo2(1, 2) ?? null; f = tmp1; null //│ f = Foo { a: 1, b: 2 } let f = new foo(1, 2) //│ JS (unsanitized): -//│ let tmp; tmp = new this.foo(1, 2); this.f = tmp; null +//│ let f1, tmp2; tmp2 = new foo2(1, 2); f1 = tmp2; null //│ f = Foo { a: 1, b: 2 } f.a //│ JS (unsanitized): -//│ this.f.a +//│ f1.a //│ = 1 f.b //│ JS (unsanitized): -//│ this.f.b +//│ f1.b //│ = 2 let f = Foo(1, 2) //│ JS (unsanitized): -//│ let tmp; tmp = this.Foo(1, 2); this.f = tmp; null +//│ let f2, tmp3; tmp3 = Foo5(1, 2); f2 = tmp3; null //│ f = Foo { a: 1, b: 2 } f.a //│ JS (unsanitized): -//│ this.f.a +//│ f2.a //│ = 1 f.b //│ JS (unsanitized): -//│ this.f.b +//│ f2.b //│ = 2 Foo(log(1), log(2)) //│ JS (unsanitized): -//│ let tmp, tmp1; tmp = this.log(1); tmp1 = this.log(2); this.Foo(tmp, tmp1) +//│ let tmp4, tmp5; tmp4 = log1(1); tmp5 = log1(2); Foo5(tmp4, tmp5) //│ > 1 //│ > 2 //│ = Foo { a: null, b: null } @@ -129,11 +132,12 @@ class Inner(c) with fun i1(d) = c + d log(c) //│ JS (unsanitized): -//│ this.Inner = function Inner(c1) { return new Inner.class(c1); }; -//│ this.Inner.class = class Inner { +//│ let Inner1; +//│ Inner1 = function Inner(c1) { return new Inner.class(c1); }; +//│ Inner1.class = class Inner { //│ constructor(c) { //│ this.c = c; -//│ globalThis.log(this.c) +//│ log1(this.c) //│ } //│ i1(d) { //│ return this.c + d; @@ -144,13 +148,13 @@ class Inner(c) with let i = new Inner(100) //│ JS (unsanitized): -//│ let tmp; tmp = new this.Inner.class(100); this.i = tmp; null +//│ let i, tmp6; tmp6 = new Inner1.class(100); i = tmp6; null //│ > 100 //│ i = Inner { c: 100 } i.i1(20) //│ JS (unsanitized): -//│ this.i.i1(20) ?? null +//│ i.i1(20) ?? null //│ = 120 diff --git a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls index dc0d72844a..94adacf81c 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls @@ -4,7 +4,7 @@ fun foo(x, y, z) = [x, y, z] let f = foo(1, _, 3) -//│ f = [Function (anonymous)] +//│ f = [Function: f] f(2) //│ = [ 1, 2, 3 ] @@ -12,14 +12,14 @@ f(2) :sjs let f = foo(1, _, _) //│ JS (unsanitized): -//│ this.f = (_, _1) => { return this.foo(1, _, _1); }; null -//│ f = [Function (anonymous)] +//│ let f1; f1 = ($_, $_1) => { return foo1(1, $_, $_1); }; null +//│ f = [Function: f1] f(2, 3) //│ = [ 1, 2, 3 ] let g = f(_, 3) -//│ g = [Function (anonymous)] +//│ g = [Function: g] g(2) //│ = [ 1, 2, 3 ] @@ -45,14 +45,14 @@ foo(..._) let h = _ - 2 -//│ h = [Function (anonymous)] +//│ h = [Function: h] h(1) //│ = -1 let i = _(0, 1, _) -//│ i = [Function (anonymous)] +//│ i = [Function: i] i((x, y, z) => x + y + z, 2) //│ = 3 @@ -87,20 +87,22 @@ _ + 1 >> _ * 2 <| 123 :todo let j = _.x //│ ═══[ERROR] Illegal position for '_' placeholder. +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: j (class hkmc2.semantics.VarSymbol) :todo let j = _.x(123) //│ ═══[ERROR] Illegal position for '_' placeholder. +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: j (class hkmc2.semantics.VarSymbol) :todo let j = _.x(1, _) //│ ═══[ERROR] Illegal position for '_' placeholder. -//│ j = [Function (anonymous)] +//│ j = [Function: j] :todo // really support this? let j = _.x.y(1, _) //│ ═══[ERROR] Illegal position for '_' placeholder. -//│ j = [Function (anonymous)] +//│ j = [Function: j1] class C(a, b, c) @@ -135,10 +137,10 @@ _ - 2 <| 1 1 |> _ - 2 //│ ╔══[PARSE ERROR] Expected end of input; found '_' keyword instead -//│ ║ l.136: |> _ - 2 +//│ ║ l.138: |> _ - 2 //│ ╙── ^ //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.135: 1 +//│ ║ l.137: 1 //│ ╙── ^ //│ = [Function: pipeInto] @@ -149,13 +151,13 @@ _ - 2 <| 1 :sjs 1 . _ - 2 //│ JS (unsanitized): -//│ ((_) => { return Predef.passTo(1, _); }) - 2 +//│ (($_) => { return Predef.passTo(1, $_); }) - 2 //│ = NaN :sjs 1 . (_ - 2)() //│ JS (unsanitized): -//│ let tmp; tmp = Predef.passTo(1, (_) => { return _ - 2; }); tmp() ?? null +//│ let tmp7; tmp7 = Predef.passTo(1, ($_) => { return $_ - 2; }); tmp7() ?? null //│ = -1 1 . (_ - _)(2) diff --git a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls index cfaca71901..76f823f3e5 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls @@ -9,50 +9,44 @@ class Foo //│ JS (unsanitized): -//│ this.Foo = class Foo { constructor() {} toString() { return "Foo"; } }; null +//│ let Foo1; Foo1 = class Foo { constructor() {} toString() { return "Foo"; } }; null Foo is Foo //│ JS (unsanitized): -//│ let scrut; scrut = this.Foo; if (scrut instanceof this.Foo) { true } else { false } +//│ let scrut; scrut = Foo1; if (scrut instanceof Foo1) { true } else { false } //│ = false (new Foo) is Foo //│ JS (unsanitized): -//│ let scrut; scrut = new this.Foo(); if (scrut instanceof this.Foo) { true } else { false } +//│ let scrut1; scrut1 = new Foo1(); if (scrut1 instanceof Foo1) { true } else { false } //│ = true new Foo //│ JS (unsanitized): -//│ new this.Foo() +//│ new Foo1() //│ = Foo {} new Foo() //│ JS (unsanitized): -//│ new this.Foo() +//│ new Foo1() //│ = Foo {} Foo //│ JS (unsanitized): -//│ this.Foo +//│ Foo1 //│ = [class Foo] :re Foo() //│ JS (unsanitized): -//│ this.Foo() +//│ Foo1() //│ ═══[RUNTIME ERROR] TypeError: Class constructor Foo cannot be invoked without 'new' class Foo with { log("hi") } log("ok") //│ JS (unsanitized): -//│ this.Foo = class Foo { -//│ constructor() { -//│ globalThis.log("hi") -//│ } -//│ toString() { return "Foo"; } -//│ }; -//│ this.log("ok") +//│ let Foo3; Foo3 = class Foo { constructor() { log1("hi") } toString() { return "Foo"; } }; log1("ok") //│ > ok fun test() = @@ -60,34 +54,35 @@ fun test() = log("ok") Foo //│ JS (unsanitized): -//│ function test() { -//│ let tmp; -//│ class Foo { +//│ let test1; +//│ test1 = function test() { +//│ let Foo4, tmp; +//│ Foo4 = class Foo { //│ constructor() { -//│ globalThis.log("hi") +//│ log1("hi") //│ } //│ toString() { return "Foo"; } -//│ } -//│ tmp = globalThis.log("ok"); -//│ return Foo; -//│ } +//│ }; +//│ tmp = log1("ok"); +//│ return Foo4; +//│ }; //│ null let t = test() //│ JS (unsanitized): -//│ let tmp; tmp = this.test(); this.t = tmp; null +//│ let t, tmp; tmp = test1(); t = tmp; null //│ > ok //│ t = [class Foo] new t //│ JS (unsanitized): -//│ new this.t() +//│ new t() //│ > hi //│ = Foo {} new t() //│ JS (unsanitized): -//│ new this.t() +//│ new t() //│ > hi //│ = Foo {} @@ -97,13 +92,14 @@ class Foo with let y = x + 1 fun z() = y + x //│ JS (unsanitized): -//│ this.Foo = class Foo { +//│ let Foo5; +//│ Foo5 = class Foo { //│ #y; //│ constructor() { -//│ let tmp; +//│ let tmp1; //│ this.x = 1; -//│ tmp = this.x + 1; -//│ this.#y = tmp; +//│ tmp1 = this.x + 1; +//│ this.#y = tmp1; //│ } //│ z() { //│ return this.#y + this.x; @@ -122,7 +118,8 @@ class Foo with fun z2() = 6 log("hello") //│ JS (unsanitized): -//│ this.Foo = class Foo { +//│ let Foo7; +//│ Foo7 = class Foo { //│ #y1; //│ #y2; //│ constructor() { @@ -130,7 +127,7 @@ class Foo with //│ this.x2 = 2; //│ this.#y1 = 3; //│ this.#y2 = 4; -//│ globalThis.log("hello") +//│ log1("hello") //│ } //│ z1() { //│ return 5; @@ -148,7 +145,8 @@ class Foo with fun foo(y) = x + y fun bar(z) = foo(z) + 1 //│ JS (unsanitized): -//│ this.Foo = class Foo { +//│ let Foo9; +//│ Foo9 = class Foo { //│ constructor() { //│ this.x = 1; //│ } @@ -156,9 +154,9 @@ class Foo with //│ return this.x + y; //│ } //│ bar(z) { -//│ let tmp; -//│ tmp = this.foo(z); -//│ return tmp + 1; +//│ let tmp1; +//│ tmp1 = this.foo(z); +//│ return tmp1 + 1; //│ } //│ toString() { return "Foo"; } //│ }; @@ -169,14 +167,14 @@ log(a.x) log(a.foo(1)) log(a.bar(1)) //│ JS (unsanitized): -//│ let tmp, tmp1, tmp2, tmp3, tmp4; -//│ tmp = new this.Foo(); -//│ this.a = tmp; -//│ tmp1 = this.log(this.a.x); -//│ tmp2 = this.a.foo(1) ?? null; -//│ tmp3 = this.log(tmp2); -//│ tmp4 = this.a.bar(1) ?? null; -//│ this.log(tmp4) +//│ let a, tmp1, tmp2, tmp3, tmp4, tmp5; +//│ tmp1 = new Foo9(); +//│ a = tmp1; +//│ tmp2 = log1(a.x); +//│ tmp3 = a.foo(1) ?? null; +//│ tmp4 = log1(tmp3); +//│ tmp5 = a.bar(1) ?? null; +//│ log1(tmp5) //│ > 1 //│ > 2 //│ > 3 @@ -194,7 +192,8 @@ class Foo with val x = 1 let x = 2 //│ JS (unsanitized): -//│ this.Foo = class Foo { +//│ let Foo11; +//│ Foo11 = class Foo { //│ #x; //│ constructor() { //│ this.x = 1; @@ -208,13 +207,13 @@ class Foo with :fixme class Foo with val x = 1 //│ ╔══[PARSE ERROR] Expected block after type declaration body; found 'val' keyword instead -//│ ║ l.209: class Foo with val x = 1 +//│ ║ l.208: class Foo with val x = 1 //│ ╙── ^^^ //│ ╔══[PARSE ERROR] Expected end of input; found '=' keyword instead -//│ ║ l.209: class Foo with val x = 1 +//│ ║ l.208: class Foo with val x = 1 //│ ╙── ^ //│ ╔══[ERROR] Illegal juxtaposition right-hand side. -//│ ║ l.209: class Foo with val x = 1 +//│ ║ l.208: class Foo with val x = 1 //│ ╙── ^ //│ JS (unsanitized): //│ /* error */ diff --git a/hkmc2/shared/src/test/mlscript/codegen/PredefJS.mls b/hkmc2/shared/src/test/mlscript/codegen/PredefJS.mls index bfe49d07c3..4905e3c071 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PredefJS.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PredefJS.mls @@ -5,4 +5,5 @@ fun id(x) = x fun log(msg) = globalThis.console.log(msg) +set globalThis.log = log diff --git a/hkmc2/shared/src/test/mlscript/codegen/Primes.mls b/hkmc2/shared/src/test/mlscript/codegen/Primes.mls index 9e77d55ebb..1108f02199 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Primes.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Primes.mls @@ -4,16 +4,12 @@ let x = 1 //│ x = 1 -:fixme :sjs -let x' = 1 +let x' = 2 //│ JS (unsanitized): -//│ this.x' = 1; null -//│ > try { this.x' = 1; null } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected string -//│ > try { this.x' } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected string +//│ let x$_; x$_ = 2; null +//│ x' = 2 +x' +//│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/codegen/Pwd.mls b/hkmc2/shared/src/test/mlscript/codegen/Pwd.mls index 41dc6c99a2..6d833ca1c5 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Pwd.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Pwd.mls @@ -7,16 +7,16 @@ let folderName1 = process.env.PWD.split("/").pop() in let folderName2 = process.cwd().split("/").pop() in folderName2 === folderName1 || folderName2 === "shared" //│ JS (unsanitized): -//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; +//│ let folderName1, folderName2, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; //│ tmp = this.process.env.PWD.split("/") ?? null; //│ tmp1 = tmp.pop() ?? null; -//│ this.folderName1 = tmp1; +//│ folderName1 = tmp1; //│ tmp2 = this.process.cwd() ?? null; //│ tmp3 = tmp2.split("/") ?? null; //│ tmp4 = tmp3.pop() ?? null; -//│ this.folderName2 = tmp4; -//│ tmp5 = this.folderName2 === this.folderName1; -//│ tmp6 = this.folderName2 === "shared"; +//│ folderName2 = tmp4; +//│ tmp5 = folderName2 === folderName1; +//│ tmp6 = folderName2 === "shared"; //│ tmp5 || tmp6 //│ = true diff --git a/hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls b/hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls index 9ecca7cd83..28966ca99d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls @@ -20,7 +20,7 @@ foo() let x = 1 let f = () => x -//│ f = [Function (anonymous)] +//│ f = [Function: f] //│ x = 1 f() @@ -30,7 +30,7 @@ let x = 2 //│ x = 2 f() -//│ = 2 +//│ = 1 @@ -44,8 +44,8 @@ class Foo with print(x) print(f()) print(g()) -//│ > try { this.Foo = class Foo { #x; #f; #x; constructor() { let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; this.#x = 1; this.#f = (...args) => { globalThis.Predef.checkArgs("", 0, true, args.length); return this.#x; }; tmp = this.#f() ?? null; tmp1 = Predef.print(tmp); this.#x = 2; tmp2 = Predef.print(this.#x); tmp3 = this.#f() ?? null; tmp4 = Predef.print(tmp3); tmp5 = this.g(); Predef.print(tmp5) } g(...args) { globalThis.Predef.checkArgs("g", 0, true, args.length); return this.#x; } toString() { return "Foo"; } }; null } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^ +//│ > let Foo1;try { Foo1 = class Foo { #x; #f; #x; constructor() { let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; this.#x = 1; this.#f = (...args) => { globalThis.Predef.checkArgs("", 0, true, args.length); return this.#x; }; tmp = this.#f() ?? null; tmp1 = Predef.print(tmp); this.#x = 2; tmp2 = Predef.print(this.#x); tmp3 = this.#f() ?? null; tmp4 = Predef.print(tmp3); tmp5 = this.g(); Predef.print(tmp5) } g(...args) { globalThis.Predef.checkArgs("g", 0, true, args.length); return this.#x; } toString() { return "Foo"; } }; null } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ > ^ //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Identifier '#x' has already been declared diff --git a/hkmc2/shared/src/test/mlscript/codegen/Repl.mls b/hkmc2/shared/src/test/mlscript/codegen/Repl.mls index 13fb0bea17..fe25de0a86 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Repl.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Repl.mls @@ -7,7 +7,7 @@ val res: Int :showRepl fun res() = 1 -//│ REPL> Sending: try { function res(...args) { globalThis.Predef.checkArgs("res", 0, true, args.length); return 1; } null } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } +//│ REPL> Sending: let res1;try { res1 = function res(...args) { globalThis.Predef.checkArgs("res", 0, true, args.length); return 1; }; null } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } //│ REPL> Collected: //│ > null //│ REPL> Parsed: @@ -24,7 +24,7 @@ fun res() = 1 :showRepl res -//│ REPL> Sending: try { this.res } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } +//│ REPL> Sending: try { res1 } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } //│ REPL> Collected: //│ > [Function: res] //│ REPL> Parsed: @@ -35,14 +35,14 @@ res :showRepl :re let x = 1, log(x), x -//│ REPL> Sending: try { let tmp; this.x = 1; tmp = this.log(this.x) ?? null; this.x } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ REPL> Sending: let x, selRes, tmp, tmp1;try { x = 1; selRes = this.log; if (selRes === undefined) { throw new this.Error("Access to required field 'log' yielded 'undefined'"); } else { tmp = selRes; } tmp1 = tmp(x) ?? null; x } catch (e) { console.log('\u200B' + e + '\u200B'); } //│ REPL> Collected: -//│ > ​TypeError: this.log is not a function​ +//│ > ​Error: Access to required field 'log' yielded 'undefined'​ //│ > undefined //│ REPL> Parsed: -//│ > [runtime error] TypeError: this.log is not a function -//│ ═══[RUNTIME ERROR] TypeError: this.log is not a function -//│ REPL> Sending: try { this.x } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ > [runtime error] Error: Access to required field 'log' yielded 'undefined' +//│ ═══[RUNTIME ERROR] Error: Access to required field 'log' yielded 'undefined' +//│ REPL> Sending: try { x } catch (e) { console.log('\u200B' + e + '\u200B'); } //│ REPL> Collected: //│ > 1 //│ REPL> Parsed: @@ -54,7 +54,7 @@ let x = 1, log(x), x :showRepl let x = 1, log(x), x -//│ REPL> Sending: try { let tmp; this.x = 1; tmp = this.log(this.x); this.x } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } +//│ REPL> Sending: let x1, tmp2;try { x1 = 1; tmp2 = log1(x1); x1 } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } //│ REPL> Collected: //│ > 1 //│ > 1 @@ -63,7 +63,7 @@ let x = 1, log(x), x //│ > 1 //│ > 1 //│ = 1 -//│ REPL> Sending: try { this.x } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } +//│ REPL> Sending: try { x1 } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } //│ REPL> Collected: //│ > 1 //│ REPL> Parsed: diff --git a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls index e5ac99758c..0eb1906acc 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls @@ -6,13 +6,13 @@ fun f(x, y) = x + y f(2, 3) //│ JS: -//│ function f(...args) { +//│ f1 = function f(...args) { //│ globalThis.Predef.checkArgs("f", 2, true, args.length); //│ let x = args[0]; //│ let y = args[1]; //│ return x + y; -//│ } -//│ this.f(2, 3) +//│ }; +//│ f1(2, 3) //│ = 5 @@ -22,14 +22,14 @@ f(2, 3) fun f2(x, y) = x + y f2(2) //│ JS: -//│ function f2(x, y) { return x + y; } this.f2(2) +//│ f21 = function f2(x, y) { return x + y; }; f21(2) //│ = NaN :ssjs :re f(2) //│ JS: -//│ this.f(2) +//│ f1(2) //│ ═══[RUNTIME ERROR] Error: Function 'f' expected 2 arguments but got 1 @@ -38,8 +38,7 @@ f(2) fun f(x)(y, z) = x + y + z f(3)(4) //│ JS: -//│ let tmp; -//│ function f(...args) { +//│ f4 = function f(...args) { //│ globalThis.Predef.checkArgs("f", 1, true, args.length); //│ let x = args[0]; //│ return (...args1) => { @@ -50,8 +49,8 @@ f(3)(4) //│ tmp1 = x + y; //│ return tmp1 + z; //│ }; -//│ } -//│ tmp = this.f(3); +//│ }; +//│ tmp = f4(3); //│ tmp(4) ?? null //│ ═══[RUNTIME ERROR] Error: Function expected 2 arguments but got 1 @@ -61,7 +60,7 @@ f(3)(4) :noSanityCheck let f = (x, y) => x + y in f(2) //│ JS: -//│ this.f = (x, y) => { return x + y; }; this.f(2) ?? null +//│ f5 = (x, y) => { return x + y; }; f5(2) ?? null //│ = NaN :ssjs @@ -69,15 +68,15 @@ let f = (x, y) => x + y in f(2) let f = (x, y) => x + y f(2) //│ JS: -//│ this.f = (...args) => { +//│ f6 = (...args) => { //│ globalThis.Predef.checkArgs("", 2, true, args.length); //│ let x = args[0]; //│ let y = args[1]; //│ return x + y; //│ }; -//│ this.f(2) ?? null +//│ f6(2) ?? null //│ ═══[RUNTIME ERROR] Error: Function expected 2 arguments but got 1 -//│ f = [Function (anonymous)] +//│ f = [Function: f6] :expect NaN @@ -87,23 +86,22 @@ class Cls(x, y) with fun f(z, p) = x + y + z + p Cls(1, 2).f(3) //│ JS: -//│ let tmp; -//│ this.Cls = function Cls(x1, y1) { return new Cls.class(x1, y1); }; -//│ this.Cls.class = class Cls { +//│ Cls1 = function Cls(x1, y1) { return new Cls.class(x1, y1); }; +//│ Cls1.class = class Cls { //│ constructor(x, y) { //│ this.x = x; //│ this.y = y; //│ } //│ f(z, p) { -//│ let tmp1, tmp2; -//│ tmp1 = this.x + this.y; -//│ tmp2 = tmp1 + z; -//│ return tmp2 + p; +//│ let tmp2, tmp3; +//│ tmp2 = this.x + this.y; +//│ tmp3 = tmp2 + z; +//│ return tmp3 + p; //│ } //│ toString() { return "Cls(" + this.x + ", " + this.y + ")"; } //│ }; -//│ tmp = this.Cls(1, 2); -//│ tmp.f(3) ?? null +//│ tmp1 = Cls1(1, 2); +//│ tmp1.f(3) ?? null //│ = NaN :ssjs @@ -112,9 +110,8 @@ class Cls(x, y) with fun f(z, p) = x + y + z + p Cls(1, 2).f(3) //│ JS: -//│ let tmp; -//│ this.Cls = function Cls(...args1) { return new Cls.class(...args1); }; -//│ this.Cls.class = class Cls { +//│ Cls3 = function Cls(...args1) { return new Cls.class(...args1); }; +//│ Cls3.class = class Cls { //│ constructor(x, y) { //│ this.x = x; //│ this.y = y; @@ -123,15 +120,15 @@ Cls(1, 2).f(3) //│ globalThis.Predef.checkArgs("f", 2, true, args.length); //│ let z = args[0]; //│ let p = args[1]; -//│ let tmp1, tmp2; -//│ tmp1 = this.x + this.y; -//│ tmp2 = tmp1 + z; -//│ return tmp2 + p; +//│ let tmp3, tmp4; +//│ tmp3 = this.x + this.y; +//│ tmp4 = tmp3 + z; +//│ return tmp4 + p; //│ } //│ toString() { return "Cls(" + this.x + ", " + this.y + ")"; } //│ }; -//│ tmp = this.Cls(1, 2); -//│ tmp.f(3) ?? null +//│ tmp2 = Cls3(1, 2); +//│ tmp2.f(3) ?? null //│ ═══[RUNTIME ERROR] Error: Function 'f' expected 2 arguments but got 1 @@ -141,9 +138,8 @@ class Cls(x, y) with fun f(z, p)(q, s) = x + y + z + p + q + s Cls(1, 2).f(3, 4)(5) //│ JS: -//│ let tmp, tmp1; -//│ this.Cls = function Cls(...args1) { return new Cls.class(...args1); }; -//│ this.Cls.class = class Cls { +//│ Cls5 = function Cls(...args1) { return new Cls.class(...args1); }; +//│ Cls5.class = class Cls { //│ constructor(x, y) { //│ this.x = x; //│ this.y = y; @@ -156,26 +152,32 @@ Cls(1, 2).f(3, 4)(5) //│ globalThis.Predef.checkArgs("", 2, true, args1.length); //│ let q = args1[0]; //│ let s = args1[1]; -//│ let tmp2, tmp3, tmp4, tmp5; -//│ tmp2 = this.x + this.y; -//│ tmp3 = tmp2 + z; -//│ tmp4 = tmp3 + p; -//│ tmp5 = tmp4 + q; -//│ return tmp5 + s; +//│ let tmp5, tmp6, tmp7, tmp8; +//│ tmp5 = this.x + this.y; +//│ tmp6 = tmp5 + z; +//│ tmp7 = tmp6 + p; +//│ tmp8 = tmp7 + q; +//│ return tmp8 + s; //│ }; //│ } //│ toString() { return "Cls(" + this.x + ", " + this.y + ")"; } //│ }; -//│ tmp = this.Cls(1, 2); -//│ tmp1 = tmp.f(3, 4); -//│ tmp1(5) ?? null +//│ tmp3 = Cls5(1, 2); +//│ tmp4 = tmp3.f(3, 4); +//│ tmp4(5) ?? null //│ ═══[RUNTIME ERROR] Error: Function expected 2 arguments but got 1 :ssjs console.log(1) //│ JS: -//│ this.console.log(1) ?? null +//│ selRes = this.console; +//│ if (selRes === undefined) { +//│ throw new this.Error("Access to required field 'console' yielded 'undefined'"); +//│ } else { +//│ tmp5 = selRes; +//│ } +//│ tmp5.log(1) ?? null //│ > 1 @@ -183,12 +185,11 @@ console.log(1) :ssjs globalThis.x //│ JS: -//│ let selRes; -//│ selRes = this.x; -//│ if (selRes === undefined) { +//│ selRes1 = this.x; +//│ if (selRes1 === undefined) { //│ throw new this.Error("Access to required field 'x' yielded 'undefined'"); //│ } else { -//│ selRes +//│ selRes1 //│ } //│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' @@ -211,7 +212,6 @@ if M.A(1).y is x and x == 1 then x else 0 //│ JS: -//│ let scrut, x, scrut1, tmp, selRes, tmp1; //│ const M$class = class M { //│ constructor() { //│ this.A = function A(...args1) { return new A.class(...args1); }; @@ -228,17 +228,16 @@ if M.A(1).y is //│ }; //│ } //│ toString() { return "M"; } -//│ }; -//│ this.M = new M$class; -//│ this.M.class = M$class; -//│ tmp = this.M.A(1); -//│ selRes = tmp.y; -//│ if (selRes === undefined) { +//│ }; M1 = new M$class; +//│ M1.class = M$class; +//│ tmp6 = M1.A(1); +//│ selRes2 = tmp6.y; +//│ if (selRes2 === undefined) { //│ throw new this.Error("Access to required field 'y' yielded 'undefined'"); //│ } else { -//│ tmp1 = selRes; +//│ tmp7 = selRes2; //│ } -//│ scrut = tmp1; +//│ scrut = tmp7; //│ x = scrut; //│ scrut1 = x == 1; //│ if (scrut1 === true) { @@ -255,15 +254,20 @@ if M.A(1).y is M.A(1).y console.log() //│ JS: -//│ let tmp, selRes, tmp1; -//│ tmp = this.M.A(1); -//│ selRes = tmp.y; -//│ if (selRes === undefined) { +//│ selRes3 = this.console; +//│ if (selRes3 === undefined) { +//│ throw new this.Error("Access to required field 'console' yielded 'undefined'"); +//│ } else { +//│ tmp8 = selRes3; +//│ } +//│ tmp9 = M1.A(1); +//│ selRes4 = tmp9.y; +//│ if (selRes4 === undefined) { //│ throw new this.Error("Access to required field 'y' yielded 'undefined'"); //│ } else { -//│ tmp1 = selRes; +//│ tmp10 = selRes4; //│ } -//│ this.console.log(tmp1) ?? null +//│ tmp8.log(tmp10) ?? null //│ ═══[RUNTIME ERROR] Error: Access to required field 'y' yielded 'undefined' @@ -273,7 +277,7 @@ M.A(1).y M.A(1).y console.log() //│ JS: -//│ let tmp; tmp = this.M.A(1); this.console.log(tmp.y) ?? null +//│ tmp11 = M1.A(1); this.console.log(tmp11.y) ?? null //│ > undefined :noSanityCheck @@ -289,15 +293,15 @@ M.A(2).y > 1 :ssjs M.A(1).g(0) //│ JS: -//│ let tmp; tmp = this.M.A(1); tmp.g(0) ?? null -//│ ═══[RUNTIME ERROR] TypeError: tmp.g is not a function +//│ tmp15 = M1.A(1); tmp15.g(0) ?? null +//│ ═══[RUNTIME ERROR] TypeError: tmp15.g is not a function :ssjs M.A(1).f(0) //│ JS: -//│ let tmp; tmp = this.M.A(1); tmp.f(0) ?? null +//│ tmp16 = M1.A(1); tmp16.f(0) ?? null //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls b/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls index 477c0508e8..e5ded96123 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls @@ -10,7 +10,7 @@ let x = 0 :sjs let x += 1 //│ JS (unsanitized): -//│ let tmp; tmp = this.x + 1; this.x = tmp; null +//│ let x1, tmp; tmp = x + 1; x1 = tmp; null //│ x = 1 x @@ -19,12 +19,12 @@ x :sjs set x = 0 //│ JS (unsanitized): -//│ this.x = 0; null +//│ x1 = 0; null :sjs set x += 1 //│ JS (unsanitized): -//│ let tmp; tmp = this.x + 1; this.x = tmp; null +//│ let tmp1; tmp1 = x1 + 1; x1 = tmp1; null x //│ = 1 @@ -33,17 +33,17 @@ x :sjs set x += 1 in log(x) //│ JS (unsanitized): -//│ let old, tmp, tmp1, tmp2; -//│ old = this.x; +//│ let old, tmp2, tmp3, tmp4; +//│ old = x1; //│ try { -//│ tmp1 = this.x + 1; -//│ this.x = tmp1; -//│ tmp2 = this.log(this.x); -//│ tmp = tmp2; +//│ tmp3 = x1 + 1; +//│ x1 = tmp3; +//│ tmp4 = log1(x1); +//│ tmp2 = tmp4; //│ } finally { -//│ this.x = old; +//│ x1 = old; //│ } -//│ tmp +//│ tmp2 //│ > 2 x @@ -79,29 +79,30 @@ fun example() = log(get_x()) example() //│ JS (unsanitized): -//│ function example() { -//│ let x, get_x, old, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; -//│ x = 0; -//│ get_x = () => { -//│ return x; +//│ let example5; +//│ example5 = function example() { +//│ let x2, get$_x, old1, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12; +//│ x2 = 0; +//│ get$_x = () => { +//│ return x2; //│ }; -//│ old = x; +//│ old1 = x2; //│ try { -//│ tmp1 = x + 1; -//│ x = tmp1; -//│ tmp2 = globalThis.log(x); -//│ tmp3 = (tmp2 , globalThis.log); -//│ tmp4 = get_x() ?? null; -//│ tmp5 = tmp3(tmp4) ?? null; -//│ tmp = tmp5; +//│ tmp6 = x2 + 1; +//│ x2 = tmp6; +//│ tmp7 = log1(x2); +//│ tmp8 = (tmp7 , log1); +//│ tmp9 = get$_x() ?? null; +//│ tmp10 = tmp8(tmp9) ?? null; +//│ tmp5 = tmp10; //│ } finally { -//│ x = old; +//│ x2 = old1; //│ } -//│ tmp6 = globalThis.log(x); -//│ tmp7 = get_x() ?? null; -//│ return globalThis.log(tmp7); -//│ } -//│ this.example() +//│ tmp11 = log1(x2); +//│ tmp12 = get$_x() ?? null; +//│ return log1(tmp12); +//│ }; +//│ example5() //│ > 1 //│ > 1 //│ > 0 @@ -118,29 +119,30 @@ fun example() = y example() //│ JS (unsanitized): -//│ function example() { -//│ let x, get_x, y, old, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; -//│ x = 0; -//│ get_x = () => { -//│ return x; +//│ let example7; +//│ example7 = function example() { +//│ let x2, get$_x, y, old1, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11; +//│ x2 = 0; +//│ get$_x = () => { +//│ return x2; //│ }; -//│ old = x; +//│ old1 = x2; //│ try { -//│ tmp1 = x + 1; -//│ x = tmp1; -//│ tmp2 = globalThis.log(x); -//│ tmp3 = (tmp2 , x); -//│ tmp = tmp3; +//│ tmp6 = x2 + 1; +//│ x2 = tmp6; +//│ tmp7 = log1(x2); +//│ tmp8 = (tmp7 , x2); +//│ tmp5 = tmp8; //│ } finally { -//│ x = old; +//│ x2 = old1; //│ } -//│ y = tmp; -//│ tmp4 = globalThis.log(x); -//│ tmp5 = get_x() ?? null; -//│ tmp6 = globalThis.log(tmp5); +//│ y = tmp5; +//│ tmp9 = log1(x2); +//│ tmp10 = get$_x() ?? null; +//│ tmp11 = log1(tmp10); //│ return y; -//│ } -//│ this.example() +//│ }; +//│ example7() //│ > 1 //│ > 0 //│ > 0 diff --git a/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls b/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls index 576c4c0f37..d696e039c9 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls @@ -19,7 +19,7 @@ foo(0, ...a) :sjs foo(1, ...[2, 3], 4) //│ JS (unsanitized): -//│ this.foo(1, ...[ 2, 3 ], 4) +//│ foo1(1, ...[ 2, 3 ], 4) //│ = [ 1, 2, 3, 4 ] :re @@ -28,7 +28,7 @@ foo(...a) let f = (...xs) => xs -//│ f = [Function (anonymous)] +//│ f = [Function: f] f(a) //│ = [ [ 1, 2, 3 ] ] diff --git a/hkmc2/shared/src/test/mlscript/codegen/This.mls b/hkmc2/shared/src/test/mlscript/codegen/This.mls index 0ecb43a815..e0fa37d011 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/This.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/This.mls @@ -1,11 +1,20 @@ :js +// TODO add syntax for this-taking detached methods :sjs +:e fun test(x) = [this.a, x] +//│ ═══[ERROR] Cannot use 'this' outside of an object scope. //│ JS (unsanitized): -//│ function test(x) { return [ globalThis.a, x ]; } null +//│ let test1; test1 = function test(x) { /* error */ }; null + +:sjs +fun test(x) = + [globalThis.a, x] +//│ JS (unsanitized): +//│ let test3; test3 = function test(x) { return [ globalThis.a, x ]; }; null :re test(123) @@ -14,23 +23,30 @@ test(123) val a = 1 //│ a = 1 +:re test(123) -//│ = [ 1, 123 ] +//│ ═══[RUNTIME ERROR] Error: Access to required field 'a' yielded 'undefined' + +set globalThis.a = 2 + +test(123) +//│ = [ 2, 123 ] :sjs module Test with - val a = 2 + val a = 3 fun test1(x) = test(x) fun test2(x) = [this.a, x] //│ JS (unsanitized): +//│ let Test1; //│ const Test$class = class Test { //│ constructor() { -//│ this.a = 2; +//│ this.a = 3; //│ } //│ test1(x) { -//│ return globalThis.test(x); +//│ return test3(x); //│ } //│ test2(x1) { //│ return [ @@ -39,19 +55,15 @@ module Test with //│ ]; //│ } //│ toString() { return "Test"; } -//│ }; -//│ this.Test = new Test$class; -//│ this.Test.class = Test$class; +//│ }; Test1 = new Test$class; +//│ Test1.class = Test$class; //│ null Test.test1(123) -//│ = [ 1, 123 ] - -Test.test2(123) //│ = [ 2, 123 ] - - +Test.test2(123) +//│ = [ 3, 123 ] diff --git a/hkmc2/shared/src/test/mlscript/codegen/ThisCallVariations.mls b/hkmc2/shared/src/test/mlscript/codegen/ThisCallVariations.mls index e3db586888..41425e4c73 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ThisCallVariations.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ThisCallVariations.mls @@ -16,8 +16,9 @@ let oops = Example.f // * JavaScript nonsense +:re oops(2) -//│ = [ 2, undefined ] +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'a') oops.call(Example, 2) //│ = [ 2, 1 ] @@ -42,7 +43,7 @@ Example . oops(2) //│ rhs = Tup of Ls of //│ IntLit of 2 //│ JS (unsanitized): -//│ let tmp; tmp = this.call(this.Example, this.oops); tmp(2) ?? null +//│ let tmp1; tmp1 = call1(Example1, oops); tmp1(2) ?? null //│ = [ 2, 1 ] Example. oops(2) @@ -53,9 +54,9 @@ Example. oops(2) :re Example .oops(2) //│ ╔══[ERROR] Object 'Example' does not contain member 'oops' -//│ ║ l.54: Example .oops(2) +//│ ║ l.55: Example .oops(2) //│ ╙── ^^^^^ -//│ ═══[RUNTIME ERROR] TypeError: this.Example.oops is not a function +//│ ═══[RUNTIME ERROR] TypeError: Example1.oops is not a function id(Example) . oops(2) @@ -75,16 +76,19 @@ Example2(1) . oops2(2) . oops2(2) Example2(1). oops2(2). oops2(2) //│ = Example2 { a: 1 } +(new Example2(1)) . oops2(2) +//│ = Example2 { a: 1 } + :todo new Example2(1) . oops2(2) -//│ ═══[RUNTIME ERROR] TypeError: tmp2 is not a constructor +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'a') :todo new Example2(1) . oops2(2) . oops2(2) -//│ ═══[RUNTIME ERROR] TypeError: tmp4 is not a constructor +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'a') // * Alternative syntax – precedence is now inappropriate @@ -92,6 +96,7 @@ fun (.>) call(receiver, f)(arg) = f.call(receiver, arg) :pt :sjs +:re Example .> oops(2) //│ Parsed tree: //│ App: @@ -103,8 +108,17 @@ Example .> oops(2) //│ rhs = Tup of Ls of //│ IntLit of 2 //│ JS (unsanitized): -//│ let tmp; tmp = this.oops(2) ?? null; this.call(this.Example, tmp) -//│ = [Function (anonymous)] +//│ let tmp25; tmp25 = oops(2) ?? null; call3(Example1, tmp25) +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'a') + +// * Note: +:re +oops(2) +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'a') + +// * Note: +(Example .> oops)(2) +//│ = [ 2, 1 ] // * Another alternative syntax – currently adopted in Predef @@ -121,6 +135,6 @@ Example2(1) |>. oops2(2) |>. oops2(2) |>. oops2(2) |>. oops2(2) ).a -//│ ═══[RUNTIME ERROR] Error: Access to required field 'a' yielded 'undefined' +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'a') diff --git a/hkmc2/shared/src/test/mlscript/codegen/ThisCalls.mls b/hkmc2/shared/src/test/mlscript/codegen/ThisCalls.mls index 776a77ab50..be67cd3376 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ThisCalls.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ThisCalls.mls @@ -14,12 +14,12 @@ let s = ex.f :todo :e s(123) -//│ = [ 123, undefined ] +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'a') :sjs ex |>. s(123) //│ JS (unsanitized): -//│ let tmp; tmp = Predef.call(this.ex, this.s); tmp(123) ?? null +//│ let tmp2; tmp2 = Predef.call(ex1, s); tmp2(123) ?? null //│ = [ 123, 456 ] diff --git a/hkmc2/shared/src/test/mlscript/codegen/Throw.mls b/hkmc2/shared/src/test/mlscript/codegen/Throw.mls index 9c75fa73f7..afa35888db 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Throw.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Throw.mls @@ -31,7 +31,7 @@ fun f(x) = return y f(1) //│ JS (unsanitized): -//│ function f(x) { throw globalThis.Error("e"); } this.f(1) +//│ let f5; f5 = function f(x) { throw globalThis.Error("e"); }; f5(1) //│ ═══[RUNTIME ERROR] Error: e @@ -41,12 +41,13 @@ fun f(x) = throw (if x then Error("x") else Error("y")) f(false) //│ JS (unsanitized): -//│ function f(x) { +//│ let f7; +//│ f7 = function f(x) { //│ if (x === true) { //│ throw globalThis.Error("x"); //│ } else { //│ throw globalThis.Error("y"); //│ } -//│ } -//│ this.f(false) +//│ }; +//│ f7(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 a7a437a388..1af9de7917 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls @@ -7,19 +7,20 @@ fun fib(a) = if a <= 1 then a else fib(a - 1) + fib(a - 2) //│ JS (unsanitized): -//│ function fib(a) { +//│ let fib1; +//│ fib1 = function fib(a) { //│ let scrut, tmp, tmp1, tmp2, tmp3; //│ scrut = a <= 1; //│ if (scrut === true) { //│ return a; //│ } else { //│ tmp = a - 1; -//│ tmp1 = globalThis.fib(tmp); +//│ tmp1 = fib1(tmp); //│ tmp2 = a - 2; -//│ tmp3 = globalThis.fib(tmp2); +//│ tmp3 = fib1(tmp2); //│ return tmp1 + tmp3; //│ } -//│ } +//│ }; //│ null fun f(x) = g(x) diff --git a/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls b/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls index 45fc55df52..e5decaca3d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls @@ -60,7 +60,7 @@ f(1,2)(-3) //│ REPL> Sending: globalThis.Predef.TraceLogger.enabled = true; globalThis.Predef.TraceLogger.resetIndent(0) //│ REPL> Collected: //│ > null -//│ REPL> Sending: try { let tmp, tmp1; tmp = this.f(1, 2); tmp1 = - 3; tmp(tmp1) ?? null } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } +//│ REPL> Sending: let tmp4, tmp5;try { tmp4 = f1(1, 2); tmp5 = - 3; tmp4(tmp5) ?? null } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } //│ REPL> Collected: //│ > CALL g(-3) //│ > | CALL g(-2) @@ -90,7 +90,7 @@ f(1,2)(-3) :showRepl f(1,2)(-4) -//│ REPL> Sending: try { let tmp, tmp1; tmp = this.f(1, 2); tmp1 = - 4; tmp(tmp1) ?? null } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } +//│ REPL> Sending: let tmp6, tmp7;try { tmp6 = f1(1, 2); tmp7 = - 4; tmp6(tmp7) ?? null } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } //│ REPL> Collected: //│ > 45 //│ REPL> Parsed: diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index ab44e59912..80ae694271 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -54,20 +54,19 @@ while x set x = false else 42 //│ JS (unsanitized): -//│ let scrut, tmp, tmp1; -//│ tmp2: while (true) { -//│ scrut = this.x; -//│ if (scrut === true) { -//│ tmp = this.log("Hello World"); -//│ this.x = false; -//│ tmp1 = null; -//│ continue tmp2; +//│ let tmp4, tmp5; +//│ tmp6: while (true) { +//│ if (x2 === true) { +//│ tmp4 = log1("Hello World"); +//│ x2 = false; +//│ tmp5 = null; +//│ continue tmp6; //│ } else { -//│ tmp1 = 42; +//│ tmp5 = 42; //│ } //│ break; //│ } -//│ tmp1 +//│ tmp5 //│ > Hello World //│ = 42 @@ -108,21 +107,21 @@ while i < 10 do set i += 1 //│ JS (unsanitized): //│ () => { -//│ let i, scrut, tmp, tmp1; -//│ tmp2: while (true) { -//│ i = 0; -//│ scrut = i < 10; -//│ if (scrut === true) { -//│ tmp = i + 1; -//│ i = tmp; -//│ tmp1 = null; -//│ continue tmp2; +//│ let i2, scrut3, tmp19, tmp20; +//│ tmp21: while (true) { +//│ i2 = 0; +//│ scrut3 = i2 < 10; +//│ if (scrut3 === true) { +//│ tmp19 = i2 + 1; +//│ i2 = tmp19; +//│ tmp20 = null; +//│ continue tmp21; //│ } else { -//│ tmp1 = null; +//│ tmp20 = null; //│ } //│ break; //│ } -//│ return tmp1; +//│ return tmp20; //│ } //│ = [Function (anonymous)] @@ -165,10 +164,8 @@ if x else 42 //│ Normalized: //│ > if -//│ > let $scrut = globalThis:block#7.x#666 -//│ > $scrut is true and -//│ > let $scrut = globalThis:block#7.y#666 -//│ > $scrut is true then 0 +//│ > x is true and +//│ > y is true then 0 //│ > else 1 //│ > else 42 //│ = 42 @@ -181,10 +178,8 @@ if x else 42 //│ Normalized: //│ > if -//│ > let $scrut = globalThis:block#7.x#666 -//│ > $scrut is true and -//│ > let $scrut = globalThis:block#7.y#666 -//│ > $scrut is true then 0 +//│ > x is true and +//│ > y is true then 0 //│ > else 1 //│ > else 42 //│ = 42 @@ -203,24 +198,25 @@ fun f(ls) = log(h) else log("Done!") //│ JS (unsanitized): -//│ function f(ls) { -//│ let param0, param1, h, tl, tmp; -//│ tmp1: while (true) { -//│ if (ls instanceof globalThis.Cons.class) { +//│ let f1; +//│ f1 = function f(ls) { +//│ let param0, param1, h, tl, tmp28; +//│ tmp29: while (true) { +//│ if (ls instanceof Cons1.class) { //│ param0 = ls.hd; //│ param1 = ls.tl; //│ h = param0; //│ tl = param1; //│ ls = tl; -//│ tmp = globalThis.log(h); -//│ continue tmp1; +//│ tmp28 = log1(h); +//│ continue tmp29; //│ } else { -//│ tmp = globalThis.log("Done!"); +//│ tmp28 = log1("Done!"); //│ } //│ break; //│ } -//│ return tmp; -//│ } +//│ return tmp28; +//│ }; //│ null f(0) @@ -259,7 +255,7 @@ while log("Hello World"); false then 0(0) else 1 //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.259: then 0(0) +//│ ║ l.255: then 0(0) //│ ╙── ^^^^ //│ ═══[ERROR] Unrecognized term split (false literal). //│ > Hello World diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index e8f22c639a..cf0c81bd73 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -3,6 +3,8 @@ declare type Any declare type Nothing +declare type untyped + declare class Object declare class Bool declare class Int diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 342cf36183..180773132b 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -1,9 +1,20 @@ :js :handler + abstract class Effect with fun perform(arg: Str): Str + +handle h = Effect with + fun perform()(k) = "b" + +:expect 42 +handle h = Effect with + fun perform()(k) = k(42) +h.perform() +//│ = 42 + :expect 'b' handle h = Effect with fun perform(arg)(k) = "b" @@ -145,27 +156,27 @@ if true do fun f() = 3 f() //│ JS (unsanitized): -//│ let tmp; -//│ function handleBlock$0() { -//│ let h, scrut, tmp1, res; -//│ class Effect$h$908 extends globalThis.Effect { +//│ let tmp11, handleBlock$023; +//│ handleBlock$023 = function handleBlock$0() { +//│ let h, scrut, tmp12, res, Cont$1074, f$0, Effect$h$1048; +//│ Effect$h$1048 = class Effect$h$1048 extends Effect1 { //│ constructor() { -//│ let tmp2; -//│ tmp2 = super(); +//│ let tmp13; +//│ tmp13 = super(); //│ } //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h, (k) => { //│ return arg; //│ }); //│ } -//│ toString() { return "Effect$h$908"; } -//│ } -//│ h = new Effect$h$908(); -//│ function Cont$932(pc1) { return new Cont$932.class(pc1); } -//│ Cont$932.class = class Cont$932 extends globalThis.Predef.__Cont.class { +//│ toString() { return "Effect$h$1048"; } +//│ }; +//│ h = new Effect$h$1048(); +//│ Cont$1074 = function Cont$1074(pc1) { return new Cont$1074.class(pc1); }; +//│ Cont$1074.class = class Cont$1074 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { -//│ let tmp2; -//│ tmp2 = super(null, null); +//│ let tmp13; +//│ tmp13 = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { @@ -174,38 +185,38 @@ if true do //│ } //│ contLoop: while (true) { //│ if (this.pc === 2) { -//│ return tmp1; +//│ return tmp12; //│ } else if (this.pc === 1) { -//│ tmp1 = res; +//│ tmp12 = res; //│ this.pc = 2; //│ continue contLoop; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$932(" + this.pc + ")"; } +//│ toString() { return "Cont$1074(" + this.pc + ")"; } //│ }; -//│ function f$0() { +//│ f$0 = function f$0() { //│ return 3; -//│ } +//│ }; //│ scrut = true; //│ if (scrut === true) { //│ res = f$0(); //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$932(1); +//│ res.tail.next = new Cont$1074(1); //│ return globalThis.Predef.__handleBlockImpl(res, h); //│ } -//│ tmp1 = res; +//│ tmp12 = res; //│ } else { -//│ tmp1 = null; +//│ tmp12 = null; //│ } -//│ return tmp1; -//│ } -//│ tmp = handleBlock$0(); -//│ if (tmp instanceof this.Predef.__EffectSig.class) { +//│ return tmp12; +//│ }; +//│ tmp11 = handleBlock$023(); +//│ if (tmp11 instanceof this.Predef.__EffectSig.class) { //│ throw new this.Error("Unhandled effects"); //│ } -//│ tmp +//│ tmp11 //│ = 3 fun f(perform) = @@ -220,8 +231,6 @@ abstract class Cell with fun setVal(Int): () let x = 0 -let k -let m handle h = Cell with fun getVal()(k) = k(x) fun setVal(value)(k) = @@ -367,5 +376,5 @@ fun foo(h) = A() * 100 + a * 10 + b handle h = Eff with fun perform()(k) = k(()) -foo(h) +foo(h) //│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls new file mode 100644 index 0000000000..71a19c742b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls @@ -0,0 +1,37 @@ +:js +:handler + + +// * Notice that the two module definitions are lifted to the top of the block. +:sjs +fun foo(h) = + if false then + module A + A + else + module A + A +//│ JS (unsanitized): +//│ let foo1; +//│ foo1 = function foo(h) { +//│ let A, scrut, A1; +//│ const A$class = class A { +//│ constructor() {} +//│ toString() { return "A"; } +//│ }; A1 = new A$class; +//│ A1.class = A$class; +//│ const A$class1 = class A { +//│ constructor() {} +//│ toString() { return "A"; } +//│ }; A = new A$class1; +//│ A.class = A$class1; +//│ scrut = false; +//│ if (scrut === true) { +//│ return A1; +//│ } else { +//│ return A; +//│ } +//│ }; +//│ null + + diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls index 315352adb3..4c33c43b16 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls @@ -6,8 +6,53 @@ abstract class Effect with fun perform(arg: Str): Str +// FIXME: the `return Lol1` is wrong here +:sjs class Lol(h) with print(h.perform("k")) +//│ JS (unsanitized): +//│ let Lol1; +//│ Lol1 = function Lol(h1) { return new Lol.class(h1); }; +//│ Lol1.class = class Lol { +//│ constructor(h) { +//│ this.h = h; +//│ let tmp, res, Cont$173; +//│ Cont$173 = function Cont$173(pc1) { return new Cont$173.class(pc1); }; +//│ Cont$173.class = class Cont$173 extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp1; +//│ tmp1 = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 0) { +//│ res = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 1) { +//│ return Lol1; +//│ } else if (this.pc === 0) { +//│ tmp = res; +//│ this.pc = 1; +//│ continue contLoop; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$173(" + this.pc + ")"; } +//│ }; +//│ res = this.h.perform("k") ?? null; +//│ if (res instanceof globalThis.Predef.__EffectSig.class) { +//│ res.tail.next = new Cont$173.class(0); +//│ res.tail = res.tail.next; +//│ return res; +//│ } +//│ tmp = res; +//│ Predef.print(tmp) +//│ } +//│ toString() { return "Lol(" + this.h + ")"; } +//│ }; +//│ null let oops = @@ -44,9 +89,10 @@ let oops = k("b") Lol(h) //│ > k -//│ oops = Lol { h: Effect$h$312 {} } +//│ oops = [Function: Lol] { class: [class Lol] } -oops -//│ = Lol { h: Effect$h$312 {} } +:fixme +oops.h +//│ ═══[RUNTIME ERROR] Error: Access to required field 'h' yielded 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlerBlockBindings.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlerBlockBindings.mls index e2dae79b4d..b97a3194bc 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlerBlockBindings.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlerBlockBindings.mls @@ -37,11 +37,11 @@ handle h = Effect with fun perform(arg)(k) = print(arg) val y = 123 -//│ ═══[RUNTIME ERROR] ReferenceError: y is not defined +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: member:y (class hkmc2.semantics.BlockMemberSymbol) :fixme :expect 123 y -//│ ═══[RUNTIME ERROR] Expected: 123, got: undefined +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: member:y (class hkmc2.semantics.BlockMemberSymbol) diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls new file mode 100644 index 0000000000..5316a31765 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls @@ -0,0 +1,7 @@ +:js +:handler + +abstract class Effect + + + diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 38a4c59118..42a866f0bd 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -121,202 +121,202 @@ if true do h1.perform(()) str //│ JS (unsanitized): -//│ let scrut, tmp, tmp1; -//│ this.str = ""; +//│ let str, scrut, tmp6, tmp7, handleBlock$07; +//│ str = ""; //│ scrut = true; //│ if (scrut === true) { -//│ function handleBlock$0() { -//│ let h1, tmp2; -//│ class Effect$h1$680 extends globalThis.Effect { +//│ handleBlock$07 = function handleBlock$0() { +//│ let h1, tmp8, Cont$785, handleBlock$1$0, Effect$h1$697; +//│ Effect$h1$697 = class Effect$h1$697 extends Effect1 { //│ constructor() { -//│ let tmp3; -//│ tmp3 = super(); +//│ let tmp9; +//│ tmp9 = super(); //│ } //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h1, (k) => { -//│ let tmp3, tmp4, tmp5, res; -//│ function Cont$802(pc1) { return new Cont$802.class(pc1); } -//│ Cont$802.class = class Cont$802 extends globalThis.Predef.__Cont.class { +//│ let tmp9, tmp10, tmp11, res3, Cont$806; +//│ Cont$806 = function Cont$806(pc1) { return new Cont$806.class(pc1); }; +//│ Cont$806.class = class Cont$806 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { -//│ let tmp6; -//│ tmp6 = super(null, null); +//│ let tmp12; +//│ tmp12 = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { //│ if (this.pc === 7) { -//│ res = value$; +//│ res3 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 7) { -//│ tmp4 = res; -//│ tmp5 = globalThis.str + "A"; -//│ globalThis.str = tmp5; +//│ tmp10 = res3; +//│ tmp11 = str + "A"; +//│ str = tmp11; //│ return null; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$802(" + this.pc + ")"; } +//│ toString() { return "Cont$806(" + this.pc + ")"; } //│ }; -//│ tmp3 = globalThis.str + "A"; -//│ globalThis.str = tmp3; -//│ res = k(arg) ?? null; -//│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$802.class(7); -//│ res.tail = res.tail.next; -//│ return res; +//│ tmp9 = str + "A"; +//│ str = tmp9; +//│ res3 = k(arg) ?? null; +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = new Cont$806.class(7); +//│ res3.tail = res3.tail.next; +//│ return res3; //│ } -//│ tmp4 = res; -//│ tmp5 = globalThis.str + "A"; -//│ globalThis.str = tmp5; +//│ tmp10 = res3; +//│ tmp11 = str + "A"; +//│ str = tmp11; //│ return null; //│ }); //│ } -//│ toString() { return "Effect$h1$680"; } -//│ } -//│ h1 = new Effect$h1$680(); -//│ function Cont$773(pc1) { return new Cont$773.class(pc1); } -//│ Cont$773.class = class Cont$773 extends globalThis.Predef.__Cont.class { +//│ toString() { return "Effect$h1$697"; } +//│ }; +//│ h1 = new Effect$h1$697(); +//│ Cont$785 = function Cont$785(pc1) { return new Cont$785.class(pc1); }; +//│ Cont$785.class = class Cont$785 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { -//│ let tmp3; -//│ tmp3 = super(null, null); +//│ let tmp9; +//│ tmp9 = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { //│ if (this.pc === 5) { -//│ tmp2 = value$; +//│ tmp8 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 5) { -//│ if (tmp2 instanceof globalThis.Predef.__Return.class) { -//│ return tmp2; +//│ if (tmp8 instanceof globalThis.Predef.__Return.class) { +//│ return tmp8; //│ } //│ this.pc = 6; //│ continue contLoop; //│ } else if (this.pc === 6) { -//│ return tmp2; +//│ return tmp8; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$773(" + this.pc + ")"; } +//│ toString() { return "Cont$785(" + this.pc + ")"; } //│ }; -//│ function handleBlock$1$0() { -//│ let h2, tmp3, res, res1; -//│ class Effect$h2$690 extends globalThis.Effect { +//│ handleBlock$1$0 = function handleBlock$1$0() { +//│ let h2, tmp9, res3, res4, Cont$750, Effect$h2$708; +//│ Effect$h2$708 = class Effect$h2$708 extends Effect1 { //│ constructor() { -//│ let tmp4; -//│ tmp4 = super(); +//│ let tmp10; +//│ tmp10 = super(); //│ } //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h2, (k) => { -//│ let tmp4, tmp5, tmp6, tmp7, tmp8, res2; -//│ function Cont$749(pc1) { return new Cont$749.class(pc1); } -//│ Cont$749.class = class Cont$749 extends globalThis.Predef.__Cont.class { +//│ let tmp10, tmp11, tmp12, tmp13, tmp14, res5, Cont$769; +//│ Cont$769 = function Cont$769(pc1) { return new Cont$769.class(pc1); }; +//│ Cont$769.class = class Cont$769 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { -//│ let tmp9; -//│ tmp9 = super(null, null); +//│ let tmp15; +//│ tmp15 = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { //│ if (this.pc === 4) { -//│ res2 = value$; +//│ res5 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 4) { -//│ tmp6 = res2; -//│ tmp7 = globalThis.str + "B"; -//│ tmp8 = globalThis.str + tmp7; -//│ globalThis.str = tmp8; +//│ tmp12 = res5; +//│ tmp13 = str + "B"; +//│ tmp14 = str + tmp13; +//│ str = tmp14; //│ return null; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$749(" + this.pc + ")"; } +//│ toString() { return "Cont$769(" + this.pc + ")"; } //│ }; -//│ tmp4 = globalThis.str + "B"; -//│ tmp5 = globalThis.str + tmp4; -//│ globalThis.str = tmp5; -//│ res2 = k(arg) ?? null; -//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$749.class(4); -//│ res2.tail = res2.tail.next; -//│ return res2; +//│ tmp10 = str + "B"; +//│ tmp11 = str + tmp10; +//│ str = tmp11; +//│ res5 = k(arg) ?? null; +//│ if (res5 instanceof globalThis.Predef.__EffectSig.class) { +//│ res5.tail.next = new Cont$769.class(4); +//│ res5.tail = res5.tail.next; +//│ return res5; //│ } -//│ tmp6 = res2; -//│ tmp7 = globalThis.str + "B"; -//│ tmp8 = globalThis.str + tmp7; -//│ globalThis.str = tmp8; +//│ tmp12 = res5; +//│ tmp13 = str + "B"; +//│ tmp14 = str + tmp13; +//│ str = tmp14; //│ return null; //│ }); //│ } -//│ toString() { return "Effect$h2$690"; } -//│ } -//│ h2 = new Effect$h2$690(); -//│ function Cont$731(pc1) { return new Cont$731.class(pc1); } -//│ Cont$731.class = class Cont$731 extends globalThis.Predef.__Cont.class { +//│ toString() { return "Effect$h2$708"; } +//│ }; +//│ h2 = new Effect$h2$708(); +//│ Cont$750 = function Cont$750(pc1) { return new Cont$750.class(pc1); }; +//│ Cont$750.class = class Cont$750 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { -//│ let tmp4; -//│ tmp4 = super(null, null); +//│ let tmp10; +//│ tmp10 = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { //│ if (this.pc === 2) { -//│ res = value$; +//│ res3 = value$; //│ } else if (this.pc === 3) { -//│ res1 = value$; +//│ res4 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 2) { -//│ tmp3 = res; -//│ res1 = h1.perform(null) ?? null; -//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = this; +//│ tmp9 = res3; +//│ res4 = h1.perform(null) ?? null; +//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { +//│ res4.tail.next = this; //│ this.pc = 3; -//│ return res1; +//│ return res4; //│ } //│ this.pc = 3; //│ continue contLoop; //│ } else if (this.pc === 3) { -//│ return res1; +//│ return res4; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$731(" + this.pc + ")"; } +//│ toString() { return "Cont$750(" + this.pc + ")"; } //│ }; -//│ res = h2.perform(null) ?? null; -//│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$731(2); -//│ return globalThis.Predef.__handleBlockImpl(res, h2); +//│ res3 = h2.perform(null) ?? null; +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = new Cont$750(2); +//│ return globalThis.Predef.__handleBlockImpl(res3, h2); //│ } -//│ tmp3 = res; -//│ res1 = h1.perform(null) ?? null; -//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$731(3); -//│ return globalThis.Predef.__handleBlockImpl(res1, h2); +//│ tmp9 = res3; +//│ res4 = h1.perform(null) ?? null; +//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { +//│ res4.tail.next = new Cont$750(3); +//│ return globalThis.Predef.__handleBlockImpl(res4, h2); //│ } -//│ return res1; -//│ } -//│ tmp2 = handleBlock$1$0(); -//│ if (tmp2 instanceof globalThis.Predef.__EffectSig.class) { -//│ tmp2.tail.next = new Cont$773(5); -//│ return globalThis.Predef.__handleBlockImpl(tmp2, h1); +//│ return res4; +//│ }; +//│ tmp8 = handleBlock$1$0(); +//│ if (tmp8 instanceof globalThis.Predef.__EffectSig.class) { +//│ tmp8.tail.next = new Cont$785(5); +//│ return globalThis.Predef.__handleBlockImpl(tmp8, h1); //│ } -//│ if (tmp2 instanceof globalThis.Predef.__Return.class) { -//│ return tmp2; +//│ if (tmp8 instanceof globalThis.Predef.__Return.class) { +//│ return tmp8; //│ } -//│ return tmp2; -//│ } -//│ tmp = handleBlock$0(); -//│ if (tmp instanceof this.Predef.__EffectSig.class) { +//│ return tmp8; +//│ }; +//│ tmp6 = handleBlock$07(); +//│ if (tmp6 instanceof this.Predef.__EffectSig.class) { //│ throw new this.Error("Unhandled effects"); //│ } -//│ tmp1 = tmp; +//│ tmp7 = tmp6; //│ } else { -//│ tmp1 = null; +//│ tmp7 = null; //│ } -//│ this.str +//│ 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 d3bd2a4e2f..0514a0637d 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ReturnInHandler.mls @@ -37,7 +37,7 @@ let l = () => fun f()(r) = r(()) return 3 4 -//│ l = [Function (anonymous)] +//│ l = [Function: l] l() //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index fafb7a1954..72bdfcba40 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -20,11 +20,11 @@ fun hi(n) = else hi(n - 1) hi(0) //│ JS (unsanitized): -//│ let res; -//│ function hi(n) { -//│ let scrut, tmp, diff, scrut1, scrut2, scrut3, tmp1, res1; -//│ function Cont$187(pc1) { return new Cont$187.class(pc1); } -//│ Cont$187.class = class Cont$187 extends globalThis.Predef.__Cont.class { +//│ let hi1, res, handleBlock$31; +//│ hi1 = function hi(n) { +//│ let scrut, tmp, diff, scrut1, scrut2, scrut3, tmp1, res1, Cont$188; +//│ Cont$188 = function Cont$188(pc1) { return new Cont$188.class(pc1); }; +//│ Cont$188.class = class Cont$188 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp2; //│ tmp2 = super(null, null); @@ -42,7 +42,7 @@ hi(0) //│ } else { //│ tmp = n - 1; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ return globalThis.hi(tmp); +//│ return hi1(tmp); //│ } //│ this.pc = 1; //│ continue contLoop; @@ -56,7 +56,7 @@ hi(0) //│ break; //│ } //│ } -//│ toString() { return "Cont$187(" + this.pc + ")"; } +//│ toString() { return "Cont$188(" + this.pc + ")"; } //│ }; //│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; //│ scrut1 = diff >= 5; @@ -65,7 +65,7 @@ hi(0) //│ if (scrut3 === true) { //│ res1 = globalThis.Predef.__stackHandler.perform(); //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$187.class(0); +//│ res1.tail.next = new Cont$188.class(0); //│ res1.tail = res1.tail.next; //│ return res1; //│ } @@ -77,21 +77,21 @@ hi(0) //│ } else { //│ tmp = n - 1; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ return globalThis.hi(tmp); +//│ return hi1(tmp); //│ } -//│ } -//│ function handleBlock$3() { -//│ let stackHandler, res1; -//│ class StackDelay$ extends globalThis.Predef.__StackDelay.class { +//│ }; +//│ handleBlock$31 = function handleBlock$3() { +//│ let stackHandler, res1, Cont$212, StackDelay$; +//│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { //│ constructor() { //│ let tmp; //│ tmp = super(); //│ } //│ perform() { //│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res2, curOffset, res3; -//│ function Cont$222(pc1) { return new Cont$222.class(pc1); } -//│ Cont$222.class = class Cont$222 extends globalThis.Predef.__Cont.class { +//│ let res2, curOffset, res3, Cont$225; +//│ Cont$225 = function Cont$225(pc1) { return new Cont$225.class(pc1); }; +//│ Cont$225.class = class Cont$225 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp; //│ tmp = super(null, null); @@ -110,13 +110,13 @@ hi(0) //│ break; //│ } //│ } -//│ toString() { return "Cont$222(" + this.pc + ")"; } +//│ toString() { return "Cont$225(" + this.pc + ")"; } //│ }; //│ curOffset = globalThis.Predef.__stackOffset; //│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; //│ res3 = resume(); //│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$222.class(5); +//│ res3.tail.next = new Cont$225.class(5); //│ res3.tail = res3.tail.next; //│ return res3; //│ } @@ -126,10 +126,10 @@ hi(0) //│ }); //│ } //│ toString() { return "StackDelay$"; } -//│ } +//│ }; //│ stackHandler = new StackDelay$(); -//│ function Cont$210(pc1) { return new Cont$210.class(pc1); } -//│ Cont$210.class = class Cont$210 extends globalThis.Predef.__Cont.class { +//│ Cont$212 = function Cont$212(pc1) { return new Cont$212.class(pc1); }; +//│ Cont$212.class = class Cont$212 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp; //│ tmp = super(null, null); @@ -146,19 +146,19 @@ hi(0) //│ break; //│ } //│ } -//│ toString() { return "Cont$210(" + this.pc + ")"; } +//│ toString() { return "Cont$212(" + this.pc + ")"; } //│ }; //│ globalThis.Predef.__stackDepth = 0; //│ globalThis.Predef.__stackHandler = stackHandler; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res1 = globalThis.hi(0); +//│ res1 = hi1(0); //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$210(4); +//│ res1.tail.next = new Cont$212(4); //│ return globalThis.Predef.__handleBlockImpl(res1, stackHandler); //│ } //│ return res1; -//│ } -//│ res = handleBlock$3(); +//│ }; +//│ res = handleBlock$31(); //│ if (res instanceof this.Predef.__EffectSig.class) { //│ throw new this.Error("Unhandled effects"); //│ } @@ -177,11 +177,11 @@ fun sum(n) = n + sum(n - 1) sum(10000) //│ JS (unsanitized): -//│ let res; -//│ function sum(n) { -//│ let scrut, tmp, tmp1, tmp2, prevDepth, diff, scrut1, scrut2, scrut3, tmp3, res1, res2; -//│ function Cont$339(pc1) { return new Cont$339.class(pc1); } -//│ Cont$339.class = class Cont$339 extends globalThis.Predef.__Cont.class { +//│ let sum3, res1, handleBlock$41; +//│ sum3 = function sum(n) { +//│ let scrut, tmp, tmp1, tmp2, prevDepth, diff, scrut1, scrut2, scrut3, tmp3, res2, res3, Cont$351; +//│ Cont$351 = function Cont$351(pc1) { return new Cont$351.class(pc1); }; +//│ Cont$351.class = class Cont$351 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp4; //│ tmp4 = super(null, null); @@ -189,9 +189,9 @@ sum(10000) //│ } //│ resume(value$) { //│ if (this.pc === 1) { -//│ res2 = value$; +//│ res3 = value$; //│ } else if (this.pc === 0) { -//│ res1 = value$; +//│ res2 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 3) { @@ -202,11 +202,11 @@ sum(10000) //│ tmp = n - 1; //│ prevDepth = globalThis.Predef.__stackDepth; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res2 = globalThis.sum(tmp); -//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = this; +//│ res3 = sum3(tmp); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = this; //│ this.pc = 1; -//│ return res2; +//│ return res3; //│ } //│ this.pc = 1; //│ continue contLoop; @@ -216,32 +216,32 @@ sum(10000) //│ } else if (this.pc === 2) { //│ break contLoop; //│ } else if (this.pc === 1) { -//│ tmp2 = res2; +//│ tmp2 = res3; //│ globalThis.Predef.__stackDepth = prevDepth; //│ tmp1 = tmp2; //│ return n + tmp1; //│ } else if (this.pc === 0) { -//│ tmp3 = res1; +//│ tmp3 = res2; //│ this.pc = 3; //│ continue contLoop; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$339(" + this.pc + ")"; } +//│ toString() { return "Cont$351(" + this.pc + ")"; } //│ }; //│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; //│ scrut1 = diff >= 1000; //│ scrut2 = globalThis.Predef.__stackHandler !== undefined; //│ scrut3 = scrut1 && scrut2; //│ if (scrut3 === true) { -//│ res1 = globalThis.Predef.__stackHandler.perform(); -//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$339.class(0); -//│ res1.tail = res1.tail.next; -//│ return res1; +//│ res2 = globalThis.Predef.__stackHandler.perform(); +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = new Cont$351.class(0); +//│ res2.tail = res2.tail.next; +//│ return res2; //│ } -//│ tmp3 = res1; +//│ tmp3 = res2; //│ } //│ scrut = n == 0; //│ if (scrut === true) { @@ -250,30 +250,30 @@ sum(10000) //│ tmp = n - 1; //│ prevDepth = globalThis.Predef.__stackDepth; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res2 = globalThis.sum(tmp); -//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$339.class(1); -//│ res2.tail = res2.tail.next; -//│ return res2; +//│ res3 = sum3(tmp); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = new Cont$351.class(1); +//│ res3.tail = res3.tail.next; +//│ return res3; //│ } -//│ tmp2 = res2; +//│ tmp2 = res3; //│ globalThis.Predef.__stackDepth = prevDepth; //│ tmp1 = tmp2; //│ return n + tmp1; //│ } -//│ } -//│ function handleBlock$4() { -//│ let stackHandler, res1; -//│ class StackDelay$ extends globalThis.Predef.__StackDelay.class { +//│ }; +//│ handleBlock$41 = function handleBlock$4() { +//│ let stackHandler, res2, Cont$378, StackDelay$; +//│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { //│ constructor() { //│ let tmp; //│ tmp = super(); //│ } //│ perform() { //│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res2, curOffset, res3; -//│ function Cont$377(pc1) { return new Cont$377.class(pc1); } -//│ Cont$377.class = class Cont$377 extends globalThis.Predef.__Cont.class { +//│ let res3, curOffset, res4, Cont$391; +//│ Cont$391 = function Cont$391(pc1) { return new Cont$391.class(pc1); }; +//│ Cont$391.class = class Cont$391 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp; //│ tmp = super(null, null); @@ -281,37 +281,37 @@ sum(10000) //│ } //│ resume(value$) { //│ if (this.pc === 6) { -//│ res3 = value$; +//│ res4 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 6) { -//│ res2 = res3; +//│ res3 = res4; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res2; +//│ return res3; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$377(" + this.pc + ")"; } +//│ toString() { return "Cont$391(" + this.pc + ")"; } //│ }; //│ curOffset = globalThis.Predef.__stackOffset; //│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; -//│ res3 = resume(); -//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$377.class(6); -//│ res3.tail = res3.tail.next; -//│ return res3; +//│ res4 = resume(); +//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { +//│ res4.tail.next = new Cont$391.class(6); +//│ res4.tail = res4.tail.next; +//│ return res4; //│ } -//│ res2 = res3; +//│ res3 = res4; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res2; +//│ return res3; //│ }); //│ } //│ toString() { return "StackDelay$"; } -//│ } +//│ }; //│ stackHandler = new StackDelay$(); -//│ function Cont$365(pc1) { return new Cont$365.class(pc1); } -//│ Cont$365.class = class Cont$365 extends globalThis.Predef.__Cont.class { +//│ Cont$378 = function Cont$378(pc1) { return new Cont$378.class(pc1); }; +//│ Cont$378.class = class Cont$378 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp; //│ tmp = super(null, null); @@ -319,34 +319,34 @@ sum(10000) //│ } //│ resume(value$) { //│ if (this.pc === 5) { -//│ res1 = value$; +//│ res2 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 5) { -//│ return res1; +//│ return res2; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$365(" + this.pc + ")"; } +//│ toString() { return "Cont$378(" + this.pc + ")"; } //│ }; //│ globalThis.Predef.__stackDepth = 0; //│ globalThis.Predef.__stackHandler = stackHandler; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res1 = globalThis.sum(10000); -//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$365(5); -//│ return globalThis.Predef.__handleBlockImpl(res1, stackHandler); +//│ res2 = sum3(10000); +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = new Cont$378(5); +//│ return globalThis.Predef.__handleBlockImpl(res2, stackHandler); //│ } -//│ return res1; -//│ } -//│ res = handleBlock$4(); -//│ if (res instanceof this.Predef.__EffectSig.class) { +//│ return res2; +//│ }; +//│ res1 = handleBlock$41(); +//│ if (res1 instanceof this.Predef.__EffectSig.class) { //│ throw new this.Error("Unhandled effects"); //│ } //│ this.Predef.__stackDepth = 0; //│ this.Predef.__stackHandler = undefined; -//│ res +//│ res1 //│ = 50005000 // stack-overflows without :stackSafe @@ -358,6 +358,7 @@ fun sum(n) = sum(10000) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded +:fixme :handler :stackSafe 100 mut val ctr = 0 @@ -367,10 +368,10 @@ fun foo(f) = else set ctr += 1 dummy(f(f)) -foo(foo) -//│ = 0 -//│ ctr = 10001 +// foo(foo) +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: member:ctr (class hkmc2.semantics.BlockMemberSymbol) +:fixme :stackSafe 1000 :handler :expect 50005000 @@ -381,7 +382,7 @@ val foo = f(10000) foo //│ = 50005000 -//│ foo = 50005000 +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: member:foo (class hkmc2.semantics.BlockMemberSymbol) :re fun foo() = @@ -426,8 +427,8 @@ foo(h) :sjs fun max(a, b) = if a < b then b else a //│ JS (unsanitized): -//│ let res; -//│ function max(a, b) { +//│ let max1, res5, handleBlock$07; +//│ max1 = function max(a, b) { //│ let scrut; //│ scrut = a < b; //│ if (scrut === true) { @@ -435,125 +436,125 @@ fun max(a, b) = if a < b then b else a //│ } else { //│ return a; //│ } -//│ } -//│ function handleBlock$0() { -//│ let stackHandler; -//│ class StackDelay$ extends globalThis.Predef.__StackDelay.class { +//│ }; +//│ handleBlock$07 = function handleBlock$0() { +//│ let stackHandler, StackDelay$; +//│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { //│ constructor() { -//│ let tmp; -//│ tmp = super(); +//│ let tmp1; +//│ tmp1 = super(); //│ } //│ perform() { //│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res1, curOffset, res2; -//│ function Cont$1009(pc1) { return new Cont$1009.class(pc1); } -//│ Cont$1009.class = class Cont$1009 extends globalThis.Predef.__Cont.class { +//│ let res6, curOffset, res7, Cont$1062; +//│ Cont$1062 = function Cont$1062(pc1) { return new Cont$1062.class(pc1); }; +//│ Cont$1062.class = class Cont$1062 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { -//│ let tmp; -//│ tmp = super(null, null); +//│ let tmp1; +//│ tmp1 = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { //│ if (this.pc === 1) { -//│ res2 = value$; +//│ res7 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 1) { -//│ res1 = res2; +//│ res6 = res7; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res1; +//│ return res6; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$1009(" + this.pc + ")"; } +//│ toString() { return "Cont$1062(" + this.pc + ")"; } //│ }; //│ curOffset = globalThis.Predef.__stackOffset; //│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; -//│ res2 = resume(); -//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$1009.class(1); -//│ res2.tail = res2.tail.next; -//│ return res2; +//│ res7 = resume(); +//│ if (res7 instanceof globalThis.Predef.__EffectSig.class) { +//│ res7.tail.next = new Cont$1062.class(1); +//│ res7.tail = res7.tail.next; +//│ return res7; //│ } -//│ res1 = res2; +//│ res6 = res7; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res1; +//│ return res6; //│ }); //│ } //│ toString() { return "StackDelay$"; } -//│ } +//│ }; //│ stackHandler = new StackDelay$(); //│ globalThis.Predef.__stackDepth = 0; //│ globalThis.Predef.__stackHandler = stackHandler; //│ return null; -//│ } -//│ res = handleBlock$0(); -//│ if (res instanceof this.Predef.__EffectSig.class) { +//│ }; +//│ res5 = handleBlock$07(); +//│ if (res5 instanceof this.Predef.__EffectSig.class) { //│ throw new this.Error("Unhandled effects"); //│ } //│ this.Predef.__stackDepth = 0; //│ this.Predef.__stackHandler = undefined; -//│ res +//│ res5 //│ JS (unsanitized): -//│ let res; -//│ function handleBlock$0() { -//│ let stackHandler; -//│ class StackDelay$ extends globalThis.Predef.__StackDelay.class { +//│ let res6, handleBlock$09; +//│ handleBlock$09 = function handleBlock$0() { +//│ let stackHandler, StackDelay$; +//│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { //│ constructor() { -//│ let tmp; -//│ tmp = super(); +//│ let tmp1; +//│ tmp1 = super(); //│ } //│ perform() { //│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res1, curOffset, res2; -//│ function Cont$1090(pc1) { return new Cont$1090.class(pc1); } -//│ Cont$1090.class = class Cont$1090 extends globalThis.Predef.__Cont.class { +//│ let res7, curOffset, res8, Cont$1153; +//│ Cont$1153 = function Cont$1153(pc1) { return new Cont$1153.class(pc1); }; +//│ Cont$1153.class = class Cont$1153 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { -//│ let tmp; -//│ tmp = super(null, null); +//│ let tmp1; +//│ tmp1 = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { //│ if (this.pc === 1) { -//│ res2 = value$; +//│ res8 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 1) { -//│ res1 = res2; +//│ res7 = res8; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res1; +//│ return res7; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$1090(" + this.pc + ")"; } +//│ toString() { return "Cont$1153(" + this.pc + ")"; } //│ }; //│ curOffset = globalThis.Predef.__stackOffset; //│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; -//│ res2 = resume(); -//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$1090.class(1); -//│ res2.tail = res2.tail.next; -//│ return res2; +//│ res8 = resume(); +//│ if (res8 instanceof globalThis.Predef.__EffectSig.class) { +//│ res8.tail.next = new Cont$1153.class(1); +//│ res8.tail = res8.tail.next; +//│ return res8; //│ } -//│ res1 = res2; +//│ res7 = res8; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res1; +//│ return res7; //│ }); //│ } //│ toString() { return "StackDelay$"; } -//│ } +//│ }; //│ stackHandler = new StackDelay$(); //│ globalThis.Predef.__stackDepth = 0; //│ globalThis.Predef.__stackHandler = stackHandler; //│ return null; -//│ } -//│ res = handleBlock$0(); -//│ if (res instanceof this.Predef.__EffectSig.class) { +//│ }; +//│ res6 = handleBlock$09(); +//│ if (res6 instanceof this.Predef.__EffectSig.class) { //│ throw new this.Error("Unhandled effects"); //│ } //│ this.Predef.__stackDepth = 0; //│ this.Predef.__stackHandler = undefined; -//│ res +//│ res6 diff --git a/hkmc2/shared/src/test/mlscript/interop/Number.mls b/hkmc2/shared/src/test/mlscript/interop/Number.mls index e1082dd9c8..da6ffd1b55 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Number.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Number.mls @@ -9,6 +9,7 @@ globalThis.Number("0.50") :fixme let num = _ globalThis.Number() //│ ═══[ERROR] Illegal position for '_' placeholder. +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: num (class hkmc2.semantics.VarSymbol) diff --git a/hkmc2/shared/src/test/mlscript/llir/BadPrograms.mls b/hkmc2/shared/src/test/mlscript/llir/BadPrograms.mls index ec2a852a72..b9b69f1f48 100644 --- a/hkmc2/shared/src/test/mlscript/llir/BadPrograms.mls +++ b/hkmc2/shared/src/test/mlscript/llir/BadPrograms.mls @@ -3,13 +3,13 @@ :llir :cpp + +:todo :ge fun oops(a) = class A with fun m = a let x = 1 -//│ ═══[COMPILATION ERROR] Non top-level definition A not supported -//│ Stopped due to an error during the Llir generation :ge let x = "oops" @@ -17,11 +17,4 @@ x.m //│ ═══[COMPILATION ERROR] Unsupported selection by users //│ Stopped due to an error during the Llir generation -:ge -fun foo(a) = - let x - if a > 0 do - x = 1 - x + 1 -//│ ═══[COMPILATION ERROR] Name not found: x -//│ Stopped due to an error during the Llir generation + diff --git a/hkmc2/shared/src/test/mlscript/llir/BasicCpp.mls b/hkmc2/shared/src/test/mlscript/llir/BasicCpp.mls new file mode 100644 index 0000000000..be1d2e5400 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/llir/BasicCpp.mls @@ -0,0 +1,46 @@ + +:global +:llir +:cpp + + +:scpp +fun foo(a) = + let x + if a > 0 do + x = 1 + x + 1 +//│ +//│ Cpp: +//│ #include "mlsprelude.h" +//│ _mlsValue _mls_j_0(_mlsValue); +//│ _mlsValue _mls_foo(_mlsValue); +//│ _mlsValue _mlsMain(); +//│ _mlsValue _mls_j_0(_mlsValue _mls_x_2) { +//│ _mlsValue _mls_retval; +//│ auto _mls_x_6 = (_mls_x_2 + _mlsValue::fromIntLit(1)); +//│ _mls_retval = _mls_x_6; +//│ return _mls_retval; +//│ } +//│ _mlsValue _mls_foo(_mlsValue _mls_a) { +//│ _mlsValue _mls_retval; +//│ auto _mls_x_0 = _mlsValue::create<_mls_Unit>(); +//│ auto _mls_x_1 = (_mls_a > _mlsValue::fromIntLit(0)); +//│ if (_mlsValue::isIntLit(_mls_x_1, 1)) { +//│ auto _mls_x_3 = _mlsValue::fromIntLit(1); +//│ auto _mls_x_4 = _mlsValue::create<_mls_Unit>(); +//│ _mls_retval = _mls_j_0(_mls_x_3); +//│ } else { +//│ auto _mls_x_5 = _mlsValue::create<_mls_Unit>(); +//│ _mls_retval = _mls_j_0(_mls_x_0); +//│ } +//│ return _mls_retval; +//│ } +//│ _mlsValue _mlsMain() { +//│ _mlsValue _mls_retval; +//│ _mls_retval = _mlsValue::create<_mls_Unit>(); +//│ return _mls_retval; +//│ } +//│ int main() { return _mlsLargeStack(_mlsMainWrapper); } + + diff --git a/hkmc2/shared/src/test/mlscript/llir/LlirScratch.mls b/hkmc2/shared/src/test/mlscript/llir/LlirScratch.mls new file mode 100644 index 0000000000..c15457caa2 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/llir/LlirScratch.mls @@ -0,0 +1,8 @@ +:js +:llir + +:global +:d + + + diff --git a/hkmc2/shared/src/test/mlscript/meta/ImporterTest.mls b/hkmc2/shared/src/test/mlscript/meta/ImporterTest.mls index c4bc84dae9..12244aa8e2 100644 --- a/hkmc2/shared/src/test/mlscript/meta/ImporterTest.mls +++ b/hkmc2/shared/src/test/mlscript/meta/ImporterTest.mls @@ -5,7 +5,18 @@ //│ Imported 1 member(s) +:sjs hello() +//│ JS (unsanitized): +//│ hello1() //│ = 'Hello!' +fun hello() = "Hello?" + +:sjs +hello() +//│ JS (unsanitized): +//│ hello3() +//│ = 'Hello?' + diff --git a/hkmc2/shared/src/test/mlscript/nofib/lambda.mls b/hkmc2/shared/src/test/mlscript/nofib/lambda.mls index 77378530de..6c5094027b 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/lambda.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/lambda.mls @@ -136,7 +136,7 @@ fun simpleEvalCon(env, e) = if e_ is Con(c) then c else throw Error("Not a Con") //│ ———————————————————————————————————————————————————————————————————————————————— -fun bracket(ot, this, t) = if this <= ot then "(" :: (t +: nofibStringToList(")")) else t +fun bracket(ot, ths, t) = if ths <= ot then "(" :: (t +: nofibStringToList(")")) else t :... //│ ———————————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/nofib/rsa.mls b/hkmc2/shared/src/test/mlscript/nofib/rsa.mls index 7e9757e219..b1124e2875 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/rsa.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/rsa.mls @@ -62,7 +62,6 @@ fun size(n) = intDiv(listLen(string_of_z(n)) * 47, 100) fun encrypt(n, e, s) = unlines of map of c => string_of_z(power(e, n, code(c))) collect(size(n), s) - fun power(n, m, x) = if z_equal(n, const0) then const1 even(n) then z_mod(z_sqr(power(z_div(n, const2), m, x)), m) diff --git a/hkmc2/shared/src/test/mlscript/parser/Extends.mls b/hkmc2/shared/src/test/mlscript/parser/Extends.mls index 03ade9d862..cada64ad4c 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Extends.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Extends.mls @@ -4,43 +4,43 @@ class Base class Derived extends Base //│ Env: -//│ Base -> SelElem(RefElem(globalThis:block#1),Base,Some(member:Base)) -//│ Derived -> SelElem(RefElem(globalThis:block#1),Derived,Some(member:Derived)) +//│ Base -> RefElem(member:Base) +//│ Derived -> RefElem(member:Derived) // Extension with parameters abstract class Box class IntBox(val value: Int) extends Box class StrBox(val value: Str) extends Box //│ Env: -//│ Box -> SelElem(RefElem(globalThis:block#2),Box,Some(member:Box)) -//│ IntBox -> SelElem(RefElem(globalThis:block#2),IntBox,Some(member:IntBox)) -//│ StrBox -> SelElem(RefElem(globalThis:block#2),StrBox,Some(member:StrBox)) +//│ Box -> RefElem(member:Box) +//│ IntBox -> RefElem(member:IntBox) +//│ StrBox -> RefElem(member:StrBox) // Extension with symbolic name abstract class Ring module End extends Ring class (++) Link(left: Ring, right: Ring) extends Ring //│ Env: -//│ ++ -> SelElem(RefElem(globalThis:block#3),Link,Some(member:Link)) -//│ End -> SelElem(RefElem(globalThis:block#3),End,Some(member:End)) -//│ Link -> SelElem(RefElem(globalThis:block#3),Link,Some(member:Link)) -//│ Ring -> SelElem(RefElem(globalThis:block#3),Ring,Some(member:Ring)) +//│ ++ -> RefElem(member:Link) +//│ End -> RefElem(member:End) +//│ Link -> RefElem(member:Link) +//│ Ring -> RefElem(member:Ring) // Extension with type parameters abstract class Option[T]: (Some[T] | None) class Some[T](val value: T) extends Option[T] module None extends Option[nothing] //│ Env: -//│ None -> SelElem(RefElem(globalThis:block#4),None,Some(member:None)) -//│ Option -> SelElem(RefElem(globalThis:block#4),Option,Some(member:Option)) -//│ Some -> SelElem(RefElem(globalThis:block#4),Some,Some(member:Some)) +//│ None -> RefElem(member:None) +//│ Option -> RefElem(member:Option) +//│ Some -> RefElem(member:Some) // Extension with type parameters and symbolic name abstract class List[T]: (Cons[T] | Nil) class (::) Cons[T](val head: T, val tail: List[T]) extends List[T] module Nil extends List[nothing] //│ Env: -//│ :: -> SelElem(RefElem(globalThis:block#5),Cons,Some(member:Cons)) -//│ Cons -> SelElem(RefElem(globalThis:block#5),Cons,Some(member:Cons)) -//│ List -> SelElem(RefElem(globalThis:block#5),List,Some(member:List)) -//│ Nil -> SelElem(RefElem(globalThis:block#5),Nil,Some(member:Nil)) +//│ :: -> RefElem(member:Cons) +//│ Cons -> RefElem(member:Cons) +//│ List -> RefElem(member:List) +//│ Nil -> RefElem(member:Nil) diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index 44c98d1072..dee52fc9e7 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$161),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } } +//│ Elab: { { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$156),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); { member:foo#666(h#666) } } } :e ( @@ -73,7 +73,7 @@ handle h = Eff with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$190),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$190),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } +//│ Elab: { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$185),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$185),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { member:foo#666(h#666) } } :e handle h = Eff with @@ -127,4 +127,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.125: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$267),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$267),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { globalThis:block#5#666(.)foo‹member:foo›(h#666) } } +//│ Elab: { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$263),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$263),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { member:foo#666(h#666) } } diff --git a/hkmc2/shared/src/test/mlscript/parser/PrefixOps.mls b/hkmc2/shared/src/test/mlscript/parser/PrefixOps.mls index d40fee427f..e8f6dfda61 100644 --- a/hkmc2/shared/src/test/mlscript/parser/PrefixOps.mls +++ b/hkmc2/shared/src/test/mlscript/parser/PrefixOps.mls @@ -19,7 +19,7 @@ //│ ║ l.15: 1 //│ ╙── ^ //│ JS (unsanitized): -//│ let tmp; tmp = + 2; + 3 +//│ let tmp2; tmp2 = + 2; + 3 //│ ╔══[WARNING] Pure expression in statement position //│ ║ l.15: 1 //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls b/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls index 05fb9033e8..c506e46304 100644 --- a/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls +++ b/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls @@ -28,13 +28,14 @@ Cross.unapply("0") :sjs fun foo(x) = x is Cross //│ JS (unsanitized): -//│ function foo(x) { +//│ let foo1; +//│ foo1 = function foo(x2) { //│ let matchResult; -//│ matchResult = globalThis.Cross.unapply(x) ?? null; +//│ matchResult = Cross1.unapply(x2) ?? null; //│ if (matchResult instanceof globalThis.Predef.MatchResult.class) { //│ return true; //│ } else { //│ return false; //│ } -//│ } +//│ }; //│ null diff --git a/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls b/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls index ec8615bb29..5632be018d 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls @@ -52,8 +52,8 @@ module \$ extends Foo //│ ╔══[PARSE ERROR] Expected end of input; found error instead //│ ║ l.46: module \$ extends Foo //│ ╙── ^ -//│ > try { const \$class = class \ { constructor() {} toString() { return "\"; } }; this.\ = new \$class; this.\.class = \$class; null } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^ +//│ > let $_1;try { const $_$class = class \ { constructor() {} toString() { return "\"; } }; $_1 = new $_$class; $_1.class = $_$class; null } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ > ^ //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Invalid or unexpected token diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls index 6f65dd0989..c37e716c80 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls @@ -88,8 +88,8 @@ fun //│ ╔══[WARNING] This annotation has no effect. //│ ║ l.82: @inline let //│ ╙── ^^^^^^ -//│ abs = [Function (anonymous)] -//│ clamp = [Function (anonymous)] +//│ abs = [Function: abs] +//│ clamp = [Function: clamp] :w // Only the first variable is annotated with @inline. diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls index d748193171..b953c7b0a0 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls @@ -17,20 +17,20 @@ open Str :w @debug 0 -//│ ╔══[WARNING] This annotation has no effect +//│ ╔══[WARNING] This annotation has no effect. //│ ║ l.19: @debug 0 //│ ║ ^^^^^ -//│ ╟── Annotations are not supported on integer literal terms. +//│ ╟── Such annotations are not supported on integer literal terms. //│ ║ l.19: @debug 0 //│ ╙── ^ //│ = 0 :w @debug 1 + 2 -//│ ╔══[WARNING] This annotation has no effect +//│ ╔══[WARNING] This annotation has no effect. //│ ║ l.29: @debug 1 + 2 //│ ║ ^^^^^ -//│ ╟── Annotations are not supported on application terms. +//│ ╟── Such annotations are not supported on application terms. //│ ║ l.29: @debug 1 + 2 //│ ╙── ^^^^^ //│ = 3 @@ -40,7 +40,7 @@ open Str //│ ╔══[WARNING] This annotation has no effect. //│ ║ l.39: (@debug 1 + 2) //│ ║ ^^^^^ -//│ ╟── Annotations are not supported on application terms. +//│ ╟── Such annotations are not supported on application terms. //│ ║ l.39: (@debug 1 + 2) //│ ╙── ^^^^^ //│ = 3 @@ -50,7 +50,7 @@ open Str //│ ╔══[WARNING] This annotation has no effect. //│ ║ l.49: (@debug 1) + 2 //│ ║ ^^^^^ -//│ ╟── Annotations are not supported on integer literal terms. +//│ ╟── Such annotations are not supported on integer literal terms. //│ ║ l.49: (@debug 1) + 2 //│ ╙── ^ //│ = 3 @@ -60,7 +60,7 @@ open Str //│ ╔══[WARNING] This annotation has no effect. //│ ║ l.59: (1 + @debug 2) //│ ║ ^^^^^ -//│ ╟── Annotations are not supported on integer literal terms. +//│ ╟── Such annotations are not supported on integer literal terms. //│ ║ l.59: (1 + @debug 2) //│ ╙── ^ //│ = 3 @@ -72,7 +72,7 @@ id(@Log 5) //│ ╔══[WARNING] This annotation has no effect. //│ ║ l.71: id(@Log 5) //│ ║ ^^^ -//│ ╟── Annotations are not supported on integer literal terms. +//│ ╟── Such annotations are not supported on integer literal terms. //│ ║ l.71: id(@Log 5) //│ ╙── ^ //│ = 5 @@ -83,10 +83,10 @@ id(@Log 5) //│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead //│ ║ l.82: @1 + 2 class Qux //│ ╙── ^^^^^ -//│ ╔══[WARNING] This annotation has no effect +//│ ╔══[WARNING] This annotation has no effect. //│ ║ l.82: @1 + 2 class Qux //│ ║ ^ -//│ ╟── Annotations are not supported on application terms. +//│ ╟── Such annotations are not supported on application terms. //│ ║ l.82: @1 + 2 class Qux //│ ╙── ^^^ //│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls b/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls index bb56f001ff..6581f59b6c 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls @@ -8,10 +8,10 @@ type List = Nil | Cons module Nil class (::) Cons(head: Int, tail: List) //│ Env: -//│ :: -> SelElem(RefElem(globalThis:block#1),Cons,Some(member:Cons)) -//│ Cons -> SelElem(RefElem(globalThis:block#1),Cons,Some(member:Cons)) -//│ List -> SelElem(RefElem(globalThis:block#1),List,Some(member:List)) -//│ Nil -> SelElem(RefElem(globalThis:block#1),Nil,Some(member:Nil)) +//│ :: -> RefElem(member:Cons) +//│ Cons -> RefElem(member:Cons) +//│ List -> RefElem(member:List) +//│ Nil -> RefElem(member:Nil) Cons(1, Nil) diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls index 3cee46b469..6069bc317f 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls @@ -11,11 +11,11 @@ x => if x is Pair(A, B) then 1 //│ JS (unsanitized): //│ (x) => { //│ let param0, param1; -//│ if (x instanceof this.Pair.class) { +//│ if (x instanceof Pair1.class) { //│ param0 = x.a; //│ param1 = x.b; -//│ if (param0 instanceof this.A) { -//│ if (param1 instanceof this.B) { +//│ if (param0 instanceof A1) { +//│ if (param1 instanceof B1) { //│ return 1; //│ } else { //│ throw new this.Error("match error"); @@ -36,20 +36,21 @@ fun f(x) = if x is Pair(A, A) then 1 Pair(B, B) then 2 //│ JS (unsanitized): -//│ function f(x) { +//│ let f1; +//│ f1 = function f(x) { //│ let param0, param1; -//│ if (x instanceof globalThis.Pair.class) { +//│ if (x instanceof Pair1.class) { //│ param0 = x.a; //│ param1 = x.b; -//│ if (param0 instanceof globalThis.A) { -//│ if (param1 instanceof globalThis.A) { +//│ if (param0 instanceof A1) { +//│ if (param1 instanceof A1) { //│ return 1; //│ } else { //│ throw new globalThis.Error("match error"); //│ } //│ } else { -//│ if (param0 instanceof globalThis.B) { -//│ if (param1 instanceof globalThis.B) { +//│ if (param0 instanceof B1) { +//│ if (param1 instanceof B1) { //│ return 2; //│ } else { //│ throw new globalThis.Error("match error"); @@ -61,7 +62,7 @@ fun f(x) = if x is //│ } else { //│ throw new globalThis.Error("match error"); //│ } -//│ } +//│ }; //│ null diff --git a/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls b/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls index 3cfc65dc9d..f353320767 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls @@ -70,7 +70,7 @@ fun example(args) = //│ Blk: //│ stats = Ls of //│ TermDefinition: -//│ owner = S of globalThis:block#2 +//│ owner = N //│ k = Fun //│ sym = member:example //│ params = Ls of @@ -88,9 +88,7 @@ fun example(args) = //│ desugared = Let: //│ sym = $scrut //│ term = App: -//│ lhs = SynthSel{member:foo}: -//│ prefix = Ref of globalThis:block#1 -//│ nme = Ident of "foo" +//│ lhs = Ref of member:foo //│ rhs = Tup of Ls of //│ Fld: //│ flags = () @@ -135,9 +133,7 @@ fun example(args) = //│ tail = Let: \ //│ sym = $scrut //│ term = App: -//│ lhs = SynthSel{member:pipe}: -//│ prefix = Ref of globalThis:block#1 -//│ nme = Ident of "pipe" +//│ lhs = Ref of member:pipe //│ rhs = Tup of Ls of //│ Fld: //│ flags = () @@ -145,9 +141,7 @@ fun example(args) = //│ asc = N //│ Fld: //│ flags = () -//│ term = SynthSel{member:abs}: -//│ prefix = Ref of globalThis:block#1 -//│ nme = Ident of "abs" +//│ term = Ref of member:abs //│ asc = N //│ tail = Let: \ //│ sym = $scrut diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls index 06180e44be..aea08d6062 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls @@ -4,6 +4,6 @@ fun id(x) = x if id(0) is t then t //│ Desugared: //│ > if -//│ > let $scrut = member:Predef#666(.)id‹member:id›(0) +//│ > let $scrut = (member:Predef#666.)id‹member:id›(0) //│ > let t = $scrut#0 //│ > else t#666 diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls index 80b364e3fb..bffbea9ad7 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls @@ -17,7 +17,8 @@ fun nonsense(xs) = if xs is [..ys] then ys [] then "empty" //│ JS (unsanitized): -//│ function nonsense(xs) { +//│ let nonsense1; +//│ nonsense1 = function nonsense(xs) { //│ let rest, ys; //│ if (globalThis.Array.isArray(xs) && xs.length >= 0) { //│ rest = globalThis.Predef.tupleSlice(xs, 0, 0) ?? null; @@ -26,7 +27,7 @@ fun nonsense(xs) = if xs is //│ } else { //│ throw new globalThis.Error("match error"); //│ } -//│ } +//│ }; //│ null nonsense([]) @@ -40,7 +41,8 @@ fun lead_and_last(xs) = if xs is [x, ..ys, y] then x + y [] then 0 //│ JS (unsanitized): -//│ function lead_and_last(xs) { +//│ let lead$_and$_last1; +//│ lead$_and$_last1 = function lead_and_last(xs) { //│ let last0, rest, first0, x, ys, y; //│ if (globalThis.Array.isArray(xs) && xs.length >= 2) { //│ first0 = xs[0]; @@ -57,7 +59,7 @@ fun lead_and_last(xs) = if xs is //│ throw new globalThis.Error("match error"); //│ } //│ } -//│ } +//│ }; //│ null lead_and_last(["foo", "bar"]) @@ -78,8 +80,9 @@ fun nested_tuple_patterns(xs) = if xs is [x, ..[y, z], w] then x + y + z + w [] then 0 //│ JS (unsanitized): -//│ function nested_tuple_patterns(xs) { -//│ let last0, rest, first0, x, first1, first01, y, z, w, tmp, tmp1; +//│ let nested$_tuple$_patterns1; +//│ nested$_tuple$_patterns1 = function nested_tuple_patterns(xs) { +//│ let last0, rest, first0, x, first1, first01, y, z, w, tmp2, tmp3; //│ if (globalThis.Array.isArray(xs) && xs.length >= 2) { //│ first0 = xs[0]; //│ rest = globalThis.Predef.tupleSlice(xs, 1, 1) ?? null; @@ -91,9 +94,9 @@ fun nested_tuple_patterns(xs) = if xs is //│ y = first01; //│ z = first1; //│ w = last0; -//│ tmp = x + y; -//│ tmp1 = tmp + z; -//│ return tmp1 + w; +//│ tmp2 = x + y; +//│ tmp3 = tmp2 + z; +//│ return tmp3 + w; //│ } else { //│ throw new globalThis.Error("match error"); //│ } @@ -104,7 +107,7 @@ fun nested_tuple_patterns(xs) = if xs is //│ throw new globalThis.Error("match error"); //│ } //│ } -//│ } +//│ }; //│ null fun hack(tupleSlice) = if tupleSlice is diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls index 02afbd7766..ece42a88e9 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls @@ -11,7 +11,7 @@ fun f(x) = //│ Blk: //│ stats = Ls of //│ TermDefinition: -//│ owner = S of globalThis:block#1 +//│ owner = N //│ k = Fun //│ sym = member:f //│ params = Ls of diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/DiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/DiffMaker.scala index 6b65d8d1d6..40beb9b134 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/DiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/DiffMaker.scala @@ -268,12 +268,12 @@ abstract class DiffMaker: case line :: ls if line.startsWith("//") => out.println(line) rec(ls) - case line :: ls if line.startsWith(output.diffBegMarker) => // Check if there are unmerged git conflicts + case begLine :: ls if begLine.startsWith(output.diffBegMarker) => // Check if there are unmerged git conflicts val diff = ls.takeWhile(l => !l.startsWith(output.diffEndMarker)) assert(diff.exists(_.startsWith(output.diffMidMarker)), diff) val rest = ls.drop(diff.length) - val hdo = rest.headOption - assert(hdo.exists(_.startsWith(output.diffEndMarker)), hdo) + val hdo = rest.head + assert(hdo.startsWith(output.diffEndMarker), hdo) val blankLines = diff.count(_.isEmpty) val hasBlankLines = diff.exists(_.isEmpty) if diff.forall(l => l.startsWith(output.outputMarker) || l.startsWith(output.diffMidMarker) || l.startsWith(output.diff3MidMarker) || l.isEmpty) then { @@ -284,9 +284,9 @@ abstract class DiffMaker: doFail(blockLineNum, s"Unmerged non-output changes at $relativeName.${file.ext}:" + blockLineNum) unmergedChanges += allLines.size - lines.size + 1 - out.println(output.diffBegMarker) + out.println(begLine) diff.foreach(out.println) - out.println(output.diffEndMarker) + out.println(hdo) } if hasBlankLines then resetCommands rec(rest.tail) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala index d6293e5680..4ac71b4a0f 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -100,26 +100,27 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: if showLoweredTree.isSet then output(s"Lowered:") output(le.showAsTree) - if ppLoweredTree.isSet then - output(s"Pretty Lowered:") - output(Printer.mkDocument(le)(using summon[Raise], baseScp.nest).toString) - // * Note that the codegen scope is not in sync with curCtx in terms of its `this` symbol. - // * We do not nest TopLevelSymbol in codegen `Scope`s - // * to avoid needlessly generating new variable names in separate blocks. - val nestedScp = baseScp.nest + // * We used to do this to avoid needlessly generating new variable names in separate blocks: + // val nestedScp = baseScp.nest + val nestedScp = baseScp // val nestedScp = codegen.js.Scope(S(baseScp), curCtx.outer, collection.mutable.Map.empty) // * not needed - val je = nestedScp.givenIn: - jsb.program(le, N, wd) - val jsStr = je.stripBreaks.mkString(100) + if ppLoweredTree.isSet then + output(s"Pretty Lowered:") + output(Printer.mkDocument(le)(using summon[Raise], nestedScp).toString) + + val (pre, js) = nestedScp.givenIn: + jsb.worksheet(le) + val preStr = pre.stripBreaks.mkString(100) + val jsStr = js.stripBreaks.mkString(100) if showSanitizedJS.isSet then output(s"JS:") output(jsStr) - def mkQuery(prefix: Str, jsStr: Str) = + def mkQuery(prefix: Str, preStr: Str, jsStr: Str) = import hkmc2.Message.MessageContext val queryStr = jsStr.replaceAll("\n", " ") - val (reply, stderr) = host.query(queryStr, !expectRuntimeOrCodeGenErrors && fixme.isUnset && todo.isUnset) + val (reply, stderr) = host.query(preStr, queryStr, !expectRuntimeOrCodeGenErrors && fixme.isUnset && todo.isUnset) reply match case ReplHost.Result(content, stdout) => stdout match @@ -161,7 +162,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: "globalThis.Predef.TraceLogger.enabled = true; " + "globalThis.Predef.TraceLogger.resetIndent(0)") - mkQuery("", jsStr) + mkQuery("", preStr, jsStr) if traceJS.isSet then host.execute("globalThis.Predef.TraceLogger.enabled = false") @@ -173,6 +174,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: case S(ts: TermSymbol) if ts.k.isInstanceOf[syntax.ValLike] => S((nme, ts)) case S(ts: BlockMemberSymbol) if ts.trmImplTree.exists(_.k.isInstanceOf[syntax.ValLike]) => S((nme, ts)) + case S(vs: VarSymbol) => S((nme, vs)) case _ => N case _ => N if silent.isUnset then definedValues.toSeq.sortBy(_._1).foreach: (nme, sym) => @@ -180,7 +182,6 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: val je = nestedScp.givenIn: jsb.block(le) val jsStr = je.stripBreaks.mkString(100) - mkQuery(s"$nme ", jsStr) - + mkQuery(s"$nme ", "", jsStr) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 2590edc769..2337c74f4d 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -115,7 +115,7 @@ abstract class MLsDiffMaker extends DiffMaker: val res = p.parseAll(p.block(allowNewlines = true)) val imprtSymbol = semantics.TopLevelSymbol("import#"+file.baseName) - given Elaborator.Ctx = curCtx.nest(S(imprtSymbol)) + given Elaborator.Ctx = curCtx.nest(N) val elab = Elaborator(etl, wd) try val resBlk = new syntax.Tree.Block(res) @@ -173,10 +173,11 @@ abstract class MLsDiffMaker extends DiffMaker: def processTrees(trees: Ls[syntax.Tree])(using Raise): Unit = val elab = Elaborator(etl, file / os.up) - val blockSymbol = - semantics.TopLevelSymbol("block#"+blockNum) + // val blockSymbol = + // semantics.TopLevelSymbol("block#"+blockNum) blockNum += 1 - given Elaborator.Ctx = curCtx.nest(S(blockSymbol)) + // given Elaborator.Ctx = curCtx.nest(S(blockSymbol)) + given Elaborator.Ctx = curCtx.nest(N) val blk = new syntax.Tree.Block(trees) val (e, newCtx) = elab.topLevel(blk) curCtx = newCtx From 68d7a2bdb7e9688218efd333c071b14a6c9e1eca Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 22 Jan 2025 23:03:15 +0800 Subject: [PATCH 084/114] complete refactor --- .../src/main/scala/hkmc2/codegen/Block.scala | 128 +++++------ .../scala/hkmc2/codegen/HandlerLowering.scala | 2 +- .../hkmc2/codegen/StackSafeTransform.scala | 210 ++++++++---------- hkmc2/shared/src/test/mlscript/HkScratch.mls | 13 +- .../test/mlscript/handlers/StackSafety.mls | 150 ++----------- 5 files changed, 182 insertions(+), 321 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 0915d3cd5a..230e2e33e7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -100,80 +100,62 @@ sealed abstract class Block extends Product with AutoLocated: case _: Return | _: Throw | _: Label | _: Break | _: Continue | _: End | _: HandleBlockReturn => Nil // Moves definitions in a block to the top. Only scans one definition deep, i.e. definitions inside other definitions - // are not moved out. - // - // outerOnly = true: only top-level definitions (which could be exported) are moved to the top, which is essentially - // a re-ordering of statements. - // - // outerOnly = false: definitions inside `if` and `while` statements are also moved out. - def floatOutDefns(outerOnly: Bool) = - def rec(b: Block, acc: List[Defn]): (Block, List[Defn]) = - b match - case Match(scrut, arms, dflt, rest) => - if outerOnly then - val (rstRes, rstDefns) = rec(rest, acc) - (Match(scrut, arms, dflt, rstRes), rstDefns) - else - val (armsRes, armsDefns) = arms.foldLeft[(List[(Case, Block)], List[Defn])](Nil, acc)( - (accc, d) => - val (accCases, accDefns) = accc - val (cse, blk) = d - val (resBlk, resDefns) = rec(blk, accDefns) - ((cse, resBlk) :: accCases, resDefns) - ) - dflt match - case None => - val (rstRes, rstDefns) = rec(rest, armsDefns) - (Match(scrut, armsRes, None, rstRes), rstDefns) - - case Some(dflt) => - val (dfltRes, dfltDefns) = rec(dflt, armsDefns) - val (rstRes, rstDefns) = rec(rest, dfltDefns) - (Match(scrut, armsRes, S(dfltRes), rstRes), rstDefns) - - case Return(res, implct) => (b, acc) - case Throw(exc) => (b, acc) - case Label(label, body, rest) => - if outerOnly then - val (rstRes, rstDefns) = rec(rest, acc) - (Label(label, body, rstRes), rstDefns) - else - val (bodyRes, bodyDefns) = rec(body, acc) - val (rstRes, rstDefns) = rec(rest, bodyDefns) - (Label(label, bodyRes, rstRes), rstDefns) - case Break(label) => (b, acc) - case Continue(label) => (b, acc) - case Begin(sub, rest) => - val (subRes, subDefns) = rec(sub, acc) - val (rstRes, rstDefns) = rec(rest, subDefns) - (Begin(subRes, rstRes), rstDefns) - case TryBlock(sub, finallyDo, rest) => - if outerOnly then - val (rstRes, rstDefns) = rec(rest, acc) - (TryBlock(sub, finallyDo, rstRes), rstDefns) - else - val (subRes, subDefns) = rec(sub, acc) - val (finallyRes, finallyDefns) = rec(rest, subDefns) - val (rstRes, rstDefns) = rec(rest, finallyDefns) - (TryBlock(subRes, finallyRes, rstRes), rstDefns) - case Assign(lhs, rhs, rest) => + // are not moved out. Definitions inside `if` and `while` statements are moved out. + def floatOutDefns = + def rec(b: Block, acc: List[Defn]): (Block, List[Defn]) = b match + case Match(scrut, arms, dflt, rest) => + val (armsRes, armsDefns) = arms.foldLeft[(List[(Case, Block)], List[Defn])](Nil, acc)( + (accc, d) => + val (accCases, accDefns) = accc + val (cse, blk) = d + val (resBlk, resDefns) = rec(blk, accDefns) + ((cse, resBlk) :: accCases, resDefns) + ) + dflt match + case None => + val (rstRes, rstDefns) = rec(rest, armsDefns) + (Match(scrut, armsRes, None, rstRes), rstDefns) + + case Some(dflt) => + val (dfltRes, dfltDefns) = rec(dflt, armsDefns) + val (rstRes, rstDefns) = rec(rest, dfltDefns) + (Match(scrut, armsRes, S(dfltRes), rstRes), rstDefns) + + case Return(res, implct) => (b, acc) + case Throw(exc) => (b, acc) + case Label(label, body, rest) => + val (bodyRes, bodyDefns) = rec(body, acc) + val (rstRes, rstDefns) = rec(rest, bodyDefns) + (Label(label, bodyRes, rstRes), rstDefns) + case Break(label) => (b, acc) + case Continue(label) => (b, acc) + case Begin(sub, rest) => + val (subRes, subDefns) = rec(sub, acc) + val (rstRes, rstDefns) = rec(rest, subDefns) + (Begin(subRes, rstRes), rstDefns) + case TryBlock(sub, finallyDo, rest) => + val (subRes, subDefns) = rec(sub, acc) + val (finallyRes, finallyDefns) = rec(rest, subDefns) + val (rstRes, rstDefns) = rec(rest, finallyDefns) + (TryBlock(subRes, finallyRes, rstRes), rstDefns) + case Assign(lhs, rhs, rest) => + val (rstRes, rstDefns) = rec(rest, acc) + (Assign(lhs, rhs, rstRes), rstDefns) + case a @ AssignField(path, nme, result, rest) => + val (rstRes, rstDefns) = rec(rest, acc) + (AssignField(path, nme, result, rstRes)(a.symbol), rstDefns) + case Define(defn, rest) => defn match + case ValDefn(owner, k, sym, rhs) => val (rstRes, rstDefns) = rec(rest, acc) - (Assign(lhs, rhs, rstRes), rstDefns) - case a @ AssignField(path, nme, result, rest) => - val (rstRes, rstDefns) = rec(rest, acc) - (AssignField(path, nme, result, rstRes)(a.symbol), rstDefns) - case Define(defn, rest) => defn match - case ValDefn(owner, k, sym, rhs) => - val (rstRes, rstDefns) = rec(rest, acc) - (Define(defn, rstRes), rstDefns) - case _ => - val (rstRes, rstDefns) = rec(rest, defn :: acc) - (rstRes, rstDefns) - case HandleBlock(lhs, res, par, cls, handlers, body, rest) => - val (rstRes, rstDefns) = rec(rest, acc) - (HandleBlock(lhs, res, par, cls, handlers, body, rstRes), rstDefns) - case HandleBlockReturn(res) => (b, acc) - case End(msg) => (b, acc) + (Define(defn, rstRes), rstDefns) + case _ => + val (rstRes, rstDefns) = rec(rest, defn :: acc) + (rstRes, rstDefns) + case HandleBlock(lhs, res, par, cls, handlers, body, rest) => + val (rstRes, rstDefns) = rec(rest, acc) + (HandleBlock(lhs, res, par, cls, handlers, body, rstRes), rstDefns) + case HandleBlockReturn(res) => (b, acc) + case End(msg) => (b, acc) rec(this, Nil) end Block diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 260dcda6a5..94d2bffb44 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -327,7 +327,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): 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(false) + val (blk, defns) = b.floatOutDefns val clsDefns = defns.collect: case ClsLikeDefn(own, isym, sym, k, paramsOpt, parentPath, methods, privateFields, publicFields, preCtor, ctor) => sym diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 85a2857304..8922a7c9e5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -8,6 +8,7 @@ import hkmc2.semantics.Elaborator.State import hkmc2.semantics.* import hkmc2.syntax.Tree import hkmc2.syntax.Keyword.`with` +import scala.compiletime.ops.boolean class StackSafeTransform(depthLimit: Int)(using State): @@ -31,66 +32,100 @@ class StackSafeTransform(depthLimit: Int)(using State): // Increases the stack depth, assigns the call to a value, then decreases the stack depth // then binds that value to a desired block def extractRes(res: Result, isTailCall: Bool, f: Result => Block) = - res match - case Call(Value.Ref(s: BuiltinSymbol), args) => f(res) - case _: Call | _: Instantiate => - if isTailCall then - blockBuilder - .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) - .ret(res) - else - val tmp = TempSymbol(None, "tmp") - val prevDepth = TempSymbol(None, "prevDepth") - blockBuilder - .assign(prevDepth, stackDepthPath) - .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) - .assign(tmp, res) - .assignFieldN(predefPath, STACK_DEPTH_IDENT, prevDepth.asPath) - .rest(f(tmp.asPath)) - case _ => f(res) + if isTailCall then + blockBuilder + .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) + .ret(res) + else + val tmp = TempSymbol(None, "tmp") + val prevDepth = TempSymbol(None, "prevDepth") + blockBuilder + .assign(prevDepth, stackDepthPath) + .assignFieldN(predefPath, STACK_DEPTH_IDENT, op("+", stackDepthPath, intLit(1))) + .assign(tmp, res) + .assignFieldN(predefPath, STACK_DEPTH_IDENT, prevDepth.asPath) + .rest(f(tmp.asPath)) - // Rewrites anything that can contain a Call to increase the stack depth - def transform(b: Block): Block = - // 1. rewrite lambdas - def firstPass(b: Block): Block = - val transform = new BlockTransformerShallow(SymbolSubst()): - override def applyValue(v: Value): Value = v match - case Value.Lam(params, body) => Value.Lam(params, rewriteBlk(body)) - case _ => super.applyValue(v) - - override def applyBlock(b: Block): Block = b match - case HandleBlock(lhs, res, par, cls, handlers, body, rest) => - HandleBlock( - lhs, res, par, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, applyBlock(h.body))), - applyBlock(body), applyBlock(rest) - ) - case _ => super.applyBlock(b) - - transform.applyBlock(b) + def extractResTopLevel(res: Result, isTailCall: Bool, f: Result => Block) = + val resumeSym = VarSymbol(Tree.Ident("resume")) + val handlerSym = TempSymbol(None, "stackHandler") + val resSym = TempSymbol(None, "res") + val handlerRes = TempSymbol(None, "res") + val curOffsetSym = TempSymbol(None, "curOffset") - // 2. rewrite calls and definitions - def secondPass(b: Block): Block = - val transform = new BlockTransformerShallow(SymbolSubst()): - override def applyDefn(defn: Defn): Defn = rewriteDefn(defn) - - override def applyBlock(b: Block): Block = b match - case Return(c: Call, implct) => extractRes(c, true, Return(_, false)) - case Return(res, implct) => extractRes(res, false, Return(_, false)) - case Assign(lhs, rhs, rest) => - extractRes(rhs, false, Assign(lhs, _, applyBlock(rest))) - case b @ AssignField(lhs, nme, rhs, rest) => extractRes(rhs, false, AssignField(lhs, nme, _, applyBlock(rest))(b.symbol)) - case Define(defn, rest) => Define(rewriteDefn(defn), applyBlock(rest)) - case HandleBlock(lhs, res, par, cls, handlers, body, rest) => - HandleBlock( - lhs, res, par, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, applyBlock(h.body))), - applyBlock(body), applyBlock(rest) - ) - case HandleBlockReturn(c: Call) => extractRes(c, true, HandleBlockReturn(_)) - case HandleBlockReturn(res) => extractRes(res, false, HandleBlockReturn(_)) - case _ => super.applyBlock(b) - transform.applyBlock(b) + val clsSym = ClassSymbol( + Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), + Tree.Ident("StackDelay$") + ) + clsSym.defn = S(ClassDef(N, syntax.Cls, clsSym, BlockMemberSymbol(clsSym.nme, Nil), Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))), Nil)) + + // the global stack handler is created here + HandleBlock( + handlerSym, resSym, + stackDelayClsPath, clsSym, + List(Handler( + BlockMemberSymbol("perform", Nil), resumeSym, List(ParamList(ParamListFlags.empty, Nil, N)), + /* + fun perform() = + let curOffset = stackOffset + stackOffset = stackDepth + let ret = resume() + stackOffset = curOffset + ret + */ + blockBuilder + .assign(curOffsetSym, stackOffsetPath) + .assignFieldN(predefPath, STACK_OFFSET_IDENT, stackDepthPath) + .assign(handlerRes, Call(Value.Ref(resumeSym), List())(true)) + .assignFieldN(predefPath, STACK_OFFSET_IDENT, curOffsetSym.asPath) + .ret(handlerRes.asPath) + )), + blockBuilder + .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 + .assignFieldN(predefPath, STACK_HANDLER_IDENT, handlerSym.asPath) // assign stack handler + .rest(HandleBlockReturn(res)), + blockBuilder // reset the stack safety values + .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 + .assignFieldN(predefPath, STACK_HANDLER_IDENT, Value.Lit(Tree.UnitLit(false))) // set stackHandler = null + .rest(f(resSym.asPath)) + ) + + // Rewrites anything that can contain a Call to increase the stack depth + def transform(b: Block, isTopLevel: Bool = false): Block = + def usesStack(r: Result) = r match + case Call(Value.Ref(_: BuiltinSymbol), _) => false + case _: Call => true + case _: Instantiate => true + case _ => false + + val extract = if isTopLevel then extractResTopLevel else extractRes - secondPass(firstPass(b)) + val transform = new BlockTransformerShallow(SymbolSubst()): + override def applyDefn(defn: Defn): Defn = rewriteDefn(defn) + + override def applyBlock(b: Block): Block = b match + case Return(res, implct) if usesStack(res) => + extract(res, true, Return(_, implct)) + case Assign(lhs, rhs, rest) if usesStack(rhs) => + extract(rhs, false, Assign(lhs, _, applyBlock(rest))) + case b @ AssignField(lhs, nme, rhs, rest) if usesStack(rhs) => + extract(rhs, false, AssignField(lhs, nme, _, applyBlock(rest))(b.symbol)) + case Define(defn, rest) => + Define(rewriteDefn(defn), applyBlock(rest)) + case HandleBlock(lhs, res, par, cls, handlers, body, rest) => + HandleBlock( + lhs, res, par, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, applyBlock(h.body))), + applyBlock(body), applyBlock(rest) + ) + case HandleBlockReturn(res) if usesStack(res) => + extract(res, true, HandleBlockReturn(_)) + case _ => super.applyBlock(b) + + override def applyValue(v: Value): Value = v match + case Value.Lam(params, body) => Value.Lam(params, rewriteBlk(body)) + case _ => super.applyValue(v) + + transform.applyBlock(b) def isTrivial(b: Block): Boolean = def resTrivial(r: Result) = r match @@ -155,63 +190,4 @@ class StackSafeTransform(depthLimit: Int)(using State): def rewriteFn(defn: FunDefn) = FunDefn(defn.owner, defn.sym, defn.params, rewriteBlk(defn.body)) - def transformTopLevel(b: Block) = - def replaceReturns(b: Block): Block = - val transform = new BlockTransformerShallow(SymbolSubst()): - override def applyBlock(b: Block): Block = b match - case Return(res, _) => HandleBlockReturn(res) - case HandleBlock(lhs, res, par, cls, handlers, body, rest) => - HandleBlock( - lhs, res, par, cls, handlers, - applyBlock(body), applyBlock(rest) - ) - case _ => super.applyBlock(b) - transform.applyBlock(b) - - // symbols - val resumeSym = VarSymbol(Tree.Ident("resume")) - val handlerSym = TempSymbol(None, "stackHandler") - val resSym = TempSymbol(None, "res") - val handlerRes = TempSymbol(None, "res") - val curOffsetSym = TempSymbol(None, "curOffset") - - val clsSym = ClassSymbol( - Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), - Tree.Ident("StackDelay$") - ) - clsSym.defn = S(ClassDef(N, syntax.Cls, clsSym, BlockMemberSymbol(clsSym.nme, Nil), Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))), Nil)) - - val (blk, defns) = b.floatOutDefns(true) - - // the global stack handler is created here - val handle = HandleBlock( - handlerSym, resSym, - stackDelayClsPath, clsSym, - List(Handler( - BlockMemberSymbol("perform", Nil), resumeSym, List(ParamList(ParamListFlags.empty, Nil, N)), - /* - fun perform() = - let curOffset = stackOffset - stackOffset = stackDepth - let ret = resume() - stackOffset = curOffset - ret - */ - blockBuilder - .assign(curOffsetSym, stackOffsetPath) - .assignFieldN(predefPath, STACK_OFFSET_IDENT, stackDepthPath) - .assign(handlerRes, Call(Value.Ref(resumeSym), List())(true)) - .assignFieldN(predefPath, STACK_OFFSET_IDENT, curOffsetSym.asPath) - .ret(handlerRes.asPath) - )), - blockBuilder - .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 - .assignFieldN(predefPath, STACK_HANDLER_IDENT, handlerSym.asPath) // assign stack handler - .rest(replaceReturns(transform(blk))), // transform the rest of the body - blockBuilder // reset the stack safety values - .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 - .assignFieldN(predefPath, STACK_HANDLER_IDENT, Value.Lit(Tree.UnitLit(false))) // set stackHandler = null - .rest(Return(Value.Ref(resSym), true)) - ) - - defns.foldLeft[Block](handle)((blk, defn) => Define(rewriteDefn(defn), blk)) \ No newline at end of file + def transformTopLevel(b: Block) = transform(b, true) \ No newline at end of file diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index f46025be79..f6c954f228 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -8,4 +8,15 @@ :d - +:stackSafe 1000 +:handler +:expect 50005000 +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +sum(10000) +//│ Elab: { ‹› fun member:sum‹146›(Param(‹›,n‹147›,None), ) = if { let $scrut‹152› = builtin:==‹7›#0(n‹147›#666, 0); scrut is true -> { else 0 }; else builtin:+‹15›#0(n‹147›#666, member:sum‹146›#666(builtin:-‹5›#0(n‹147›#666, 1))) }; member:sum‹146›#666(10000) } +//│ > let sum1, res, handleBlock$41;try { sum1 = function sum(...args) { globalThis.Predef.checkArgs("sum", 1, true, args.length); let n = args[0]; let scrut, tmp, tmp1, tmp2, prevDepth, diff, scrut1, scrut2, scrut3, tmp3, res1, res2, Cont$178; Cont$178 = function Cont$178(...args2) { return new Cont$178.class(...args2); }; Cont$178.class = class Cont$178 extends globalThis.Predef.__Cont.class { constructor(pc) { let tmp4; tmp4 = super(null, null); this.pc = pc; } resume(...args1) { globalThis.Predef.checkArgs("resume", 1, true, args1.length); let value$ = args1[0]; if (this.pc === 1) { res2 = value$; } else if (this.pc === 0) { res1 = value$; } contLoop: while (true) { if (this.pc === 3) { scrut = n == 0; if (scrut === true) { return 0; } else { tmp = n - 1; prevDepth = globalThis.Predef.__stackDepth; globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; res2 = sum1(tmp); if (res2 instanceof globalThis.Predef.__EffectSig.class) { res2.tail.next = this; this.pc = 1; return res2; } this.pc = 1; continue contLoop; } this.pc = 2; continue contLoop; } else if (this.pc === 2) { break contLoop; } else if (this.pc === 1) { tmp2 = res2; globalThis.Predef.__stackDepth = prevDepth; tmp1 = tmp2; return n + tmp1; } else if (this.pc === 0) { tmp3 = res1; this.pc = 3; continue contLoop; } break; } } toString() { return "Cont$178(" + this.pc + ")"; } }; diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; scrut1 = diff >= 1000; scrut2 = globalThis.Predef.__stackHandler !== undefined; scrut3 = scrut1 && scrut2; if (scrut3 === true) { res1 = globalThis.Predef.__stackHandler.perform(); if (res1 instanceof globalThis.Predef.__EffectSig.class) { res1.tail.next = new Cont$178.class(0); res1.tail = res1.tail.next; return res1; } tmp3 = res1; } scrut = n == 0; if (scrut === true) { return 0; } else { tmp = n - 1; prevDepth = globalThis.Predef.__stackDepth; globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; res2 = sum1(tmp); if (res2 instanceof globalThis.Predef.__EffectSig.class) { res2.tail.next = new Cont$178.class(1); res2.tail = res2.tail.next; return res2; } tmp2 = res2; globalThis.Predef.__stackDepth = prevDepth; tmp1 = tmp2; return n + tmp1; } }; handleBlock$41 = function handleBlock$4(...args) { globalThis.Predef.checkArgs("handleBlock$4", 0, true, args.length); let stackHandler, res1, Cont$205, StackDelay$; StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { constructor() { let tmp; tmp = super(); } perform(...args1) { globalThis.Predef.checkArgs("perform", 0, true, args1.length); return globalThis.Predef.__mkEffect(stackHandler, (...args2) => { globalThis.Predef.checkArgs("", 1, true, args2.length); let resume = args2[0]; let res2, curOffset, res3, Cont$218; Cont$218 = function Cont$218(...args4) { return new Cont$218.class(...args4); }; Cont$218.class = class Cont$218 extends globalThis.Predef.__Cont.class { constructor(pc) { let tmp; tmp = super(null, null); this.pc = pc; } resume(...args3) { globalThis.Predef.checkArgs("resume", 1, true, args3.length); let value$ = args3[0]; if (this.pc === 6) { res3 = value$; } contLoop: while (true) { if (this.pc === 6) { res2 = res3; globalThis.Predef.__stackOffset = curOffset; return res2; } break; } } toString() { return "Cont$218(" + this.pc + ")"; } }; curOffset = globalThis.Predef.__stackOffset; globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; res3 = resume(); if (res3 instanceof globalThis.Predef.__EffectSig.class) { res3.tail.next = new Cont$218.class(6); res3.tail = res3.tail.next; return res3; } res2 = res3; globalThis.Predef.__stackOffset = curOffset; return res2; }); } toString() { return "StackDelay$"; } }; stackHandler = new StackDelay$(); Cont$205 = function Cont$205(...args2) { return new Cont$205.class(...args2); }; Cont$205.class = class Cont$205 extends globalThis.Predef.__Cont.class { constructor(pc) { let tmp; tmp = super(null, null); this.pc = pc; } resume(...args1) { globalThis.Predef.checkArgs("resume", 1, true, args1.length); let value$ = args1[0]; if (this.pc === 5) { res1 = value$; } contLoop: while (true) { if (this.pc === 5) { res = res1; break contLoop; } break; } } toString() { return "Cont$205(" + this.pc + ")"; } }; globalThis.Predef.__stackDepth = 0; globalThis.Predef.__stackHandler = stackHandler; res1 = sum1(10000); if (res1 instanceof globalThis.Predef.__EffectSig.class) { res1.tail.next = new Cont$205(5); return globalThis.Predef.__handleBlockImpl(res1, stackHandler); } res = res1; }; res = handleBlock$41(); if (res instanceof this.Predef.__EffectSig.class) { throw new this.Error("Unhandled effects"); } this.Predef.__stackDepth = 0; this.Predef.__stackHandler = undefined; return res; } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } +//│ FAILURE: Unexpected compilation error +//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Illegal return statement diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 72bdfcba40..8df91cda9c 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -150,7 +150,6 @@ hi(0) //│ }; //│ globalThis.Predef.__stackDepth = 0; //│ globalThis.Predef.__stackHandler = stackHandler; -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; //│ res1 = hi1(0); //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { //│ res1.tail.next = new Cont$212(4); @@ -332,7 +331,6 @@ sum(10000) //│ }; //│ globalThis.Predef.__stackDepth = 0; //│ globalThis.Predef.__stackHandler = stackHandler; -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; //│ res2 = sum3(10000); //│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { //│ res2.tail.next = new Cont$378(5); @@ -358,7 +356,6 @@ fun sum(n) = sum(10000) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded -:fixme :handler :stackSafe 100 mut val ctr = 0 @@ -368,10 +365,10 @@ fun foo(f) = else set ctr += 1 dummy(f(f)) -// foo(foo) -//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: member:ctr (class hkmc2.semantics.BlockMemberSymbol) +foo(foo) +//│ = 0 +//│ ctr = 10001 -:fixme :stackSafe 1000 :handler :expect 50005000 @@ -382,7 +379,7 @@ val foo = f(10000) foo //│ = 50005000 -//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: member:foo (class hkmc2.semantics.BlockMemberSymbol) +//│ foo = 50005000 :re fun foo() = @@ -410,6 +407,21 @@ handle h = Eff with foo(h) //│ = 50005000 +// function call and defn inside handler +:handler +:stackSafe 100 +:expect 50005000 +handle h = Eff with + fun perform(resume) = + val f = n => + if n <= 0 then 0 + else n + f(n-1) + resume(f(10000)) +in + fun foo(h) = h.perform + foo(h) +//│ = 50005000 + :re :handler fun foo(h) = h.perform(2) @@ -427,7 +439,7 @@ foo(h) :sjs fun max(a, b) = if a < b then b else a //│ JS (unsanitized): -//│ let max1, res5, handleBlock$07; +//│ let max1; //│ max1 = function max(a, b) { //│ let scrut; //│ scrut = a < b; @@ -437,124 +449,4 @@ fun max(a, b) = if a < b then b else a //│ return a; //│ } //│ }; -//│ handleBlock$07 = function handleBlock$0() { -//│ let stackHandler, StackDelay$; -//│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { -//│ constructor() { -//│ let tmp1; -//│ tmp1 = super(); -//│ } -//│ perform() { -//│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res6, curOffset, res7, Cont$1062; -//│ Cont$1062 = function Cont$1062(pc1) { return new Cont$1062.class(pc1); }; -//│ Cont$1062.class = class Cont$1062 extends globalThis.Predef.__Cont.class { -//│ constructor(pc) { -//│ let tmp1; -//│ tmp1 = super(null, null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 1) { -//│ res7 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 1) { -//│ res6 = res7; -//│ globalThis.Predef.__stackOffset = curOffset; -//│ return res6; -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return "Cont$1062(" + this.pc + ")"; } -//│ }; -//│ curOffset = globalThis.Predef.__stackOffset; -//│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; -//│ res7 = resume(); -//│ if (res7 instanceof globalThis.Predef.__EffectSig.class) { -//│ res7.tail.next = new Cont$1062.class(1); -//│ res7.tail = res7.tail.next; -//│ return res7; -//│ } -//│ res6 = res7; -//│ globalThis.Predef.__stackOffset = curOffset; -//│ return res6; -//│ }); -//│ } -//│ toString() { return "StackDelay$"; } -//│ }; -//│ stackHandler = new StackDelay$(); -//│ globalThis.Predef.__stackDepth = 0; -//│ globalThis.Predef.__stackHandler = stackHandler; -//│ return null; -//│ }; -//│ res5 = handleBlock$07(); -//│ if (res5 instanceof this.Predef.__EffectSig.class) { -//│ throw new this.Error("Unhandled effects"); -//│ } -//│ this.Predef.__stackDepth = 0; -//│ this.Predef.__stackHandler = undefined; -//│ res5 - -//│ JS (unsanitized): -//│ let res6, handleBlock$09; -//│ handleBlock$09 = function handleBlock$0() { -//│ let stackHandler, StackDelay$; -//│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { -//│ constructor() { -//│ let tmp1; -//│ tmp1 = super(); -//│ } -//│ perform() { -//│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res7, curOffset, res8, Cont$1153; -//│ Cont$1153 = function Cont$1153(pc1) { return new Cont$1153.class(pc1); }; -//│ Cont$1153.class = class Cont$1153 extends globalThis.Predef.__Cont.class { -//│ constructor(pc) { -//│ let tmp1; -//│ tmp1 = super(null, null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 1) { -//│ res8 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 1) { -//│ res7 = res8; -//│ globalThis.Predef.__stackOffset = curOffset; -//│ return res7; -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return "Cont$1153(" + this.pc + ")"; } -//│ }; -//│ curOffset = globalThis.Predef.__stackOffset; -//│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; -//│ res8 = resume(); -//│ if (res8 instanceof globalThis.Predef.__EffectSig.class) { -//│ res8.tail.next = new Cont$1153.class(1); -//│ res8.tail = res8.tail.next; -//│ return res8; -//│ } -//│ res7 = res8; -//│ globalThis.Predef.__stackOffset = curOffset; -//│ return res7; -//│ }); -//│ } -//│ toString() { return "StackDelay$"; } -//│ }; -//│ stackHandler = new StackDelay$(); -//│ globalThis.Predef.__stackDepth = 0; -//│ globalThis.Predef.__stackHandler = stackHandler; -//│ return null; -//│ }; -//│ res6 = handleBlock$09(); -//│ if (res6 instanceof this.Predef.__EffectSig.class) { -//│ throw new this.Error("Unhandled effects"); -//│ } -//│ this.Predef.__stackDepth = 0; -//│ this.Predef.__stackHandler = undefined; -//│ res6 +//│ null From a812181855a1cc0bdc4cdcc1adb16cf964d70286 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 22 Jan 2025 23:07:45 +0800 Subject: [PATCH 085/114] fix some things and clean up --- .../scala/hkmc2/codegen/StackSafeTransform.scala | 10 +++------- hkmc2/shared/src/test/mlscript/HkScratch.mls | 13 +------------ .../src/test/mlscript/handlers/StackSafety.mls | 4 ++-- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 8922a7c9e5..d9b7d4acdf 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -1,15 +1,11 @@ package hkmc2 import mlscript.utils.*, shorthands.* -import utils.* - import hkmc2.codegen.* import hkmc2.semantics.Elaborator.State import hkmc2.semantics.* import hkmc2.syntax.Tree -import hkmc2.syntax.Keyword.`with` -import scala.compiletime.ops.boolean - +import hkmc2.utils.* class StackSafeTransform(depthLimit: Int)(using State): private val STACK_DEPTH_IDENT: Tree.Ident = Tree.Ident("__stackDepth") @@ -81,11 +77,11 @@ class StackSafeTransform(depthLimit: Int)(using State): .ret(handlerRes.asPath) )), blockBuilder - .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 + .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(1)) // set stackDepth = 1 before call .assignFieldN(predefPath, STACK_HANDLER_IDENT, handlerSym.asPath) // assign stack handler .rest(HandleBlockReturn(res)), blockBuilder // reset the stack safety values - .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 + .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(0)) // set stackDepth = 0 after call .assignFieldN(predefPath, STACK_HANDLER_IDENT, Value.Lit(Tree.UnitLit(false))) // set stackHandler = null .rest(f(resSym.asPath)) ) diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index f6c954f228..f46025be79 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -8,15 +8,4 @@ :d -:stackSafe 1000 -:handler -:expect 50005000 -fun sum(n) = - if n == 0 then 0 - else - n + sum(n - 1) -sum(10000) -//│ Elab: { ‹› fun member:sum‹146›(Param(‹›,n‹147›,None), ) = if { let $scrut‹152› = builtin:==‹7›#0(n‹147›#666, 0); scrut is true -> { else 0 }; else builtin:+‹15›#0(n‹147›#666, member:sum‹146›#666(builtin:-‹5›#0(n‹147›#666, 1))) }; member:sum‹146›#666(10000) } -//│ > let sum1, res, handleBlock$41;try { sum1 = function sum(...args) { globalThis.Predef.checkArgs("sum", 1, true, args.length); let n = args[0]; let scrut, tmp, tmp1, tmp2, prevDepth, diff, scrut1, scrut2, scrut3, tmp3, res1, res2, Cont$178; Cont$178 = function Cont$178(...args2) { return new Cont$178.class(...args2); }; Cont$178.class = class Cont$178 extends globalThis.Predef.__Cont.class { constructor(pc) { let tmp4; tmp4 = super(null, null); this.pc = pc; } resume(...args1) { globalThis.Predef.checkArgs("resume", 1, true, args1.length); let value$ = args1[0]; if (this.pc === 1) { res2 = value$; } else if (this.pc === 0) { res1 = value$; } contLoop: while (true) { if (this.pc === 3) { scrut = n == 0; if (scrut === true) { return 0; } else { tmp = n - 1; prevDepth = globalThis.Predef.__stackDepth; globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; res2 = sum1(tmp); if (res2 instanceof globalThis.Predef.__EffectSig.class) { res2.tail.next = this; this.pc = 1; return res2; } this.pc = 1; continue contLoop; } this.pc = 2; continue contLoop; } else if (this.pc === 2) { break contLoop; } else if (this.pc === 1) { tmp2 = res2; globalThis.Predef.__stackDepth = prevDepth; tmp1 = tmp2; return n + tmp1; } else if (this.pc === 0) { tmp3 = res1; this.pc = 3; continue contLoop; } break; } } toString() { return "Cont$178(" + this.pc + ")"; } }; diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; scrut1 = diff >= 1000; scrut2 = globalThis.Predef.__stackHandler !== undefined; scrut3 = scrut1 && scrut2; if (scrut3 === true) { res1 = globalThis.Predef.__stackHandler.perform(); if (res1 instanceof globalThis.Predef.__EffectSig.class) { res1.tail.next = new Cont$178.class(0); res1.tail = res1.tail.next; return res1; } tmp3 = res1; } scrut = n == 0; if (scrut === true) { return 0; } else { tmp = n - 1; prevDepth = globalThis.Predef.__stackDepth; globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; res2 = sum1(tmp); if (res2 instanceof globalThis.Predef.__EffectSig.class) { res2.tail.next = new Cont$178.class(1); res2.tail = res2.tail.next; return res2; } tmp2 = res2; globalThis.Predef.__stackDepth = prevDepth; tmp1 = tmp2; return n + tmp1; } }; handleBlock$41 = function handleBlock$4(...args) { globalThis.Predef.checkArgs("handleBlock$4", 0, true, args.length); let stackHandler, res1, Cont$205, StackDelay$; StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { constructor() { let tmp; tmp = super(); } perform(...args1) { globalThis.Predef.checkArgs("perform", 0, true, args1.length); return globalThis.Predef.__mkEffect(stackHandler, (...args2) => { globalThis.Predef.checkArgs("", 1, true, args2.length); let resume = args2[0]; let res2, curOffset, res3, Cont$218; Cont$218 = function Cont$218(...args4) { return new Cont$218.class(...args4); }; Cont$218.class = class Cont$218 extends globalThis.Predef.__Cont.class { constructor(pc) { let tmp; tmp = super(null, null); this.pc = pc; } resume(...args3) { globalThis.Predef.checkArgs("resume", 1, true, args3.length); let value$ = args3[0]; if (this.pc === 6) { res3 = value$; } contLoop: while (true) { if (this.pc === 6) { res2 = res3; globalThis.Predef.__stackOffset = curOffset; return res2; } break; } } toString() { return "Cont$218(" + this.pc + ")"; } }; curOffset = globalThis.Predef.__stackOffset; globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; res3 = resume(); if (res3 instanceof globalThis.Predef.__EffectSig.class) { res3.tail.next = new Cont$218.class(6); res3.tail = res3.tail.next; return res3; } res2 = res3; globalThis.Predef.__stackOffset = curOffset; return res2; }); } toString() { return "StackDelay$"; } }; stackHandler = new StackDelay$(); Cont$205 = function Cont$205(...args2) { return new Cont$205.class(...args2); }; Cont$205.class = class Cont$205 extends globalThis.Predef.__Cont.class { constructor(pc) { let tmp; tmp = super(null, null); this.pc = pc; } resume(...args1) { globalThis.Predef.checkArgs("resume", 1, true, args1.length); let value$ = args1[0]; if (this.pc === 5) { res1 = value$; } contLoop: while (true) { if (this.pc === 5) { res = res1; break contLoop; } break; } } toString() { return "Cont$205(" + this.pc + ")"; } }; globalThis.Predef.__stackDepth = 0; globalThis.Predef.__stackHandler = stackHandler; res1 = sum1(10000); if (res1 instanceof globalThis.Predef.__EffectSig.class) { res1.tail.next = new Cont$205(5); return globalThis.Predef.__handleBlockImpl(res1, stackHandler); } res = res1; }; res = handleBlock$41(); if (res instanceof this.Predef.__EffectSig.class) { throw new this.Error("Unhandled effects"); } this.Predef.__stackDepth = 0; this.Predef.__stackHandler = undefined; return res; } catch (e) { console.log('\u200B' + e.stack + '\u200B'); } -//│ FAILURE: Unexpected compilation error -//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Illegal return statement + diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 8df91cda9c..859e7cff3b 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -148,7 +148,7 @@ hi(0) //│ } //│ toString() { return "Cont$212(" + this.pc + ")"; } //│ }; -//│ globalThis.Predef.__stackDepth = 0; +//│ globalThis.Predef.__stackDepth = 1; //│ globalThis.Predef.__stackHandler = stackHandler; //│ res1 = hi1(0); //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { @@ -329,7 +329,7 @@ sum(10000) //│ } //│ toString() { return "Cont$378(" + this.pc + ")"; } //│ }; -//│ globalThis.Predef.__stackDepth = 0; +//│ globalThis.Predef.__stackDepth = 1; //│ globalThis.Predef.__stackHandler = stackHandler; //│ res2 = sum3(10000); //│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { From c2ce8773efc49c551b7a1c618f7c968adb885859 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 22 Jan 2025 23:15:01 +0800 Subject: [PATCH 086/114] merge hkmc2 --- .../src/test/mlscript/handlers/Effects.mls | 16 +++--- .../mlscript/handlers/EffectsInClasses.mls | 10 ++-- .../mlscript/handlers/RecursiveHandlers.mls | 54 +++++++++---------- .../src/test/mlscript/parser/Handler.mls | 6 +-- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 180773132b..e684de4b9f 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -158,8 +158,8 @@ if true do //│ JS (unsanitized): //│ let tmp11, handleBlock$023; //│ handleBlock$023 = function handleBlock$0() { -//│ let h, scrut, tmp12, res, Cont$1074, f$0, Effect$h$1048; -//│ Effect$h$1048 = class Effect$h$1048 extends Effect1 { +//│ let h, scrut, tmp12, res, Cont$1069, f$0, Effect$h$1043; +//│ Effect$h$1043 = class Effect$h$1043 extends Effect1 { //│ constructor() { //│ let tmp13; //│ tmp13 = super(); @@ -169,11 +169,11 @@ if true do //│ return arg; //│ }); //│ } -//│ toString() { return "Effect$h$1048"; } +//│ toString() { return "Effect$h$1043"; } //│ }; -//│ h = new Effect$h$1048(); -//│ Cont$1074 = function Cont$1074(pc1) { return new Cont$1074.class(pc1); }; -//│ Cont$1074.class = class Cont$1074 extends globalThis.Predef.__Cont.class { +//│ h = new Effect$h$1043(); +//│ Cont$1069 = function Cont$1069(pc1) { return new Cont$1069.class(pc1); }; +//│ Cont$1069.class = class Cont$1069 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp13; //│ tmp13 = super(null, null); @@ -194,7 +194,7 @@ if true do //│ break; //│ } //│ } -//│ toString() { return "Cont$1074(" + this.pc + ")"; } +//│ toString() { return "Cont$1069(" + this.pc + ")"; } //│ }; //│ f$0 = function f$0() { //│ return 3; @@ -203,7 +203,7 @@ if true do //│ if (scrut === true) { //│ res = f$0(); //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$1074(1); +//│ res.tail.next = new Cont$1069(1); //│ return globalThis.Predef.__handleBlockImpl(res, h); //│ } //│ tmp12 = res; diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls index 4c33c43b16..94808e07ef 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls @@ -16,9 +16,9 @@ class Lol(h) with //│ Lol1.class = class Lol { //│ constructor(h) { //│ this.h = h; -//│ let tmp, res, Cont$173; -//│ Cont$173 = function Cont$173(pc1) { return new Cont$173.class(pc1); }; -//│ Cont$173.class = class Cont$173 extends globalThis.Predef.__Cont.class { +//│ let tmp, res, Cont$168; +//│ Cont$168 = function Cont$168(pc1) { return new Cont$168.class(pc1); }; +//│ Cont$168.class = class Cont$168 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp1; //│ tmp1 = super(null, null); @@ -39,11 +39,11 @@ class Lol(h) with //│ break; //│ } //│ } -//│ toString() { return "Cont$173(" + this.pc + ")"; } +//│ toString() { return "Cont$168(" + this.pc + ")"; } //│ }; //│ res = this.h.perform("k") ?? null; //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$173.class(0); +//│ res.tail.next = new Cont$168.class(0); //│ res.tail = res.tail.next; //│ return res; //│ } diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 42a866f0bd..a13afbc0b3 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -126,17 +126,17 @@ str //│ scrut = true; //│ if (scrut === true) { //│ handleBlock$07 = function handleBlock$0() { -//│ let h1, tmp8, Cont$785, handleBlock$1$0, Effect$h1$697; -//│ Effect$h1$697 = class Effect$h1$697 extends Effect1 { +//│ let h1, tmp8, Cont$780, handleBlock$1$0, Effect$h1$692; +//│ Effect$h1$692 = class Effect$h1$692 extends Effect1 { //│ constructor() { //│ let tmp9; //│ tmp9 = super(); //│ } //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h1, (k) => { -//│ let tmp9, tmp10, tmp11, res3, Cont$806; -//│ Cont$806 = function Cont$806(pc1) { return new Cont$806.class(pc1); }; -//│ Cont$806.class = class Cont$806 extends globalThis.Predef.__Cont.class { +//│ let tmp9, tmp10, tmp11, res3, Cont$801; +//│ Cont$801 = function Cont$801(pc1) { return new Cont$801.class(pc1); }; +//│ Cont$801.class = class Cont$801 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp12; //│ tmp12 = super(null, null); @@ -156,13 +156,13 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$806(" + this.pc + ")"; } +//│ toString() { return "Cont$801(" + this.pc + ")"; } //│ }; //│ tmp9 = str + "A"; //│ str = tmp9; //│ res3 = k(arg) ?? null; //│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$806.class(7); +//│ res3.tail.next = new Cont$801.class(7); //│ res3.tail = res3.tail.next; //│ return res3; //│ } @@ -172,11 +172,11 @@ str //│ return null; //│ }); //│ } -//│ toString() { return "Effect$h1$697"; } +//│ toString() { return "Effect$h1$692"; } //│ }; -//│ h1 = new Effect$h1$697(); -//│ Cont$785 = function Cont$785(pc1) { return new Cont$785.class(pc1); }; -//│ Cont$785.class = class Cont$785 extends globalThis.Predef.__Cont.class { +//│ h1 = new Effect$h1$692(); +//│ Cont$780 = function Cont$780(pc1) { return new Cont$780.class(pc1); }; +//│ Cont$780.class = class Cont$780 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp9; //│ tmp9 = super(null, null); @@ -199,20 +199,20 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$785(" + this.pc + ")"; } +//│ toString() { return "Cont$780(" + this.pc + ")"; } //│ }; //│ handleBlock$1$0 = function handleBlock$1$0() { -//│ let h2, tmp9, res3, res4, Cont$750, Effect$h2$708; -//│ Effect$h2$708 = class Effect$h2$708 extends Effect1 { +//│ let h2, tmp9, res3, res4, Cont$745, Effect$h2$703; +//│ Effect$h2$703 = class Effect$h2$703 extends Effect1 { //│ constructor() { //│ let tmp10; //│ tmp10 = super(); //│ } //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h2, (k) => { -//│ let tmp10, tmp11, tmp12, tmp13, tmp14, res5, Cont$769; -//│ Cont$769 = function Cont$769(pc1) { return new Cont$769.class(pc1); }; -//│ Cont$769.class = class Cont$769 extends globalThis.Predef.__Cont.class { +//│ let tmp10, tmp11, tmp12, tmp13, tmp14, res5, Cont$764; +//│ Cont$764 = function Cont$764(pc1) { return new Cont$764.class(pc1); }; +//│ Cont$764.class = class Cont$764 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp15; //│ tmp15 = super(null, null); @@ -233,14 +233,14 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$769(" + this.pc + ")"; } +//│ toString() { return "Cont$764(" + this.pc + ")"; } //│ }; //│ tmp10 = str + "B"; //│ tmp11 = str + tmp10; //│ str = tmp11; //│ res5 = k(arg) ?? null; //│ if (res5 instanceof globalThis.Predef.__EffectSig.class) { -//│ res5.tail.next = new Cont$769.class(4); +//│ res5.tail.next = new Cont$764.class(4); //│ res5.tail = res5.tail.next; //│ return res5; //│ } @@ -251,11 +251,11 @@ str //│ return null; //│ }); //│ } -//│ toString() { return "Effect$h2$708"; } +//│ toString() { return "Effect$h2$703"; } //│ }; -//│ h2 = new Effect$h2$708(); -//│ Cont$750 = function Cont$750(pc1) { return new Cont$750.class(pc1); }; -//│ Cont$750.class = class Cont$750 extends globalThis.Predef.__Cont.class { +//│ h2 = new Effect$h2$703(); +//│ Cont$745 = function Cont$745(pc1) { return new Cont$745.class(pc1); }; +//│ Cont$745.class = class Cont$745 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp10; //│ tmp10 = super(null, null); @@ -284,24 +284,24 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$750(" + this.pc + ")"; } +//│ toString() { return "Cont$745(" + this.pc + ")"; } //│ }; //│ res3 = h2.perform(null) ?? null; //│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$750(2); +//│ res3.tail.next = new Cont$745(2); //│ return globalThis.Predef.__handleBlockImpl(res3, h2); //│ } //│ tmp9 = res3; //│ res4 = h1.perform(null) ?? null; //│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { -//│ res4.tail.next = new Cont$750(3); +//│ res4.tail.next = new Cont$745(3); //│ return globalThis.Predef.__handleBlockImpl(res4, h2); //│ } //│ return res4; //│ }; //│ tmp8 = handleBlock$1$0(); //│ if (tmp8 instanceof globalThis.Predef.__EffectSig.class) { -//│ tmp8.tail.next = new Cont$785(5); +//│ tmp8.tail.next = new Cont$780(5); //│ return globalThis.Predef.__handleBlockImpl(tmp8, h1); //│ } //│ if (tmp8 instanceof globalThis.Predef.__Return.class) { diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index dee52fc9e7..12c47ce202 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$156),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); { member:foo#666(h#666) } } } +//│ Elab: { { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$151),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); { member:foo#666(h#666) } } } :e ( @@ -73,7 +73,7 @@ handle h = Eff with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$185),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$185),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { member:foo#666(h#666) } } +//│ Elab: { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$180),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$180),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { member:foo#666(h#666) } } :e handle h = Eff with @@ -127,4 +127,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.125: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$263),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$263),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { member:foo#666(h#666) } } +//│ Elab: { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$258),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$258),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { member:foo#666(h#666) } } From 546072952f542984fd402d5c3b5c0e55d94fae90 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Wed, 22 Jan 2025 23:17:57 +0800 Subject: [PATCH 087/114] update tests --- .../src/test/mlscript/handlers/Effects.mls | 16 +++--- .../mlscript/handlers/EffectsInClasses.mls | 10 ++-- .../mlscript/handlers/RecursiveHandlers.mls | 54 +++++++++---------- .../src/test/mlscript/parser/Handler.mls | 6 +-- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index e684de4b9f..180773132b 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -158,8 +158,8 @@ if true do //│ JS (unsanitized): //│ let tmp11, handleBlock$023; //│ handleBlock$023 = function handleBlock$0() { -//│ let h, scrut, tmp12, res, Cont$1069, f$0, Effect$h$1043; -//│ Effect$h$1043 = class Effect$h$1043 extends Effect1 { +//│ let h, scrut, tmp12, res, Cont$1074, f$0, Effect$h$1048; +//│ Effect$h$1048 = class Effect$h$1048 extends Effect1 { //│ constructor() { //│ let tmp13; //│ tmp13 = super(); @@ -169,11 +169,11 @@ if true do //│ return arg; //│ }); //│ } -//│ toString() { return "Effect$h$1043"; } +//│ toString() { return "Effect$h$1048"; } //│ }; -//│ h = new Effect$h$1043(); -//│ Cont$1069 = function Cont$1069(pc1) { return new Cont$1069.class(pc1); }; -//│ Cont$1069.class = class Cont$1069 extends globalThis.Predef.__Cont.class { +//│ h = new Effect$h$1048(); +//│ Cont$1074 = function Cont$1074(pc1) { return new Cont$1074.class(pc1); }; +//│ Cont$1074.class = class Cont$1074 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp13; //│ tmp13 = super(null, null); @@ -194,7 +194,7 @@ if true do //│ break; //│ } //│ } -//│ toString() { return "Cont$1069(" + this.pc + ")"; } +//│ toString() { return "Cont$1074(" + this.pc + ")"; } //│ }; //│ f$0 = function f$0() { //│ return 3; @@ -203,7 +203,7 @@ if true do //│ if (scrut === true) { //│ res = f$0(); //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$1069(1); +//│ res.tail.next = new Cont$1074(1); //│ return globalThis.Predef.__handleBlockImpl(res, h); //│ } //│ tmp12 = res; diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls index 94808e07ef..4c33c43b16 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls @@ -16,9 +16,9 @@ class Lol(h) with //│ Lol1.class = class Lol { //│ constructor(h) { //│ this.h = h; -//│ let tmp, res, Cont$168; -//│ Cont$168 = function Cont$168(pc1) { return new Cont$168.class(pc1); }; -//│ Cont$168.class = class Cont$168 extends globalThis.Predef.__Cont.class { +//│ let tmp, res, Cont$173; +//│ Cont$173 = function Cont$173(pc1) { return new Cont$173.class(pc1); }; +//│ Cont$173.class = class Cont$173 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp1; //│ tmp1 = super(null, null); @@ -39,11 +39,11 @@ class Lol(h) with //│ break; //│ } //│ } -//│ toString() { return "Cont$168(" + this.pc + ")"; } +//│ toString() { return "Cont$173(" + this.pc + ")"; } //│ }; //│ res = this.h.perform("k") ?? null; //│ if (res instanceof globalThis.Predef.__EffectSig.class) { -//│ res.tail.next = new Cont$168.class(0); +//│ res.tail.next = new Cont$173.class(0); //│ res.tail = res.tail.next; //│ return res; //│ } diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index a13afbc0b3..42a866f0bd 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -126,17 +126,17 @@ str //│ scrut = true; //│ if (scrut === true) { //│ handleBlock$07 = function handleBlock$0() { -//│ let h1, tmp8, Cont$780, handleBlock$1$0, Effect$h1$692; -//│ Effect$h1$692 = class Effect$h1$692 extends Effect1 { +//│ let h1, tmp8, Cont$785, handleBlock$1$0, Effect$h1$697; +//│ Effect$h1$697 = class Effect$h1$697 extends Effect1 { //│ constructor() { //│ let tmp9; //│ tmp9 = super(); //│ } //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h1, (k) => { -//│ let tmp9, tmp10, tmp11, res3, Cont$801; -//│ Cont$801 = function Cont$801(pc1) { return new Cont$801.class(pc1); }; -//│ Cont$801.class = class Cont$801 extends globalThis.Predef.__Cont.class { +//│ let tmp9, tmp10, tmp11, res3, Cont$806; +//│ Cont$806 = function Cont$806(pc1) { return new Cont$806.class(pc1); }; +//│ Cont$806.class = class Cont$806 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp12; //│ tmp12 = super(null, null); @@ -156,13 +156,13 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$801(" + this.pc + ")"; } +//│ toString() { return "Cont$806(" + this.pc + ")"; } //│ }; //│ tmp9 = str + "A"; //│ str = tmp9; //│ res3 = k(arg) ?? null; //│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$801.class(7); +//│ res3.tail.next = new Cont$806.class(7); //│ res3.tail = res3.tail.next; //│ return res3; //│ } @@ -172,11 +172,11 @@ str //│ return null; //│ }); //│ } -//│ toString() { return "Effect$h1$692"; } +//│ toString() { return "Effect$h1$697"; } //│ }; -//│ h1 = new Effect$h1$692(); -//│ Cont$780 = function Cont$780(pc1) { return new Cont$780.class(pc1); }; -//│ Cont$780.class = class Cont$780 extends globalThis.Predef.__Cont.class { +//│ h1 = new Effect$h1$697(); +//│ Cont$785 = function Cont$785(pc1) { return new Cont$785.class(pc1); }; +//│ Cont$785.class = class Cont$785 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp9; //│ tmp9 = super(null, null); @@ -199,20 +199,20 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$780(" + this.pc + ")"; } +//│ toString() { return "Cont$785(" + this.pc + ")"; } //│ }; //│ handleBlock$1$0 = function handleBlock$1$0() { -//│ let h2, tmp9, res3, res4, Cont$745, Effect$h2$703; -//│ Effect$h2$703 = class Effect$h2$703 extends Effect1 { +//│ let h2, tmp9, res3, res4, Cont$750, Effect$h2$708; +//│ Effect$h2$708 = class Effect$h2$708 extends Effect1 { //│ constructor() { //│ let tmp10; //│ tmp10 = super(); //│ } //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h2, (k) => { -//│ let tmp10, tmp11, tmp12, tmp13, tmp14, res5, Cont$764; -//│ Cont$764 = function Cont$764(pc1) { return new Cont$764.class(pc1); }; -//│ Cont$764.class = class Cont$764 extends globalThis.Predef.__Cont.class { +//│ let tmp10, tmp11, tmp12, tmp13, tmp14, res5, Cont$769; +//│ Cont$769 = function Cont$769(pc1) { return new Cont$769.class(pc1); }; +//│ Cont$769.class = class Cont$769 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp15; //│ tmp15 = super(null, null); @@ -233,14 +233,14 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$764(" + this.pc + ")"; } +//│ toString() { return "Cont$769(" + this.pc + ")"; } //│ }; //│ tmp10 = str + "B"; //│ tmp11 = str + tmp10; //│ str = tmp11; //│ res5 = k(arg) ?? null; //│ if (res5 instanceof globalThis.Predef.__EffectSig.class) { -//│ res5.tail.next = new Cont$764.class(4); +//│ res5.tail.next = new Cont$769.class(4); //│ res5.tail = res5.tail.next; //│ return res5; //│ } @@ -251,11 +251,11 @@ str //│ return null; //│ }); //│ } -//│ toString() { return "Effect$h2$703"; } +//│ toString() { return "Effect$h2$708"; } //│ }; -//│ h2 = new Effect$h2$703(); -//│ Cont$745 = function Cont$745(pc1) { return new Cont$745.class(pc1); }; -//│ Cont$745.class = class Cont$745 extends globalThis.Predef.__Cont.class { +//│ h2 = new Effect$h2$708(); +//│ Cont$750 = function Cont$750(pc1) { return new Cont$750.class(pc1); }; +//│ Cont$750.class = class Cont$750 extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp10; //│ tmp10 = super(null, null); @@ -284,24 +284,24 @@ str //│ break; //│ } //│ } -//│ toString() { return "Cont$745(" + this.pc + ")"; } +//│ toString() { return "Cont$750(" + this.pc + ")"; } //│ }; //│ res3 = h2.perform(null) ?? null; //│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$745(2); +//│ res3.tail.next = new Cont$750(2); //│ return globalThis.Predef.__handleBlockImpl(res3, h2); //│ } //│ tmp9 = res3; //│ res4 = h1.perform(null) ?? null; //│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { -//│ res4.tail.next = new Cont$745(3); +//│ res4.tail.next = new Cont$750(3); //│ return globalThis.Predef.__handleBlockImpl(res4, h2); //│ } //│ return res4; //│ }; //│ tmp8 = handleBlock$1$0(); //│ if (tmp8 instanceof globalThis.Predef.__EffectSig.class) { -//│ tmp8.tail.next = new Cont$780(5); +//│ tmp8.tail.next = new Cont$785(5); //│ return globalThis.Predef.__handleBlockImpl(tmp8, h1); //│ } //│ if (tmp8 instanceof globalThis.Predef.__Return.class) { diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index 12c47ce202..dee52fc9e7 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$151),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); { member:foo#666(h#666) } } } +//│ Elab: { { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$156),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); { member:foo#666(h#666) } } } :e ( @@ -73,7 +73,7 @@ handle h = Eff with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$180),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$180),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { member:foo#666(h#666) } } +//│ Elab: { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$185),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$185),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { member:foo#666(h#666) } } :e handle h = Eff with @@ -127,4 +127,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.125: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$258),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$258),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { member:foo#666(h#666) } } +//│ Elab: { handle h = Ref(member:Eff) List(HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$263),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(class:Eff$h$263),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); { member:foo#666(h#666) } } From 2ef0f4ed30d149a7c3bfe987d6379cb5fcfa261c Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 23 Jan 2025 13:06:07 +0800 Subject: [PATCH 088/114] Add problematic test cases --- .../test/mlscript/handlers/ZCombinator.mls | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls new file mode 100644 index 0000000000..febacc3d2d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -0,0 +1,57 @@ +:js +:handler +:stackSafe 1000 + + +fun selfApp(f) = f(f) + +fun mkrec(g) = + selfApp of self => + g of y => self(self)(y) + +let fact = mkrec of self => x => + if x == 0 then 1 else self(x - 1) * x +//│ fact = [Function (anonymous)] + +fact(3) +//│ = 6 + +fact(10) +//│ = 3628800 + +:fixme +fact(5000) +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + + +// * Interestingly, this variation works: + +fun mkrec(g) = + selfApp of self => + g of y => selfApp(self)(y) + +let fact = mkrec of self => x => + if x == 0 then 1 else self(x - 1) * x +//│ fact = [Function (anonymous)] + +// * Without `:stackSafe`, gives `RangeError: Maximum call stack size exceeded` +fact(5000) +//│ = Infinity + + +// * FIXME: changing the instrumentation's stack depth shouldn't change the result! + +:stackSafe 100 +set selfApp = f => f(f) + +fact(5000) +//│ = 9.33262154439441e+157 + + +:stackSafe 10 +set selfApp = f => f(f) + +fact(5000) +//│ = 3628800 + + From 1dafc9c7e406c6744330d0425da8f671448d2079 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 23 Jan 2025 17:21:04 +0800 Subject: [PATCH 089/114] fix one bug --- .../hkmc2/codegen/StackSafeTransform.scala | 18 ++++-- .../test/mlscript/handlers/ZCombinator.mls | 58 ++++++++++++++++--- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index d9b7d4acdf..96f25fe27d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -101,11 +101,14 @@ class StackSafeTransform(depthLimit: Int)(using State): override def applyBlock(b: Block): Block = b match case Return(res, implct) if usesStack(res) => - extract(res, true, Return(_, implct)) + applyResult2(res): res => + extract(res, true, Return(_, implct)) case Assign(lhs, rhs, rest) if usesStack(rhs) => - extract(rhs, false, Assign(lhs, _, applyBlock(rest))) + applyResult2(rhs): res => + extract(res, false, Assign(lhs, _, applyBlock(rest))) case b @ AssignField(lhs, nme, rhs, rest) if usesStack(rhs) => - extract(rhs, false, AssignField(lhs, nme, _, applyBlock(rest))(b.symbol)) + applyResult2(rhs): res => + extract(res, false, AssignField(lhs, nme, _, applyBlock(rest))(b.symbol)) case Define(defn, rest) => Define(rewriteDefn(defn), applyBlock(rest)) case HandleBlock(lhs, res, par, cls, handlers, body, rest) => @@ -114,11 +117,14 @@ class StackSafeTransform(depthLimit: Int)(using State): applyBlock(body), applyBlock(rest) ) case HandleBlockReturn(res) if usesStack(res) => - extract(res, true, HandleBlockReturn(_)) + applyResult2(res): res => + extract(res, true, HandleBlockReturn(_)) case _ => super.applyBlock(b) - override def applyValue(v: Value): Value = v match - case Value.Lam(params, body) => Value.Lam(params, rewriteBlk(body)) + override def applyValue(v: Value): Value = + v match + case Value.Lam(params, body) => + Value.Lam(params, rewriteBlk(body)) case _ => super.applyValue(v) transform.applyBlock(b) diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls index febacc3d2d..f6fd6ae3a0 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -2,7 +2,6 @@ :handler :stackSafe 1000 - fun selfApp(f) = f(f) fun mkrec(g) = @@ -19,12 +18,9 @@ fact(3) fact(10) //│ = 3628800 -:fixme fact(5000) -//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded - +//│ = Infinity -// * Interestingly, this variation works: fun mkrec(g) = selfApp of self => @@ -41,17 +37,61 @@ fact(5000) // * FIXME: changing the instrumentation's stack depth shouldn't change the result! -:stackSafe 100 +:stackSafe 5 set selfApp = f => f(f) -fact(5000) -//│ = 9.33262154439441e+157 +:fixme +:expect 3628800 +fact(10) +//│ ═══[RUNTIME ERROR] Expected: 3628800, got: 120 +//│ = 120 :stackSafe 10 set selfApp = f => f(f) fact(5000) -//│ = 3628800 +//│ = 120 +// simplified version without lambdas for easier debugging +:stackSafe 5 +fun mkrec(g) = + fun a(self) = + fun b(y) = selfApp(self)(y) + g(b) + selfApp(a) + +:stackSafe 5 +let fact = + fun a(self) = + fun b(x) = + if x == 0 then 1 else + console.log(__stackDepth, __stackOffset) + let prev = self(x - 1) + console.log("resumed:", x) + x * prev + b + mkrec(a) +//│ fact = [Function: b$0] +:fixme +:expect 3628800 +:stackSafe 5 +fact(10) +//│ > 2 0 +//│ > 4 0 +//│ > 6 6 +//│ > 8 6 +//│ > 10 6 +//│ > 12 12 +//│ > 14 12 +//│ > 16 12 +//│ > 18 18 +//│ > 20 18 +//│ > resumed: 1 +//│ > resumed: 2 +//│ > resumed: 3 +//│ > resumed: 4 +//│ > resumed: 5 +//│ ═══[RUNTIME ERROR] Expected: 3628800, got: 120 +//│ = 120 From 23768b39fabef1e569fb90d73ba18a3736b7dae9 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 23 Jan 2025 18:18:23 +0800 Subject: [PATCH 090/114] update tests --- .../test/mlscript/handlers/StackSafety.mls | 90 +++++++++---------- .../test/mlscript/handlers/ZCombinator.mls | 2 +- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 859e7cff3b..60b88d9870 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -20,11 +20,11 @@ fun hi(n) = else hi(n - 1) hi(0) //│ JS (unsanitized): -//│ let hi1, res, handleBlock$31; +//│ let hi1, res, handleBlock$1; //│ hi1 = function hi(n) { -//│ let scrut, tmp, diff, scrut1, scrut2, scrut3, tmp1, res1, Cont$188; -//│ Cont$188 = function Cont$188(pc1) { return new Cont$188.class(pc1); }; -//│ Cont$188.class = class Cont$188 extends globalThis.Predef.__Cont.class { +//│ let scrut, tmp, diff, scrut1, scrut2, scrut3, tmp1, res1, Cont$; +//│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; +//│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp2; //│ tmp2 = super(null, null); @@ -56,7 +56,7 @@ hi(0) //│ break; //│ } //│ } -//│ toString() { return "Cont$188(" + this.pc + ")"; } +//│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; //│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; //│ scrut1 = diff >= 5; @@ -65,7 +65,7 @@ hi(0) //│ if (scrut3 === true) { //│ res1 = globalThis.Predef.__stackHandler.perform(); //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$188.class(0); +//│ res1.tail.next = new Cont$.class(0); //│ res1.tail = res1.tail.next; //│ return res1; //│ } @@ -80,8 +80,8 @@ hi(0) //│ return hi1(tmp); //│ } //│ }; -//│ handleBlock$31 = function handleBlock$3() { -//│ let stackHandler, res1, Cont$212, StackDelay$; +//│ handleBlock$1 = function handleBlock$() { +//│ let stackHandler, res1, Cont$, StackDelay$; //│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { //│ constructor() { //│ let tmp; @@ -89,20 +89,20 @@ hi(0) //│ } //│ perform() { //│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res2, curOffset, res3, Cont$225; -//│ Cont$225 = function Cont$225(pc1) { return new Cont$225.class(pc1); }; -//│ Cont$225.class = class Cont$225 extends globalThis.Predef.__Cont.class { +//│ let res2, curOffset, res3, Cont$1; +//│ Cont$1 = function Cont$(pc1) { return new Cont$.class(pc1); }; +//│ Cont$1.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp; //│ tmp = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 5) { +//│ if (this.pc === 4) { //│ res3 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 5) { +//│ if (this.pc === 4) { //│ res2 = res3; //│ globalThis.Predef.__stackOffset = curOffset; //│ return res2; @@ -110,13 +110,13 @@ hi(0) //│ break; //│ } //│ } -//│ toString() { return "Cont$225(" + this.pc + ")"; } +//│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; //│ curOffset = globalThis.Predef.__stackOffset; //│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; //│ res3 = resume(); //│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$225.class(5); +//│ res3.tail.next = new Cont$1.class(4); //│ res3.tail = res3.tail.next; //│ return res3; //│ } @@ -128,36 +128,36 @@ hi(0) //│ toString() { return "StackDelay$"; } //│ }; //│ stackHandler = new StackDelay$(); -//│ Cont$212 = function Cont$212(pc1) { return new Cont$212.class(pc1); }; -//│ Cont$212.class = class Cont$212 extends globalThis.Predef.__Cont.class { +//│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; +//│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp; //│ tmp = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 4) { +//│ if (this.pc === 3) { //│ res1 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 4) { +//│ if (this.pc === 3) { //│ return res1; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$212(" + this.pc + ")"; } +//│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; //│ globalThis.Predef.__stackDepth = 1; //│ globalThis.Predef.__stackHandler = stackHandler; //│ res1 = hi1(0); //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$212(4); +//│ res1.tail.next = new Cont$(3); //│ return globalThis.Predef.__handleBlockImpl(res1, stackHandler); //│ } //│ return res1; //│ }; -//│ res = handleBlock$31(); +//│ res = handleBlock$1(); //│ if (res instanceof this.Predef.__EffectSig.class) { //│ throw new this.Error("Unhandled effects"); //│ } @@ -176,11 +176,11 @@ fun sum(n) = n + sum(n - 1) sum(10000) //│ JS (unsanitized): -//│ let sum3, res1, handleBlock$41; +//│ let sum3, res1, handleBlock$3; //│ sum3 = function sum(n) { -//│ let scrut, tmp, tmp1, tmp2, prevDepth, diff, scrut1, scrut2, scrut3, tmp3, res2, res3, Cont$351; -//│ Cont$351 = function Cont$351(pc1) { return new Cont$351.class(pc1); }; -//│ Cont$351.class = class Cont$351 extends globalThis.Predef.__Cont.class { +//│ let scrut, tmp, tmp1, tmp2, prevDepth, diff, scrut1, scrut2, scrut3, tmp3, res2, res3, Cont$; +//│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; +//│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp4; //│ tmp4 = super(null, null); @@ -227,7 +227,7 @@ sum(10000) //│ break; //│ } //│ } -//│ toString() { return "Cont$351(" + this.pc + ")"; } +//│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; //│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; //│ scrut1 = diff >= 1000; @@ -236,7 +236,7 @@ sum(10000) //│ if (scrut3 === true) { //│ res2 = globalThis.Predef.__stackHandler.perform(); //│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$351.class(0); +//│ res2.tail.next = new Cont$.class(0); //│ res2.tail = res2.tail.next; //│ return res2; //│ } @@ -251,7 +251,7 @@ sum(10000) //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; //│ res3 = sum3(tmp); //│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$351.class(1); +//│ res3.tail.next = new Cont$.class(1); //│ res3.tail = res3.tail.next; //│ return res3; //│ } @@ -261,8 +261,8 @@ sum(10000) //│ return n + tmp1; //│ } //│ }; -//│ handleBlock$41 = function handleBlock$4() { -//│ let stackHandler, res2, Cont$378, StackDelay$; +//│ handleBlock$3 = function handleBlock$() { +//│ let stackHandler, res2, Cont$, StackDelay$; //│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { //│ constructor() { //│ let tmp; @@ -270,20 +270,20 @@ sum(10000) //│ } //│ perform() { //│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res3, curOffset, res4, Cont$391; -//│ Cont$391 = function Cont$391(pc1) { return new Cont$391.class(pc1); }; -//│ Cont$391.class = class Cont$391 extends globalThis.Predef.__Cont.class { +//│ let res3, curOffset, res4, Cont$1; +//│ Cont$1 = function Cont$(pc1) { return new Cont$.class(pc1); }; +//│ Cont$1.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp; //│ tmp = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 6) { +//│ if (this.pc === 5) { //│ res4 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 6) { +//│ if (this.pc === 5) { //│ res3 = res4; //│ globalThis.Predef.__stackOffset = curOffset; //│ return res3; @@ -291,13 +291,13 @@ sum(10000) //│ break; //│ } //│ } -//│ toString() { return "Cont$391(" + this.pc + ")"; } +//│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; //│ curOffset = globalThis.Predef.__stackOffset; //│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; //│ res4 = resume(); //│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { -//│ res4.tail.next = new Cont$391.class(6); +//│ res4.tail.next = new Cont$1.class(5); //│ res4.tail = res4.tail.next; //│ return res4; //│ } @@ -309,36 +309,36 @@ sum(10000) //│ toString() { return "StackDelay$"; } //│ }; //│ stackHandler = new StackDelay$(); -//│ Cont$378 = function Cont$378(pc1) { return new Cont$378.class(pc1); }; -//│ Cont$378.class = class Cont$378 extends globalThis.Predef.__Cont.class { +//│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; +//│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { //│ let tmp; //│ tmp = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 5) { +//│ if (this.pc === 4) { //│ res2 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 5) { +//│ if (this.pc === 4) { //│ return res2; //│ } //│ break; //│ } //│ } -//│ toString() { return "Cont$378(" + this.pc + ")"; } +//│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; //│ globalThis.Predef.__stackDepth = 1; //│ globalThis.Predef.__stackHandler = stackHandler; //│ res2 = sum3(10000); //│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$378(5); +//│ res2.tail.next = new Cont$(4); //│ return globalThis.Predef.__handleBlockImpl(res2, stackHandler); //│ } //│ return res2; //│ }; -//│ res1 = handleBlock$41(); +//│ res1 = handleBlock$3(); //│ if (res1 instanceof this.Predef.__EffectSig.class) { //│ throw new this.Error("Unhandled effects"); //│ } diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls index f6fd6ae3a0..b61b2c0016 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -72,7 +72,7 @@ let fact = x * prev b mkrec(a) -//│ fact = [Function: b$0] +//│ fact = [Function: b] :fixme :expect 3628800 From 7f6378e137d308ce31d8aaa54d189fad1fb43d20 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 18:38:16 +0800 Subject: [PATCH 091/114] Style fix and remove symbol.defn --- .../scala/hkmc2/codegen/HandlerLowering.scala | 15 ++------------- .../scala/hkmc2/codegen/StackSafeTransform.scala | 11 +++++------ 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index c0d1409cbb..f10a92801c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -374,7 +374,6 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case b1 -> b2 => b1.asCls match case Some(value) => val newSym = ClassSymbol(value.tree, Tree.Ident(b2.nme)) - newSym.defn = value.defn S(value -> newSym) case None => None .collect: @@ -386,7 +385,6 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case b1 -> b2 => b1.asMod match case Some(value) => val newSym = ModuleSymbol(value.tree, Tree.Ident(b2.nme)) - newSym.defn = value.defn S(value -> newSym) case None => None .collect: @@ -455,7 +453,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): h.cls, BlockMemberSymbol(h.cls.id.name, Nil), syntax.Cls, - h.cls.defn.get.paramsOpt, + N, S(h.par), handlers, Nil, Nil, Assign(freshTmp(), SimpleCall(Value.Ref(State.builtinOpsMap("super")), Nil), End()), End()) @@ -478,15 +476,6 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): ) val pcVar = VarSymbol(Tree.Ident("pc")) - clsSym.defn = S(ClassDef( - N, - syntax.Cls, - clsSym, - BlockMemberSymbol(clsSym.nme, Nil), - Nil, - S(PlainParamList(Param(FldFlags.empty, pcVar, N) :: Nil)), - ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))), - List())) var trivial = true def prepareBlock(b: Block): Block = @@ -582,7 +571,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): clsSym, BlockMemberSymbol(clsSym.nme, Nil), syntax.Cls, - clsSym.defn.get.paramsOpt, + S(PlainParamList(Param(FldFlags.empty, pcVar, N) :: Nil)), S(contClsPath), resumeFnDef :: Nil, Nil, diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 96f25fe27d..f2b1f42cdb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -12,11 +12,11 @@ class StackSafeTransform(depthLimit: Int)(using State): private val STACK_OFFSET_IDENT: Tree.Ident = Tree.Ident("__stackOffset") private val STACK_HANDLER_IDENT: Tree.Ident = Tree.Ident("__stackHandler") - private val stackDelayClsPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(Tree.Ident("__StackDelay")).selN(Tree.Ident("class")) - private val stackDepthPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(STACK_DEPTH_IDENT) - private val stackOffsetPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(STACK_OFFSET_IDENT) - private val stackHandlerPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")).selN(STACK_HANDLER_IDENT) private val predefPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")) + private val stackDelayClsPath: Path = predefPath.selN(Tree.Ident("__StackDelay")).selN(Tree.Ident("class")) + private val stackDepthPath: Path = predefPath.selN(STACK_DEPTH_IDENT) + private val stackOffsetPath: Path = predefPath.selN(STACK_OFFSET_IDENT) + private val stackHandlerPath: Path = predefPath.selN(STACK_HANDLER_IDENT) private def intLit(n: BigInt) = Value.Lit(Tree.IntLit(n)) @@ -53,7 +53,6 @@ class StackSafeTransform(depthLimit: Int)(using State): Tree.TypeDef(syntax.Cls, Tree.Error(), N, N), Tree.Ident("StackDelay$") ) - clsSym.defn = S(ClassDef(N, syntax.Cls, clsSym, BlockMemberSymbol(clsSym.nme, Nil), Nil, N, ObjBody(Term.Blk(Nil, Term.Lit(Tree.UnitLit(true)))), Nil)) // the global stack handler is created here HandleBlock( @@ -192,4 +191,4 @@ class StackSafeTransform(depthLimit: Int)(using State): def rewriteFn(defn: FunDefn) = FunDefn(defn.owner, defn.sym, defn.params, rewriteBlk(defn.body)) - def transformTopLevel(b: Block) = transform(b, true) \ No newline at end of file + def transformTopLevel(b: Block) = transform(b, true) From f259fe3f8f71d489c3b7c75cdd2f69d4cde1f4bb Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 18:42:36 +0800 Subject: [PATCH 092/114] changes to transformation code --- .../hkmc2/codegen/StackSafeTransform.scala | 52 ++- .../test/mlscript/handlers/StackSafety.mls | 327 +++++++++++++----- .../test/mlscript/handlers/ZCombinator.mls | 14 +- 3 files changed, 264 insertions(+), 129 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index f2b1f42cdb..1089a9191f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -95,33 +95,26 @@ class StackSafeTransform(depthLimit: Int)(using State): val extract = if isTopLevel then extractResTopLevel else extractRes - val transform = new BlockTransformerShallow(SymbolSubst()): - override def applyDefn(defn: Defn): Defn = rewriteDefn(defn) + val transform = new BlockTransformer(SymbolSubst()): + + override def applyFunDefn(fun: FunDefn): FunDefn = rewriteFn(fun) + override def applyDefn(defn: Defn): Defn = defn match + case defn: ClsLikeDefn => rewriteCls(defn) + case _: FunDefn | _: ValDefn => super.applyDefn(defn) override def applyBlock(b: Block): Block = b match case Return(res, implct) if usesStack(res) => applyResult2(res): res => extract(res, true, Return(_, implct)) - case Assign(lhs, rhs, rest) if usesStack(rhs) => - applyResult2(rhs): res => - extract(res, false, Assign(lhs, _, applyBlock(rest))) - case b @ AssignField(lhs, nme, rhs, rest) if usesStack(rhs) => - applyResult2(rhs): res => - extract(res, false, AssignField(lhs, nme, _, applyBlock(rest))(b.symbol)) - case Define(defn, rest) => - Define(rewriteDefn(defn), applyBlock(rest)) - case HandleBlock(lhs, res, par, cls, handlers, body, rest) => - HandleBlock( - lhs, res, par, cls, handlers.map(h => Handler(h.sym, h.resumeSym, h.params, applyBlock(h.body))), - applyBlock(body), applyBlock(rest) - ) - case HandleBlockReturn(res) if usesStack(res) => - applyResult2(res): res => - extract(res, true, HandleBlockReturn(_)) case _ => super.applyBlock(b) - - override def applyValue(v: Value): Value = - v match + + override def applyResult2(r: Result)(k: Result => Block): Block = + if usesStack(r) then + extract(r, false, k) + else + super.applyResult2(r)(k) + + override def applyValue(v: Value): Value = v match case Value.Lam(params, body) => Value.Lam(params, rewriteBlk(body)) case _ => super.applyValue(v) @@ -152,16 +145,13 @@ class StackSafeTransform(depthLimit: Int)(using State): case HandleBlockReturn(res) => true case End(msg) => true - def rewriteDefn(defn: Defn) = - defn match - case d: FunDefn => rewriteFn(d) - case _: ValDefn => defn - case ClsLikeDefn(owner, isym, sym, k, paramsOpt, - parentPath, methods, privateFields, publicFields, preCtor, ctor) => - ClsLikeDefn( - owner, isym, sym, k, paramsOpt, parentPath, methods.map(rewriteFn), privateFields, - publicFields, rewriteBlk(preCtor), rewriteBlk(ctor) - ) + def rewriteCls(defn: ClsLikeDefn): ClsLikeDefn = + val ClsLikeDefn(owner, isym, sym, k, paramsOpt, + parentPath, methods, privateFields, publicFields, preCtor, ctor) = defn + ClsLikeDefn( + owner, isym, sym, k, paramsOpt, parentPath, methods.map(rewriteFn), privateFields, + publicFields, rewriteBlk(preCtor), rewriteBlk(ctor) + ) def rewriteBlk(blk: Block) = val newBody = transform(blk) diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 60b88d9870..63294eaa8a 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -20,37 +20,52 @@ fun hi(n) = else hi(n - 1) hi(0) //│ JS (unsanitized): -//│ let hi1, res, handleBlock$1; +//│ let hi1, res, res1, handleBlock$2, handleBlock$3; //│ hi1 = function hi(n) { -//│ let scrut, tmp, diff, scrut1, scrut2, scrut3, tmp1, res1, Cont$; +//│ let scrut, tmp, tmp1, prevDepth, diff, scrut1, scrut2, scrut3, tmp2, res2, res3, Cont$; //│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { -//│ let tmp2; -//│ tmp2 = super(null, null); +//│ let tmp3; +//│ tmp3 = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 0) { -//│ res1 = value$; +//│ if (this.pc === 1) { +//│ res3 = value$; +//│ } else if (this.pc === 0) { +//│ res2 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 2) { +//│ if (this.pc === 3) { //│ scrut = n == 0; //│ if (scrut === true) { //│ return 0; //│ } else { //│ tmp = n - 1; +//│ prevDepth = globalThis.Predef.__stackDepth; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ return hi1(tmp); +//│ res3 = hi1(tmp); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = this; +//│ this.pc = 1; +//│ return res3; +//│ } +//│ this.pc = 1; +//│ continue contLoop; //│ } -//│ this.pc = 1; +//│ this.pc = 2; //│ continue contLoop; -//│ } else if (this.pc === 1) { +//│ } else if (this.pc === 2) { //│ break contLoop; +//│ } else if (this.pc === 1) { +//│ tmp1 = res3; +//│ globalThis.Predef.__stackDepth = prevDepth; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ return tmp1; //│ } else if (this.pc === 0) { -//│ tmp1 = res1; -//│ this.pc = 2; +//│ tmp2 = res2; +//│ this.pc = 3; //│ continue contLoop; //│ } //│ break; @@ -63,25 +78,35 @@ hi(0) //│ scrut2 = globalThis.Predef.__stackHandler !== undefined; //│ scrut3 = scrut1 && scrut2; //│ if (scrut3 === true) { -//│ res1 = globalThis.Predef.__stackHandler.perform(); -//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$.class(0); -//│ res1.tail = res1.tail.next; -//│ return res1; +//│ res2 = globalThis.Predef.__stackHandler.perform(); +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = new Cont$.class(0); +//│ res2.tail = res2.tail.next; +//│ return res2; //│ } -//│ tmp1 = res1; +//│ tmp2 = res2; //│ } //│ scrut = n == 0; //│ if (scrut === true) { //│ return 0; //│ } else { //│ tmp = n - 1; +//│ prevDepth = globalThis.Predef.__stackDepth; +//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; +//│ res3 = hi1(tmp); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = new Cont$.class(1); +//│ res3.tail = res3.tail.next; +//│ return res3; +//│ } +//│ tmp1 = res3; +//│ globalThis.Predef.__stackDepth = prevDepth; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ return hi1(tmp); +//│ return tmp1; //│ } //│ }; -//│ handleBlock$1 = function handleBlock$() { -//│ let stackHandler, res1, Cont$, StackDelay$; +//│ handleBlock$3 = function handleBlock$() { +//│ let stackHandler, res2, Cont$, StackDelay$; //│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { //│ constructor() { //│ let tmp; @@ -89,7 +114,7 @@ hi(0) //│ } //│ perform() { //│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res2, curOffset, res3, Cont$1; +//│ let res3, curOffset, res4, Cont$1; //│ Cont$1 = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$1.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { @@ -98,14 +123,14 @@ hi(0) //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 4) { -//│ res3 = value$; +//│ if (this.pc === 7) { +//│ res4 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 4) { -//│ res2 = res3; +//│ if (this.pc === 7) { +//│ res3 = res4; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res2; +//│ return res3; //│ } //│ break; //│ } @@ -114,15 +139,15 @@ hi(0) //│ }; //│ curOffset = globalThis.Predef.__stackOffset; //│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; -//│ res3 = resume(); -//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$1.class(4); -//│ res3.tail = res3.tail.next; -//│ return res3; +//│ res4 = resume(); +//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { +//│ res4.tail.next = new Cont$1.class(7); +//│ res4.tail = res4.tail.next; +//│ return res4; //│ } -//│ res2 = res3; +//│ res3 = res4; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res2; +//│ return res3; //│ }); //│ } //│ toString() { return "StackDelay$"; } @@ -136,12 +161,12 @@ hi(0) //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 3) { -//│ res1 = value$; +//│ if (this.pc === 6) { +//│ res2 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 3) { -//│ return res1; +//│ if (this.pc === 6) { +//│ return res2; //│ } //│ break; //│ } @@ -150,20 +175,78 @@ hi(0) //│ }; //│ globalThis.Predef.__stackDepth = 1; //│ globalThis.Predef.__stackHandler = stackHandler; -//│ res1 = hi1(0); -//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { -//│ res1.tail.next = new Cont$(3); -//│ return globalThis.Predef.__handleBlockImpl(res1, stackHandler); +//│ res2 = hi1(0); +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = new Cont$(6); +//│ return globalThis.Predef.__handleBlockImpl(res2, stackHandler); //│ } -//│ return res1; +//│ return res2; //│ }; -//│ res = handleBlock$1(); +//│ res = handleBlock$3(); //│ if (res instanceof this.Predef.__EffectSig.class) { //│ throw new this.Error("Unhandled effects"); //│ } //│ this.Predef.__stackDepth = 0; //│ this.Predef.__stackHandler = undefined; -//│ res +//│ handleBlock$2 = function handleBlock$() { +//│ let stackHandler, StackDelay$; +//│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { +//│ constructor() { +//│ let tmp; +//│ tmp = super(); +//│ } +//│ perform() { +//│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { +//│ let res2, curOffset, res3, Cont$; +//│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; +//│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp; +//│ tmp = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 4) { +//│ res3 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 4) { +//│ res2 = res3; +//│ globalThis.Predef.__stackOffset = curOffset; +//│ return res2; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$(" + this.pc + ")"; } +//│ }; +//│ curOffset = globalThis.Predef.__stackOffset; +//│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; +//│ res3 = resume(); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = new Cont$.class(4); +//│ res3.tail = res3.tail.next; +//│ return res3; +//│ } +//│ res2 = res3; +//│ globalThis.Predef.__stackOffset = curOffset; +//│ return res2; +//│ }); +//│ } +//│ toString() { return "StackDelay$"; } +//│ }; +//│ stackHandler = new StackDelay$(); +//│ globalThis.Predef.__stackDepth = 1; +//│ globalThis.Predef.__stackHandler = stackHandler; +//│ return res; +//│ }; +//│ res1 = handleBlock$2(); +//│ if (res1 instanceof this.Predef.__EffectSig.class) { +//│ throw new this.Error("Unhandled effects"); +//│ } +//│ this.Predef.__stackDepth = 0; +//│ this.Predef.__stackHandler = undefined; +//│ res1 //│ = 0 :sjs @@ -176,9 +259,9 @@ fun sum(n) = n + sum(n - 1) sum(10000) //│ JS (unsanitized): -//│ let sum3, res1, handleBlock$3; +//│ let sum3, res2, res3, handleBlock$6, handleBlock$7; //│ sum3 = function sum(n) { -//│ let scrut, tmp, tmp1, tmp2, prevDepth, diff, scrut1, scrut2, scrut3, tmp3, res2, res3, Cont$; +//│ let scrut, tmp, tmp1, tmp2, prevDepth, diff, scrut1, scrut2, scrut3, tmp3, res4, res5, Cont$; //│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { @@ -188,9 +271,9 @@ sum(10000) //│ } //│ resume(value$) { //│ if (this.pc === 1) { -//│ res3 = value$; +//│ res5 = value$; //│ } else if (this.pc === 0) { -//│ res2 = value$; +//│ res4 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 3) { @@ -201,11 +284,11 @@ sum(10000) //│ tmp = n - 1; //│ prevDepth = globalThis.Predef.__stackDepth; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res3 = sum3(tmp); -//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = this; +//│ res5 = sum3(tmp); +//│ if (res5 instanceof globalThis.Predef.__EffectSig.class) { +//│ res5.tail.next = this; //│ this.pc = 1; -//│ return res3; +//│ return res5; //│ } //│ this.pc = 1; //│ continue contLoop; @@ -215,12 +298,12 @@ sum(10000) //│ } else if (this.pc === 2) { //│ break contLoop; //│ } else if (this.pc === 1) { -//│ tmp2 = res3; +//│ tmp2 = res5; //│ globalThis.Predef.__stackDepth = prevDepth; //│ tmp1 = tmp2; //│ return n + tmp1; //│ } else if (this.pc === 0) { -//│ tmp3 = res2; +//│ tmp3 = res4; //│ this.pc = 3; //│ continue contLoop; //│ } @@ -234,13 +317,13 @@ sum(10000) //│ scrut2 = globalThis.Predef.__stackHandler !== undefined; //│ scrut3 = scrut1 && scrut2; //│ if (scrut3 === true) { -//│ res2 = globalThis.Predef.__stackHandler.perform(); -//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$.class(0); -//│ res2.tail = res2.tail.next; -//│ return res2; +//│ res4 = globalThis.Predef.__stackHandler.perform(); +//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { +//│ res4.tail.next = new Cont$.class(0); +//│ res4.tail = res4.tail.next; +//│ return res4; //│ } -//│ tmp3 = res2; +//│ tmp3 = res4; //│ } //│ scrut = n == 0; //│ if (scrut === true) { @@ -249,20 +332,20 @@ sum(10000) //│ tmp = n - 1; //│ prevDepth = globalThis.Predef.__stackDepth; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res3 = sum3(tmp); -//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$.class(1); -//│ res3.tail = res3.tail.next; -//│ return res3; +//│ res5 = sum3(tmp); +//│ if (res5 instanceof globalThis.Predef.__EffectSig.class) { +//│ res5.tail.next = new Cont$.class(1); +//│ res5.tail = res5.tail.next; +//│ return res5; //│ } -//│ tmp2 = res3; +//│ tmp2 = res5; //│ globalThis.Predef.__stackDepth = prevDepth; //│ tmp1 = tmp2; //│ return n + tmp1; //│ } //│ }; -//│ handleBlock$3 = function handleBlock$() { -//│ let stackHandler, res2, Cont$, StackDelay$; +//│ handleBlock$7 = function handleBlock$() { +//│ let stackHandler, res4, Cont$, StackDelay$; //│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { //│ constructor() { //│ let tmp; @@ -270,7 +353,7 @@ sum(10000) //│ } //│ perform() { //│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res3, curOffset, res4, Cont$1; +//│ let res5, curOffset, res6, Cont$1; //│ Cont$1 = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$1.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { @@ -279,14 +362,14 @@ sum(10000) //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 5) { -//│ res4 = value$; +//│ if (this.pc === 7) { +//│ res6 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 5) { -//│ res3 = res4; +//│ if (this.pc === 7) { +//│ res5 = res6; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res3; +//│ return res5; //│ } //│ break; //│ } @@ -295,15 +378,15 @@ sum(10000) //│ }; //│ curOffset = globalThis.Predef.__stackOffset; //│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; -//│ res4 = resume(); -//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { -//│ res4.tail.next = new Cont$1.class(5); -//│ res4.tail = res4.tail.next; -//│ return res4; +//│ res6 = resume(); +//│ if (res6 instanceof globalThis.Predef.__EffectSig.class) { +//│ res6.tail.next = new Cont$1.class(7); +//│ res6.tail = res6.tail.next; +//│ return res6; //│ } -//│ res3 = res4; +//│ res5 = res6; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res3; +//│ return res5; //│ }); //│ } //│ toString() { return "StackDelay$"; } @@ -317,12 +400,12 @@ sum(10000) //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 4) { -//│ res2 = value$; +//│ if (this.pc === 6) { +//│ res4 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 4) { -//│ return res2; +//│ if (this.pc === 6) { +//│ return res4; //│ } //│ break; //│ } @@ -331,20 +414,78 @@ sum(10000) //│ }; //│ globalThis.Predef.__stackDepth = 1; //│ globalThis.Predef.__stackHandler = stackHandler; -//│ res2 = sum3(10000); -//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$(4); -//│ return globalThis.Predef.__handleBlockImpl(res2, stackHandler); +//│ res4 = sum3(10000); +//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { +//│ res4.tail.next = new Cont$(6); +//│ return globalThis.Predef.__handleBlockImpl(res4, stackHandler); //│ } +//│ return res4; +//│ }; +//│ res2 = handleBlock$7(); +//│ if (res2 instanceof this.Predef.__EffectSig.class) { +//│ throw new this.Error("Unhandled effects"); +//│ } +//│ this.Predef.__stackDepth = 0; +//│ this.Predef.__stackHandler = undefined; +//│ handleBlock$6 = function handleBlock$() { +//│ let stackHandler, StackDelay$; +//│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { +//│ constructor() { +//│ let tmp; +//│ tmp = super(); +//│ } +//│ perform() { +//│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { +//│ let res4, curOffset, res5, Cont$; +//│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; +//│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { +//│ constructor(pc) { +//│ let tmp; +//│ tmp = super(null, null); +//│ this.pc = pc; +//│ } +//│ resume(value$) { +//│ if (this.pc === 4) { +//│ res5 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 4) { +//│ res4 = res5; +//│ globalThis.Predef.__stackOffset = curOffset; +//│ return res4; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$(" + this.pc + ")"; } +//│ }; +//│ curOffset = globalThis.Predef.__stackOffset; +//│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; +//│ res5 = resume(); +//│ if (res5 instanceof globalThis.Predef.__EffectSig.class) { +//│ res5.tail.next = new Cont$.class(4); +//│ res5.tail = res5.tail.next; +//│ return res5; +//│ } +//│ res4 = res5; +//│ globalThis.Predef.__stackOffset = curOffset; +//│ return res4; +//│ }); +//│ } +//│ toString() { return "StackDelay$"; } +//│ }; +//│ stackHandler = new StackDelay$(); +//│ globalThis.Predef.__stackDepth = 1; +//│ globalThis.Predef.__stackHandler = stackHandler; //│ return res2; //│ }; -//│ res1 = handleBlock$3(); -//│ if (res1 instanceof this.Predef.__EffectSig.class) { +//│ res3 = handleBlock$6(); +//│ if (res3 instanceof this.Predef.__EffectSig.class) { //│ throw new this.Error("Unhandled effects"); //│ } //│ this.Predef.__stackDepth = 0; //│ this.Predef.__stackHandler = undefined; -//│ res1 +//│ res3 //│ = 50005000 // stack-overflows without :stackSafe diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls index b61b2c0016..1333042d4a 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -18,8 +18,9 @@ fact(3) fact(10) //│ = 3628800 +:re fact(5000) -//│ = Infinity +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded fun mkrec(g) = @@ -51,7 +52,7 @@ fact(10) set selfApp = f => f(f) fact(5000) -//│ = 120 +//│ = 3628800 // simplified version without lambdas for easier debugging :stackSafe 5 @@ -74,7 +75,6 @@ let fact = mkrec(a) //│ fact = [Function: b] -:fixme :expect 3628800 :stackSafe 5 fact(10) @@ -93,5 +93,9 @@ fact(10) //│ > resumed: 3 //│ > resumed: 4 //│ > resumed: 5 -//│ ═══[RUNTIME ERROR] Expected: 3628800, got: 120 -//│ = 120 +//│ > resumed: 6 +//│ > resumed: 7 +//│ > resumed: 8 +//│ > resumed: 9 +//│ > resumed: 10 +//│ = 3628800 From ab55e5a66945fc8bb36d6ed2dff5ce461cfd5d99 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 23 Jan 2025 18:59:37 +0800 Subject: [PATCH 093/114] update tests --- .../mlscript/handlers/ManualStackSafety.mls | 108 ++++++++++++++++++ .../test/mlscript/handlers/ZCombinator.mls | 22 +++- .../test/scala/hkmc2/JSBackendDiffMaker.scala | 5 +- 3 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls diff --git a/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls new file mode 100644 index 0000000000..635b747fd6 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls @@ -0,0 +1,108 @@ +:js +:handler + +fun test(n, stackLimit) = + let stackDepth = 0 + let stackOffset = 0 + abstract class StackDelay with + fun perform() + handle h = StackDelay with + fun perform()(resume) = + let curOffset = stackOffset + set stackOffset = stackDepth + console.log("resuming at offset:", curOffset) + let tmp = resume() + console.log("finished at offset:", curOffset) + set stackOffset = curOffset + tmp + fun selfApp(f) = + if stackDepth - stackOffset >= stackLimit do h.perform() + set stackDepth += 1 + f(f) + fun mkrec(g) = + if stackDepth - stackOffset >= stackLimit do h.perform() + fun a(self) = + if stackDepth - stackOffset >= stackLimit do h.perform() + fun b(y) = + if stackDepth - stackOffset >= stackLimit do h.perform() + let tmp = stackDepth + set stackDepth += 1 + let res = selfApp(self) + set stackDepth = tmp + set stackDepth += 1 + res(y) + set stackDepth += 1 + g(b) + set stackDepth += 1 + selfApp(a) + let fact = + fun a(self) = + if stackDepth - stackOffset >= stackLimit do h.perform() + fun b(x) = + if stackDepth - stackOffset >= stackLimit do h.perform() + if x == 0 then 1 else + console.log(stackDepth, stackOffset) + let tmp = stackDepth + set stackDepth += 1 + let prev = self(x - 1) + set stackDepth = tmp + console.log("resumed:", x) + x * prev + b + mkrec(a) + set stackDepth = 1 + let ans = fact(n) + set stackDepth = 0 + ans + +:expect 3628800 +test(10, 100) +//│ > 1 0 +//│ > 3 0 +//│ > 5 0 +//│ > 7 0 +//│ > 9 0 +//│ > 11 0 +//│ > 13 0 +//│ > 15 0 +//│ > 17 0 +//│ > 19 0 +//│ > resumed: 1 +//│ > resumed: 2 +//│ > resumed: 3 +//│ > resumed: 4 +//│ > resumed: 5 +//│ > resumed: 6 +//│ > resumed: 7 +//│ > resumed: 8 +//│ > resumed: 9 +//│ > resumed: 10 +//│ = 3628800 + +:fixme +:expect 3628800 +test(10, 5) +//│ > 1 0 +//│ > resuming at offset: 0 +//│ > 3 5 +//│ > 5 5 +//│ > 7 5 +//│ > resuming at offset: 5 +//│ > 9 10 +//│ > 11 10 +//│ > resuming at offset: 10 +//│ > 13 15 +//│ > 15 15 +//│ > 17 15 +//│ > resuming at offset: 15 +//│ > 19 20 +//│ > resumed: 1 +//│ > resumed: 2 +//│ > resumed: 3 +//│ > resumed: 4 +//│ > finished at offset: 15 +//│ > finished at offset: 10 +//│ > finished at offset: 5 +//│ > finished at offset: 0 +//│ ═══[RUNTIME ERROR] Expected: 3628800, got: 24 +//│ = 24 diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls index 1333042d4a..1bd5baad53 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -19,6 +19,7 @@ fact(10) //│ = 3628800 :re +:stackSafe off fact(5000) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded @@ -28,10 +29,27 @@ fun mkrec(g) = g of y => selfApp(self)(y) let fact = mkrec of self => x => - if x == 0 then 1 else self(x - 1) * x + if x == 0 then 1 + else self(x - 1) * x //│ fact = [Function (anonymous)] +:stackSafe 1000 +fact(5000) +//│ = Infinity + // * Without `:stackSafe`, gives `RangeError: Maximum call stack size exceeded` +:re +:stackSafe off +fact(5000) +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + +:stackSafe 1000 +fact(5000) +//│ = Infinity + +// ???????????? +:todo +:stackSafe off fact(5000) //│ = Infinity @@ -51,7 +69,7 @@ fact(10) :stackSafe 10 set selfApp = f => f(f) -fact(5000) +fact(1000) //│ = 3628800 // simplified version without lambdas for easier debugging diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala index 4ac71b4a0f..58cc0601ad 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -57,9 +57,10 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: super.processTerm(blk, inImport) val outerRaise: Raise = summon var showingJSYieldedCompileError = false - val stackLimit = stackSafe.get.map(_.toIntOption) match + val stackLimit = stackSafe.get match case None => None - case Some(value) => value match + case Some("off") => None + case Some(value) => value.toIntOption match case None => Some(DEFAULT_STACK_LIMT) case Some(value) => if value < 0 then From 70c502d1f94e3f38a582225d802d704c584fa186 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 19:14:27 +0800 Subject: [PATCH 094/114] Fix top level effectful check --- .../scala/hkmc2/codegen/HandlerLowering.scala | 12 +- .../mlscript/handlers/HandlersScratch.mls | 105 +++++++++++++++++- .../mlscript/handlers/RecursiveHandlers.mls | 74 ++++++------ .../test/mlscript/handlers/ZCombinator.mls | 3 +- 4 files changed, 148 insertions(+), 46 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index f10a92801c..b36c4c9fe3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -288,10 +288,10 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case b: HandleBlock => val rest = applyBlock(b.rest) translateHandleBlock(b.copy(rest = rest)) - case Return(c: Call, implct) if handlerCtx.isHandleFree => - val fun2 = applyPath(c.fun) - val args2 = c.args.map(applyArg) - val c2 = if (fun2 is c.fun) && (args2 zip c.args).forall(_ is _) then c else Call(fun2, args2)(c.isMlsFun) + case Return(c @ Call(fun, args), implct) if handlerCtx.isHandleFree && !handlerCtx.isTopLevel => + val fun2 = applyPath(fun) + val args2 = args.map(applyArg) + val c2 = if (fun2 is fun) && (args2 zip args).forall(_ is _) then c else Call(fun2, args2)(c.isMlsFun) if c2 is c then b else Return(c2, implct) case _ => super.applyBlock(b) override def applyResult2(r: Result)(k: Result => Block): Block = r match @@ -299,13 +299,13 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case c @ Call(fun, args) => val res = freshTmp("res") val fun2 = applyPath(fun) - val args2 = c.args.map(applyArg) + val args2 = args.map(applyArg) val c2 = if (fun2 is fun) && (args2 zip args).forall(_ is _) then c else Call(fun2, args2)(c.isMlsFun) ResultPlaceholder(res, freshId(), false, c2, k(Value.Ref(res))) case c @ Instantiate(cls, args) => val res = freshTmp("res") val cls2 = applyPath(cls) - val args2 = c.args.map(applyPath) + val args2 = args.map(applyPath) val c2 = if (cls2 is cls) && (args2 zip args).forall(_ is _) then c else Instantiate(cls2, args2) ResultPlaceholder(res, freshId(), false, c2, k(Value.Ref(res))) case r => super.applyResult2(r)(k) diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls index 5316a31765..635b747fd6 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls @@ -1,7 +1,108 @@ :js :handler -abstract class Effect - +fun test(n, stackLimit) = + let stackDepth = 0 + let stackOffset = 0 + abstract class StackDelay with + fun perform() + handle h = StackDelay with + fun perform()(resume) = + let curOffset = stackOffset + set stackOffset = stackDepth + console.log("resuming at offset:", curOffset) + let tmp = resume() + console.log("finished at offset:", curOffset) + set stackOffset = curOffset + tmp + fun selfApp(f) = + if stackDepth - stackOffset >= stackLimit do h.perform() + set stackDepth += 1 + f(f) + fun mkrec(g) = + if stackDepth - stackOffset >= stackLimit do h.perform() + fun a(self) = + if stackDepth - stackOffset >= stackLimit do h.perform() + fun b(y) = + if stackDepth - stackOffset >= stackLimit do h.perform() + let tmp = stackDepth + set stackDepth += 1 + let res = selfApp(self) + set stackDepth = tmp + set stackDepth += 1 + res(y) + set stackDepth += 1 + g(b) + set stackDepth += 1 + selfApp(a) + let fact = + fun a(self) = + if stackDepth - stackOffset >= stackLimit do h.perform() + fun b(x) = + if stackDepth - stackOffset >= stackLimit do h.perform() + if x == 0 then 1 else + console.log(stackDepth, stackOffset) + let tmp = stackDepth + set stackDepth += 1 + let prev = self(x - 1) + set stackDepth = tmp + console.log("resumed:", x) + x * prev + b + mkrec(a) + set stackDepth = 1 + let ans = fact(n) + set stackDepth = 0 + ans +:expect 3628800 +test(10, 100) +//│ > 1 0 +//│ > 3 0 +//│ > 5 0 +//│ > 7 0 +//│ > 9 0 +//│ > 11 0 +//│ > 13 0 +//│ > 15 0 +//│ > 17 0 +//│ > 19 0 +//│ > resumed: 1 +//│ > resumed: 2 +//│ > resumed: 3 +//│ > resumed: 4 +//│ > resumed: 5 +//│ > resumed: 6 +//│ > resumed: 7 +//│ > resumed: 8 +//│ > resumed: 9 +//│ > resumed: 10 +//│ = 3628800 +:fixme +:expect 3628800 +test(10, 5) +//│ > 1 0 +//│ > resuming at offset: 0 +//│ > 3 5 +//│ > 5 5 +//│ > 7 5 +//│ > resuming at offset: 5 +//│ > 9 10 +//│ > 11 10 +//│ > resuming at offset: 10 +//│ > 13 15 +//│ > 15 15 +//│ > 17 15 +//│ > resuming at offset: 15 +//│ > 19 20 +//│ > resumed: 1 +//│ > resumed: 2 +//│ > resumed: 3 +//│ > resumed: 4 +//│ > finished at offset: 15 +//│ > finished at offset: 10 +//│ > finished at offset: 5 +//│ > finished at offset: 0 +//│ ═══[RUNTIME ERROR] Expected: 3628800, got: 24 +//│ = 24 diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 4080b9bc8e..d720fbb82f 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -134,7 +134,7 @@ str //│ } //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h1, (k) => { -//│ let tmp9, tmp10, tmp11, res3, Cont$1; +//│ let tmp9, tmp10, tmp11, res5, Cont$1; //│ Cont$1 = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$1.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { @@ -144,11 +144,11 @@ str //│ } //│ resume(value$) { //│ if (this.pc === 5) { -//│ res3 = value$; +//│ res5 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 5) { -//│ tmp10 = res3; +//│ tmp10 = res5; //│ tmp11 = str + "A"; //│ str = tmp11; //│ return null; @@ -160,13 +160,13 @@ str //│ }; //│ tmp9 = str + "A"; //│ str = tmp9; -//│ res3 = k(arg) ?? null; -//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$1.class(5); -//│ res3.tail = res3.tail.next; -//│ return res3; +//│ res5 = k(arg) ?? null; +//│ if (res5 instanceof globalThis.Predef.__EffectSig.class) { +//│ res5.tail.next = new Cont$1.class(5); +//│ res5.tail = res5.tail.next; +//│ return res5; //│ } -//│ tmp10 = res3; +//│ tmp10 = res5; //│ tmp11 = str + "A"; //│ str = tmp11; //│ return null; @@ -202,7 +202,7 @@ str //│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; //│ handleBlock$10 = function handleBlock$() { -//│ let h2, tmp9, res3, res4, Cont$1, Effect$h2$; +//│ let h2, tmp9, res5, res6, Cont$1, Effect$h2$; //│ Effect$h2$ = class Effect$h2$ extends Effect1 { //│ constructor() { //│ let tmp10; @@ -210,7 +210,7 @@ str //│ } //│ perform(arg) { //│ return globalThis.Predef.__mkEffect(h2, (k) => { -//│ let tmp10, tmp11, tmp12, tmp13, tmp14, res5, Cont$2; +//│ let tmp10, tmp11, tmp12, tmp13, tmp14, res7, Cont$2; //│ Cont$2 = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$2.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { @@ -220,11 +220,11 @@ str //│ } //│ resume(value$) { //│ if (this.pc === 2) { -//│ res5 = value$; +//│ res7 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 2) { -//│ tmp12 = res5; +//│ tmp12 = res7; //│ tmp13 = str + "B"; //│ tmp14 = str + tmp13; //│ str = tmp14; @@ -238,13 +238,13 @@ str //│ tmp10 = str + "B"; //│ tmp11 = str + tmp10; //│ str = tmp11; -//│ res5 = k(arg) ?? null; -//│ if (res5 instanceof globalThis.Predef.__EffectSig.class) { -//│ res5.tail.next = new Cont$2.class(2); -//│ res5.tail = res5.tail.next; -//│ return res5; +//│ res7 = k(arg) ?? null; +//│ if (res7 instanceof globalThis.Predef.__EffectSig.class) { +//│ res7.tail.next = new Cont$2.class(2); +//│ res7.tail = res7.tail.next; +//│ return res7; //│ } -//│ tmp12 = res5; +//│ tmp12 = res7; //│ tmp13 = str + "B"; //│ tmp14 = str + tmp13; //│ str = tmp14; @@ -263,41 +263,41 @@ str //│ } //│ resume(value$) { //│ if (this.pc === 0) { -//│ res3 = value$; +//│ res5 = value$; //│ } else if (this.pc === 1) { -//│ res4 = value$; +//│ res6 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 0) { -//│ tmp9 = res3; -//│ res4 = h1.perform(null) ?? null; -//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { -//│ res4.tail.next = this; +//│ tmp9 = res5; +//│ res6 = h1.perform(null) ?? null; +//│ if (res6 instanceof globalThis.Predef.__EffectSig.class) { +//│ res6.tail.next = this; //│ this.pc = 1; -//│ return res4; +//│ return res6; //│ } //│ this.pc = 1; //│ continue contLoop; //│ } else if (this.pc === 1) { -//│ return res4; +//│ return res6; //│ } //│ break; //│ } //│ } //│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; -//│ res3 = h2.perform(null) ?? null; -//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$1(0); -//│ return globalThis.Predef.__handleBlockImpl(res3, h2); +//│ res5 = h2.perform(null) ?? null; +//│ if (res5 instanceof globalThis.Predef.__EffectSig.class) { +//│ res5.tail.next = new Cont$1(0); +//│ return globalThis.Predef.__handleBlockImpl(res5, h2); //│ } -//│ tmp9 = res3; -//│ res4 = h1.perform(null) ?? null; -//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { -//│ res4.tail.next = new Cont$1(1); -//│ return globalThis.Predef.__handleBlockImpl(res4, h2); +//│ tmp9 = res5; +//│ res6 = h1.perform(null) ?? null; +//│ if (res6 instanceof globalThis.Predef.__EffectSig.class) { +//│ res6.tail.next = new Cont$1(1); +//│ return globalThis.Predef.__handleBlockImpl(res6, h2); //│ } -//│ return res4; +//│ return res6; //│ }; //│ tmp8 = handleBlock$10(); //│ if (tmp8 instanceof globalThis.Predef.__EffectSig.class) { diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls index 1bd5baad53..6891381b33 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -41,7 +41,8 @@ fact(5000) :re :stackSafe off fact(5000) -//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded +//│ = Infinity +//│ FAILURE: Unexpected lack of runtime error :stackSafe 1000 fact(5000) From c93e8443f91d14951a45e3c31d82043051ef475f Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 19:15:16 +0800 Subject: [PATCH 095/114] revert scratch changes --- .../mlscript/handlers/HandlersScratch.mls | 104 +----------------- 1 file changed, 1 insertion(+), 103 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls index 635b747fd6..ab7ea30cae 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls @@ -1,108 +1,6 @@ :js :handler -fun test(n, stackLimit) = - let stackDepth = 0 - let stackOffset = 0 - abstract class StackDelay with - fun perform() - handle h = StackDelay with - fun perform()(resume) = - let curOffset = stackOffset - set stackOffset = stackDepth - console.log("resuming at offset:", curOffset) - let tmp = resume() - console.log("finished at offset:", curOffset) - set stackOffset = curOffset - tmp - fun selfApp(f) = - if stackDepth - stackOffset >= stackLimit do h.perform() - set stackDepth += 1 - f(f) - fun mkrec(g) = - if stackDepth - stackOffset >= stackLimit do h.perform() - fun a(self) = - if stackDepth - stackOffset >= stackLimit do h.perform() - fun b(y) = - if stackDepth - stackOffset >= stackLimit do h.perform() - let tmp = stackDepth - set stackDepth += 1 - let res = selfApp(self) - set stackDepth = tmp - set stackDepth += 1 - res(y) - set stackDepth += 1 - g(b) - set stackDepth += 1 - selfApp(a) - let fact = - fun a(self) = - if stackDepth - stackOffset >= stackLimit do h.perform() - fun b(x) = - if stackDepth - stackOffset >= stackLimit do h.perform() - if x == 0 then 1 else - console.log(stackDepth, stackOffset) - let tmp = stackDepth - set stackDepth += 1 - let prev = self(x - 1) - set stackDepth = tmp - console.log("resumed:", x) - x * prev - b - mkrec(a) - set stackDepth = 1 - let ans = fact(n) - set stackDepth = 0 - ans +abstract class Effect -:expect 3628800 -test(10, 100) -//│ > 1 0 -//│ > 3 0 -//│ > 5 0 -//│ > 7 0 -//│ > 9 0 -//│ > 11 0 -//│ > 13 0 -//│ > 15 0 -//│ > 17 0 -//│ > 19 0 -//│ > resumed: 1 -//│ > resumed: 2 -//│ > resumed: 3 -//│ > resumed: 4 -//│ > resumed: 5 -//│ > resumed: 6 -//│ > resumed: 7 -//│ > resumed: 8 -//│ > resumed: 9 -//│ > resumed: 10 -//│ = 3628800 -:fixme -:expect 3628800 -test(10, 5) -//│ > 1 0 -//│ > resuming at offset: 0 -//│ > 3 5 -//│ > 5 5 -//│ > 7 5 -//│ > resuming at offset: 5 -//│ > 9 10 -//│ > 11 10 -//│ > resuming at offset: 10 -//│ > 13 15 -//│ > 15 15 -//│ > 17 15 -//│ > resuming at offset: 15 -//│ > 19 20 -//│ > resumed: 1 -//│ > resumed: 2 -//│ > resumed: 3 -//│ > resumed: 4 -//│ > finished at offset: 15 -//│ > finished at offset: 10 -//│ > finished at offset: 5 -//│ > finished at offset: 0 -//│ ═══[RUNTIME ERROR] Expected: 3628800, got: 24 -//│ = 24 From 8baa3fcb69cc542bd6c8eaea9fb1f763646fa4c7 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 19:15:51 +0800 Subject: [PATCH 096/114] revert scratch changes --- hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls | 1 + 1 file changed, 1 insertion(+) diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls index ab7ea30cae..5316a31765 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls @@ -4,3 +4,4 @@ abstract class Effect + From 5470a9e8adba252ee86be7ec7e09e6fe5bc2bd1a Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 19:37:30 +0800 Subject: [PATCH 097/114] Add broken test --- .../src/test/mlscript-compile/Predef.mls | 1 + .../handlers/TailCallOptimization.mls | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 2f9f67277a..fa2c4c154d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -234,6 +234,7 @@ fun __resumeHandleBlocks(handleBlock, tailHandleBlock, value) = set value = handleBlock.contHead.next.resume(value) if value is __EffectSig then // this checks when continuation resume results in tail call to effectful func + // when a tail call happens, the continuation will not be appended so this will be different if handleBlock.contHead.next !== value.tail.next do // if this is a tail call that results in effect, the continuation is already completed // and should be removed diff --git a/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls b/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls new file mode 100644 index 0000000000..3b23aa8f40 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls @@ -0,0 +1,21 @@ +:js +:handler + +abstract class StackDelay with + fun perform(): () + +handle h = StackDelay with + fun perform()(resume) = resume() +fun foo(x) = + h.perform() + x + 4 +fun bar(y) = + h.perform() + // tail call + foo(y + 2) +fun foobar(z) = + bar(z + 1) + // stuff after tail call is linked differently + 3 +foobar(0) +//│ = 7 From aae14de913bab1bb8d78da2724cd86b38ce5cd3b Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 19:47:48 +0800 Subject: [PATCH 098/114] Fix unhandled tail call optimization case --- .../src/test/mlscript-compile/Predef.mjs | 41 +++++++++++-------- .../src/test/mlscript-compile/Predef.mls | 6 ++- .../mlscript/handlers/ManualStackSafety.mls | 10 +++-- .../handlers/TailCallOptimization.mls | 3 +- .../test/mlscript/handlers/ZCombinator.mls | 9 ++-- 5 files changed, 41 insertions(+), 28 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index fc380b8a0c..c85580ae36 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -419,7 +419,7 @@ const Predef$class = class Predef { } __resume(cur2, tail) { return (value) => { - let scrut, cont, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; + let scrut, cont, scrut1, scrut2, scrut3, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; scrut = cur2.resumed; if (scrut === true) { throw globalThis.Error("Multiple resumption"); @@ -428,43 +428,50 @@ const Predef$class = class Predef { } cur2.resumed = true; cont = cur2.next; - tmp7: while (true) { + tmp8: while (true) { if (cont instanceof this.__Cont.class) { tmp1 = cont.resume(value) ?? null; value = tmp1; if (value instanceof this.__EffectSig.class) { - value.tail = tail; - scrut1 = cur2.handleBlockList.next !== null; + scrut1 = value.tail.next === null; if (scrut1 === true) { - value.handleBlockList.tail.next = cur2.handleBlockList.next; - value.handleBlockList.tail = cur2.handleBlockList.tail; + value.tail.next = cont.next; tmp2 = null; } else { tmp2 = null; } + value.tail = tail; + scrut2 = cur2.handleBlockList.next !== null; + if (scrut2 === true) { + value.handleBlockList.tail.next = cur2.handleBlockList.next; + value.handleBlockList.tail = cur2.handleBlockList.tail; + tmp3 = null; + } else { + tmp3 = null; + } return value; } else { cont = cont.next; - tmp3 = null; + tmp4 = null; } - tmp4 = tmp3; - continue tmp7; + tmp5 = tmp4; + continue tmp8; } else { - tmp4 = null; + tmp5 = null; } break; } - scrut2 = cur2.handleBlockList.next === null; - if (scrut2 === true) { + scrut3 = cur2.handleBlockList.next === null; + if (scrut3 === true) { return value; } else { - tmp5 = this.__resumeHandleBlocks(cur2.handleBlockList.next, cur2.handleBlockList.tail, value); - cur2 = tmp5; + tmp6 = this.__resumeHandleBlocks(cur2.handleBlockList.next, cur2.handleBlockList.tail, value); + cur2 = tmp6; if (cur2 instanceof this.__EffectSig.class) { cur2.tail = tail; - tmp6 = null; + tmp7 = null; } else { - tmp6 = null; + tmp7 = null; } return cur2; } @@ -478,7 +485,7 @@ const Predef$class = class Predef { tmp = handleBlock.contHead.next.resume(value) ?? null; value = tmp; if (value instanceof this.__EffectSig.class) { - scrut2 = handleBlock.contHead.next !== value.tail.next; + scrut2 = value.tail.next === null; if (scrut2 === true) { handleBlock.contHead.next = handleBlock.contHead.next.next; tmp1 = null; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index fa2c4c154d..8f3773a4e1 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -207,6 +207,10 @@ fun __resume(cur, tail)(value) = cont is __Cont do set value = cont.resume(value) if value is __EffectSig then + // in tail call optimization, the continuation is not appended, we append it here + if value.tail.next === null do + // since it is a tail call, it is already completed so we append the continuation after it + set value.tail.next = cont.next // we are returning to handler, which performs unwinding so tail is needed set value.tail = tail if cur.handleBlockList.next !== null do @@ -235,7 +239,7 @@ fun __resumeHandleBlocks(handleBlock, tailHandleBlock, value) = if value is __EffectSig then // this checks when continuation resume results in tail call to effectful func // when a tail call happens, the continuation will not be appended so this will be different - if handleBlock.contHead.next !== value.tail.next do + if value.tail.next === null do // if this is a tail call that results in effect, the continuation is already completed // and should be removed set handleBlock.contHead.next = handleBlock.contHead.next.next diff --git a/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls index 635b747fd6..706c5b5dce 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls @@ -79,7 +79,6 @@ test(10, 100) //│ > resumed: 10 //│ = 3628800 -:fixme :expect 3628800 test(10, 5) //│ > 1 0 @@ -100,9 +99,14 @@ test(10, 5) //│ > resumed: 2 //│ > resumed: 3 //│ > resumed: 4 +//│ > resumed: 5 +//│ > resumed: 6 +//│ > resumed: 7 +//│ > resumed: 8 +//│ > resumed: 9 +//│ > resumed: 10 //│ > finished at offset: 15 //│ > finished at offset: 10 //│ > finished at offset: 5 //│ > finished at offset: 0 -//│ ═══[RUNTIME ERROR] Expected: 3628800, got: 24 -//│ = 24 +//│ = 3628800 diff --git a/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls b/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls index 3b23aa8f40..99ca1276bd 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/TailCallOptimization.mls @@ -4,6 +4,7 @@ abstract class StackDelay with fun perform(): () +:expect 3 handle h = StackDelay with fun perform()(resume) = resume() fun foo(x) = @@ -18,4 +19,4 @@ fun foobar(z) = // stuff after tail call is linked differently 3 foobar(0) -//│ = 7 +//│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls index 6891381b33..b6c418d537 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -38,11 +38,10 @@ fact(5000) //│ = Infinity // * Without `:stackSafe`, gives `RangeError: Maximum call stack size exceeded` -:re +// :re :stackSafe off fact(5000) //│ = Infinity -//│ FAILURE: Unexpected lack of runtime error :stackSafe 1000 fact(5000) @@ -60,18 +59,16 @@ fact(5000) :stackSafe 5 set selfApp = f => f(f) -:fixme :expect 3628800 fact(10) -//│ ═══[RUNTIME ERROR] Expected: 3628800, got: 120 -//│ = 120 +//│ = 3628800 :stackSafe 10 set selfApp = f => f(f) fact(1000) -//│ = 3628800 +//│ = Infinity // simplified version without lambdas for easier debugging :stackSafe 5 From 434b705cd935282876daf53436a28e144bf889a5 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 19:54:57 +0800 Subject: [PATCH 099/114] Increased depth --- .../test/mlscript/handlers/ZCombinator.mls | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls index b6c418d537..13c821155e 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -20,7 +20,7 @@ fact(10) :re :stackSafe off -fact(5000) +fact(10000) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded @@ -34,24 +34,23 @@ let fact = mkrec of self => x => //│ fact = [Function (anonymous)] :stackSafe 1000 -fact(5000) +fact(10000) //│ = Infinity // * Without `:stackSafe`, gives `RangeError: Maximum call stack size exceeded` -// :re +:re :stackSafe off -fact(5000) -//│ = Infinity +fact(10000) +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded :stackSafe 1000 -fact(5000) +fact(10000) //│ = Infinity -// ???????????? -:todo +:re :stackSafe off -fact(5000) -//│ = Infinity +fact(10000) +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded // * FIXME: changing the instrumentation's stack depth shouldn't change the result! From 8dcd3deef96f0bb18f3c67bd806255fd613f3a08 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 19:57:56 +0800 Subject: [PATCH 100/114] Update obselete comment --- hkmc2/shared/src/test/mlscript-compile/Predef.mls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 8f3773a4e1..936855c4a0 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -238,7 +238,7 @@ fun __resumeHandleBlocks(handleBlock, tailHandleBlock, value) = set value = handleBlock.contHead.next.resume(value) if value is __EffectSig then // this checks when continuation resume results in tail call to effectful func - // when a tail call happens, the continuation will not be appended so this will be different + // when a tail call happens, the continuation will not be appended so this will be null if value.tail.next === null do // if this is a tail call that results in effect, the continuation is already completed // and should be removed From 5beec3c4ba8adaa53efca77eb00667f18916b93f Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 19:59:54 +0800 Subject: [PATCH 101/114] Removed comment --- hkmc2/shared/src/test/mlscript-compile/Predef.mls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 936855c4a0..4eb4e92c37 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -260,4 +260,4 @@ val __stackDepth = 0 // Tracks the virtual + real stack depth val __stackOffset = 0 // How much to offset __stackDepth by to get the true stack depth (i.e. the virtual depth) val __stackHandler = null abstract class __StackDelay() with - fun perform()// Private definitions for algebraic effects + fun perform() From 4e2f460fe6522c85e7d86510df09c8e20dced360 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 20:09:49 +0800 Subject: [PATCH 102/114] Fix transformation code --- .../hkmc2/codegen/StackSafeTransform.scala | 5 +- .../test/mlscript/handlers/StackSafety.mls | 327 +++++------------- 2 files changed, 95 insertions(+), 237 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 1089a9191f..360be224f2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -103,9 +103,8 @@ class StackSafeTransform(depthLimit: Int)(using State): case _: FunDefn | _: ValDefn => super.applyDefn(defn) override def applyBlock(b: Block): Block = b match - case Return(res, implct) if usesStack(res) => - applyResult2(res): res => - extract(res, true, Return(_, implct)) + case Return(res, implct) if usesStack(res) => + extract(applyResult(res), true, Return(_, implct)) case _ => super.applyBlock(b) override def applyResult2(r: Result)(k: Result => Block): Block = diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 63294eaa8a..60b88d9870 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -20,52 +20,37 @@ fun hi(n) = else hi(n - 1) hi(0) //│ JS (unsanitized): -//│ let hi1, res, res1, handleBlock$2, handleBlock$3; +//│ let hi1, res, handleBlock$1; //│ hi1 = function hi(n) { -//│ let scrut, tmp, tmp1, prevDepth, diff, scrut1, scrut2, scrut3, tmp2, res2, res3, Cont$; +//│ let scrut, tmp, diff, scrut1, scrut2, scrut3, tmp1, res1, Cont$; //│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { -//│ let tmp3; -//│ tmp3 = super(null, null); +//│ let tmp2; +//│ tmp2 = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 1) { -//│ res3 = value$; -//│ } else if (this.pc === 0) { -//│ res2 = value$; +//│ if (this.pc === 0) { +//│ res1 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 3) { +//│ if (this.pc === 2) { //│ scrut = n == 0; //│ if (scrut === true) { //│ return 0; //│ } else { //│ tmp = n - 1; -//│ prevDepth = globalThis.Predef.__stackDepth; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res3 = hi1(tmp); -//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = this; -//│ this.pc = 1; -//│ return res3; -//│ } -//│ this.pc = 1; -//│ continue contLoop; +//│ return hi1(tmp); //│ } -//│ this.pc = 2; +//│ this.pc = 1; //│ continue contLoop; -//│ } else if (this.pc === 2) { -//│ break contLoop; //│ } else if (this.pc === 1) { -//│ tmp1 = res3; -//│ globalThis.Predef.__stackDepth = prevDepth; -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ return tmp1; +//│ break contLoop; //│ } else if (this.pc === 0) { -//│ tmp2 = res2; -//│ this.pc = 3; +//│ tmp1 = res1; +//│ this.pc = 2; //│ continue contLoop; //│ } //│ break; @@ -78,35 +63,25 @@ hi(0) //│ scrut2 = globalThis.Predef.__stackHandler !== undefined; //│ scrut3 = scrut1 && scrut2; //│ if (scrut3 === true) { -//│ res2 = globalThis.Predef.__stackHandler.perform(); -//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$.class(0); -//│ res2.tail = res2.tail.next; -//│ return res2; +//│ res1 = globalThis.Predef.__stackHandler.perform(); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = new Cont$.class(0); +//│ res1.tail = res1.tail.next; +//│ return res1; //│ } -//│ tmp2 = res2; +//│ tmp1 = res1; //│ } //│ scrut = n == 0; //│ if (scrut === true) { //│ return 0; //│ } else { //│ tmp = n - 1; -//│ prevDepth = globalThis.Predef.__stackDepth; -//│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res3 = hi1(tmp); -//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$.class(1); -//│ res3.tail = res3.tail.next; -//│ return res3; -//│ } -//│ tmp1 = res3; -//│ globalThis.Predef.__stackDepth = prevDepth; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ return tmp1; +//│ return hi1(tmp); //│ } //│ }; -//│ handleBlock$3 = function handleBlock$() { -//│ let stackHandler, res2, Cont$, StackDelay$; +//│ handleBlock$1 = function handleBlock$() { +//│ let stackHandler, res1, Cont$, StackDelay$; //│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { //│ constructor() { //│ let tmp; @@ -114,7 +89,7 @@ hi(0) //│ } //│ perform() { //│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res3, curOffset, res4, Cont$1; +//│ let res2, curOffset, res3, Cont$1; //│ Cont$1 = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$1.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { @@ -123,14 +98,14 @@ hi(0) //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 7) { -//│ res4 = value$; +//│ if (this.pc === 4) { +//│ res3 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 7) { -//│ res3 = res4; +//│ if (this.pc === 4) { +//│ res2 = res3; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res3; +//│ return res2; //│ } //│ break; //│ } @@ -139,15 +114,15 @@ hi(0) //│ }; //│ curOffset = globalThis.Predef.__stackOffset; //│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; -//│ res4 = resume(); -//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { -//│ res4.tail.next = new Cont$1.class(7); -//│ res4.tail = res4.tail.next; -//│ return res4; +//│ res3 = resume(); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = new Cont$1.class(4); +//│ res3.tail = res3.tail.next; +//│ return res3; //│ } -//│ res3 = res4; +//│ res2 = res3; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res3; +//│ return res2; //│ }); //│ } //│ toString() { return "StackDelay$"; } @@ -161,12 +136,12 @@ hi(0) //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 6) { -//│ res2 = value$; +//│ if (this.pc === 3) { +//│ res1 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 6) { -//│ return res2; +//│ if (this.pc === 3) { +//│ return res1; //│ } //│ break; //│ } @@ -175,78 +150,20 @@ hi(0) //│ }; //│ globalThis.Predef.__stackDepth = 1; //│ globalThis.Predef.__stackHandler = stackHandler; -//│ res2 = hi1(0); -//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { -//│ res2.tail.next = new Cont$(6); -//│ return globalThis.Predef.__handleBlockImpl(res2, stackHandler); +//│ res1 = hi1(0); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = new Cont$(3); +//│ return globalThis.Predef.__handleBlockImpl(res1, stackHandler); //│ } -//│ return res2; +//│ return res1; //│ }; -//│ res = handleBlock$3(); +//│ res = handleBlock$1(); //│ if (res instanceof this.Predef.__EffectSig.class) { //│ throw new this.Error("Unhandled effects"); //│ } //│ this.Predef.__stackDepth = 0; //│ this.Predef.__stackHandler = undefined; -//│ handleBlock$2 = function handleBlock$() { -//│ let stackHandler, StackDelay$; -//│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { -//│ constructor() { -//│ let tmp; -//│ tmp = super(); -//│ } -//│ perform() { -//│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res2, curOffset, res3, Cont$; -//│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; -//│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { -//│ constructor(pc) { -//│ let tmp; -//│ tmp = super(null, null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 4) { -//│ res3 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 4) { -//│ res2 = res3; -//│ globalThis.Predef.__stackOffset = curOffset; -//│ return res2; -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return "Cont$(" + this.pc + ")"; } -//│ }; -//│ curOffset = globalThis.Predef.__stackOffset; -//│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; -//│ res3 = resume(); -//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { -//│ res3.tail.next = new Cont$.class(4); -//│ res3.tail = res3.tail.next; -//│ return res3; -//│ } -//│ res2 = res3; -//│ globalThis.Predef.__stackOffset = curOffset; -//│ return res2; -//│ }); -//│ } -//│ toString() { return "StackDelay$"; } -//│ }; -//│ stackHandler = new StackDelay$(); -//│ globalThis.Predef.__stackDepth = 1; -//│ globalThis.Predef.__stackHandler = stackHandler; -//│ return res; -//│ }; -//│ res1 = handleBlock$2(); -//│ if (res1 instanceof this.Predef.__EffectSig.class) { -//│ throw new this.Error("Unhandled effects"); -//│ } -//│ this.Predef.__stackDepth = 0; -//│ this.Predef.__stackHandler = undefined; -//│ res1 +//│ res //│ = 0 :sjs @@ -259,9 +176,9 @@ fun sum(n) = n + sum(n - 1) sum(10000) //│ JS (unsanitized): -//│ let sum3, res2, res3, handleBlock$6, handleBlock$7; +//│ let sum3, res1, handleBlock$3; //│ sum3 = function sum(n) { -//│ let scrut, tmp, tmp1, tmp2, prevDepth, diff, scrut1, scrut2, scrut3, tmp3, res4, res5, Cont$; +//│ let scrut, tmp, tmp1, tmp2, prevDepth, diff, scrut1, scrut2, scrut3, tmp3, res2, res3, Cont$; //│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { @@ -271,9 +188,9 @@ sum(10000) //│ } //│ resume(value$) { //│ if (this.pc === 1) { -//│ res5 = value$; +//│ res3 = value$; //│ } else if (this.pc === 0) { -//│ res4 = value$; +//│ res2 = value$; //│ } //│ contLoop: while (true) { //│ if (this.pc === 3) { @@ -284,11 +201,11 @@ sum(10000) //│ tmp = n - 1; //│ prevDepth = globalThis.Predef.__stackDepth; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res5 = sum3(tmp); -//│ if (res5 instanceof globalThis.Predef.__EffectSig.class) { -//│ res5.tail.next = this; +//│ res3 = sum3(tmp); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = this; //│ this.pc = 1; -//│ return res5; +//│ return res3; //│ } //│ this.pc = 1; //│ continue contLoop; @@ -298,12 +215,12 @@ sum(10000) //│ } else if (this.pc === 2) { //│ break contLoop; //│ } else if (this.pc === 1) { -//│ tmp2 = res5; +//│ tmp2 = res3; //│ globalThis.Predef.__stackDepth = prevDepth; //│ tmp1 = tmp2; //│ return n + tmp1; //│ } else if (this.pc === 0) { -//│ tmp3 = res4; +//│ tmp3 = res2; //│ this.pc = 3; //│ continue contLoop; //│ } @@ -317,13 +234,13 @@ sum(10000) //│ scrut2 = globalThis.Predef.__stackHandler !== undefined; //│ scrut3 = scrut1 && scrut2; //│ if (scrut3 === true) { -//│ res4 = globalThis.Predef.__stackHandler.perform(); -//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { -//│ res4.tail.next = new Cont$.class(0); -//│ res4.tail = res4.tail.next; -//│ return res4; +//│ res2 = globalThis.Predef.__stackHandler.perform(); +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = new Cont$.class(0); +//│ res2.tail = res2.tail.next; +//│ return res2; //│ } -//│ tmp3 = res4; +//│ tmp3 = res2; //│ } //│ scrut = n == 0; //│ if (scrut === true) { @@ -332,20 +249,20 @@ sum(10000) //│ tmp = n - 1; //│ prevDepth = globalThis.Predef.__stackDepth; //│ globalThis.Predef.__stackDepth = globalThis.Predef.__stackDepth + 1; -//│ res5 = sum3(tmp); -//│ if (res5 instanceof globalThis.Predef.__EffectSig.class) { -//│ res5.tail.next = new Cont$.class(1); -//│ res5.tail = res5.tail.next; -//│ return res5; +//│ res3 = sum3(tmp); +//│ if (res3 instanceof globalThis.Predef.__EffectSig.class) { +//│ res3.tail.next = new Cont$.class(1); +//│ res3.tail = res3.tail.next; +//│ return res3; //│ } -//│ tmp2 = res5; +//│ tmp2 = res3; //│ globalThis.Predef.__stackDepth = prevDepth; //│ tmp1 = tmp2; //│ return n + tmp1; //│ } //│ }; -//│ handleBlock$7 = function handleBlock$() { -//│ let stackHandler, res4, Cont$, StackDelay$; +//│ handleBlock$3 = function handleBlock$() { +//│ let stackHandler, res2, Cont$, StackDelay$; //│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { //│ constructor() { //│ let tmp; @@ -353,7 +270,7 @@ sum(10000) //│ } //│ perform() { //│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res5, curOffset, res6, Cont$1; +//│ let res3, curOffset, res4, Cont$1; //│ Cont$1 = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$1.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { @@ -362,14 +279,14 @@ sum(10000) //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 7) { -//│ res6 = value$; +//│ if (this.pc === 5) { +//│ res4 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 7) { -//│ res5 = res6; +//│ if (this.pc === 5) { +//│ res3 = res4; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res5; +//│ return res3; //│ } //│ break; //│ } @@ -378,15 +295,15 @@ sum(10000) //│ }; //│ curOffset = globalThis.Predef.__stackOffset; //│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; -//│ res6 = resume(); -//│ if (res6 instanceof globalThis.Predef.__EffectSig.class) { -//│ res6.tail.next = new Cont$1.class(7); -//│ res6.tail = res6.tail.next; -//│ return res6; +//│ res4 = resume(); +//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { +//│ res4.tail.next = new Cont$1.class(5); +//│ res4.tail = res4.tail.next; +//│ return res4; //│ } -//│ res5 = res6; +//│ res3 = res4; //│ globalThis.Predef.__stackOffset = curOffset; -//│ return res5; +//│ return res3; //│ }); //│ } //│ toString() { return "StackDelay$"; } @@ -400,12 +317,12 @@ sum(10000) //│ this.pc = pc; //│ } //│ resume(value$) { -//│ if (this.pc === 6) { -//│ res4 = value$; +//│ if (this.pc === 4) { +//│ res2 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 6) { -//│ return res4; +//│ if (this.pc === 4) { +//│ return res2; //│ } //│ break; //│ } @@ -414,78 +331,20 @@ sum(10000) //│ }; //│ globalThis.Predef.__stackDepth = 1; //│ globalThis.Predef.__stackHandler = stackHandler; -//│ res4 = sum3(10000); -//│ if (res4 instanceof globalThis.Predef.__EffectSig.class) { -//│ res4.tail.next = new Cont$(6); -//│ return globalThis.Predef.__handleBlockImpl(res4, stackHandler); +//│ res2 = sum3(10000); +//│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { +//│ res2.tail.next = new Cont$(4); +//│ return globalThis.Predef.__handleBlockImpl(res2, stackHandler); //│ } -//│ return res4; -//│ }; -//│ res2 = handleBlock$7(); -//│ if (res2 instanceof this.Predef.__EffectSig.class) { -//│ throw new this.Error("Unhandled effects"); -//│ } -//│ this.Predef.__stackDepth = 0; -//│ this.Predef.__stackHandler = undefined; -//│ handleBlock$6 = function handleBlock$() { -//│ let stackHandler, StackDelay$; -//│ StackDelay$ = class StackDelay$ extends globalThis.Predef.__StackDelay.class { -//│ constructor() { -//│ let tmp; -//│ tmp = super(); -//│ } -//│ perform() { -//│ return globalThis.Predef.__mkEffect(stackHandler, (resume) => { -//│ let res4, curOffset, res5, Cont$; -//│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; -//│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { -//│ constructor(pc) { -//│ let tmp; -//│ tmp = super(null, null); -//│ this.pc = pc; -//│ } -//│ resume(value$) { -//│ if (this.pc === 4) { -//│ res5 = value$; -//│ } -//│ contLoop: while (true) { -//│ if (this.pc === 4) { -//│ res4 = res5; -//│ globalThis.Predef.__stackOffset = curOffset; -//│ return res4; -//│ } -//│ break; -//│ } -//│ } -//│ toString() { return "Cont$(" + this.pc + ")"; } -//│ }; -//│ curOffset = globalThis.Predef.__stackOffset; -//│ globalThis.Predef.__stackOffset = globalThis.Predef.__stackDepth; -//│ res5 = resume(); -//│ if (res5 instanceof globalThis.Predef.__EffectSig.class) { -//│ res5.tail.next = new Cont$.class(4); -//│ res5.tail = res5.tail.next; -//│ return res5; -//│ } -//│ res4 = res5; -//│ globalThis.Predef.__stackOffset = curOffset; -//│ return res4; -//│ }); -//│ } -//│ toString() { return "StackDelay$"; } -//│ }; -//│ stackHandler = new StackDelay$(); -//│ globalThis.Predef.__stackDepth = 1; -//│ globalThis.Predef.__stackHandler = stackHandler; //│ return res2; //│ }; -//│ res3 = handleBlock$6(); -//│ if (res3 instanceof this.Predef.__EffectSig.class) { +//│ res1 = handleBlock$3(); +//│ if (res1 instanceof this.Predef.__EffectSig.class) { //│ throw new this.Error("Unhandled effects"); //│ } //│ this.Predef.__stackDepth = 0; //│ this.Predef.__stackHandler = undefined; -//│ res3 +//│ res1 //│ = 50005000 // stack-overflows without :stackSafe From 203fcf048a3a954380d5f0a57413a14e101ec4e3 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 20:14:53 +0800 Subject: [PATCH 103/114] Change comment to match codegen --- hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 60b88d9870..bbef6b3a52 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -10,7 +10,7 @@ sum(100) //│ = 5050 // preserve tail calls -// MUST see "return globalThis.hi(tmp)" in the output +// MUST see "return hi1(tmp)" in the output :stackSafe 5 :handler :expect 0 From 8275e31f984f2e6221ced81f9b7340ba676cfb57 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 22:05:53 +0800 Subject: [PATCH 104/114] Minor changes --- .../hkmc2/codegen/StackSafeTransform.scala | 43 ++++++------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 360be224f2..53569285a3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -86,11 +86,10 @@ class StackSafeTransform(depthLimit: Int)(using State): ) // Rewrites anything that can contain a Call to increase the stack depth - def transform(b: Block, isTopLevel: Bool = false): Block = + def transform(b: Block, isTopLevel: Bool = false): Block = def usesStack(r: Result) = r match case Call(Value.Ref(_: BuiltinSymbol), _) => false - case _: Call => true - case _: Instantiate => true + case _: Call | _: Instantiate => true case _ => false val extract = if isTopLevel then extractResTopLevel else extractRes @@ -113,36 +112,20 @@ class StackSafeTransform(depthLimit: Int)(using State): else super.applyResult2(r)(k) - override def applyValue(v: Value): Value = v match - case Value.Lam(params, body) => - Value.Lam(params, rewriteBlk(body)) - case _ => super.applyValue(v) + override def applyLam(lam: Value.Lam): Value.Lam = + Value.Lam(lam.params, rewriteBlk(lam.body)) transform.applyBlock(b) - def isTrivial(b: Block): Boolean = - def resTrivial(r: Result) = r match - case Call(Value.Ref(_: BuiltinSymbol), _) => true - case _: Call => false - case _: Instantiate => false - case _ => true - - b match - case Match(scrut, arms, dflt, rest) => - arms.foldLeft(dflt.map(isTrivial).getOrElse(true))((acc, bl) => acc && isTrivial(bl._2)) && isTrivial(rest) - case Return(res, implct) => resTrivial(res) - case Throw(exc) => resTrivial(exc) - case Label(label, body, rest) => isTrivial(body) && isTrivial(rest) - case Break(label) => true - case Continue(label) => true - case Begin(sub, rest) => isTrivial(sub) && isTrivial(rest) - case TryBlock(sub, finallyDo, rest) => isTrivial(sub) && isTrivial(finallyDo) && isTrivial(rest) - case Assign(lhs, rhs, rest) => resTrivial(rhs) && isTrivial(rest) - case AssignField(lhs, nme, rhs, rest) => resTrivial(rhs) && isTrivial(rest) - case Define(defn, rest) => isTrivial(rest) - case HandleBlock(lhs, res, par, cls, handlers, body, rest) => isTrivial(body) && isTrivial(rest) - case HandleBlockReturn(res) => true - case End(msg) => true + def isTrivial(b: Block): Boolean = + var trivial = true + val walker = new BlockTransformerShallow(SymbolSubst()): + override def applyResult(r: Result): Result = r match + case Call(Value.Ref(_: BuiltinSymbol), _) => r + case _: Call | _: Instantiate => trivial = false; r + case _ => r + walker.applyBlock(b) + trivial def rewriteCls(defn: ClsLikeDefn): ClsLikeDefn = val ClsLikeDefn(owner, isym, sym, k, paramsOpt, From 692dfb956491a9eaae57064e365365902d02705c Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 23 Jan 2025 22:09:57 +0800 Subject: [PATCH 105/114] address PR comments --- .../src/main/scala/hkmc2/codegen/Block.scala | 74 ++++--------------- .../hkmc2/codegen/StackSafeTransform.scala | 4 +- 2 files changed, 19 insertions(+), 59 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 230e2e33e7..202fbf3c49 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -14,6 +14,7 @@ import semantics.* import semantics.Term.* import sem.Elaborator.State +import scala.collection.mutable.ListBuffer case class Program( imports: Ls[Local -> Str], @@ -99,65 +100,22 @@ sealed abstract class Block extends Product with AutoLocated: case HandleBlock(_, _, _, _, handlers, body, rest) => handlers.map(_.body) :+ body :+ rest case _: Return | _: Throw | _: Label | _: Break | _: Continue | _: End | _: HandleBlockReturn => Nil - // Moves definitions in a block to the top. Only scans one definition deep, i.e. definitions inside other definitions - // are not moved out. Definitions inside `if` and `while` statements are moved out. + // Moves definitions in a block to the top. Only scans the top-level definitions of the block; + // i.e, definitions inside other definitions are not moved out. Definitions inside `if` and + // `while` statements are moved out. def floatOutDefns = - def rec(b: Block, acc: List[Defn]): (Block, List[Defn]) = b match - case Match(scrut, arms, dflt, rest) => - val (armsRes, armsDefns) = arms.foldLeft[(List[(Case, Block)], List[Defn])](Nil, acc)( - (accc, d) => - val (accCases, accDefns) = accc - val (cse, blk) = d - val (resBlk, resDefns) = rec(blk, accDefns) - ((cse, resBlk) :: accCases, resDefns) - ) - dflt match - case None => - val (rstRes, rstDefns) = rec(rest, armsDefns) - (Match(scrut, armsRes, None, rstRes), rstDefns) - - case Some(dflt) => - val (dfltRes, dfltDefns) = rec(dflt, armsDefns) - val (rstRes, rstDefns) = rec(rest, dfltDefns) - (Match(scrut, armsRes, S(dfltRes), rstRes), rstDefns) - - case Return(res, implct) => (b, acc) - case Throw(exc) => (b, acc) - case Label(label, body, rest) => - val (bodyRes, bodyDefns) = rec(body, acc) - val (rstRes, rstDefns) = rec(rest, bodyDefns) - (Label(label, bodyRes, rstRes), rstDefns) - case Break(label) => (b, acc) - case Continue(label) => (b, acc) - case Begin(sub, rest) => - val (subRes, subDefns) = rec(sub, acc) - val (rstRes, rstDefns) = rec(rest, subDefns) - (Begin(subRes, rstRes), rstDefns) - case TryBlock(sub, finallyDo, rest) => - val (subRes, subDefns) = rec(sub, acc) - val (finallyRes, finallyDefns) = rec(rest, subDefns) - val (rstRes, rstDefns) = rec(rest, finallyDefns) - (TryBlock(subRes, finallyRes, rstRes), rstDefns) - case Assign(lhs, rhs, rest) => - val (rstRes, rstDefns) = rec(rest, acc) - (Assign(lhs, rhs, rstRes), rstDefns) - case a @ AssignField(path, nme, result, rest) => - val (rstRes, rstDefns) = rec(rest, acc) - (AssignField(path, nme, result, rstRes)(a.symbol), rstDefns) - case Define(defn, rest) => defn match - case ValDefn(owner, k, sym, rhs) => - val (rstRes, rstDefns) = rec(rest, acc) - (Define(defn, rstRes), rstDefns) - case _ => - val (rstRes, rstDefns) = rec(rest, defn :: acc) - (rstRes, rstDefns) - case HandleBlock(lhs, res, par, cls, handlers, body, rest) => - val (rstRes, rstDefns) = rec(rest, acc) - (HandleBlock(lhs, res, par, cls, handlers, body, rstRes), rstDefns) - case HandleBlockReturn(res) => (b, acc) - case End(msg) => (b, acc) - rec(this, Nil) - + val defns = ListBuffer[Defn]() + val transformer = new BlockTransformerShallow(SymbolSubst()): + override def applyBlock(b: Block): Block = b match + case Define(defn, rest) => defn match + case v: ValDefn => super.applyBlock(b) + case _ => + defns.addOne(defn) + applyBlock(rest) + case _ => super.applyBlock(b) + + (transformer.applyBlock(this), defns.reverse.toList) + end Block sealed abstract class BlockTail extends Block diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 53569285a3..eed15ba22c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -1,11 +1,12 @@ package hkmc2 import mlscript.utils.*, shorthands.* +import utils.* + import hkmc2.codegen.* import hkmc2.semantics.Elaborator.State import hkmc2.semantics.* import hkmc2.syntax.Tree -import hkmc2.utils.* class StackSafeTransform(depthLimit: Int)(using State): private val STACK_DEPTH_IDENT: Tree.Ident = Tree.Ident("__stackDepth") @@ -97,6 +98,7 @@ class StackSafeTransform(depthLimit: Int)(using State): val transform = new BlockTransformer(SymbolSubst()): override def applyFunDefn(fun: FunDefn): FunDefn = rewriteFn(fun) + override def applyDefn(defn: Defn): Defn = defn match case defn: ClsLikeDefn => rewriteCls(defn) case _: FunDefn | _: ValDefn => super.applyDefn(defn) From dfaab8e942c2c92abbd0d73e8b16b61dd7ff59d3 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 23 Jan 2025 22:11:44 +0800 Subject: [PATCH 106/114] remove obselete comment --- .../src/main/scala/hkmc2/codegen/StackSafeTransform.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index eed15ba22c..8c2fe75355 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -24,8 +24,6 @@ class StackSafeTransform(depthLimit: Int)(using State): private def op(op: String, a: Path, b: Path) = Call(State.builtinOpsMap(op).asPath, List(a.asArg, b.asArg))(true) - // TODO: this code is copied from HandlerLowering and is quite useful. Maybe refactor it into a utils file - // Increases the stack depth, assigns the call to a value, then decreases the stack depth // then binds that value to a desired block def extractRes(res: Result, isTailCall: Bool, f: Result => Block) = From c2099a2b8f268d88b955edc5a62b16c8313f501e Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 23 Jan 2025 22:18:53 +0800 Subject: [PATCH 107/114] remove symbol remapping code due to symbol refactor --- .../scala/hkmc2/codegen/HandlerLowering.scala | 82 +------------------ .../src/test/mlscript/handlers/Effects.mls | 2 +- .../mlscript/handlers/RecursiveHandlers.mls | 2 +- 3 files changed, 3 insertions(+), 83 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index b36c4c9fe3..e41b7b373b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -327,87 +327,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): // 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 - val clsDefns = defns.collect: - case ClsLikeDefn(own, isym, sym, k, paramsOpt, parentPath, methods, privateFields, publicFields, preCtor, ctor) => sym - - val funDefns = defns.collect: - case FunDefn(own, sym, params, body) => sym - - def getBms = - var l: List[BlockMemberSymbol] = Nil - val subst = new SymbolSubst: - override def mapBlockMemberSym(b: BlockMemberSymbol) = - l = b :: l - b - BlockTransformer(subst).applyBlock(b) - l - - val toConvert = getBms - .map: b => - val clsDefn = b.asCls - val modDefn = b.asMod - // check if this BlockMemberSymbol belongs to a definition in this block - val isThisBlock = clsDefn match - case None => modDefn match - case None => false - case Some(value) => clsDefns.contains(value) - case Some(value) => clsDefns.contains(value) - if isThisBlock then Some(b) - else None - .collect: - case Some(b) => b - - val fnBmsMap = funDefns - .map: b => - b -> BlockMemberSymbol(b.nme, b.trees) - .toMap - - val clsBmsMap = toConvert - .map: b => - b -> BlockMemberSymbol(b.nme, b.trees) - .toMap - - val bmsMap = (fnBmsMap ++ clsBmsMap).toMap - - val clsMap = clsBmsMap - .map: - case b1 -> b2 => b1.asCls match - case Some(value) => - val newSym = ClassSymbol(value.tree, Tree.Ident(b2.nme)) - S(value -> newSym) - case None => None - .collect: - case Some(x) => x - .toMap - - val modMap = clsBmsMap - .map: - case b1 -> b2 => b1.asMod match - case Some(value) => - val newSym = ModuleSymbol(value.tree, Tree.Ident(b2.nme)) - S(value -> newSym) - case None => None - .collect: - case Some(x) => x - .toMap - - val newBlk = defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) - - val subst = new SymbolSubst: - override def mapBlockMemberSym(b: BlockMemberSymbol) = bmsMap.get(b) match - case None => b.asCls match - case None => b - case Some(cls) => - clsMap.get(cls) match - case None => b - case Some(sym) => - BlockMemberSymbol(sym.nme, b.trees) // TODO: properly map trees - case Some(value) => value - override def mapClsSym(s: ClassSymbol): ClassSymbol = clsMap.get(s).getOrElse(s) - override def mapModuleSym(s: ModuleSymbol): ModuleSymbol = modMap.get(s).getOrElse(s) - override def mapTermSym(s: TermSymbol): TermSymbol = TermSymbol(s.k, s.owner.map(_.subst(using this)), s.id) - - BlockTransformer(subst).applyBlock(newBlk) + defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) private def translateFun(f: FunDefn): FunDefn = FunDefn(f.owner, f.sym, f.params, translateBlock(f.body, functionHandlerCtx)) diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 124df6b2ba..d21f306656 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -158,7 +158,7 @@ if true do //│ JS (unsanitized): //│ let tmp11, handleBlock$23; //│ handleBlock$23 = function handleBlock$() { -//│ let h, scrut, tmp12, res, Cont$, f, Effect$h$; +//│ let h, scrut, f, tmp12, res, Cont$, Effect$h$; //│ Effect$h$ = class Effect$h$ extends Effect1 { //│ constructor() { //│ let tmp13; diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index d720fbb82f..88dc9733e8 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -126,7 +126,7 @@ str //│ scrut = true; //│ if (scrut === true) { //│ handleBlock$9 = function handleBlock$() { -//│ let h1, tmp8, Cont$, handleBlock$10, Effect$h1$; +//│ let h1, tmp8, handleBlock$10, Cont$, Effect$h1$; //│ Effect$h1$ = class Effect$h1$ extends Effect1 { //│ constructor() { //│ let tmp9; From 39050cd79bb1e0ca086793906ee74de1f49eba85 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Thu, 23 Jan 2025 22:32:19 +0800 Subject: [PATCH 108/114] make stack limit a variable --- .../scala/hkmc2/codegen/StackSafeTransform.scala | 5 ++++- .../shared/src/test/mlscript-compile/Predef.mjs | 1 + .../shared/src/test/mlscript-compile/Predef.mls | 1 + .../src/test/mlscript/handlers/StackSafety.mls | 6 ++++-- .../src/test/mlscript/handlers/ZCombinator.mls | 16 ++++++++-------- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 8c2fe75355..b109165b47 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -9,12 +9,14 @@ import hkmc2.semantics.* import hkmc2.syntax.Tree class StackSafeTransform(depthLimit: Int)(using State): + private val STACK_LIMIT_IDENT: Tree.Ident = Tree.Ident("__stackLimit") private val STACK_DEPTH_IDENT: Tree.Ident = Tree.Ident("__stackDepth") private val STACK_OFFSET_IDENT: Tree.Ident = Tree.Ident("__stackOffset") private val STACK_HANDLER_IDENT: Tree.Ident = Tree.Ident("__stackHandler") private val predefPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")) private val stackDelayClsPath: Path = predefPath.selN(Tree.Ident("__StackDelay")).selN(Tree.Ident("class")) + private val stackLimitPath: Path = predefPath.selN(STACK_LIMIT_IDENT) private val stackDepthPath: Path = predefPath.selN(STACK_DEPTH_IDENT) private val stackOffsetPath: Path = predefPath.selN(STACK_OFFSET_IDENT) private val stackHandlerPath: Path = predefPath.selN(STACK_HANDLER_IDENT) @@ -75,6 +77,7 @@ class StackSafeTransform(depthLimit: Int)(using State): .ret(handlerRes.asPath) )), blockBuilder + .assignFieldN(predefPath, STACK_LIMIT_IDENT, intLit(depthLimit)) // set stackLimit before call .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(1)) // set stackDepth = 1 before call .assignFieldN(predefPath, STACK_HANDLER_IDENT, handlerSym.asPath) // assign stack handler .rest(HandleBlockReturn(res)), @@ -146,7 +149,7 @@ class StackSafeTransform(depthLimit: Int)(using State): val scrut2Sym = TempSymbol(None, "scrut2") val scrutSym = TempSymbol(None, "scrut") val diff = op("-", stackDepthPath, stackOffsetPath) - val scrut1 = op(">=", diffSym.asPath, intLit(depthLimit)) + val scrut1 = op(">=", diffSym.asPath, stackLimitPath) val scrut2 = op("!==", stackHandlerPath, Value.Lit(Tree.UnitLit(false))) val scrutVal = op("&&", scrut1Sym.asPath, scrut2Sym.asPath) blockBuilder diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index c85580ae36..71b31ac368 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -127,6 +127,7 @@ const Predef$class = class Predef { } toString() { return "__Return(" + this.value + ")"; } }; + this.__stackLimit = 0; this.__stackDepth = 0; this.__stackOffset = 0; this.__stackHandler = null; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 4eb4e92c37..8f615a4a77 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -256,6 +256,7 @@ fun __resumeHandleBlocks(handleBlock, tailHandleBlock, value) = return value // stack safety +val __stackLimit = 0 // How deep the stack can go before heapifying the stack val __stackDepth = 0 // Tracks the virtual + real stack depth val __stackOffset = 0 // How much to offset __stackDepth by to get the true stack depth (i.e. the virtual depth) val __stackHandler = null diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index bbef6b3a52..da07661224 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -59,7 +59,7 @@ hi(0) //│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; //│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; -//│ scrut1 = diff >= 5; +//│ scrut1 = diff >= globalThis.Predef.__stackLimit; //│ scrut2 = globalThis.Predef.__stackHandler !== undefined; //│ scrut3 = scrut1 && scrut2; //│ if (scrut3 === true) { @@ -148,6 +148,7 @@ hi(0) //│ } //│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; +//│ globalThis.Predef.__stackLimit = 5; //│ globalThis.Predef.__stackDepth = 1; //│ globalThis.Predef.__stackHandler = stackHandler; //│ res1 = hi1(0); @@ -230,7 +231,7 @@ sum(10000) //│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; //│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; -//│ scrut1 = diff >= 1000; +//│ scrut1 = diff >= globalThis.Predef.__stackLimit; //│ scrut2 = globalThis.Predef.__stackHandler !== undefined; //│ scrut3 = scrut1 && scrut2; //│ if (scrut3 === true) { @@ -329,6 +330,7 @@ sum(10000) //│ } //│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; +//│ globalThis.Predef.__stackLimit = 1000; //│ globalThis.Predef.__stackDepth = 1; //│ globalThis.Predef.__stackHandler = stackHandler; //│ res2 = sum3(10000); diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls index 13c821155e..f50b05a847 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -95,14 +95,14 @@ let fact = fact(10) //│ > 2 0 //│ > 4 0 -//│ > 6 6 -//│ > 8 6 -//│ > 10 6 -//│ > 12 12 -//│ > 14 12 -//│ > 16 12 -//│ > 18 18 -//│ > 20 18 +//│ > 6 5 +//│ > 8 5 +//│ > 10 10 +//│ > 12 10 +//│ > 14 10 +//│ > 16 15 +//│ > 18 15 +//│ > 20 20 //│ > resumed: 1 //│ > resumed: 2 //│ > resumed: 3 From e3630b690fe48e5f19f326f7cacacb1e986c3f32 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 23 Jan 2025 23:36:43 +0800 Subject: [PATCH 109/114] Removed fixme --- hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls | 2 -- 1 file changed, 2 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls index f50b05a847..7d7cb41220 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -53,8 +53,6 @@ fact(10000) //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded -// * FIXME: changing the instrumentation's stack depth shouldn't change the result! - :stackSafe 5 set selfApp = f => f(f) From cb8b9176c4a6461539fe9a1dea60cabdb15114e1 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Fri, 24 Jan 2025 09:45:59 +0800 Subject: [PATCH 110/114] Make minor improvements --- .../src/main/scala/hkmc2/codegen/Block.scala | 4 +++- .../scala/hkmc2/codegen/js/JSBuilder.scala | 23 ++++++++++++------ .../mlscript/handlers/ManualStackSafety.mls | 9 +++++++ .../test/mlscript/handlers/StackSafety.mls | 24 +++++++++++++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 202fbf3c49..d00d2c65a3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -115,7 +115,7 @@ sealed abstract class Block extends Product with AutoLocated: case _ => super.applyBlock(b) (transformer.applyBlock(this), defns.reverse.toList) - + end Block sealed abstract class BlockTail extends Block @@ -285,3 +285,5 @@ def blockBuilder: Block => Block = identity extension (l: Local) def asPath: Path = Value.Ref(l) + + 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 3a6bd6b545..dc9ed4da0b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -40,10 +40,18 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: case Argument case Operand(prec: Int) - def err(errMsg: Message)(using Raise, Scope): Document = + def mkErr(errMsg: Message)(using Raise, Scope): Document = + doc"throw globalThis.Error(${result(Value.Lit(syntax.Tree.StrLit(errMsg.show)))})" + + def errExpr(errMsg: Message)(using Raise, Scope): Document = + raise(ErrorReport(errMsg -> N :: Nil, + source = Diagnostic.Source.Compilation)) + doc"(()=>{${mkErr(errMsg)}})()" + + def errStmt(errMsg: Message)(using Raise, Scope): Document = raise(ErrorReport(errMsg -> N :: Nil, source = Diagnostic.Source.Compilation)) - doc"(()=>{throw globalThis.Error(${result(Value.Lit(syntax.Tree.StrLit(errMsg.show)))})})()" + doc" # ${mkErr(errMsg)};" def getVar(l: Local)(using Raise, Scope): Document = l match case ts: semantics.TermSymbol => @@ -75,24 +83,24 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: case Value.Lit(lit) => lit.idStr case Value.Ref(l: BuiltinSymbol) => if l.nullary then l.nme - else err(msg"Illegal reference to builtin symbol '${l.nme}'") + else errExpr(msg"Illegal reference to builtin symbol '${l.nme}'") case Value.Ref(l) => getVar(l) case Call(Value.Ref(l: BuiltinSymbol), lhs :: rhs :: Nil) if !l.functionLike => if l.binary then val res = doc"${operand(lhs)} ${l.nme} ${operand(rhs)}" if needsParens(l.nme) then doc"(${res})" else res - else err(msg"Cannot call non-binary builtin symbol '${l.nme}'") + else errExpr(msg"Cannot call non-binary builtin symbol '${l.nme}'") case Call(Value.Ref(l: BuiltinSymbol), rhs :: Nil) if !l.functionLike => if l.unary then val res = doc"${l.nme} ${operand(rhs)}" if needsParens(l.nme) then doc"(${res})" else res - else err(msg"Cannot call non-unary builtin symbol '${l.nme}'") + else errExpr(msg"Cannot call non-unary builtin symbol '${l.nme}'") case Call(Value.Ref(l: BuiltinSymbol), args) => if l.functionLike then val argsDoc = args.map(argument).mkDocument(", ") doc"${l.nme}(${argsDoc})" - else err(msg"Illegal arity for builtin symbol '${l.nme}'") + else errExpr(msg"Illegal arity for builtin symbol '${l.nme}'") case Call(s @ Select(_, id), lhs :: rhs :: Nil) => Elaborator.ctx.Builtins.getBuiltinOp(id.name) match @@ -122,7 +130,8 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: case Value.Arr(es) => doc"[ #{ # ${es.map(argument).mkDocument(doc", # ")} #} # ]" def returningTerm(t: Block)(using Raise, Scope): Document = t match - case _: (HandleBlockReturn | HandleBlock) => die + case _: (HandleBlockReturn | HandleBlock) => + errStmt(msg"This code requires effect handler instrumentation but was compiled without it.") case Assign(l, r, rst) => doc" # ${getVar(l)} = ${result(r)};${returningTerm(rst)}" case AssignField(p, n, r, rst) => diff --git a/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls index 706c5b5dce..3ef7b374f1 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ManualStackSafety.mls @@ -1,6 +1,13 @@ :js :handler +// * This file demonstrates the handler-based mechanism of our stack safety implementation +// * by manually applying the transformation of a recursive factorial function +// * defined through the Z combinator. +// * It is notably interesting in that it demonstrates the ability to preserve tail calls. +// * The original function can be found in `hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls` + + fun test(n, stackLimit) = let stackDepth = 0 let stackOffset = 0 @@ -110,3 +117,5 @@ test(10, 5) //│ > finished at offset: 5 //│ > finished at offset: 0 //│ = 3628800 + + diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index da07661224..c799f539fb 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -452,3 +452,27 @@ fun max(a, b) = if a < b then b else a //│ } //│ }; //│ null + + +// * Note that currently the `:sjs` command will not run the code if there is a compilation error +:sjs +:stackSafe 42 +:ge +fun hi(n) = n +hi(0) +//│ ═══[COMPILATION ERROR] This code requires effect handler instrumentation but was compiled without it. +//│ JS (unsanitized): +//│ let hi3, stackHandler; +//│ hi3 = function hi(n) { +//│ return n; +//│ }; +//│ throw globalThis.Error("This code requires effect handler instrumentation but was compiled without it."); + +:stackSafe 42 +:ge +:re +hi(0) +//│ ═══[COMPILATION ERROR] This code requires effect handler instrumentation but was compiled without it. +//│ ═══[RUNTIME ERROR] Error: This code requires effect handler instrumentation but was compiled without it. + + From 1f1ce3faca0c801479fd913cd2f8e1b08cfe2152 Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Fri, 24 Jan 2025 16:34:53 +0800 Subject: [PATCH 111/114] address PR comments --- .../src/main/scala/hkmc2/MLsCompiler.scala | 2 +- .../src/main/scala/hkmc2/codegen/Block.scala | 16 +++++---- .../scala/hkmc2/codegen/HandlerLowering.scala | 15 ++++---- .../hkmc2/codegen/StackSafeTransform.scala | 32 ++++++++--------- .../test/mlscript/handlers/StackSafety.mls | 36 +++++++++---------- 5 files changed, 53 insertions(+), 48 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala index 53a487db11..c599c86a58 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala @@ -74,7 +74,7 @@ class MLsCompiler(preludeFile: os.Path): val parsed = mainParse.resultBlk val (blk, newCtx) = elab.importFrom(parsed) val low = ltl.givenIn: - codegen.Lowering(lowerHandlers = false, None) // TODO: properly hook up stack limit + codegen.Lowering(lowerHandlers = false, stackLimit = None) // TODO: properly hook up stack limit val jsb = codegen.js.JSBuilder() val le = low.program(blk) val baseScp: utils.Scope = diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index d00d2c65a3..b17ce159c1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -14,8 +14,6 @@ import semantics.* import semantics.Term.* import sem.Elaborator.State -import scala.collection.mutable.ListBuffer - case class Program( imports: Ls[Local -> Str], main: Block, @@ -101,20 +99,24 @@ sealed abstract class Block extends Product with AutoLocated: case _: Return | _: Throw | _: Label | _: Break | _: Continue | _: End | _: HandleBlockReturn => Nil // Moves definitions in a block to the top. Only scans the top-level definitions of the block; - // i.e, definitions inside other definitions are not moved out. Definitions inside `if` and - // `while` statements are moved out. + // i.e, definitions inside other definitions are not moved out. Definitions inside `match`/`if` + // and `while` statements are moved out. + // + // Note that this returns the definitions in reverse order, with the bottommost definiton appearing + // last. This is so that using defns.foldLeft later to add the definitions to the front of a block, + // we don't need to reverse the list again to preserve the order of the definitions. def floatOutDefns = - val defns = ListBuffer[Defn]() + var defns: List[Defn] = Nil val transformer = new BlockTransformerShallow(SymbolSubst()): override def applyBlock(b: Block): Block = b match case Define(defn, rest) => defn match case v: ValDefn => super.applyBlock(b) case _ => - defns.addOne(defn) + defns ::= defn applyBlock(rest) case _ => super.applyBlock(b) - (transformer.applyBlock(this), defns.reverse.toList) + (transformer.applyBlock(this), defns) end Block diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index e41b7b373b..226f350327 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -288,10 +288,13 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): 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 tail-calls + // on the top-level since it does not make sense. This optimization also prevents the + // "throw 'Unhandled effects'" code from being added at the top level. case Return(c @ Call(fun, args), implct) if handlerCtx.isHandleFree && !handlerCtx.isTopLevel => val fun2 = applyPath(fun) - val args2 = args.map(applyArg) - val c2 = if (fun2 is fun) && (args2 zip args).forall(_ is _) then c else Call(fun2, args2)(c.isMlsFun) + val args2 = args.mapConserve(applyArg) + val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun) if c2 is c then b else Return(c2, implct) case _ => super.applyBlock(b) override def applyResult2(r: Result)(k: Result => Block): Block = r match @@ -299,14 +302,14 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case c @ Call(fun, args) => val res = freshTmp("res") val fun2 = applyPath(fun) - val args2 = args.map(applyArg) - val c2 = if (fun2 is fun) && (args2 zip args).forall(_ is _) then c else Call(fun2, args2)(c.isMlsFun) + val args2 = args.mapConserve(applyArg) + val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun) ResultPlaceholder(res, freshId(), false, c2, k(Value.Ref(res))) case c @ Instantiate(cls, args) => val res = freshTmp("res") val cls2 = applyPath(cls) - val args2 = args.map(applyPath) - val c2 = if (cls2 is cls) && (args2 zip args).forall(_ is _) then c else Instantiate(cls2, args2) + val args2 = args.mapConserve(applyPath) + val c2 = if (cls2 is cls) && (args2 is args) then c else Instantiate(cls2, args2) ResultPlaceholder(res, freshId(), false, c2, k(Value.Ref(res))) case r => super.applyResult2(r)(k) override def applyLam(lam: Value.Lam): Value.Lam = Value.Lam(lam.params, translateBlock(lam.body, functionHandlerCtx)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index b109165b47..7baab171dc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -24,7 +24,7 @@ class StackSafeTransform(depthLimit: Int)(using State): private def intLit(n: BigInt) = Value.Lit(Tree.IntLit(n)) private def op(op: String, a: Path, b: Path) = - Call(State.builtinOpsMap(op).asPath, List(a.asArg, b.asArg))(true) + Call(State.builtinOpsMap(op).asPath, a.asArg :: b.asArg :: Nil)(true) // Increases the stack depth, assigns the call to a value, then decreases the stack depth // then binds that value to a desired block @@ -59,8 +59,8 @@ class StackSafeTransform(depthLimit: Int)(using State): HandleBlock( handlerSym, resSym, stackDelayClsPath, clsSym, - List(Handler( - BlockMemberSymbol("perform", Nil), resumeSym, List(ParamList(ParamListFlags.empty, Nil, N)), + Handler( + BlockMemberSymbol("perform", Nil), resumeSym, ParamList(ParamListFlags.empty, Nil, N) :: Nil, /* fun perform() = let curOffset = stackOffset @@ -72,10 +72,10 @@ class StackSafeTransform(depthLimit: Int)(using State): blockBuilder .assign(curOffsetSym, stackOffsetPath) .assignFieldN(predefPath, STACK_OFFSET_IDENT, stackDepthPath) - .assign(handlerRes, Call(Value.Ref(resumeSym), List())(true)) + .assign(handlerRes, Call(Value.Ref(resumeSym), Nil)(true)) .assignFieldN(predefPath, STACK_OFFSET_IDENT, curOffsetSym.asPath) .ret(handlerRes.asPath) - )), + ) :: Nil, blockBuilder .assignFieldN(predefPath, STACK_LIMIT_IDENT, intLit(depthLimit)) // set stackLimit before call .assignFieldN(predefPath, STACK_DEPTH_IDENT, intLit(1)) // set stackDepth = 1 before call @@ -145,22 +145,22 @@ class StackSafeTransform(depthLimit: Int)(using State): newBody else val diffSym = TempSymbol(None, "diff") - val scrut1Sym = TempSymbol(None, "scrut1") - val scrut2Sym = TempSymbol(None, "scrut2") + val diffGeqLimitSym = TempSymbol(None, "diffGeqLimit") + val handlerExistsSym = TempSymbol(None, "handlerExists") val scrutSym = TempSymbol(None, "scrut") val diff = op("-", stackDepthPath, stackOffsetPath) - val scrut1 = op(">=", diffSym.asPath, stackLimitPath) - val scrut2 = op("!==", stackHandlerPath, Value.Lit(Tree.UnitLit(false))) - val scrutVal = op("&&", scrut1Sym.asPath, scrut2Sym.asPath) + val diffGeqLimit = op(">=", diffSym.asPath, stackLimitPath) + val handlerExists = op("!==", stackHandlerPath, Value.Lit(Tree.UnitLit(false))) + val scrutVal = op("&&", diffGeqLimitSym.asPath, handlerExistsSym.asPath) blockBuilder - .assign(diffSym, diff) // diff = stackDepth - stackOffset - .assign(scrut1Sym, scrut1) // diff >= depthLimit - .assign(scrut2Sym, scrut2) // stackHandler !== null - .assign(scrutSym, scrutVal) // diff >= depthLimit && stackHandler !== null + .assign(diffSym, diff) // diff = stackDepth - stackOffset + .assign(diffGeqLimitSym, diffGeqLimit) // diff >= depthLimit + .assign(handlerExistsSym, handlerExists) // stackHandler !== null + .assign(scrutSym, scrutVal) // diff >= depthLimit && stackHandler !== null .ifthen( scrutSym.asPath, Case.Lit(Tree.BoolLit(true)), - blockBuilder.assign( // tmp = perform(undefined) - TempSymbol(None, "tmp"), + blockBuilder.assign( // dummy = perform(undefined) (is called `dummy` as the value is not used) + TempSymbol(None, "dummy"), Call(Select(stackHandlerPath, Tree.Ident("perform"))(N), Nil)(true)).end) .rest(newBody) diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index c799f539fb..e6444f1ce0 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -22,12 +22,12 @@ hi(0) //│ JS (unsanitized): //│ let hi1, res, handleBlock$1; //│ hi1 = function hi(n) { -//│ let scrut, tmp, diff, scrut1, scrut2, scrut3, tmp1, res1, Cont$; +//│ let scrut, tmp, diff, diffGeqLimit, handlerExists, scrut1, dummy, res1, Cont$; //│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { -//│ let tmp2; -//│ tmp2 = super(null, null); +//│ let tmp1; +//│ tmp1 = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { @@ -49,7 +49,7 @@ hi(0) //│ } else if (this.pc === 1) { //│ break contLoop; //│ } else if (this.pc === 0) { -//│ tmp1 = res1; +//│ dummy = res1; //│ this.pc = 2; //│ continue contLoop; //│ } @@ -59,17 +59,17 @@ hi(0) //│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; //│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; -//│ scrut1 = diff >= globalThis.Predef.__stackLimit; -//│ scrut2 = globalThis.Predef.__stackHandler !== undefined; -//│ scrut3 = scrut1 && scrut2; -//│ if (scrut3 === true) { +//│ diffGeqLimit = diff >= globalThis.Predef.__stackLimit; +//│ handlerExists = globalThis.Predef.__stackHandler !== undefined; +//│ scrut1 = diffGeqLimit && handlerExists; +//│ if (scrut1 === true) { //│ res1 = globalThis.Predef.__stackHandler.perform(); //│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { //│ res1.tail.next = new Cont$.class(0); //│ res1.tail = res1.tail.next; //│ return res1; //│ } -//│ tmp1 = res1; +//│ dummy = res1; //│ } //│ scrut = n == 0; //│ if (scrut === true) { @@ -179,12 +179,12 @@ sum(10000) //│ JS (unsanitized): //│ let sum3, res1, handleBlock$3; //│ sum3 = function sum(n) { -//│ let scrut, tmp, tmp1, tmp2, prevDepth, diff, scrut1, scrut2, scrut3, tmp3, res2, res3, Cont$; +//│ let scrut, tmp, tmp1, tmp2, prevDepth, diff, diffGeqLimit, handlerExists, scrut1, dummy, res2, res3, Cont$; //│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { //│ constructor(pc) { -//│ let tmp4; -//│ tmp4 = super(null, null); +//│ let tmp3; +//│ tmp3 = super(null, null); //│ this.pc = pc; //│ } //│ resume(value$) { @@ -221,7 +221,7 @@ sum(10000) //│ tmp1 = tmp2; //│ return n + tmp1; //│ } else if (this.pc === 0) { -//│ tmp3 = res2; +//│ dummy = res2; //│ this.pc = 3; //│ continue contLoop; //│ } @@ -231,17 +231,17 @@ sum(10000) //│ toString() { return "Cont$(" + this.pc + ")"; } //│ }; //│ diff = globalThis.Predef.__stackDepth - globalThis.Predef.__stackOffset; -//│ scrut1 = diff >= globalThis.Predef.__stackLimit; -//│ scrut2 = globalThis.Predef.__stackHandler !== undefined; -//│ scrut3 = scrut1 && scrut2; -//│ if (scrut3 === true) { +//│ diffGeqLimit = diff >= globalThis.Predef.__stackLimit; +//│ handlerExists = globalThis.Predef.__stackHandler !== undefined; +//│ scrut1 = diffGeqLimit && handlerExists; +//│ if (scrut1 === true) { //│ res2 = globalThis.Predef.__stackHandler.perform(); //│ if (res2 instanceof globalThis.Predef.__EffectSig.class) { //│ res2.tail.next = new Cont$.class(0); //│ res2.tail = res2.tail.next; //│ return res2; //│ } -//│ tmp3 = res2; +//│ dummy = res2; //│ } //│ scrut = n == 0; //│ if (scrut === true) { From 8d9205008f1d4c1b7fa69926a2e910d8641cbf6d Mon Sep 17 00:00:00 2001 From: CAG2Mark Date: Fri, 24 Jan 2025 16:37:17 +0800 Subject: [PATCH 112/114] add parameter names to llirdiffmaker --- hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala index 5fb2839157..802382911d 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala @@ -37,7 +37,7 @@ abstract class LlirDiffMaker extends BbmlDiffMaker: super.processTerm(trm, inImport) if llir.isSet then val low = ltl.givenIn: - codegen.Lowering(false, None) + codegen.Lowering(lowerHandlers = false, stackLimit = None) val le = low.program(trm) given Scope = Scope.empty val fresh = Fresh() From ea340666c7f56a74ade878314ebda52e90c0eb92 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Fri, 24 Jan 2025 17:35:53 +0800 Subject: [PATCH 113/114] Fix constructor bug and add broken test --- .../scala/hkmc2/codegen/HandlerLowering.scala | 13 +-- .../src/test/mlscript-compile/Predef.mjs | 88 ++++++++++++------- .../src/test/mlscript-compile/Predef.mls | 7 +- .../mlscript/handlers/EffectsInClasses.mls | 24 ++++- .../src/test/mlscript/handlers/Generators.mls | 28 ++++++ 5 files changed, 120 insertions(+), 40 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 226f350327..bcf7c1195a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -277,7 +277,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): */ private def translateBlock(b: Block, h: HandlerCtx): Block = - given HandlerCtx = h + given HandlerCtx = h val stage1 = firstPass(b) val stage2 = secondPass(stage1) if h.isTopLevel then stage2 else thirdPass(stage2) @@ -288,14 +288,15 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): 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 tail-calls - // on the top-level since it does not make sense. This optimization also prevents the - // "throw 'Unhandled effects'" code from being added at the top level. - case Return(c @ Call(fun, args), implct) if handlerCtx.isHandleFree && !handlerCtx.isTopLevel => + // 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.isHandleFree => val fun2 = applyPath(fun) val args2 = args.mapConserve(applyArg) val c2 = if (fun2 is fun) && (args2 is args) then c else Call(fun2, args2)(c.isMlsFun) - if c2 is c then b else Return(c2, implct) + if c2 is c then b else Return(c2, false) case _ => super.applyBlock(b) override def applyResult2(r: Result)(k: Result => Block): Block = r match case r @ Call(Value.Ref(_: BuiltinSymbol), _) => super.applyResult2(r)(k) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 71b31ac368..983aca7c0a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -420,7 +420,7 @@ const Predef$class = class Predef { } __resume(cur2, tail) { return (value) => { - let scrut, cont, scrut1, scrut2, scrut3, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + let scrut, cont, scrut1, scrut2, scrut3, scrut4, scrut5, scrut6, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9; scrut = cur2.resumed; if (scrut === true) { throw globalThis.Error("Multiple resumption"); @@ -429,93 +429,121 @@ const Predef$class = class Predef { } cur2.resumed = true; cont = cur2.next; - tmp8: while (true) { + tmp10: while (true) { if (cont instanceof this.__Cont.class) { tmp1 = cont.resume(value) ?? null; value = tmp1; if (value instanceof this.__EffectSig.class) { - scrut1 = value.tail.next === null; + scrut1 = value.tail.next !== cont; if (scrut1 === true) { + scrut2 = cont.next !== null; + if (scrut2 === true) { + scrut3 = value.tail.next !== null; + if (scrut3 === true) { + throw globalThis.Error("Internal Error: unexpected continuation"); + } else { + tmp2 = null; + } + } else { + tmp2 = null; + } + tmp3 = tmp2; + } else { + tmp3 = null; + } + scrut4 = value.tail.next === null; + if (scrut4 === true) { value.tail.next = cont.next; - tmp2 = null; + tmp4 = null; } else { - tmp2 = null; + tmp4 = null; } value.tail = tail; - scrut2 = cur2.handleBlockList.next !== null; - if (scrut2 === true) { + scrut5 = cur2.handleBlockList.next !== null; + if (scrut5 === true) { value.handleBlockList.tail.next = cur2.handleBlockList.next; value.handleBlockList.tail = cur2.handleBlockList.tail; - tmp3 = null; + tmp5 = null; } else { - tmp3 = null; + tmp5 = null; } return value; } else { cont = cont.next; - tmp4 = null; + tmp6 = null; } - tmp5 = tmp4; - continue tmp8; + tmp7 = tmp6; + continue tmp10; } else { - tmp5 = null; + tmp7 = null; } break; } - scrut3 = cur2.handleBlockList.next === null; - if (scrut3 === true) { + scrut6 = cur2.handleBlockList.next === null; + if (scrut6 === true) { return value; } else { - tmp6 = this.__resumeHandleBlocks(cur2.handleBlockList.next, cur2.handleBlockList.tail, value); - cur2 = tmp6; + tmp8 = this.__resumeHandleBlocks(cur2.handleBlockList.next, cur2.handleBlockList.tail, value); + cur2 = tmp8; if (cur2 instanceof this.__EffectSig.class) { cur2.tail = tail; - tmp7 = null; + tmp9 = null; } else { - tmp7 = null; + tmp9 = null; } return cur2; } }; } __resumeHandleBlocks(handleBlock, tailHandleBlock, value) { - let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3; - tmp4: while (true) { + let scrut, scrut1, scrut2, scrut3, scrut4, tmp, tmp1, tmp2, tmp3, tmp4; + tmp5: while (true) { scrut1 = handleBlock.contHead.next; if (scrut1 instanceof this.__Cont.class) { tmp = handleBlock.contHead.next.resume(value) ?? null; value = tmp; if (value instanceof this.__EffectSig.class) { - scrut2 = value.tail.next === null; + scrut2 = value.tail.next !== handleBlock.contHead.next; if (scrut2 === true) { - handleBlock.contHead.next = handleBlock.contHead.next.next; - tmp1 = null; + scrut3 = value.tail.next !== null; + if (scrut3 === true) { + throw globalThis.Error("Internal Error: unexpected continuation during handle block resumption"); + } else { + tmp1 = null; + } } else { tmp1 = null; } + scrut4 = value.tail.next !== handleBlock.contHead.next; + if (scrut4 === true) { + handleBlock.contHead.next = handleBlock.contHead.next.next; + tmp2 = null; + } else { + tmp2 = null; + } value.tail.next = null; value.handleBlockList.tail.next = handleBlock; value.handleBlockList.tail = tailHandleBlock; return value; } else { handleBlock.contHead.next = handleBlock.contHead.next.next; - tmp2 = null; + tmp3 = null; } - tmp3 = tmp2; - continue tmp4; + tmp4 = tmp3; + continue tmp5; } else { scrut = handleBlock.next; if (scrut instanceof this.__HandleBlock.class) { handleBlock = handleBlock.next; - tmp3 = null; - continue tmp4; + tmp4 = null; + continue tmp5; } else { return value; } } break; } - return tmp3; + return tmp4; } toString() { return "Predef"; } }; Predef1 = new Predef$class; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 8f615a4a77..2b9bde9dfe 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -207,6 +207,9 @@ fun __resume(cur, tail)(value) = cont is __Cont do set value = cont.resume(value) if value is __EffectSig then + if value.tail.next !== cont do + if cont.next !== null and value.tail.next !== null do + throw Error("Internal Error: unexpected continuation") // in tail call optimization, the continuation is not appended, we append it here if value.tail.next === null do // since it is a tail call, it is already completed so we append the continuation after it @@ -237,9 +240,11 @@ fun __resumeHandleBlocks(handleBlock, tailHandleBlock, value) = // resuming tailHandlerList or post handler continuations set value = handleBlock.contHead.next.resume(value) if value is __EffectSig then + if value.tail.next !== handleBlock.contHead.next and value.tail.next !== null do + throw Error("Internal Error: unexpected continuation during handle block resumption") // this checks when continuation resume results in tail call to effectful func // when a tail call happens, the continuation will not be appended so this will be null - if value.tail.next === null do + if value.tail.next !== handleBlock.contHead.next do // if this is a tail call that results in effect, the continuation is already completed // and should be removed set handleBlock.contHead.next = handleBlock.contHead.next.next diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls index d568c1d325..d417f13bde 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls @@ -15,7 +15,7 @@ class Lol(h) with //│ Lol1.class = class Lol { //│ constructor(h) { //│ this.h = h; -//│ let tmp, res, Cont$; +//│ let tmp, res, res1, Cont$; //│ const this$Lol = this; //│ Cont$ = function Cont$(pc1) { return new Cont$.class(pc1); }; //│ Cont$.class = class Cont$ extends globalThis.Predef.__Cont.class { @@ -27,14 +27,25 @@ class Lol(h) with //│ resume(value$) { //│ if (this.pc === 0) { //│ res = value$; +//│ } else if (this.pc === 1) { +//│ res1 = value$; //│ } //│ contLoop: while (true) { -//│ if (this.pc === 1) { +//│ if (this.pc === 2) { //│ return this$Lol; //│ } else if (this.pc === 0) { //│ tmp = res; +//│ res1 = Predef.print(tmp); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = this; +//│ this.pc = 1; +//│ return res1; +//│ } //│ this.pc = 1; //│ continue contLoop; +//│ } else if (this.pc === 1) { +//│ this.pc = 2; +//│ continue contLoop; //│ } //│ break; //│ } @@ -48,7 +59,13 @@ class Lol(h) with //│ return res; //│ } //│ tmp = res; -//│ Predef.print(tmp) +//│ res1 = Predef.print(tmp); +//│ if (res1 instanceof globalThis.Predef.__EffectSig.class) { +//│ res1.tail.next = new Cont$.class(1); +//│ res1.tail = res1.tail.next; +//│ return res1; +//│ } +//│ res1 //│ } //│ toString() { return "Lol(" + this.h + ")"; } //│ }; @@ -89,6 +106,7 @@ let oops = k("b") Lol(h) //│ > k +//│ > b //│ oops = Lol { h: Effect$h$ {} } oops.h diff --git a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls index 7627b30f42..75acb3fd80 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls @@ -54,3 +54,31 @@ permutations_foreach([1, 2, 3], print) //│ > 2,3,1 //│ > 3,1,2 //│ > 3,2,1 + + +fun permutations_impl(gen, l1, l2) = + if l2 is + [f, ...t] do + handle genWithPrefix = Generator with + fun produce(result)(resume) = + result.unshift(f) + gen.produce(result) + let x = resume(()) + x + permutations_impl(genWithPrefix, [], l1.concat(t)) + l1.push(f) + permutations_impl(gen, l1, t) + [] and l1 is [] do + gen.produce([]) +fun permutations(gen, l) = + permutations_impl(gen, [], l) + +// FIXME: wrong code +let res = [] +handle gen = Generator with + fun produce(result)(resume) = + res.push(result) + let x = resume(()) + x +in permutations(gen, [1, 2, 3]) +//│ res = [ [ 1, 2, 3 ], [ 1, 3, 2 ] ] From 9d1ef86f805690b0dbb15cd020568f7a18230e24 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Fri, 24 Jan 2025 17:42:54 +0800 Subject: [PATCH 114/114] Added comment for HandlerCtx --- .../src/main/scala/hkmc2/codegen/HandlerLowering.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index bcf7c1195a..8f2b8c85e7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -27,6 +27,15 @@ object HandlerLowering: private case class LinkState(res: Path, cls: Path, uid: StateId) + // isHandleFree: whether the current block is inside a function or top level directly free of any handler in scope + // 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) + // and we do not have any `return` statement in the top level block so we do not need the `ReturnCont` workarounds. + // ctorThis: the path to `this` in the constructor, this is used to insert `return this;` at the end of constructor. + // linkAndHandle: + // a function that takes a LinkState and returns a block that links the continuation class and handles the effect + // this is a convenience function which initializes the continuation class in function context or throw an error in top level private case class HandlerCtx( isHandleFree: Bool, isTopLevel: Bool,