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

Add first draft of instrumented interpreter #713

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
304 changes: 304 additions & 0 deletions effekt/jvm/src/test/scala/effekt/core/InterpreterTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
package effekt
package core

import effekt.core.Interpreter.{ InterpreterError, State }
import effekt.source.FeatureFlag
import effekt.symbols.QualifiedName
import effekt.symbols.given
import kiama.util.FileSource

class InterpreterTests extends munit.FunSuite {

import effekt.context.{ Context, IOModuleDB }
import effekt.util.AnsiColoredMessaging
import kiama.util.{ Positions, StringSource }

// object driver extends effekt.Driver
//
// def run(content: String): String =
// var options = Seq(
// "--Koutput", "string",
// "--backend", "js",
// )
// val configs = driver.createConfig(options)
// configs.verify()
//
// val compiler = new TestFrontend
// compiler.compile(StringSource(content, "input.effekt"))(using context).map {
// case (_, decl) => decl
// }
// configs.stringEmitter.result()
val positions = new Positions
object ansiMessaging extends AnsiColoredMessaging
object context extends Context(positions) with IOModuleDB {
val messaging = ansiMessaging
object testFrontend extends TestFrontend
override lazy val compiler = testFrontend.asInstanceOf
}

def run(content: String) =
val config = new EffektConfig(Seq("--Koutput", "string"))
config.verify()
context.setup(config)
context.testFrontend.compile(StringSource(content, "input.effekt"))(using context).map {
case (_, decl) => decl
}

def runFile(path: String) =
val config = new EffektConfig(Seq("--Koutput", "string"))
config.verify()
context.setup(config)
context.testFrontend.compile(FileSource(path))(using context).map {
case (_, decl) => decl
}


val recursion =
"""def countdown(n: Int): Int =
| if (n == 0) 42
| else countdown(n - 1)
|
|def fib(n: Int): Int =
| if (n == 0) 1
| else if (n == 1) 1
| else fib(n - 2) + fib(n - 1)
|
|def main() = {
| println(fib(10))
|}
|""".stripMargin

val dynamicDispatch = """def size[T](l: List[T]): Int =
| l match {
| case Nil() => 0
| case Cons(hd, tl) => 1 + size(tl)
| }
|
|def map[A, B](l: List[A]) { f: A => B }: List[B] =
| l match {
| case Nil() => Nil()
| case Cons(hd, tl) => Cons(f(hd), map(tl){f})
| }
|
|def main() = {
| println(size([1, 2, 3].map { x => x + 1 }))
|}
|""".stripMargin

val eraseUnused =
"""def replicate(v: Int, n: Int, a: List[Int]): List[Int] =
| if (n == 0) {
| a
| } else {
| replicate(v, n - 1, Cons(v, a))
| }
|
|def useless(i: Int, n: Int, _: List[Int]): Int =
| if (i < n) {
| useless(i + 1, n, replicate(0, i, Nil()))
| } else {
| i
| }
|
|def run(n: Int) =
| useless(0, n, Nil())
|
|def main() = {
| println(run(10))
|}
|""".stripMargin

val simpleObject =
"""interface Counter {
| def inc(): Unit
| def get(): Int
|}
|
|def main() = {
| def c = new Counter {
| def inc() = println("tick")
| def get() = 0
| };
| c.inc()
| c.inc()
| c.inc()
|}
|
|""".stripMargin

val factorialAccumulator =
"""import examples/benchmarks/runner
|
|def factorial(a: Int, i: Int): Int =
| if (i == 0) {
| a
| } else {
| factorial((i * a).mod(1000000007), i - 1)
| }
|
|def run(n: Int): Int =
| factorial(1, n)
|
|def main() = benchmark(5){run}
|
|""".stripMargin

val sort =
"""import list
|
|def main() = {
| // synchronized with doctest in `sortBy`
| println([1, 3, -1, 5].sortBy { (a, b) => a <= b })
|}
|""".stripMargin

val mutableState =
"""def main() = {
| var x = 42;
| x = x + 1;
| println(x.show)
| [1, 2, 3].map { x => x + 1 }.foreach { x => println(x) };
|
| region r {
| var x in r = 42;
| x = x + 1
| println(x)
| }
|}
|""".stripMargin

val simpleException =
"""effect raise(): Unit
|
|def main() = {
| try {
| println("before");
| do raise()
| println("after")
| } with raise { println("caught") }
|}
|
|""".stripMargin

val triples =
"""import examples/benchmarks/runner
|
|record Triple(a: Int, b: Int, c: Int)
|
|interface Flip {
| def flip(): Bool
|}
|
|interface Fail {
| def fail(): Nothing
|}
|
|def choice(n: Int): Int / {Flip, Fail} = {
| if (n < 1) {
| do fail() match {}
| } else if (do flip()) {
| n
| } else {
| choice(n - 1)
| }
|}
|
|def triple(n: Int, s: Int): Triple / {Flip, Fail} = {
| val i = choice(n)
| val j = choice(i - 1)
| val k = choice(j - 1)
| if (i + j + k == s) {
| Triple(i, j, k)
| } else {
| do fail() match {}
| }
|}
|
|def hash(triple: Triple): Int = triple match {
| case Triple(a, b, c) => mod(((53 * a) + 2809 * b + 148877 * c), 1000000007)
|}
|
|def run(n: Int) =
| try {
| hash(triple(n, n))
| } with Flip {
| def flip() = mod(resume(true) + resume(false), 1000000007)
| } with Fail {
| def fail() = 0
| }
|
|def main() = benchmark(10){run}
|
|
|""".stripMargin

// doesn't work: product_early (since it SO due to run run run)

val Some(main, mod, decl) = runFile("examples/benchmarks/effect_handlers_bench/triples.effekt"): @unchecked
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You can already experiment with it by changing the filename here and then running testOnly effekt.core.InterpreterTests

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For example, here is the result of running triples:

Static dispatches: 232
Dynamic dispatches: 231
Branches: 701
Pattern matches: 5
Frames (pushed: 405, popped: 530)
Allocations: 5
Closures: 112
Field lookups: 0
Variable reads: 0
Variable writes: 0
Installed delimiters: 1
Captured continuations: 347
Resumed continuations: 350


val gced = Deadcode.remove(main, decl)

val inlined = Inline.full(Set(main), gced, 40)

try {
object data extends Counting {
override def step(state: Interpreter.State) = state match {
case State.Done(result) => ???
case State.Step(stmt, env, stack) =>
//println(Interpreter.show(stack))
}
}
Interpreter(data).run(main, inlined)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

By changing this to

Suggested change
Interpreter(data).run(main, inlined)
Interpreter(data).run(main, gced)

you can compare the numbers to those without inlining.


data.report()

} catch {
case err: InterpreterError =>
err match {
case InterpreterError.NotFound(id) => println(s"Not found: ${util.show(id)}")
case InterpreterError.NotAnExternFunction(id) => err.printStackTrace()
case InterpreterError.MissingBuiltin(name) => println(s"Missing ${name}")
case InterpreterError.RuntimeTypeError(msg) => err.printStackTrace()
case InterpreterError.NonExhaustive(missingCase) => err.printStackTrace()
case InterpreterError.Hole() => err.printStackTrace()
case InterpreterError.NoMain() => err.printStackTrace()
}
}
}

class TestFrontend extends Compiler[(Id, symbols.Module, core.ModuleDecl)] {


import effekt.PhaseResult.CoreTransformed
import effekt.context.Context
import kiama.output.PrettyPrinterTypes.Document
import kiama.util.Source


// Implementation of the Compiler Interface:
// -----------------------------------------
def extension = ".class"

override def supportedFeatureFlags: List[String] = List("jvm")

override def prettyIR(source: Source, stage: Stage)(using C: Context): Option[Document] = None

override def treeIR(source: Source, stage: Stage)(using Context): Option[Any] = None

override def compile(source: Source)(using C: Context): Option[(Map[String, String], (Id, symbols.Module, core.ModuleDecl))] =
Optimized.run(source).map { res => (Map.empty, res) }


// The Compilation Pipeline
// ------------------------
// Source => Core => CPS => JS
lazy val Core = Phase.cached("core") {
Frontend andThen Middleend
}

lazy val Optimized = allToCore(Core) andThen Aggregate map {
case input @ CoreTransformed(source, tree, mod, core) =>
val mainSymbol = Context.checkMain(mod)
(mainSymbol, mod, core)
}
}
17 changes: 9 additions & 8 deletions effekt/shared/src/main/scala/effekt/core/Deadcode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ class Deadcode(entrypoints: Set[Id], definitions: Map[Id, Definition]) extends c
}, rewrite(stmt))
}

override def rewrite(m: ModuleDecl): ModuleDecl = m.copy(
// Remove top-level unused definitions
definitions = m.definitions.collect { case d if reachable.isDefinedAt(d.id) => rewrite(d) },
externs = m.externs.collect {
case e: Extern.Def if reachable.isDefinedAt(e.id) => e
case e: Extern.Include => e
}
)
override def rewrite(m: ModuleDecl): ModuleDecl =
m.copy(
// Remove top-level unused definitions
definitions = m.definitions.collect { case d if reachable.isDefinedAt(d.id) => rewrite(d) },
externs = m.externs.collect {
case e: Extern.Def if reachable.isDefinedAt(e.id) => e
case e: Extern.Include => e
}
)
}

object Deadcode {
Expand Down
4 changes: 2 additions & 2 deletions effekt/shared/src/main/scala/effekt/core/Inline.scala
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ object Inline {
}

def rewrite(p: Pure)(using InlineContext): Pure = p match {
case Pure.PureApp(b, targs, vargs) => pureApp(rewrite(b), targs, vargs.map(rewrite))
case Pure.PureApp(b, targs, vargs) => pureApp(b, targs, vargs.map(rewrite))
case Pure.Make(data, tag, vargs) => make(data, tag, vargs.map(rewrite))
// currently, we don't inline values, but we can dealias them
case x @ Pure.ValueVar(id, annotatedType) => dealias(x)
Expand All @@ -200,7 +200,7 @@ object Inline {
}

def rewrite(e: Expr)(using InlineContext): Expr = e match {
case DirectApp(b, targs, vargs, bargs) => directApp(rewrite(b), targs, vargs.map(rewrite), bargs.map(rewrite))
case DirectApp(b, targs, vargs, bargs) => directApp(b, targs, vargs.map(rewrite), bargs.map(rewrite))

// congruences
case Run(s) => run(rewrite(s))
Expand Down
Loading