Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Direct-style (DS) JS Backend #316

Merged
merged 49 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
59496d2
First draft of direct-style (DS) version of the JS backend
b-studios Nov 24, 2023
6fe7bf4
Support run
b-studios Nov 24, 2023
99e0009
Use bind monad to work around JS limitations
b-studios Nov 24, 2023
6eef4de
Add try-catch around entrypoints
b-studios Nov 24, 2023
9a6f349
Implement free variables, once more...
b-studios Nov 24, 2023
c6923f7
debug free variables
b-studios Nov 24, 2023
8c16835
Refactor rename
b-studios Nov 24, 2023
92e52ea
Track locals explicitly
b-studios Nov 24, 2023
7dcd981
Emit continuations
b-studios Nov 24, 2023
73d390f
Start translating handler
b-studios Nov 24, 2023
1ea3705
Support discarding the continuation
b-studios Nov 25, 2023
9e10001
First support of resuming continuation
b-studios Nov 25, 2023
4871641
Minor improvements
b-studios Nov 25, 2023
8389346
Start working on lambda lifting (closures still need to be eta-expanded)
b-studios Nov 25, 2023
6e2d08d
Implement lambda lifting
b-studios Nov 27, 2023
c803500
Start to support pattern matching
b-studios Nov 27, 2023
8da634b
Add some tests
b-studios Nov 27, 2023
22b1715
Mark tests that rely on local mutable state, regions, or bidirectiona…
b-studios Nov 27, 2023
cd357da
Implement local backtrackable mutable state
b-studios Nov 27, 2023
9d4c748
Support regions
b-studios Nov 28, 2023
dbff4c6
Support bidirectional effects
b-studios Nov 28, 2023
86a448e
Also rename types when testing for alpha equivalence
b-studios Nov 30, 2023
bdb5c91
Move LambdaLifting to separate file
b-studios Nov 30, 2023
1d113ea
Adapt Renamer to new structure of BlockParam
b-studios Dec 1, 2023
ee17b9a
Stub compileSeparate and implement it later
b-studios Dec 1, 2023
eaf9165
Make DS backend work with the website (note that these changes need t…
b-studios Dec 2, 2023
5dbcab6
Add two simple lambda lifting tests
b-studios Dec 2, 2023
2af69d7
Fix some bugs in Renamer and simplify Optimizer
b-studios Dec 3, 2023
42124ac
fix local non-recursive definitions in renamer
b-studios Dec 3, 2023
1117134
Transform binder in its scope, even though this is wrong
b-studios Dec 3, 2023
99440a7
Add some optimizer tests
b-studios Dec 3, 2023
0e5745b
Fix one bug where inferred capture was not propagated
b-studios Dec 3, 2023
675ae60
Also fix same bug for regions
b-studios Dec 3, 2023
dcfa4ba
Perform inlining only once and then iterate
b-studios Dec 3, 2023
0ffeea3
Add macro based tracing tool
b-studios Dec 3, 2023
ce93739
Forgot file
b-studios Dec 3, 2023
5eff913
Slightly refactor optimizer
b-studios Dec 4, 2023
beb1124
Fix inline full to not drop count
b-studios Dec 4, 2023
cf05dbd
Fix annotated free variables and avoid passing result twice
b-studios Dec 4, 2023
f86c82b
Share common code between JS backends
b-studios Dec 4, 2023
cf73a03
Prepare runtime to deal with tailcall trampolining
b-studios Dec 4, 2023
cb22749
Reify continuation
b-studios Dec 4, 2023
ac865aa
Use effects to express tail calls (UNSOUND)
b-studios Dec 4, 2023
86b295a
Deprecate tail calls for now
b-studios Dec 4, 2023
a584793
Add while and continue
b-studios Dec 4, 2023
c58a5da
Support obvious tail calls manually in JS
b-studios Dec 4, 2023
eea5266
Fix Renamer to conform to contract given in docs
marzipankaiser Dec 4, 2023
e9d56de
Drop unbind
b-studios Dec 4, 2023
95d8d32
Reactive last failing test
b-studios Dec 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion effekt/js/src/test/scala/effekt/WebTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ object WebTests extends TestSuite {
def evaluate[A](imports: List[String], content: String) = {
server.writeFile("interactive.effekt", imports.map(i => s"import $i").mkString("\n") + s"\n\ndef main() = ${content}")
val mainFile = server.compileFile("interactive.effekt")
load(mainFile.replace("out/", "")).main().run().asInstanceOf[A]
load(mainFile.replace("out/", "")).main().asInstanceOf[A]
}

test("Evaluate simple expressions in REPL") {
Expand Down
4 changes: 2 additions & 2 deletions effekt/jvm/src/main/scala/effekt/Runner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ object JSRunner extends Runner[String] {
val jsFilePath = (out / path).unixPath
// create "executable" using shebang besides the .js file
val jsScriptFilePath = jsFilePath.stripSuffix(s".$extension")
val jsScript = s"require('${jsFilePath}').main().run()"
val jsScript = s"require('${jsFilePath}').main()"
val shebang = "#!/usr/bin/env node"
IO.createFile(jsScriptFilePath, s"$shebang\n$jsScript", true)
jsScriptFilePath
Expand Down Expand Up @@ -213,7 +213,7 @@ object MLRunner extends Runner[String] {
* Requires the MLton compiler to be installed on the machine.
* Assumes [[path]] has the format "SOMEPATH.sml".
*/
override def build(path: String)(using C: Context): String =
override def build(path: String)(using C: Context): String =
val out = C.config.outputPath()
val buildFile = (out / "main.mlb").canonicalPath
val executable = (out / "mlton-main").canonicalPath
Expand Down
6 changes: 6 additions & 0 deletions effekt/jvm/src/test/scala/effekt/JavaScriptTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ class JavaScriptTests extends EffektTests {
// we deprecated locally defined type and effect declarations, for now.
examplesDir / "neg" / "existential_effect_leaks.effekt",
examplesDir / "neg" / "scoped.effekt",

// Missing features in new direct style backend:
// ---------------------------------------------

// trampolining
examplesDir / "pos" / "stacksafe.effekt"
)
}

Expand Down
33 changes: 25 additions & 8 deletions effekt/jvm/src/test/scala/effekt/core/CoreTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,40 @@ import effekt.PhaseResult.CoreTransformed
*/
trait CoreTests extends munit.FunSuite {

protected val defaultNames = new Names(symbols.builtins.rootTypes ++ symbols.builtins.rootTerms ++ symbols.builtins.rootCaptures)
protected def defaultNames = symbols.builtins.rootTypes ++ symbols.builtins.rootTerms ++ symbols.builtins.rootCaptures

def shouldBeEqual(obtained: ModuleDecl, expected: ModuleDecl, clue: => Any)(using Location) =
assertEquals(obtained, expected, {
s"""${clue}
|=====================
|Got:
|----
|${effekt.core.PrettyPrinter.format(obtained).layout}
|
|Expected:
|---------
|${effekt.core.PrettyPrinter.format(expected).layout}
|
|""".stripMargin
})

def assertAlphaEquivalent(obtained: ModuleDecl,
expected: ModuleDecl,
clue: => Any = "values are not alpha-equivalent",
names: Names = defaultNames)(using Location): Unit = {
val renamer = Renamer(names)
assertEquals(renamer(obtained), renamer(expected), clue=clue)
names: Names = Names(defaultNames))(using Location): Unit = {
val renamer = Renamer(names, "$")
shouldBeEqual(renamer(obtained), renamer(expected), clue)
}
def parse(input: String,
nickname: String = "input",
names: Names = defaultNames): ModuleDecl = {
names: Names = Names(defaultNames))(using Location): ModuleDecl = {
CoreParsers.module(input, names) match {
case Success(result, next) if next.atEnd => result
case Success(result, next) => fail(s"Parsing ${nickname} had trailing garbage: " +
s"${next.source.toString.substring(next.offset)}")
case err: NoSuccess => fail(s"Parsing ${nickname} failed: ${err.message}")
case err: NoSuccess =>
val pos = err.next.position
fail(s"Parsing ${nickname} failed\n[${pos.line}:${pos.column}] ${err.message}")
}
}

Expand All @@ -38,7 +55,7 @@ trait CoreTransformationTests extends CoreTests {

def assertTransformsTo(input: String, expected: String,
clue: => Any = "transformation result is not the expected one",
names: Names = defaultNames)(using Location): Unit = {
names: Names = Names(defaultNames))(using Location): Unit = {
val pInput = parse(input, "input", names = names)
val pExpected = parse(expected, "expected result", names = names)
val obtained = transform(pInput)
Expand Down Expand Up @@ -68,4 +85,4 @@ trait CorePhaseTests[P <: Phase[CoreTransformed, CoreTransformed]](phase: P) ext
}
}
}
}
}
39 changes: 39 additions & 0 deletions effekt/jvm/src/test/scala/effekt/core/LambdaLiftingTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package effekt.core


class LambdaLiftingTests extends CorePhaseTests(LambdaLifting) {

test("toplevel functions stay unchanged"){
val from =
"""module main
|
|def id = { ['A](a: 'A) => return a: 'A }
|""".stripMargin
assertTransformsTo(from, from)
}

test("local functions are lifted out"){
val from =
"""module main
|
|def outer = { () =>
| def local = { () => return 42 }
| val res = (local: () => Int @ {})();
| return res:Int
|}
|""".stripMargin

val to =
"""module main
|
|def outer = { () =>
| val res = (local: () => Int @ {})();
| return res:Int
|}
|
|def local = { () => return 42 }
|""".stripMargin
assertTransformsTo(from, to)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,10 @@ abstract class AbstractPolymorphismBoxingTests extends CorePhaseTests(Polymorphi
}
}

override protected val defaultNames = new Names(boxtpes ++
override protected val defaultNames = boxtpes ++
symbols.builtins.rootTypes ++ Map(
// TODO maybe add used names
))

)
}
class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {

Expand Down
62 changes: 0 additions & 62 deletions effekt/jvm/src/test/scala/effekt/core/Renamer.scala

This file was deleted.

61 changes: 56 additions & 5 deletions effekt/jvm/src/test/scala/effekt/core/RenamerTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ class RenamerTests extends CoreTests {
def assertRenamedTo(input: String,
renamed: String,
clue: => Any = "Not renamed to given value",
names: Names = defaultNames)(using munit.Location) = {
names: Names = Names(defaultNames))(using munit.Location) = {
val pInput = parse(input, "input", names)
val pExpected = parse(renamed, "expected", names)
val renamer = new Renamer(names, "renamed") // use "renamed" as prefix so we can refer to it
val obtained = renamer(pInput)
assertEquals(obtained, pExpected, clue)
shouldBeEqual(obtained, pExpected, clue)
}

test("No bound local variables"){
Expand Down Expand Up @@ -100,12 +100,13 @@ class RenamerTests extends CoreTests {
|type Data { X(a:Int, b:Int) }
|def foo = { () =>
| 12 match {
| X : {(renamed2:Int, renamed1:Int) => return renamed2:Int }
| X : {(renamed1:Int, renamed2:Int) => return renamed1:Int }
| }
|}
|""".stripMargin
assertRenamedTo(input, expected)
}

test("type parameters"){
val input =
"""module main
Expand All @@ -117,10 +118,60 @@ class RenamerTests extends CoreTests {
val expected =
"""module main
|
|def foo = { ['renamed2](renamed1: renamed2) =>
| return renamed1:Identity[renamed2]
|def foo = { ['renamed1](renamed2: renamed1) =>
| return renamed2:Identity[renamed1]
|}
|""".stripMargin
assertRenamedTo(input, expected)
}

test("pseudo recursive"){
val input =
""" module main
|
| def bar = { () => return 1 }
| def main = { () =>
| def foo = { () => (bar : () => Unit @ {})() }
| def bar = { () => return 2 }
| (foo : () => Unit @ {})()
| }
|""".stripMargin

val expected =
""" module main
|
| def bar = { () => return 1 }
| def main = { () =>
| def renamed1 = { () => (bar : () => Unit @ {})() }
| def renamed2 = { () => return 2 }
| (renamed1 : () => Unit @ {})()
| }
|""".stripMargin

assertRenamedTo(input, expected)
}
// TODO this needs to be fixed
// test("shadowing let bindings"){
// val input =
// """ module main
// |
// | def main = { () =>
// | let x = 1
// | let x = 2
// | return x:Int
// | }
// |""".stripMargin
//
// val expected =
// """ module main
// |
// | def main = { () =>
// | let renamed1 = 1
// | let renamed2 = 2
// | return renamed2:Int
// | }
// |""".stripMargin
//
// assertRenamedTo(input, expected)
// }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marzipankaiser this test shows that Renamer right now is not correct.

}
20 changes: 18 additions & 2 deletions effekt/shared/src/main/scala/effekt/Compiler.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package effekt

import effekt.PhaseResult.{ CoreLifted, CoreTransformed }
import effekt.PhaseResult.{ AllTransformed, CoreLifted, CoreTransformed }
import effekt.context.Context
import effekt.core.{ DirectStyleMutableState, Transformer, Optimizer }
import effekt.core.{ DirectStyleMutableState, Transformer }
import effekt.lifted.LiftInference
import effekt.namer.Namer
import effekt.source.{ AnnotateCaptures, ExplicitCapabilities, ModuleDecl }
Expand Down Expand Up @@ -236,6 +236,22 @@ trait Compiler[Executable] {
Transformer
}

/**
* Maps the phase over all core modules (dependencies and the main module)
*/
def all(phase: Phase[CoreTransformed, CoreTransformed]): Phase[AllTransformed, AllTransformed] =
new Phase[AllTransformed, AllTransformed] {
val phaseName = s"all-${phase.phaseName}"

def run(input: AllTransformed)(using Context) = for {
main <- phase(input.main)
dependencies <- input.dependencies.foldRight[Option[List[CoreTransformed]]](Some(Nil)) {
case (dep, Some(deps)) => phase(dep).map(_ :: deps)
case (_, _) => None
}
} yield AllTransformed(input.source, main, dependencies)
}

def allToCore(phase: Phase[Source, CoreTransformed]): Phase[Source, AllTransformed] = new Phase[Source, AllTransformed] {
val phaseName = "core-dependencies"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Annotations private(

def copy: Annotations = new Annotations(annotations)

private def annotationsAt[K, V](ann: Annotation[K, V]): Map[Key[K], V] =
def annotationsAt[K, V](ann: Annotation[K, V]): Map[Key[K], V] =
annotations.getOrElse(ann, Map.empty).asInstanceOf

def update[K, V](ann: Annotation[K, V], key: K, value: V): Unit = {
Expand Down Expand Up @@ -90,6 +90,10 @@ object Annotations {
}
}

object Key {
def unapply[T](k: Key[T]): Option[T] = Some(k.key)
}

private def makeKey[K, V](ann: Annotation[K, V], k: K): Key[K] =
if (ann.bindToObjectIdentity) new HashKey(k)
else new IdKey(k)
Expand Down
Loading