diff --git a/effekt/jvm/src/test/scala/effekt/core/OptimizerTests.scala b/effekt/jvm/src/test/scala/effekt/core/OptimizerTests.scala index 033e15df8..07006e818 100644 --- a/effekt/jvm/src/test/scala/effekt/core/OptimizerTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/OptimizerTests.scala @@ -108,8 +108,8 @@ class OptimizerTests extends CoreTests { test("drop pure let expressions"){ val input = """ def main = { () => - | let x = (add : (Int, Int) => Int @ {})(1, 2) - | let ! y = (println: (String) => Unit @ {io})("hello") + | let ! x = (add : (Int, Int) => Int @ {})(1, 2) + | let !! y = (println: (String) => Unit @ {io})("hello") | let z = 7 | return z:Int | } diff --git a/effekt/jvm/src/test/scala/effekt/core/RenamerTests.scala b/effekt/jvm/src/test/scala/effekt/core/RenamerTests.scala index 653d8ff6e..081555d63 100644 --- a/effekt/jvm/src/test/scala/effekt/core/RenamerTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/RenamerTests.scala @@ -77,7 +77,7 @@ class RenamerTests extends CoreTests { """module main | |def foo = { () => - | return (bar: (Int) => Int @ {})(baz:Int) + | (bar: (Int) => Int @ {})(baz:Int) |} |""".stripMargin assertRenamingPreservesAlpha(code) @@ -102,7 +102,7 @@ class RenamerTests extends CoreTests { """module main | |def foo = { () => - | var x @ global = (foo:(Int)=>Int@{})(4) ; + | var x @ global = 4 ; | return x:Int |} |""".stripMargin diff --git a/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala b/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala index 42f4b2e81..9adb07c31 100644 --- a/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala @@ -25,14 +25,14 @@ class TestRenamerTests extends CoreTests { """module main | |def foo = { () => - | return (bar: (Int) => Int @ {})(baz:Int) + | (bar: (Int) => Int @ {})(baz:Int) |} |""".stripMargin val expected = """module main | |def foo_renamed_0() = { - | return (bar: (Int) => Int @ {})(baz: Int) + | (bar: (Int) => Int @ {})(baz: Int) |} |""".stripMargin @@ -66,7 +66,7 @@ class TestRenamerTests extends CoreTests { """module main | |def foo = { () => - | var x @ global = (foo:(Int)=>Int@{})(4) ; + | var x @ global = 4; | return x:Int |} |""".stripMargin @@ -74,7 +74,7 @@ class TestRenamerTests extends CoreTests { """module main | |def foo_renamed_0() = { - | var x_renamed_1 @ global = (foo_renamed_0: (Int) => Int @ {})(4); + | var x_renamed_1 @ global = 4; | return x_renamed_1: Int |} |""".stripMargin diff --git a/effekt/jvm/src/test/scala/effekt/core/TypeInferenceTests.scala b/effekt/jvm/src/test/scala/effekt/core/TypeInferenceTests.scala index 12e67f6bf..18f29d43e 100644 --- a/effekt/jvm/src/test/scala/effekt/core/TypeInferenceTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/TypeInferenceTests.scala @@ -124,7 +124,7 @@ class TypeInferenceTests extends CoreTests { val ex3 = Stmt.Val(x, Stmt.Return(Expr.Literal(42, TInt)), - Stmt.Return(Expr.PureApp(add, Nil, Expr.ValueVar(x, TInt) :: Expr.ValueVar(x, TInt) :: Nil))) + Stmt.ExternApp(y, Pure, add, Nil, Expr.ValueVar(x, TInt) :: Expr.ValueVar(x, TInt) :: Nil, Nil, Stmt.Return(Expr.ValueVar(y, TInt)))) val result3 = typecheck(ex3) assertEquals(result3.tpe, TInt) @@ -134,16 +134,16 @@ class TypeInferenceTests extends CoreTests { // [A](Option[A], A): A val orElse: Block.BlockVar = Block.BlockVar(f, BlockType.Function(A :: Nil, Nil, List(OptionT(ValueType.Var(A)), ValueType.Var(A)), Nil, ValueType.Var(A)), Set.empty) - shouldTypeCheckAs(TInt, Expr.PureApp(orElse, TInt :: Nil, Expr.ValueVar(x, OptionT(TInt)) :: Expr.ValueVar(y, TInt) :: Nil)) + shouldTypeCheckAs(TInt, Stmt.App(orElse, TInt :: Nil, Expr.ValueVar(x, OptionT(TInt)) :: Expr.ValueVar(y, TInt) :: Nil, Nil)) // swapped arguments intercept[TypeError] { - val res = typecheck(Expr.PureApp(orElse, TInt :: Nil, Expr.ValueVar(y, TInt) :: Expr.ValueVar(x, OptionT(TInt)) :: Nil)) + val res = typecheck(Stmt.App(orElse, TInt :: Nil, Expr.ValueVar(y, TInt) :: Expr.ValueVar(x, OptionT(TInt)) :: Nil, Nil)) res.check() } // too few arguments intercept[TypeError] { - val res = typecheck(Expr.PureApp(orElse, TInt :: Nil, Expr.ValueVar(x, OptionT(TInt)) :: Nil)) + val res = typecheck(Stmt.App(orElse, TInt :: Nil, Expr.ValueVar(x, OptionT(TInt)) :: Nil, Nil)) res.check() } @@ -166,7 +166,7 @@ class TypeInferenceTests extends CoreTests { inline def assertSameType(got: ValueType, expected: ValueType)(using DeclarationContext): Unit = assertEquals(Type.equals(got, expected), true) - inline def shouldTypeCheckAs(expected: ValueType, expr: Expr)(using DeclarationContext): Unit = - expr.typing.check() - assertSameType(expr.tpe, expected) + inline def shouldTypeCheckAs(expected: ValueType, stmt: Stmt)(using DeclarationContext): Unit = + stmt.typing.check() + assertSameType(stmt.tpe, expected) } diff --git a/effekt/jvm/src/test/scala/effekt/core/VMTests.scala b/effekt/jvm/src/test/scala/effekt/core/VMTests.scala index 76cd2a421..ce0030760 100644 --- a/effekt/jvm/src/test/scala/effekt/core/VMTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/VMTests.scala @@ -674,14 +674,14 @@ class VMTests extends munit.FunSuite { )), examplesDir / "casestudies" / "lexer.effekt.md" -> Some(Summary( - staticDispatches = 314, + staticDispatches = 222, dynamicDispatches = 15, patternMatches = 232, branches = 405, - pushedFrames = 199, - poppedFrames = 199, + pushedFrames = 156, + poppedFrames = 156, allocations = 106, - closures = 23, + closures = 6, variableReads = 164, variableWrites = 51, resets = 29, @@ -690,14 +690,14 @@ class VMTests extends munit.FunSuite { )), examplesDir / "casestudies" / "parser.effekt.md" -> Some(Summary( - staticDispatches = 11414, - dynamicDispatches = 783, + staticDispatches = 8541, + dynamicDispatches = 768, patternMatches = 9468, branches = 14892, - pushedFrames = 7296, - poppedFrames = 7189, - allocations = 3848, - closures = 521, + pushedFrames = 6429, + poppedFrames = 6352, + allocations = 3833, + closures = 69, variableReads = 6742, variableWrites = 1901, resets = 776, @@ -706,14 +706,14 @@ class VMTests extends munit.FunSuite { )), examplesDir / "casestudies" / "anf.effekt.md" -> Some(Summary( - staticDispatches = 6147, - dynamicDispatches = 443, + staticDispatches = 4611, + dynamicDispatches = 431, patternMatches = 5109, branches = 8110, - pushedFrames = 4150, - poppedFrames = 4081, - allocations = 2143, - closures = 358, + pushedFrames = 3674, + poppedFrames = 3629, + allocations = 2131, + closures = 105, variableReads = 4080, variableWrites = 1343, resets = 451, @@ -738,13 +738,13 @@ class VMTests extends munit.FunSuite { )), examplesDir / "pos" / "raytracer.effekt" -> Some(Summary( - staticDispatches = 71065, + staticDispatches = 85740, dynamicDispatches = 0, - patternMatches = 633105, + patternMatches = 763548, branches = 65951, - pushedFrames = 31478, - poppedFrames = 31478, - allocations = 56669, + pushedFrames = 46153, + poppedFrames = 46153, + allocations = 70805, closures = 0, variableReads = 77886, variableWrites = 26904, @@ -1119,7 +1119,7 @@ class VMTests extends munit.FunSuite { // resumes = 0 // )), examplesDir / "benchmarks" / "nofib" / "fish.effekt" -> Some(Summary( - staticDispatches = 989577, + staticDispatches = 989497, dynamicDispatches = 4840, patternMatches = 1657577, branches = 21, @@ -1230,7 +1230,7 @@ class VMTests extends munit.FunSuite { )), examplesDir / "benchmarks" / "other" / "unify.effekt" -> Some(Summary( - staticDispatches = 2519232, + staticDispatches = 2511040, dynamicDispatches = 1460350, patternMatches = 3969252, branches = 2791752, diff --git a/effekt/shared/src/main/scala/effekt/core/Parser.scala b/effekt/shared/src/main/scala/effekt/core/Parser.scala index 8bf43db6f..091b0592f 100644 --- a/effekt/shared/src/main/scala/effekt/core/Parser.scala +++ b/effekt/shared/src/main/scala/effekt/core/Parser.scala @@ -72,12 +72,14 @@ class EffektLexers extends Parsers { lazy val `<>` = literal("<>") lazy val `<{` = literal("<{") lazy val `}>` = literal("}>") + lazy val `!!` = literal("!!") lazy val `!` = literal("!") lazy val `|` = literal("|") lazy val `++` = literal("++") lazy val `get` = keyword("get") lazy val `put` = keyword("put") + lazy val `run` = keyword("run") lazy val `let` = keyword("let") lazy val `true` = keyword("true") lazy val `false` = keyword("false") @@ -117,7 +119,7 @@ class EffektLexers extends Parsers { lazy val `namespace` = keyword("namespace") def keywordStrings: List[String] = List( - "def", "let", "val", "var", "true", "false", "else", "type", + "def", "let", "run", "val", "var", "true", "false", "else", "type", "effect", "interface", "try", "with", "case", "do", "if", "while", "match", "module", "import", "extern", "fun", "at", "box", "unbox", "return", "region", "new", "resource", "and", "is", "namespace", @@ -264,7 +266,6 @@ class CoreParsers(names: Names) extends EffektLexers { None } - lazy val `run` = keyword("run") lazy val `;` = super.literal(";") lazy val `make` = keyword("make") @@ -415,9 +416,17 @@ class CoreParsers(names: Names) extends EffektLexers { ) lazy val stmts: P[Stmt] = - ( (`let` ~ `!` ~/> id) ~ (`=` ~/> maybeParens(blockVar)) ~ maybeTypeArgs ~ valueArgs ~ blockArgs ~ stmts ^^ { + ( (`run` ~ `!!` ~/> id) ~ (`=` ~/> maybeParens(blockVar)) ~ maybeTypeArgs ~ valueArgs ~ blockArgs ~ stmts ^^ { case (name ~ callee ~ targs ~ vargs ~ bargs ~ body) => - ImpureApp(name, callee, targs, vargs, bargs, body) + ExternApp(name, Purity.Async, callee, targs, vargs, bargs, body) + } + | (`run` ~ `!` ~/> id) ~ (`=` ~/> maybeParens(blockVar)) ~ maybeTypeArgs ~ valueArgs ~ blockArgs ~ stmts ^^ { + case (name ~ callee ~ targs ~ vargs ~ bargs ~ body) => + ExternApp(name, Purity.Impure, callee, targs, vargs, bargs, body) + } + | (`run` ~/> id) ~ (`=` ~/> maybeParens(blockVar)) ~ maybeTypeArgs ~ valueArgs ~ blockArgs ~ stmts ^^ { + case (name ~ callee ~ targs ~ vargs ~ bargs ~ body) => + ExternApp(name, Purity.Pure, callee, targs, vargs, bargs, body) } | `let` ~/> id ~ (`=` ~/> expr) ~ stmts ^^ { case (name ~ binding ~ body) => @@ -463,7 +472,6 @@ class CoreParsers(names: Names) extends EffektLexers { | `box` ~> captures ~ block ^^ { case capt ~ block => Expr.Box(block, capt) } | `make` ~> dataType ~ id ~ maybeTypeArgs ~ valueArgs ^^ Expr.Make.apply | id ~ (`:` ~> valueType) ^^ Expr.ValueVar.apply - | maybeParens(blockVar) ~ maybeTypeArgs ~ valueArgs ^^ Expr.PureApp.apply | failure("Expected a pure expression.") ) diff --git a/effekt/shared/src/main/scala/effekt/core/PatternMatchingCompiler.scala b/effekt/shared/src/main/scala/effekt/core/PatternMatchingCompiler.scala index d56eab8f4..163082748 100644 --- a/effekt/shared/src/main/scala/effekt/core/PatternMatchingCompiler.scala +++ b/effekt/shared/src/main/scala/effekt/core/PatternMatchingCompiler.scala @@ -75,7 +75,7 @@ object PatternMatchingCompiler { case Ignore() case Any(id: Id) case Or(patterns: List[Pattern]) - case Literal(l: core.Literal, equals: (core.Expr, core.Expr) => core.Expr) + case Literal(l: core.Literal, equals: (core.Expr, core.Expr, core.ValueVar => core.Stmt) => core.Stmt) } /** @@ -100,10 +100,11 @@ object PatternMatchingCompiler { return binding.toStmt(compile(Clause(rest, target, targs, args) :: remainingClauses, motif)) // - We need to check a predicate case Clause(Condition.Predicate(pred) :: rest, target, targs, args) => - return core.If(pred, - compile(Clause(rest, target, targs, args) :: remainingClauses, motif), - compile(remainingClauses, motif) - ) + val blockLit = core.BlockLit(List(), List(), List(), List(), compile(remainingClauses, motif)) + val blockVar: core.BlockVar = core.BlockVar(Id("k"), blockLit.tpe, blockLit.capt) + return core.Def(blockVar.id, blockLit, core.If(pred, + compile(Clause(rest, target, targs, args) :: Clause(Nil, blockVar, List(), List()) :: Nil, motif), + core.App(blockVar, List(), List(), List()))) case Clause(Condition.Patterns(patterns) :: rest, target, targs, args) => patterns } @@ -121,7 +122,7 @@ object PatternMatchingCompiler { } // (3a) Match on a literal - def splitOnLiteral(lit: Literal, equals: (Expr, Expr) => Expr): core.Stmt = { + def splitOnLiteral(lit: Literal, equals: (Expr, Expr, ValueVar => Stmt) => Stmt): core.Stmt = { // the different literal values that we match on val variants: List[core.Literal] = normalized.collect { case Clause(Split(Pattern.Literal(lit, _), _, _), _, _, _) => lit @@ -171,7 +172,7 @@ object PatternMatchingCompiler { case false => core.If(scrutinee, elsStmt, thnStmt) case _ => - core.If(equals(scrutinee, lit), thnStmt, elsStmt) + equals(scrutinee, lit, result => core.If(result, thnStmt, elsStmt)) } } } diff --git a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala index cdedd4071..a15cd134f 100644 --- a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala @@ -144,7 +144,6 @@ class PrettyPrinter(printDetails: Boolean, printInternalIds: Boolean = true) ext case Literal(s: String, _) => stringLiteral(s) case Literal(value, _) => value.toString case ValueVar(id, tpe) => toDoc(id) <> (if printDetails then ":" <+> toDoc(tpe) else emptyDoc) - case PureApp(b, targs, vargs) => (if printDetails then parens(toDoc(b)) else toDoc(b)) <> argsToDoc(targs, vargs, Nil) case Make(data, tag, targs, vargs) => if printDetails then "make" <+> toDoc(data) <+> toDoc(tag) <> argsToDoc(targs, vargs, Nil) @@ -240,8 +239,13 @@ class PrettyPrinter(printDetails: Boolean, printInternalIds: Boolean = true) ext "let" <+> toDoc(id) <+> "=" <+> toDoc(binding) <> line <> toDocStmts(rest) - case ImpureApp(id, callee, targs, vargs, bargs, rest) => - "let" <+> "!" <+> toDoc(id) <+> "=" <+> toDoc(callee) <> argsToDoc(targs, vargs, bargs) <> line <> + case ExternApp(id, purity, callee, targs, vargs, bargs, rest) => + val purityKeyword = purity match { + case core.Pure => "run" + case core.Impure => "run !" + case core.Async => "run !!" + } + purityKeyword <+> toDoc(id) <+> "=" <+> toDoc(callee) <> argsToDoc(targs, vargs, bargs) <> line <> toDocStmts(rest) case Return(e) => diff --git a/effekt/shared/src/main/scala/effekt/core/Recursive.scala b/effekt/shared/src/main/scala/effekt/core/Recursive.scala index e288e3dcd..38da22407 100644 --- a/effekt/shared/src/main/scala/effekt/core/Recursive.scala +++ b/effekt/shared/src/main/scala/effekt/core/Recursive.scala @@ -44,7 +44,7 @@ class Recursive( def process(s: Stmt): Unit = s match { case Stmt.Def(id, block, body) => process(id, block); process(body) case Stmt.Let(id, binding, body) => process(binding); process(body) - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => process(callee) vargs.foreach(process) bargs.foreach(process) @@ -94,7 +94,6 @@ class Recursive( def process(e: Expr): Unit = e match { case Expr.ValueVar(id, annotatedType) => () case Expr.Literal(value, annotatedType) => () - case Expr.PureApp(b, targs, vargs) => process(b); vargs.foreach(process) case Expr.Make(data, tag, targs, vargs) => vargs.foreach(process) case Expr.Box(b, annotatedCapture) => process(b) } diff --git a/effekt/shared/src/main/scala/effekt/core/Renamer.scala b/effekt/shared/src/main/scala/effekt/core/Renamer.scala index b28978f1e..07248e51b 100644 --- a/effekt/shared/src/main/scala/effekt/core/Renamer.scala +++ b/effekt/shared/src/main/scala/effekt/core/Renamer.scala @@ -53,12 +53,12 @@ class Renamer(names: Names = Names(Map.empty), prefix: String = "") extends core val resolvedBinding = rewrite(binding) withBinding(id) { core.Let(rewrite(id), resolvedBinding, rewrite(body)) } - case core.ImpureApp(id, callee, targs, vargs, bargs, body) => + case core.ExternApp(id, purity, callee, targs, vargs, bargs, body) => val resolvedCallee = rewrite(callee) val resolvedTargs = targs map rewrite val resolvedVargs = vargs map rewrite val resolvedBargs = bargs map rewrite - withBinding(id) { core.ImpureApp(rewrite(id), resolvedCallee, resolvedTargs, resolvedVargs, resolvedBargs, rewrite(body)) } + withBinding(id) { core.ExternApp(rewrite(id), purity, resolvedCallee, resolvedTargs, resolvedVargs, resolvedBargs, rewrite(body)) } case core.Val(id, binding, body) => val resolvedBinding = rewrite(binding) diff --git a/effekt/shared/src/main/scala/effekt/core/Show.scala b/effekt/shared/src/main/scala/effekt/core/Show.scala index 1ca27dc29..4e89fa1c6 100644 --- a/effekt/shared/src/main/scala/effekt/core/Show.scala +++ b/effekt/shared/src/main/scala/effekt/core/Show.scala @@ -108,17 +108,6 @@ object Show extends Phase[CoreTransformed, CoreTransformed] { def transform(stmt: Stmt)(using ctx: ShowContext)(using Context, DeclarationContext): Stmt = stmt match { - case Let(id, PureApp(BlockVar(bid, bannotatedTpe, bannotatedCapt), targs, vargs), body) if bid.name.name == FUNCTION_NAME => - val targ = targs match { - case targ :: Nil => targ - case _ => Context.abort(pretty"Expected targs for '${FUNCTION_NAME}' to have exactly one argument") - } - // We need to wrap here, because vargs might have been extern show calls that are now not - // this might happen everywhere we directly transform an expression - val vargs_ = vargs map transform - ctx.withBindings { - Stmt.Val(id, Stmt.App(getShowBlockVar(targ), List.empty, vargs_, List.empty), transform(body)) - } case Let(id, binding, body) => val binding_ = transform(binding) ctx.withBindings { @@ -129,7 +118,7 @@ object Show extends Phase[CoreTransformed, CoreTransformed] { ctx.withBindings { Return(expr_) } - case ImpureApp(id, BlockVar(bid, annotatedTpe, annotatedCapt), targs, vargs, bargs, body) if bid.name.name == FUNCTION_NAME => + case ExternApp(id, purity, BlockVar(bid, annotatedTpe, annotatedCapt), targs, vargs, bargs, body) if bid.name.name == FUNCTION_NAME => val targ = targs match { case targ :: Nil => targ case _ => Context.abort(pretty"Expected targs for '${FUNCTION_NAME}' to have exactly one argument") @@ -139,11 +128,11 @@ object Show extends Phase[CoreTransformed, CoreTransformed] { ctx.withBindings { Stmt.Val(id, Stmt.App(getShowBlockVar(targ), List.empty, vargs_, bargs_), transform(body)) } - case ImpureApp(id, callee, targs, vargs, bargs, body) => + case ExternApp(id, purity, callee, targs, vargs, bargs, body) => val vargs_ = vargs map transform val bargs_ = bargs map transform ctx.withBindings { - ImpureApp(id, callee, targs, vargs_, bargs_, transform(body)) + ExternApp(id, purity, callee, targs, vargs_, bargs_, transform(body)) } case Def(id, block, body) => Def(id, transform(block), transform(body)) case Val(id, binding, body) => Val(id, transform(binding), transform(body)) @@ -194,19 +183,6 @@ object Show extends Phase[CoreTransformed, CoreTransformed] { def transform(expr: Expr)(using ctx: ShowContext)(using Context, DeclarationContext): Expr = expr match { case Make(data, tag, targs, vargs) => Make(data, tag, targs, vargs map transform) - case PureApp(BlockVar(id, annotatedTpe, annotatedCapt), targs, vargs) if id.name.name == FUNCTION_NAME => - val targ = targs match { - case targ :: Nil => targ - case _ => Context.abort(pretty"Expected targs for '${FUNCTION_NAME}' to have exactly one argument") - } - // We are switching from Extern call to normal Call, - // so we need to wrap the new call into a Val, store it and finally emit it before the statement that called this - // therefore we return the name of the Val that we store this call into - val stmt = Stmt.App(getShowBlockVar(targ), List.empty, vargs map transform, List.empty) - val letId = Id("showApp") - ctx.emit(letId, stmt) - Expr.ValueVar(letId, TString) - case PureApp(b, targs, vargs) => PureApp(transform(b), targs, vargs map transform) case Literal(value, annotatedType) => Literal(value, annotatedType) case ValueVar(id, annotatedType) => ValueVar(id, annotatedType) case Box(b, annotatedCapture) => Box(transform(b), annotatedCapture) @@ -226,7 +202,7 @@ object Show extends Phase[CoreTransformed, CoreTransformed] { def generateValAppRet(valueType: ValueType): Stmt = val blockVar = findExternShowDef(valueType) val retId = Id("ret") - Stmt.ImpureApp(retId, blockVar, List.empty, List(paramValueVar), List.empty, Stmt.Return(Expr.ValueVar(retId, TString))) + Stmt.ExternApp(retId, Purity.Impure, blockVar, List.empty, List(paramValueVar), List.empty, Stmt.Return(Expr.ValueVar(retId, TString))) def generateDef(stmt: Stmt): Toplevel.Def = val block = generateBlockLit(stmt) val defn: Toplevel.Def = Toplevel.Def(showId, block) @@ -346,36 +322,40 @@ object Show extends Phase[CoreTransformed, CoreTransformed] { def constructorStmt(constr: Constructor)(using ctx: ShowContext, dctx: DeclarationContext)(using Context): Stmt = constr match case Constructor(id, tparams, fields) => val infixConcatBlockVar: Block.BlockVar = findExternDef(STRING_CONCAT_NAME, List(TString, TString)) - val pureFields = fields map fieldPure - val concatenated = PureApp(infixConcatBlockVar, List.empty, List(Literal(id.name.name ++ "(", TString), concatPure(pureFields))) + val pureFields = fields map { case Field(id, tpe) => + val paramTpe = lookupType(tpe) + val app: Stmt.App = App(getShowBlockVar(paramTpe), List.empty, List(ValueVar(id, paramTpe)), List.empty) + (Id("field"), app) + } def fieldValStmts(idapps: List[(Id, Stmt.App)]): Stmt = idapps match { case (id, app) :: next => Stmt.Val(id, app, fieldValStmts(next)) - case Nil => Return(concatenated) + case Nil => concatPure(pureFields, Literal(id.name.name ++ "(", TString)) } fieldValStmts(pureFields) - // Convert a list of pure statements to comma-separated concatenated version - // Literal("Just("), PureApp(show, x), Literal(", "), PureApp(show, y), Literal(")") - // => - // PureApp(concat, List(Literal("Just("), PureApp(concat, List(PureApp(show, x), PureApp(concat, List(Literal(", "), ...)))) - def concatPure(pures: List[(Id, Stmt.App)])(using ctx: ShowContext)(using Context, DeclarationContext): Expr = + // Concatenate the results of a list of statements, separated with commas + def concatPure(pures: List[(Id, Stmt.App)], current: Expr)(using ctx: ShowContext)(using Context, DeclarationContext): Stmt = val infixConcatDef = findExternDef(STRING_CONCAT_NAME, List(TString, TString)) pures match - case (fieldId, _) :: next :: rest => PureApp(infixConcatDef, List.empty, List(Expr.ValueVar(fieldId, TString), PureApp(infixConcatDef, List.empty, List(Literal(", ", TString), concatPure(next :: rest))))) - case (fieldId, _) :: Nil => PureApp(infixConcatDef, List.empty, List(Expr.ValueVar(fieldId, TString), Literal(")", TString))) - case Nil => Literal(")", TString) - - def fieldValueVar(field: Field): Expr = field match - case Field(id, tpe) => ValueVar(id, tpe) - - def fieldPure(field: Field)(using ctx: ShowContext)(using Context, DeclarationContext): (Id, Stmt.App) = field match - case Field(id, tpe) => - val paramTpe = lookupType(tpe) - val app: Stmt.App = App(getShowBlockVar(paramTpe), List.empty, List(ValueVar(id, paramTpe)), List.empty) - (Id("field"), app) + case (fieldId, _) :: Nil => + val afterField = Id("current") + val afterClose = Id("currentClose") + ExternApp(afterField, Purity.Pure, infixConcatDef, List.empty, List(current, Expr.ValueVar(fieldId, TString)), List.empty, + ExternApp(afterClose, Purity.Pure, infixConcatDef, List.empty, List(ValueVar(afterField, TString), Literal(")", TString)), List.empty, + Return(ValueVar(afterClose, TString)))) + case (fieldId, _) :: rest => + val afterField = Id("current") + val afterSep = Id("currentSep") + ExternApp(afterField, Purity.Pure, infixConcatDef, List.empty, List(current, Expr.ValueVar(fieldId, TString)), List.empty, + ExternApp(afterSep, Purity.Pure, infixConcatDef, List.empty, List(ValueVar(afterField, TString), Literal(", ", TString)), List.empty, + concatPure(rest, ValueVar(afterSep, TString)))) + case Nil => + val next = Id("current") + ExternApp(next, Purity.Pure, infixConcatDef, List.empty, List(current, Literal(")", TString)), List.empty, + Return(ValueVar(next, TString))) var freshShowCounter = 0 def freshShowId: Id = diff --git a/effekt/shared/src/main/scala/effekt/core/TestRenamer.scala b/effekt/shared/src/main/scala/effekt/core/TestRenamer.scala index a5cb15d0f..266c87dff 100644 --- a/effekt/shared/src/main/scala/effekt/core/TestRenamer.scala +++ b/effekt/shared/src/main/scala/effekt/core/TestRenamer.scala @@ -96,12 +96,12 @@ class TestRenamer(names: Names = Names(Map.empty), prefix: String = "$", preserv val resolvedBinding = rewrite(binding) withBinding(id) { core.Let(rewrite(id), resolvedBinding, rewrite(body)) } - case core.ImpureApp(id, callee, targs, vargs, bargs, body) => + case core.ExternApp(id, purity, callee, targs, vargs, bargs, body) => val resolvedCallee = rewrite(callee) val resolvedTargs = targs map rewrite val resolvedVargs = vargs map rewrite val resolvedBargs = bargs map rewrite - withBinding(id) { core.ImpureApp(rewrite(id), resolvedCallee, resolvedTargs, resolvedVargs, resolvedBargs, rewrite(body)) } + withBinding(id) { core.ExternApp(rewrite(id), purity, resolvedCallee, resolvedTargs, resolvedVargs, resolvedBargs, rewrite(body)) } case core.Val(id, binding, body) => val resolvedBinding = rewrite(binding) diff --git a/effekt/shared/src/main/scala/effekt/core/Transformer.scala b/effekt/shared/src/main/scala/effekt/core/Transformer.scala index 0177adc15..a06178304 100644 --- a/effekt/shared/src/main/scala/effekt/core/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/core/Transformer.scala @@ -246,21 +246,12 @@ object Transformer extends Phase[Typechecked, CoreTransformed] { val tpe = Context.blockTypeOf(sym) tpe match { case BlockType.FunctionType(tparams, cparams, vparamtps, bparamtps, restpe, effects) => - // if this block argument expects to be called using PureApp or ImpureApp, make sure it is + // if this block argument expects to be called using PureApp or ExternApp, make sure it is // by wrapping it in a BlockLit val targs = tparams.map(core.ValueType.Var.apply) val vparams = vparamtps.map { t => core.ValueParam(TmpValue("valueParam"), transform(t))} val vargs = vparams.map { case core.ValueParam(id, tpe) => Expr.ValueVar(id, tpe) } - // [[ f ]] = { (x) => f(x) } - def etaExpandPure(b: ExternFunction): BlockLit = { - util.assert(bparamtps.isEmpty) - util.assert(effects.isEmpty) - util.assert(cparams.isEmpty) - BlockLit(tparams, Nil, vparams, Nil, - Stmt.Return(PureApp(BlockVar(b), targs, vargs))) - } - // [[ f ]] = { [A](x) => make f[A](x) } def etaExpandConstructor(b: Constructor): BlockLit = { util.assert(bparamtps.isEmpty) @@ -271,24 +262,31 @@ object Transformer extends Phase[Typechecked, CoreTransformed] { } // [[ f ]] = { (x){g} => let r = f(x){g}; return r } - def etaExpandDirect(f: ExternFunction): BlockLit = { + def etaExpandExtern(f: ExternFunction): BlockLit = { util.assert(effects.isEmpty) + // TODO calculate purity from capture + val purity = if f.capture.pure then Pure else if f.capture.pureOrIO then Impure else Async val bparams = bparamtps.map { t => val id = TmpBlock("etaParam"); core.BlockParam(id, transform(t), Set(id)) } val bargs = bparams.map { case core.BlockParam(id, tpe, capt) => Block.BlockVar(id, tpe, capt) } val result = TmpValue("etaBinding") val callee = BlockVar(f) - BlockLit(tparams, bparams.map(_.id), vparams, bparams, - core.ImpureApp(result, callee, targs, vargs, bargs, - Stmt.Return(Expr.ValueVar(result, transform(restpe))))) + ResolveExternDefs.findPreferred(f.bodies) match { + case b: source.ExternBody.EffektExternBody[_] => + BlockLit(tparams, bparams.map(_.id), vparams, bparams, + App(callee, targs, vargs, bargs)) + case _ => + BlockLit(tparams, bparams.map(_.id), vparams, bparams, + core.ExternApp(result, purity, callee, targs, vargs, bargs, + Stmt.Return(Expr.ValueVar(result, transform(restpe))))) + } } sym match { case _: ValueSymbol => transformUnbox(tree) case cns: Constructor => etaExpandConstructor(cns) - case f: ExternFunction if callingConvention(f) == CallingConvention.Pure => etaExpandPure(f) - case f: ExternFunction if callingConvention(f) == CallingConvention.Direct => etaExpandDirect(f) + case f: ExternFunction => etaExpandExtern(f) // does not require change of calling convention, so no eta expansion case sym: BlockSymbol => BlockVar(sym) } @@ -504,6 +502,9 @@ object Transformer extends Phase[Typechecked, CoreTransformed] { case other: (source.If | source.While | source.TryHandle | source.Hole | source.MethodCall | source.Region | source.Match | source.Select | source.Call) => transformAsStmt(other) match { + case ExternApp(id, purity, callee, targs, vargs, bargs, Return(expr)) => + Context.emit(Binding.ExternApp(id, purity, callee, targs, vargs, bargs)) + expr case Return(expr) => expr case other => Context.bind(other) } @@ -657,7 +658,7 @@ object Transformer extends Phase[Typechecked, CoreTransformed] { case MatchGuard.BooleanGuard(condition, _) => Nil case MatchGuard.PatternGuard(scrutinee, pattern, _) => boundTypesInPattern(pattern) } - def equalsFor(tpe: symbols.ValueType): (Expr, Expr) => Expr = + def equalsFor(tpe: symbols.ValueType): (Expr, Expr, ValueVar => Stmt) => Stmt = val prelude = Context.module.findDependency(QualifiedName(Nil, "effekt")).getOrElse { Context.panic(pp"${Context.module.name.name}: Cannot find 'effekt' in prelude, which is necessary to compile pattern matching.") } @@ -666,11 +667,19 @@ object Transformer extends Phase[Typechecked, CoreTransformed] { } collectFirst { // specialized version case (sym, FunctionType(Nil, Nil, List(`tpe`, `tpe`), Nil, builtins.TBoolean, _)) => - (lhs: Expr, rhs: Expr) => core.PureApp(BlockVar(sym), Nil, List(lhs, rhs)) + (lhs: Expr, rhs: Expr, cont: ValueVar => Stmt) => { + val result = Id("eqres") + val resultVar: ValueVar = Expr.ValueVar(result, core.Type.TBoolean) + core.ExternApp(result, Purity.Pure, BlockVar(sym), Nil, List(lhs, rhs), Nil, cont(resultVar)) + } // generic version case (sym, FunctionType(List(tparam), Nil, List(ValueTypeRef(t1), ValueTypeRef(t2)), Nil, builtins.TBoolean, _)) if t1 == tparam && t2 == tparam => - (lhs: Expr, rhs: Expr) => core.PureApp(BlockVar(sym), List(transform(tpe)), List(lhs, rhs)) + (lhs: Expr, rhs: Expr, cont: ValueVar => Stmt) => { + val result = Id("eqres") + val resultVar: ValueVar = Expr.ValueVar(result, core.Type.TBoolean) + core.ExternApp(result, Purity.Pure, BlockVar(sym), List(transform(tpe)), List(lhs, rhs), Nil, cont(resultVar)) + } } getOrElse { Context.panic(pp"Cannot find == for type ${tpe} in prelude!") } // create joinpoint @@ -781,10 +790,6 @@ object Transformer extends Phase[Typechecked, CoreTransformed] { val bargsT = bargs.map(transformAsBlock) sym match { - case f: Callable if callingConvention(f) == CallingConvention.Pure => - Return(PureApp(BlockVar(f), targs, vargsT)) - case f: Callable if callingConvention(f) == CallingConvention.Direct => - Return(Context.bind(BlockVar(f), targs, vargsT, bargsT)) case r: Constructor => if (bargs.nonEmpty) Context.abort("Constructors cannot take block arguments.") val universals = targs.take(r.tpe.tparams.length) @@ -794,6 +799,17 @@ object Transformer extends Phase[Typechecked, CoreTransformed] { Context.panic("Should have been translated to a method call!") case f: Field => Context.panic("Should have been translated to a select!") + case f: ExternFunction => + ResolveExternDefs.findPreferred(f.bodies) match { + case b: source.ExternBody.EffektExternBody[_] => + App(BlockVar(f), targs, vargsT, bargsT) + case _ => + val purity = if f.capture.pure then Pure else if f.capture.pureOrIO then Impure else Async + val x = TmpValue("r") + val callee = BlockVar(f) + val tpe = Type.instantiate(callee.tpe.asInstanceOf[core.BlockType.Function], targs, bargsT.map(_.capt)).result + ExternApp(x, purity, callee, targs, vargsT, bargsT, Return(core.ValueVar(x, tpe))) + } case f: BlockSymbol => App(BlockVar(f), targs, vargsT, bargsT) case f: ValueSymbol => @@ -859,22 +875,6 @@ object Transformer extends Phase[Typechecked, CoreTransformed] { case _ => Context.panic("All capture unification variables should have been replaced by now.") } - private enum CallingConvention { - case Pure, Direct, Control - } - private def callingConvention(callable: Callable)(using Context): CallingConvention = callable match { - case f @ ExternFunction(name, _, _, _, _, _, capture, bodies, _) => - // resolve the preferred body again and hope it's the same - val body = ResolveExternDefs.findPreferred(bodies) - body match { - case b: source.ExternBody.EffektExternBody[_] => CallingConvention.Control - case _ if f.capture.pure => CallingConvention.Pure - case _ if f.capture.pureOrIO => CallingConvention.Direct - case _ => CallingConvention.Control - } - case _ => CallingConvention.Control - } - // we can conservatively approximate to false, in order to disable the optimizations private def isPureOrIO(t: source.Tree)(using Context): Boolean = Context.inferredCaptureOption(t) match { @@ -960,9 +960,10 @@ trait TransformerOps extends ContextOps { Context: Context => } private[core] def bind(callee: Block.BlockVar, targs: List[core.ValueType], vargs: List[Expr], bargs: List[Block]): ValueVar = { + // FIXME EXTERNAPP: make it work for all purities // create a fresh symbol and assign the type val x = TmpValue("r") - val binding: Binding.ImpureApp = Binding.ImpureApp(x, callee, targs, vargs, bargs) + val binding: Binding.ExternApp = Binding.ExternApp(x, Purity.Impure, callee, targs, vargs, bargs) bindings += binding ValueVar(x, Type.bindingType(binding)) diff --git a/effekt/shared/src/main/scala/effekt/core/Tree.scala b/effekt/shared/src/main/scala/effekt/core/Tree.scala index e3cdfc64b..66479d2a5 100644 --- a/effekt/shared/src/main/scala/effekt/core/Tree.scala +++ b/effekt/shared/src/main/scala/effekt/core/Tree.scala @@ -168,7 +168,6 @@ enum Toplevel { * ─ [[ Expr ]] * │─ [[ ValueVar ]] * │─ [[ Literal ]] - * │─ [[ PureApp ]] * │─ [[ Make ]] * │─ [[ Box ]] * @@ -180,11 +179,6 @@ enum Expr extends Tree { case Literal(value: Any, annotatedType: ValueType) - /** - * Pure FFI calls. Invariant, block b is pure. - */ - case PureApp(b: Block.BlockVar, targs: List[ValueType], vargs: List[Expr]) - /** * Constructor calls * @@ -271,7 +265,7 @@ enum Stmt extends Tree { // Definitions case Def(id: Id, block: Block, body: Stmt) case Let(id: Id, binding: Expr, body: Stmt) - case ImpureApp(id: Id, callee: Block.BlockVar, targs: List[ValueType], vargs: List[Expr], bargs: List[Block], body: Stmt) + case ExternApp(id: Id, purity: Purity, callee: Block.BlockVar, targs: List[ValueType], vargs: List[Expr], bargs: List[Block], body: Stmt) // Fine-grain CBV case Return(expr: Expr) @@ -322,6 +316,12 @@ enum Stmt extends Tree { } export Stmt.* +enum Purity { + case Pure + case Impure + case Async +} +export Purity.* /** * An instance of an interface, concretely implementing the operations. @@ -356,7 +356,7 @@ case class Operation(name: Id, tparams: List[Id], cparams: List[Id], vparams: Li private[core] enum Binding { case Val(id: Id, binding: Stmt) case Let(id: Id, binding: Expr) - case ImpureApp(id: Id, callee: Block.BlockVar, targs: List[ValueType], vargs: List[Expr], bargs: List[Block]) + case ExternApp(id: Id, purity: Purity, callee: Block.BlockVar, targs: List[ValueType], vargs: List[Expr], bargs: List[Block]) case Def(id: Id, binding: Block) case Alloc(id: Id, init: Expr, region: Id) case Var(ref: Id, init: Expr, capture: Id) @@ -366,7 +366,7 @@ private[core] enum Binding { def toStmt(rest: Stmt): Stmt = this match { case Binding.Val(id, binding) => Stmt.Val(id, binding, rest) case Binding.Let(id, binding) => Stmt.Let(id, binding, rest) - case Binding.ImpureApp(id, callee, targs, vargs, bargs) => Stmt.ImpureApp(id, callee, targs, vargs, bargs, rest) + case Binding.ExternApp(id, purity, callee, targs, vargs, bargs) => Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, rest) case Binding.Def(id, binding) => Stmt.Def(id, binding, rest) case Binding.Alloc(id, init, region) => Stmt.Alloc(id, init, region, rest) case Binding.Var(ref, init, capture) => Stmt.Var(ref, init, capture, rest) @@ -383,7 +383,7 @@ private[core] object Binding { def toToplevel(b: Binding): Toplevel = b match { case Binding.Val(name, binding) => Toplevel.Val(name, binding) case Binding.Let(name, binding) => ??? //Toplevel.Val(name, tpe, Stmt.Return(binding)) - case Binding.ImpureApp(name, callee, targs, vargs, bargs) => ??? //Toplevel.Val(name, tpe, ???) + case Binding.ExternApp(name, purity, callee, targs, vargs, bargs) => ??? //Toplevel.Val(name, tpe, ???) case Binding.Def(name, binding) => Toplevel.Def(name, binding) case Binding.Alloc(name, init, region) => ??? case Binding.Var(ref, init, capture) => ??? @@ -410,8 +410,8 @@ object Bind { def bind[A](b: Block.BlockVar, targs: List[ValueType], vargs: List[Expr], bargs: List[Block]): Bind[ValueVar] = val id = Id("tmp") - val binding: Binding.ImpureApp = Binding.ImpureApp(id, b, targs, vargs, bargs) - Bind(ValueVar(id, Type.bindingType(binding)), List(Binding.ImpureApp(id, b, targs, vargs, bargs))) + val binding: Binding.ExternApp = Binding.ExternApp(id, Impure, b, targs, vargs, bargs) + Bind(ValueVar(id, Type.bindingType(binding)), List(Binding.ExternApp(id, Impure, b, targs, vargs, bargs))) def bind[A](block: Block): Bind[BlockVar] = val id = Id("tmp") @@ -491,8 +491,6 @@ object Tree { Expr.ValueVar(rewrite(id), rewrite(annotatedType)) case Expr.Literal(value, annotatedType) => Expr.Literal(value, rewrite(annotatedType)) - case Expr.PureApp(b, targs, vargs) => - Expr.PureApp(rewrite(b), targs map rewrite, vargs map rewrite) case Expr.Make(data, tag, targs, vargs) => Expr.Make(rewrite(data), rewrite(tag), targs map rewrite, vargs map rewrite) case Expr.Box(b, annotatedCapture) => @@ -503,8 +501,8 @@ object Tree { Stmt.Def(rewrite(id), rewrite(block), rewrite(body)) case Stmt.Let(id, binding, body) => Stmt.Let(rewrite(id), rewrite(binding), rewrite(body)) - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => - Stmt.ImpureApp(rewrite(id), rewrite(callee), targs map rewrite, vargs map rewrite, bargs map rewrite, rewrite(body)) + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => + Stmt.ExternApp(rewrite(id), purity, rewrite(callee), targs map rewrite, vargs map rewrite, bargs map rewrite, rewrite(body)) case Stmt.Return(expr) => Stmt.Return(rewrite(expr)) case Stmt.Val(id, binding, body) => @@ -646,11 +644,6 @@ object Tree { def rewrite(e: Expr): Trampoline[Expr] = e match { case Expr.ValueVar(id, tpe) => done(Expr.ValueVar(rewrite(id), rewrite(tpe))) case Expr.Literal(value, tpe) => done(Expr.Literal(value, rewrite(tpe))) - case Expr.PureApp(b, targs, vargs) => for { - b2 <- done(rewrite(b)) - targs2 <- done(targs.map(rewrite)) - vargs2 <- all(vargs, rewrite) - } yield Expr.PureApp(b2, targs2, vargs2) case Expr.Make(data, tag, targs, vargs) => for { data2 <- done(rewrite(data)) tag2 <- done(rewrite(tag)) @@ -676,14 +669,14 @@ object Tree { body2 <- rewrite(body) } yield Stmt.Let(id2, binding2, body2) - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => for { + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => for { id2 <- done(rewrite(id)) callee2 <- done(rewrite(callee)) targs2 <- done(targs.map(rewrite)) vargs2 <- all(vargs, rewrite) bargs2 <- all(bargs, rewrite) body2 <- rewrite(body) - } yield Stmt.ImpureApp(id2, callee2, targs2, vargs2, bargs2, body2) + } yield Stmt.ExternApp(id2, purity, callee2, targs2, vargs2, bargs2, body2) case Stmt.Return(expr) => for { expr2 <- rewrite(expr) @@ -893,8 +886,6 @@ object Tree { Expr.ValueVar(rewrite(id), rewrite(annotatedType)) case Expr.Literal(value, annotatedType) => Expr.Literal(value, rewrite(annotatedType)) - case Expr.PureApp(b, targs, vargs) => - Expr.PureApp(rewrite(b), targs map rewrite, vargs map rewrite) case Expr.Make(data, tag, targs, vargs) => Expr.Make(rewrite(data), rewrite(tag), targs map rewrite, vargs map rewrite) case Expr.Box(b, annotatedCapture) => @@ -905,8 +896,8 @@ object Tree { Stmt.Def(rewrite(id), rewrite(block), rewrite(body)) case Stmt.Let(id, binding, body) => Stmt.Let(rewrite(id), rewrite(binding), rewrite(body)) - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => - Stmt.ImpureApp(rewrite(id), rewrite(callee), targs map rewrite, vargs map rewrite, bargs map rewrite, rewrite(body)) + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => + Stmt.ExternApp(rewrite(id), purity, rewrite(callee), targs map rewrite, vargs map rewrite, bargs map rewrite, rewrite(body)) case Stmt.Return(expr) => Stmt.Return(rewrite(expr)) case Stmt.Val(id, binding, body) => @@ -1075,10 +1066,10 @@ object substitutions { Let(id, substitute(binding), substitute(body)(using subst shadowValues List(id))) - case ImpureApp(id, callee, targs, vargs, bargs, body) => + case ExternApp(id, purity, callee, targs, vargs, bargs, body) => substitute(callee) match { case g : Block.BlockVar => - ImpureApp(id, g, targs.map(substitute), vargs.map(substitute), bargs.map(substitute), + ExternApp(id, purity, g, targs.map(substitute), vargs.map(substitute), bargs.map(substitute), substitute(body)(using subst shadowValues List(id))) case _ => INTERNAL_ERROR("Should never substitute a concrete block for an FFI function.") } @@ -1142,7 +1133,7 @@ object substitutions { vparams.map(p => substitute(p)(using shadowedTypelevel)), bparams.map(p => substitute(p)(using shadowedTypelevel)), substitute(body)(using shadowedTypelevel.shadowParams(vparams, bparams))) - } + } def substituteAsVar(id: Id)(using subst: Substitution): Id = subst.blocks.get(id) map { @@ -1170,11 +1161,6 @@ object substitutions { case Make(tpe, tag, targs, vargs) => Make(substitute(tpe).asInstanceOf, tag, targs.map(substitute), vargs.map(substitute)) - case PureApp(f, targs, vargs) => substitute(f) match { - case g : Block.BlockVar => PureApp(g, targs.map(substitute), vargs.map(substitute)) - case _ => INTERNAL_ERROR("Should never substitute a concrete block for an FFI function.") - } - case Box(b, annotatedCapture) => Box(substitute(b), substitute(annotatedCapture)) } @@ -1222,10 +1208,10 @@ object substitutions { case Binding.Let(id, binding) => Binding.Let(id, substitute(binding)) - case Binding.ImpureApp(id, callee, targs, vargs, bargs) => + case Binding.ExternApp(id, purity, callee, targs, vargs, bargs) => substitute(callee) match { case g: Block.BlockVar => - Binding.ImpureApp(id, g, targs.map(substitute), vargs.map(substitute), bargs.map(substitute)) + Binding.ExternApp(id, purity, g, targs.map(substitute), vargs.map(substitute), bargs.map(substitute)) case _ => INTERNAL_ERROR("Should never substitute a concrete block for an FFI function.") } @@ -1256,7 +1242,7 @@ object sizes { inline def size(stmt: Stmt): Int = stmt match { case Stmt.Def(id, block, body) => block.size + body.size + 1 case Stmt.Let(id, binding, body) => binding.size + body.size + 1 - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => all(vargs, _.size) + all(bargs, _.size) + body.size + 1 + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => all(vargs, _.size) + all(bargs, _.size) + body.size + 1 case Stmt.Return(expr) => expr.size + 1 case Stmt.Val(id, binding, body) => binding.size + body.size + 1 case Stmt.App(callee, targs, vargs, bargs) => callee.size + all(vargs, _.size) + all(bargs, _.size) + 1 @@ -1277,7 +1263,6 @@ object sizes { inline def size(expr: Expr): Int = expr match { case Expr.ValueVar(id, annotatedType) => 1 case Expr.Literal(value, annotatedType) => 1 - case Expr.PureApp(b, targs, vargs) => all(vargs, _.size) + 1 case Expr.Make(data, tag, targs, vargs) => all(vargs, _.size) + 1 case Expr.Box(b, annotatedCapture) => b.size + 1 } @@ -1362,8 +1347,8 @@ object freeVariables { case Stmt.Let(id, binding, body) => body.free.withoutValue(id, binding.tpe) ++ binding.free - case s @ Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => - val retType = Type.bindingType(s) + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => + val retType = Type.bindingType(Binding.ExternApp(id, purity, callee, targs, vargs, bargs)) Free.block(callee.id, callee.annotatedTpe, callee.annotatedCapt) ++ all(vargs, _.free) ++ all(bargs, _.free) ++ @@ -1423,10 +1408,6 @@ object freeVariables { case Expr.Literal(value, annotatedType) => Free.empty - case Expr.PureApp(callee, targs, vargs) => - all(vargs, _.free) ++ - Free.block(callee.id, callee.annotatedTpe, callee.annotatedCapt) - case Expr.Make(data, tag, targs, vargs) => all(vargs, _.free) diff --git a/effekt/shared/src/main/scala/effekt/core/Type.scala b/effekt/shared/src/main/scala/effekt/core/Type.scala index fe4a70423..84979f480 100644 --- a/effekt/shared/src/main/scala/effekt/core/Type.scala +++ b/effekt/shared/src/main/scala/effekt/core/Type.scala @@ -181,13 +181,8 @@ object Type { ValueType.Boxed(substitute(tpe, vsubst, csubst), substitute(capt, csubst)) } - def bindingType(stmt: Stmt.ImpureApp): ValueType = stmt match { - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => - Type.instantiate(callee.tpe.asInstanceOf[core.BlockType.Function], targs, bargs.map(_.capt)).result - } - - def bindingType(bind: Binding.ImpureApp): ValueType = bind match { - case Binding.ImpureApp(id, callee, targs, vargs, bargs) => + def bindingType(bind: Binding.ExternApp): ValueType = bind match { + case Binding.ExternApp(id, purity, callee, targs, vargs, bargs) => Type.instantiate(callee.tpe.asInstanceOf[core.BlockType.Function], targs, bargs.map(_.capt)).result } @@ -320,14 +315,12 @@ object Type { } inline def typecheck(expr: Expr): Typing[ValueType] = checking(expr) { - case Expr.ValueVar(id, annotatedType) => Typing(annotatedType, Set.empty, Constraints.empty) - case Expr.Literal(value, annotatedType) => Typing(annotatedType, Set.empty, Constraints.empty) - case Expr.PureApp(callee, targs, vargs) => - val BlockType.Function(tparams, cparams, vparams, bparams, result) = instantiate(callee.functionType, targs, Nil) - val Typing(argTypes, _, cs) = all(vargs, e => e.typing) - if bparams.nonEmpty then typeError("Pure apps cannot have block params") - valuesShouldEqual(vparams, argTypes) - Typing(result, Set.empty, cs) + + case Expr.ValueVar(id, annotatedType) => + Typing(annotatedType, Set.empty, Constraints.empty) + + case Expr.Literal(value, annotatedType) => + Typing(annotatedType, Set.empty, Constraints.empty) case make @ Expr.Make(data, tag, targs, vargs) => val Typing(argTypes, argCapt, argsCs) = all(vargs, arg => arg.typing) @@ -464,12 +457,12 @@ object Type { case Stmt.Resume(k, body) => typeError(s"Continuation has wrong type: ${k}") - case Stmt.ImpureApp(id, BlockVar(f, tpe: BlockType.Function, annotatedCapt), targs, vargs, bargs, body) => + case Stmt.ExternApp(id, purity, BlockVar(f, tpe: BlockType.Function, annotatedCapt), targs, vargs, bargs, body) => val Typing(retType, callCapts, callCs) = typecheckFunctionLike(Typing(tpe, annotatedCapt, Constraints.empty), targs, vargs, bargs) val Typing(bodyType, bodyCapts, bodyCs) = body.typing Typing(bodyType, callCapts ++ bodyCapts, callCs ++ bodyCs) - case s: Stmt.ImpureApp => typeError("Impure app should have a function type") + case s: Stmt.ExternApp => typeError("Extern app should have a function type") case Stmt.App(callee, targs, vargs, bargs) => typecheckFunctionLike(asFunctionTyping(callee.typing), targs, vargs, bargs) @@ -528,7 +521,7 @@ object Type { inline def inferType(stmt: Stmt): ValueType = stmt match { case Stmt.Def(id, block, body) => body.tpe case Stmt.Let(id, binding, body) => body.tpe - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => body.tpe + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => body.tpe case Stmt.Return(expr) => expr.tpe case Stmt.Val(id, binding, body) => body.tpe case Stmt.App(callee, targs, vargs, bargs) => @@ -559,8 +552,6 @@ object Type { inline def inferType(expr: Expr): ValueType = expr match { case Expr.ValueVar(id, annotatedType) => annotatedType case Expr.Literal(value, annotatedType) => annotatedType - case Expr.PureApp(b, targs, vargs) => - Type.instantiate(b.functionType, targs, Nil).result case Expr.Make(data, tag, targs, vargs) => data case Expr.Box(b, annotatedCapture) => ValueType.Boxed(b.tpe, annotatedCapture) @@ -584,7 +575,7 @@ object Type { inline def inferCapt(stmt: Stmt): Captures = stmt match { case Stmt.Def(id, block, body) => body.capt case Stmt.Let(id, binding, body) => body.capt - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => callee.capt ++ bargs.flatMap(_.capt) ++ body.capt case Stmt.Return(expr) => Set.empty case Stmt.Val(id, binding, body) => body.capt ++ binding.capt diff --git a/effekt/shared/src/main/scala/effekt/core/optimizer/BindSubexpressions.scala b/effekt/shared/src/main/scala/effekt/core/optimizer/BindSubexpressions.scala index b96809118..70cb0a0ad 100644 --- a/effekt/shared/src/main/scala/effekt/core/optimizer/BindSubexpressions.scala +++ b/effekt/shared/src/main/scala/effekt/core/optimizer/BindSubexpressions.scala @@ -51,11 +51,11 @@ object BindSubexpressions { Binding(bindings, Stmt.Let(id, other, transform(body))) } - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => delimit { + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => delimit { for { vs <- transformExprs(vargs) bs <- transformBlocks(bargs) - } yield Stmt.ImpureApp(id, transform(callee), targs.map(transform), vs, bs, transform(body)) + } yield Stmt.ExternApp(id, purity, transform(callee), targs.map(transform), vs, bs, transform(body)) } case Stmt.App(callee, targs, vargs, bargs) => delimit { @@ -136,10 +136,6 @@ object BindSubexpressions { case Expr.Make(data, tag, targs, vargs) => transformExprs(vargs) { vs => bind(Expr.Make(data, tag, targs, vs)) } - case Expr.PureApp(f, targs, vargs) => for { - vs <- transformExprs(vargs); - res <- bind(Expr.PureApp(f, targs.map(transform), vs)) - } yield res case Expr.Box(block, capt) => transform(block) { b => bind(Expr.Box(b, transform(capt))) } } diff --git a/effekt/shared/src/main/scala/effekt/core/optimizer/DirectStyle.scala b/effekt/shared/src/main/scala/effekt/core/optimizer/DirectStyle.scala index a8b33b894..1435fdd7a 100644 --- a/effekt/shared/src/main/scala/effekt/core/optimizer/DirectStyle.scala +++ b/effekt/shared/src/main/scala/effekt/core/optimizer/DirectStyle.scala @@ -44,7 +44,7 @@ object DirectStyle extends Tree.Rewrite { // Congruences case Def(id, block, body) => canBeDirect(body) case Let(id, binding, body) => canBeDirect(body) - case ImpureApp(id, callee, targs, vargs, bargs, body) => canBeDirect(body) + case ExternApp(id, purity, callee, targs, vargs, bargs, body) => canBeDirect(body) case Val(id, binding, body) => canBeDirect(body) case If(cond, thn, els) => canBeDirect(thn) && canBeDirect(els) case Match(scrutinee, tpe, clauses, default) => @@ -83,8 +83,8 @@ object DirectStyle extends Tree.Rewrite { case Let(id, binding, body) => Let(id, binding, toDirectStyle(body, label)) - case ImpureApp(id, callee, targs, vargs, bargs, body) => - ImpureApp(id, callee, targs, vargs, bargs, toDirectStyle(body, label)) + case ExternApp(id, purity, callee, targs, vargs, bargs, body) => + ExternApp(id, purity, callee, targs, vargs, bargs, toDirectStyle(body, label)) case Val(id, binding, body) => Val(id, binding, toDirectStyle(body, label)) diff --git a/effekt/shared/src/main/scala/effekt/core/optimizer/Normalizer.scala b/effekt/shared/src/main/scala/effekt/core/optimizer/Normalizer.scala index 2657720c8..769aba69e 100644 --- a/effekt/shared/src/main/scala/effekt/core/optimizer/Normalizer.scala +++ b/effekt/shared/src/main/scala/effekt/core/optimizer/Normalizer.scala @@ -178,8 +178,8 @@ object Normalizer { normal => Stmt.Let(id, normalized, normalize(body)(using C.bind(id, normalized))) } - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => - Stmt.ImpureApp(id, callee, targs, vargs.map(normalize), bargs.map(normalize), normalize(body)) + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => + Stmt.ExternApp(id, purity, callee, targs, vargs.map(normalize), bargs.map(normalize), normalize(body)) // Redexes // ------- @@ -311,8 +311,8 @@ object Normalizer { normal => case Stmt.Let(id2, binding2, body2) => Stmt.Let(id2, binding2, normalizeVal(id, body2, body)) - case Stmt.ImpureApp(id2, callee2, targs2, vargs2, bargs2, body2) => - Stmt.ImpureApp(id2, callee2, targs2, vargs2, bargs2, normalizeVal(id, body2, body)) + case Stmt.ExternApp(id2, purity, callee2, targs2, vargs2, bargs2, body2) => + Stmt.ExternApp(id2, purity, callee2, targs2, vargs2, bargs2, normalizeVal(id, body2, body)) // Flatten vals. This should be non-leaking since we use garbage free refcounting. // [[ val x = { val y = stmt1; stmt2 }; stmt3 ]] = [[ val y = stmt1; val x = stmt2; stmt3 ]] @@ -393,7 +393,6 @@ object Normalizer { normal => } // congruences - case Expr.PureApp(f, targs, vargs) => Expr.PureApp(f, targs, vargs.map(normalize)) case Expr.Make(data, tag, targs, vargs) => Expr.Make(data, tag, targs, vargs.map(normalize)) case Expr.ValueVar(id, annotatedType) => p case Expr.Literal(value, annotatedType) => p diff --git a/effekt/shared/src/main/scala/effekt/core/optimizer/Reachable.scala b/effekt/shared/src/main/scala/effekt/core/optimizer/Reachable.scala index ba775c14d..71a2d76d1 100644 --- a/effekt/shared/src/main/scala/effekt/core/optimizer/Reachable.scala +++ b/effekt/shared/src/main/scala/effekt/core/optimizer/Reachable.scala @@ -118,7 +118,7 @@ class Reachable( // We would need to process the binding if it was impure, // to keep it for its side effects; however, the binding is guaranteed to be pure process(body)(using defs + (id -> binding)) - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => process(callee) targs.foreach(process) vargs.foreach(process) @@ -167,7 +167,6 @@ class Reachable( def process(e: Expr)(using defs: Definitions): Unit = e match { case Expr.ValueVar(id, annotatedType) => process(id); process(annotatedType) case Expr.Literal(value, annotatedType) => process(annotatedType) - case Expr.PureApp(b, targs, vargs) => process(b); targs.foreach(process); vargs.foreach(process) case Expr.Make(data, tag, targs, vargs) => process(data); process(tag); targs.foreach(process); vargs.foreach(process) case Expr.Box(b, annotatedCapture) => process(b) diff --git a/effekt/shared/src/main/scala/effekt/core/optimizer/RemoveTailResumptions.scala b/effekt/shared/src/main/scala/effekt/core/optimizer/RemoveTailResumptions.scala index ac03de91b..9ceabd6b0 100644 --- a/effekt/shared/src/main/scala/effekt/core/optimizer/RemoveTailResumptions.scala +++ b/effekt/shared/src/main/scala/effekt/core/optimizer/RemoveTailResumptions.scala @@ -24,7 +24,7 @@ object RemoveTailResumptions { stmt match { case Stmt.Def(id, block, body) => !freeInBlock(block) && tailResumptive(k, body) case Stmt.Let(id, binding, body) => !freeInExpr(binding) && tailResumptive(k, body) - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => tailResumptive(k, body) && !freeInBlock(callee) && !vargs.exists(freeInExpr) && !bargs.exists(freeInBlock) + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => tailResumptive(k, body) && !freeInBlock(callee) && !vargs.exists(freeInExpr) && !bargs.exists(freeInBlock) case Stmt.Return(expr) => false case Stmt.Val(id, binding, body) => tailResumptive(k, body) && !freeInStmt(binding) case Stmt.App(callee, targs, vargs, bargs) => false @@ -51,7 +51,7 @@ object RemoveTailResumptions { def removeTailResumption(k: Id, tpe: ValueType, stmt: Stmt): Stmt = stmt match { case Stmt.Def(id, block, body) => Stmt.Def(id, block, removeTailResumption(k, tpe, body)) case Stmt.Let(id, binding, body) => Stmt.Let(id, binding, removeTailResumption(k, tpe, body)) - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => Stmt.ImpureApp(id, callee, targs, vargs, bargs, removeTailResumption(k, tpe, body)) + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, removeTailResumption(k, tpe, body)) case Stmt.Val(id, binding, body) => Stmt.Val(id, binding, removeTailResumption(k, tpe, body)) case Stmt.If(cond, thn, els) => Stmt.If(cond, removeTailResumption(k, tpe, thn), removeTailResumption(k, tpe, els)) case Stmt.Match(scrutinee, _, clauses, default) => Stmt.Match(scrutinee, tpe, clauses.map { diff --git a/effekt/shared/src/main/scala/effekt/core/vm/VM.scala b/effekt/shared/src/main/scala/effekt/core/vm/VM.scala index 792ccb9d2..d384b9005 100644 --- a/effekt/shared/src/main/scala/effekt/core/vm/VM.scala +++ b/effekt/shared/src/main/scala/effekt/core/vm/VM.scala @@ -243,7 +243,7 @@ class Interpreter(instrumentation: Instrumentation, runtime: Runtime) { case Stmt.Let(id, binding, body) => State.Step(body, env.bind(id, eval(binding, env)), stack, heap) - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => val result = env.lookupBuiltin(callee.id) match { case Builtin(name, impl) => val arguments = vargs.map(a => eval(a, env)) @@ -496,15 +496,6 @@ class Interpreter(instrumentation: Instrumentation, runtime: Runtime) { def eval(e: Expr, env: Env): Value = e match { case Expr.ValueVar(id, annotatedType) => env.lookupValue(id) case Expr.Literal(value, annotatedType) => Value.Literal(value) - case Expr.PureApp(x, targs, vargs) => env.lookupBuiltin(x.id) match { - case Builtin(name, impl) => - val arguments = vargs.map(a => eval(a, env)) - instrumentation.builtin(name) - try { impl(runtime)(arguments) } catch { case e => sys error s"Cannot call ${x} with arguments ${arguments.map { - case Value.Literal(l) => s"${l}: ${l.getClass.getName}\n${e.getMessage}" - case other => other.toString - }.mkString(", ")}" } - } case Expr.Make(data, tag, targs, vargs) => val result: Value.Data = Value.Data(data, tag, vargs.map(a => eval(a, env))) instrumentation.allocate(result) diff --git a/effekt/shared/src/main/scala/effekt/cps/Contify.scala b/effekt/shared/src/main/scala/effekt/cps/Contify.scala index 2c6997417..7a7ef3f1c 100644 --- a/effekt/shared/src/main/scala/effekt/cps/Contify.scala +++ b/effekt/shared/src/main/scala/effekt/cps/Contify.scala @@ -37,7 +37,6 @@ object Contify { def rewrite(pure: Expr): Expr = pure match { case Expr.ValueVar(id) => Expr.ValueVar(id) case Expr.Literal(value, tpe) => Expr.Literal(value, tpe) - case Expr.PureApp(id, vargs) => Expr.PureApp(id, vargs.map(rewrite)) case Expr.Make(data, tag, vargs) => Expr.Make(data, tag, vargs.map(rewrite)) case Expr.Box(b) => Expr.Box(rewrite(b)) } @@ -124,8 +123,8 @@ object Contify { case Stmt.LetCont(id, binding, body) => Stmt.LetCont(id, rewrite(binding), rewrite(body)) - case Stmt.ImpureApp(id, callee, vargs, bargs, body) => - Stmt.ImpureApp(id, callee, vargs.map(rewrite), bargs.map(rewrite), rewrite(body)) + case Stmt.ExternApp(id, purity, callee, vargs, bargs, body) => + Stmt.ExternApp(id, purity, callee, vargs.map(rewrite), bargs.map(rewrite), rewrite(body)) case Stmt.Region(id, ks, body) => Stmt.Region(id, ks, rewrite(body)) @@ -208,7 +207,7 @@ object Contify { returnsTo(id, binding) ++ returnsTo(id, body) case Stmt.LetCont(_, binding, body) => returnsTo(id, binding) ++ returnsTo(id, body) - case Stmt.ImpureApp(_, callee, vargs, bargs, body) => + case Stmt.ExternApp(_, purity, callee, vargs, bargs, body) => all(vargs, returnsTo(id, _)) ++ all(bargs, returnsTo(id, _)) ++ returnsTo(id, body) case Stmt.Region(_, _, body) => returnsTo(id, body) case Stmt.Alloc(_, init, _, body) => @@ -240,7 +239,6 @@ object Contify { def returnsTo(id: Id, e: Expr): Set[Cont] = e match { case Expr.ValueVar(_) => Set.empty case Expr.Literal(_, _) => Set.empty - case Expr.PureApp(_, vargs) => all(vargs, returnsTo(id, _)) case Expr.Make(_, _, vargs) => all(vargs, returnsTo(id, _)) case Expr.Box(b) => returnsTo(id, b) } @@ -281,8 +279,8 @@ object Contify { case Stmt.LetCont(id2, binding, body) => Stmt.LetCont(id2, contify(id, binding), contify(id, body)) - case Stmt.ImpureApp(id2, callee, vargs, bargs, body) => - Stmt.ImpureApp(id2, callee, vargs.map(contify(id, _)), bargs.map(contify(id, _)), contify(id, body)) + case Stmt.ExternApp(id2, purity, callee, vargs, bargs, body) => + Stmt.ExternApp(id2, purity, callee, vargs.map(contify(id, _)), bargs.map(contify(id, _)), contify(id, body)) case Stmt.Region(id2, ks, body) => Stmt.Region(id2, ks, contify(id, body)) @@ -324,7 +322,6 @@ object Contify { def contify(id: Id, p: Expr): Expr = p match { case Expr.ValueVar(_) => p case Expr.Literal(_, _) => p - case Expr.PureApp(id2, vargs) => Expr.PureApp(id2, vargs.map(contify(id, _))) case Expr.Make(data, tag, vargs) => Expr.Make(data, tag, vargs.map(contify(id, _))) case Expr.Box(b) => Expr.Box(contify(id, b)) } diff --git a/effekt/shared/src/main/scala/effekt/cps/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/cps/PrettyPrinter.scala index 2b6c3fbc9..9306689f0 100644 --- a/effekt/shared/src/main/scala/effekt/cps/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/cps/PrettyPrinter.scala @@ -60,7 +60,6 @@ object PrettyPrinter extends ParenPrettyPrinter { case Expr.Literal((), core.Type.TUnit) => "()" case Expr.Literal(s: String, core.Type.TString) => "\"" + s + "\"" case Expr.Literal(value, _) => value.toString // TODO this should match on the actual types... - case Expr.PureApp(id, vargs) => toDoc(id) <> argsToDoc(vargs, Nil) case Expr.Make(data, tag, vargs) => "make" <+> toDoc(data.name) <+> toDoc(tag) <> argsToDoc(vargs, Nil) case Expr.Box(b) => parens("box" <+> toDoc(b)) } @@ -116,10 +115,14 @@ object PrettyPrinter extends ParenPrettyPrinter { "let" <+> toDoc(id) <+> "=" <+> toDoc(binding) <> line <> toDoc(body) - case Stmt.ImpureApp(id, callee, vargs, bargs, body) => + case Stmt.ExternApp(id, core.Purity.Impure, callee, vargs, bargs, body) => "let!" <+> toDoc(id) <+> "=" <+> toDoc(callee) <> argsToDoc(vargs, bargs) <> line <> toDoc(body) + case Stmt.ExternApp(id, core.Purity.Pure, _, vargs, _, _) => toDoc(id) <> argsToDoc(vargs, Nil) + + case Stmt.ExternApp(id, core.Purity.Async, _, vargs, _, _) => ??? // FIXME EXTERNAPP + case Stmt.Region(id, ks, body) => "region" <+> toDoc(id) <+> "@" <+> toDoc(ks) <+> block(toDoc(body)) diff --git a/effekt/shared/src/main/scala/effekt/cps/Transformer.scala b/effekt/shared/src/main/scala/effekt/cps/Transformer.scala index ffd843574..7fc26655d 100644 --- a/effekt/shared/src/main/scala/effekt/cps/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/cps/Transformer.scala @@ -73,10 +73,15 @@ object Transformer { case core.Stmt.Let(id, core.Expr.ValueVar(x, _), body) => binding(id, C.lookupValue(x)) { transform(body, ks, k) } - case core.Stmt.ImpureApp(id, b, targs, vargs, bargs, body) => + case core.Stmt.ExternApp(id, core.Async, callee, _, vargs, bargs, body) => + App(transform(callee), vargs.map(transform), bargs.map(transform), MetaCont(ks), Continuation.Static(id) { (value, ks) => + binding(id, value) { transform(body, ks, k) } + }.reifyAt(stmt.tpe)) + + case core.Stmt.ExternApp(id, purity, b, targs, vargs, bargs, body) => transform(b) match { case Block.BlockVar(f) => - ImpureApp(id, f, vargs.map(transform), bargs.map(transform), + ExternApp(id, purity, f, vargs.map(transform), bargs.map(transform), transform(body, ks, k)) case _ => sys error "Should not happen" } @@ -190,10 +195,6 @@ object Transformer { def transform(pure: core.Expr)(using C: TransformationContext): Expr = pure match { case core.Expr.ValueVar(id, annotatedType) => C.lookupValue(id) case core.Expr.Literal(value, annotatedType) => Literal(value, annotatedType) - case core.Expr.PureApp(b, targs, vargs) => transform(b) match { - case Block.BlockVar(id) => PureApp(id, vargs.map(transform)) - case _ => sys error "Should not happen" - } case core.Expr.Make(data, tag, targs, vargs) => Make(data, tag, vargs.map(transform)) case core.Expr.Box(b, annotatedCapture) => Box(transform(b)) } diff --git a/effekt/shared/src/main/scala/effekt/cps/Tree.scala b/effekt/shared/src/main/scala/effekt/cps/Tree.scala index 6c2ff74a6..d368e3d6e 100644 --- a/effekt/shared/src/main/scala/effekt/cps/Tree.scala +++ b/effekt/shared/src/main/scala/effekt/cps/Tree.scala @@ -69,11 +69,6 @@ enum Expr extends Tree { case Literal(value: Any, annotatedType: core.ValueType) - /** - * Pure FFI calls. Invariant, block b is pure. - */ - case PureApp(id: Id, vargs: List[Expr]) - case Make(data: ValueType.Data, tag: Id, vargs: List[Expr]) case Box(b: Block) @@ -101,7 +96,7 @@ enum Stmt extends Tree { case LetDef(id: Id, binding: Block, body: Stmt) case LetExpr(id: Id, binding: Expr, body: Stmt) case LetCont(id: Id, binding: Cont.ContLam, body: Stmt) - case ImpureApp(id: Id, callee: Id, vargs: List[Expr], bargs: List[Block], body: Stmt) + case ExternApp(id: Id, purity: core.Purity, callee: Id, vargs: List[Expr], bargs: List[Block], body: Stmt) // Regions case Region(id: Id, ks: MetaCont, body: Stmt) @@ -168,7 +163,6 @@ object Variables { def free(e: Expr): Variables = e match { case Expr.ValueVar(id) => value(id) case Expr.Literal(value, tpe) => empty - case Expr.PureApp(id, vargs) => block(id) ++ all(vargs, free) case Expr.Make(data, tag, vargs) => all(vargs, free) case Expr.Box(b) => free(b) } @@ -196,7 +190,7 @@ object Variables { case Stmt.LetDef(id, binding, body) => (free(binding) ++ free(body)) -- block(id) case Stmt.LetExpr(id, binding, body) => free(binding) ++ (free(body) -- value(id)) case Stmt.LetCont(id, binding, body) => free(binding) ++ (free(body) -- cont(id)) - case Stmt.ImpureApp(id, callee, vargs, bargs, body) => block(callee) ++ all(vargs, free) ++ all(bargs, free) ++ (free(body) -- value(id)) + case Stmt.ExternApp(id, purity, callee, vargs, bargs, body) => block(callee) ++ all(vargs, free) ++ all(bargs, free) ++ (free(body) -- value(id)) case Stmt.Region(id, ks, body) => free(ks) ++ (free(body) -- block(id)) case Stmt.Alloc(id, init, region, body) => free(init) ++ block(region) ++ (free(body) -- block(id)) @@ -251,7 +245,6 @@ object substitutions { case ValueVar(id) => ValueVar(id) case Literal(value, tpe) => Literal(value, tpe) case Make(tpe, tag, vargs) => Make(tpe, tag, vargs.map(substitute)) - case PureApp(id, vargs) => PureApp(id, vargs.map(substitute)) case Box(b) => Box(substitute(b)) } @@ -317,8 +310,8 @@ object substitutions { LetCont(id, substitute(binding), substitute(body)(using subst.shadowConts(List(id)))) - case ImpureApp(id, callee, vargs, bargs, body) => - ImpureApp(id, callee, vargs.map(substitute), bargs.map(substitute), + case ExternApp(id, purity, callee, vargs, bargs, body) => + ExternApp(id, purity, callee, vargs.map(substitute), bargs.map(substitute), substitute(body)(using subst.shadowValues(List(id)))) case Region(id, ks, body) => diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/Transformer.scala b/effekt/shared/src/main/scala/effekt/generator/chez/Transformer.scala index 2683a3e18..0392d5871 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/Transformer.scala @@ -110,7 +110,7 @@ trait Transformer { case Region(body) => chez.Builtin("with-region", toChez(body)) - case stmt: (Def | Let | ImpureApp | Get | Put) => + case stmt: (Def | Let | ExternApp | Get | Put) => chez.Let(Nil, toChez(stmt)) } @@ -175,7 +175,7 @@ trait Transformer { val chez.Block(defs, exprs, result) = toChez(body) chez.Block(chez.Constant(nameDef(id), toChez(binding)) :: defs, exprs, result) - case Stmt.ImpureApp(id, callee, targs, vargs, bargs, body) => + case Stmt.ExternApp(id, purity, callee, targs, vargs, bargs, body) => val chez.Block(defs, exprs, result) = toChez(body) chez.Block(chez.Constant(nameDef(id), chez.Call(toChez(callee), vargs.map(toChez) ++ bargs.map(toChez))) :: defs, exprs, result) @@ -229,7 +229,6 @@ trait Transformer { case l: Literal => chez.RawValue(l.value.toString) case ValueVar(id, _) => chez.Variable(nameRef(id)) - case PureApp(b, targs, args) => chez.Call(toChez(b), args map toChez) case Make(data, tag, targs, args) => chez.Call(chez.Variable(nameRef(tag)), args map toChez) case Box(b, _) => toChez(b) diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/TransformerCPS.scala b/effekt/shared/src/main/scala/effekt/generator/chez/TransformerCPS.scala index 302799763..230901669 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/TransformerCPS.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/TransformerCPS.scala @@ -110,7 +110,7 @@ object TransformerCPS { } def toChez(stmt: cps.Stmt): chez.Block = stmt match { - case ImpureApp(id, callee, vargs, bargs, body) => + case ExternApp(id, purity, callee, vargs, bargs, body) => val binding = chez.Call(toChez(callee), vargs.map(toChez) ++ bargs.map(toChez)) resolveLet(id, binding, body) case LetCont(id, binding, body) => resolveLet(id, toChez(binding), body) @@ -176,7 +176,7 @@ object TransformerCPS { val args = vargs.map(toChez) ++ bargs.map(toChez) ++ List(toChez(ks), toChez(k)) chez.Call(methodLam, args) - case let: (LetCont | LetDef | LetExpr | ImpureApp + case let: (LetCont | LetDef | LetExpr | ExternApp | Region | Alloc | Get | Put | Dealloc | Var) => chez.Let(Nil, toChez(stmt)) } @@ -191,7 +191,6 @@ object TransformerCPS { if (b) chez.RawValue("#t") else chez.RawValue("#f") case Literal(b: Byte, core.Type.TByte) => chez.RawValue(UByte.unsafeFromByte(b).toInt.toString) case Literal(value, _) => chez.RawValue(value.toString()) // TODO this should match on the actual types... - case PureApp(id, vargs) => chez.Call(toChez(id), vargs.map(toChez)) case Make(_, tag, vargs) => chez.Call(nameRef(tag), vargs.map(toChez)) case Box(id) => toChez(id) diff --git a/effekt/shared/src/main/scala/effekt/generator/js/TransformerCps.scala b/effekt/shared/src/main/scala/effekt/generator/js/TransformerCps.scala index 2bf013c0b..1c9c789f8 100644 --- a/effekt/shared/src/main/scala/effekt/generator/js/TransformerCps.scala +++ b/effekt/shared/src/main/scala/effekt/generator/js/TransformerCps.scala @@ -220,7 +220,6 @@ object TransformerCps extends Transformer { case Expr.Literal(s: String, core.Type.TString) => JsString(escape(s)) case Expr.Literal(b: Byte, core.Type.TByte) => js.RawExpr(UByte.unsafeFromByte(b).toHexString) case literal: Expr.Literal => js.RawExpr(literal.value.toString) // TODO This should match on the type... - case Expr.PureApp(id, vargs) => inlineExtern(id, vargs) case Expr.Make(data, tag, vargs) => js.New(nameRef(tag), vargs map toJS) case Expr.Box(b) => argumentToJS(b) } @@ -279,12 +278,12 @@ object TransformerCps extends Transformer { js.Const(nameDef(id), toJS(binding)(using nonrecursive(ks2))) :: requiringThunk { toJS(body) }.run(k) } - case cps.Stmt.ImpureApp(id, callee, vargs, Nil, body) => + case cps.Stmt.ExternApp(id, purity, callee, vargs, Nil, body) => Binding { k => js.Const(nameDef(id), inlineExtern(callee, vargs)) :: toJS(body).run(k) } - case cps.Stmt.ImpureApp(id, callee, vargs, bargs, body) => + case cps.Stmt.ExternApp(id, purity, callee, vargs, bargs, body) => Binding { k => js.Const(nameDef(id), js.Call(nameRef(callee), vargs.map(toJS) ++ bargs.map(argumentToJS))) :: toJS(body).run(k) } @@ -579,7 +578,7 @@ object TransformerCps extends Transformer { } && default.forall(body => canBeDirect(k, body)) case Stmt.LetDef(id, binding, body) => notIn(binding) && canBeDirect(k, body) case Stmt.LetExpr(id, binding, body) => notIn(binding) && canBeDirect(k, body) - case Stmt.ImpureApp(id, callee, vargs, bargs, body) => vargs.forall(notIn) && bargs.forall(notIn) && canBeDirect(k, body) + case Stmt.ExternApp(id, purity, callee, vargs, bargs, body) => vargs.forall(notIn) && bargs.forall(notIn) && canBeDirect(k, body) case Stmt.LetCont(id, Cont.ContLam(result, ks2, body), body2) => def willBeDirectItself = canBeDirect(id, body2) && canBeDirect(k, maintainDirectStyle(ks2, body)) def notFreeinContinuation = notIn(body) && canBeDirect(k, body2) diff --git a/effekt/shared/src/main/scala/effekt/machine/Transformer.scala b/effekt/shared/src/main/scala/effekt/machine/Transformer.scala index d49d7cce9..29b3d8d7e 100644 --- a/effekt/shared/src/main/scala/effekt/machine/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/machine/Transformer.scala @@ -65,7 +65,9 @@ object Transformer { } val transformedRet = transformUnboxed(ret) val isExternAsync = capture.contains(symbols.builtins.AsyncCapability.capture) - noteDefinition(name, transformedParams, Nil, isExternAsync) + + // TODO keep track of async externs differently + noteDefinition(name, transformedParams, Nil) Extern(transform(name), transformedParams, transformedRet, isExternAsync, transform(body)) case core.Extern.Include(ff, contents) => @@ -86,6 +88,7 @@ object Transformer { case core.ExternBody.Unsupported(err) => ExternBody.Unsupported(err) } ExternInterface(transform(id), tparams.map(transform), tBody) + } def transform(body: core.ExternBody[core.Expr])(using ErrorReporter): machine.ExternBody[Variable] = body match { @@ -118,7 +121,7 @@ object Transformer { case (pid, (tpe, capt)) => BPC.info.get(pid) match { // For each known free block we have to add its free variables to this one (flat closure) - case Some(BlockInfo.Definition(freeParams, blockParams, _)) => + case Some(BlockInfo.Definition(freeParams, blockParams)) => freeParams.toSet // Unknown free blocks stay free variables case Some(BlockInfo.Parameter(tpe)) => @@ -145,7 +148,7 @@ object Transformer { case core.Def(id, core.BlockVar(other, tpe, capt), rest) => getBlockInfo(other) match { - case BlockInfo.Definition(free, params, _) => + case BlockInfo.Definition(free, params) => noteDefinition(id, free, params) val label = transformLabel(id) emitDefinition(label, Jump(transformLabel(other), label.environment)) @@ -171,7 +174,28 @@ object Transformer { transform(rest) } - case core.ImpureApp(id, core.BlockVar(blockName, core.BlockType.Function(_, _, vparamTypes, _, resultType), capt), targs, vargs, bargs, rest) => + case core.ExternApp(id, core.Async, core.BlockVar(blockName, annotatedTpe, capt), targs, vargs, bargs, rest) => + // TODO keep track of async externs differently + val variable = Variable(transform(id), Positive()) + BPC.info.getOrElse(blockName, sys.error(pp"In ${stmt}. Cannot find block info for ${id}: ${annotatedTpe}.\n${BPC.info}")) match { + case BlockInfo.Definition(freeParams, blockParams) => + transform(vargs, bargs).run { (values, blocks) => + annotatedTpe match { + case core.BlockType.Function(_, _, vparamTypes, _, _) => + perhapsUnbox(values, vparamTypes).run { unboxeds => + transform(rest).map { rest => + PushFrame(Clause(List(variable), rest), + Jump(Label(transform(blockName), blockParams ++ freeParams), unboxeds ++ blocks ++ freeParams)) + } + } + case _ => + ErrorReporter.panic("Extern definition does not have function type") + } + } + case _ => ErrorReporter.panic("Applying an object") + } + + case core.ExternApp(id, purity, core.BlockVar(blockName, core.BlockType.Function(_, _, vparamTypes, _, resultType), capt), targs, vargs, bargs, rest) => val variable = Variable(transform(id), Positive()) transform(rest).flatMap { rest => transform(vargs, bargs).run { (values, blocks) => @@ -209,21 +233,9 @@ object Transformer { Trampoline.Done(Invoke(Variable(transform(id), transform(tpe)), builtins.Apply, values ++ blocks)) // Known Jump - case BlockInfo.Definition(freeParams, blockParams, false) => + case BlockInfo.Definition(freeParams, blockParams) => Trampoline.Done(Jump(Label(transform(id), blockParams ++ freeParams), values ++ blocks ++ freeParams)) - // Extern Async - case BlockInfo.Definition(freeParams, blockParams, true) => - // TODO better way to deal with extern async functions - annotatedTpe match { - case core.BlockType.Function(_, _, vparamTypes, _, _) => - perhapsUnbox(values, vparamTypes).run { unboxeds => - Trampoline.Done(Jump(Label(transform(id), blockParams ++ freeParams), unboxeds ++ blocks ++ freeParams)) - } - case _ => - ErrorReporter.panic("Extern definition does not have function type") - } - case _ => ErrorReporter.panic("Applying an object") } @@ -402,7 +414,7 @@ object Transformer { PushFrame(Clause(List(variable), k(variable)), Jump(label, label.environment)) } case core.BlockVar(id, tpe, capt) => getBlockInfo(id) match { - case BlockInfo.Definition(_, parameters, _) => + case BlockInfo.Definition(_, parameters) => // Passing a top-level function directly, so we need to eta-expand turning it into a closure // TODO cache the closure somehow to prevent it from being created on every call val label = transformLabel(id) @@ -498,21 +510,6 @@ object Transformer { LiteralUTF8String(variable, javastring.getBytes("utf-8"), k(variable)) } - case core.PureApp(core.BlockVar(blockName, core.BlockType.Function(_, _, vparamTypes, _, resultType), _), _, vargs) => - transform(vargs).flatMap { values => - perhapsUnbox(values, vparamTypes).flatMap { unboxeds => - shift { k => - transformUnboxed(resultType) match { - case Type.Positive() => - ForeignCall(variable, transform(blockName), unboxeds, k(variable)) - case unboxedTpe => - val unboxed = Variable(freshName("unboxed"), unboxedTpe) - ForeignCall(unboxed, transform(blockName), unboxeds, Coerce(variable, unboxed, k(variable))) - } - } - } - } - case core.Make(data, constructor, targs, vargs) => val tag = DeclarationContext.getConstructorTag(constructor) @@ -581,7 +578,7 @@ object Transformer { } def transformLabel(id: Id)(using BPC: BlocksParamsContext): Label = getBlockInfo(id) match { - case BlockInfo.Definition(freeParams, boundParams, _) => Label(transform(id), boundParams ++ freeParams) + case BlockInfo.Definition(freeParams, boundParams) => Label(transform(id), boundParams ++ freeParams) case BlockInfo.Parameter(_) => sys error s"Expected a function definition, but got a block parameter: ${id}" } @@ -634,18 +631,15 @@ object Transformer { } enum BlockInfo { - case Definition(free: Environment, params: Environment, async: Boolean) + case Definition(free: Environment, params: Environment) case Parameter(tpe: core.BlockType) } def DeclarationContext(using DC: DeclarationContext): DeclarationContext = DC - def noteDefinition(id: Id, params: Environment, free: Environment, async: Boolean)(using BC: BlocksParamsContext): Unit = - assert(!BC.info.isDefinedAt(id), s"Registering info twice for ${id} (was: ${BC.info(id)}, now: Definition)") - BC.info += (id -> BlockInfo.Definition(free, params, async)) - def noteDefinition(id: Id, params: Environment, free: Environment)(using BC: BlocksParamsContext): Unit = - noteDefinition(id, params, free, false) + assert(!BC.info.isDefinedAt(id), s"Registering info twice for ${id} (was: ${BC.info(id)}, now: Definition)") + BC.info += (id -> BlockInfo.Definition(free, params)) def noteParameter(id: Id, tpe: core.BlockType)(using BC: BlocksParamsContext): Unit = assert(!BC.info.isDefinedAt(id), s"Registering info twice for ${id} (was: ${BC.info(id)}, now: Parameter)")