├── .gitignore
├── LICENSE.md
├── README.md
├── build.sbt
├── core
├── build.sbt
├── project
│ ├── build.properties
│ └── effekt.sbt
└── src
│ └── main
│ ├── java
│ └── effekt
│ │ ├── CPS.java
│ │ ├── Continuation.java
│ │ ├── Effectful.java
│ │ ├── Effects.java
│ │ ├── Effekt.java
│ │ ├── Handler.java
│ │ ├── Prog.java
│ │ ├── Prompt.java
│ │ ├── StatefulHandler.java
│ │ ├── instrumentation
│ │ ├── annotations
│ │ │ ├── AlreadyInstrumented.java
│ │ │ ├── DontInstrument.java
│ │ │ └── Effectful.java
│ │ └── exceptions
│ │ │ └── NotInstrumented.java
│ │ ├── runtime
│ │ ├── Frame.java
│ │ └── Runtime.java
│ │ └── stateful
│ │ ├── CPS.java
│ │ ├── Continuation.java
│ │ ├── State.java
│ │ └── Stateful.java
│ └── scala
│ └── effekt
│ └── runtime
│ └── SplitSeq.scala
├── instrumentation
└── src
│ └── main
│ ├── resources
│ └── application.conf
│ └── scala
│ └── effekt
│ ├── EffektClassLoader.scala
│ └── instrumentation
│ ├── Config.scala
│ ├── Effekt.scala
│ ├── EntryPoint.scala
│ ├── Frame.scala
│ ├── FreshNames.scala
│ ├── InstrumentClass.scala
│ ├── InstrumentEntryPoint.scala
│ ├── InstrumentMethod.scala
│ ├── InstrumentProject.scala
│ ├── InstrumentationAnalysis.scala
│ ├── JavaAgent.scala
│ ├── MethodAnalysis.scala
│ ├── MethodContext.scala
│ └── package.scala
├── project
├── assembly.sbt
└── build.properties
└── sbtplugin
└── src
└── main
└── scala
├── EffektPlugin.scala
└── Keys.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | target/
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2018 Jonathan Brachthäuser
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Java-Effekt
2 | An Implementation of effect handlers using bytecode manipulation.
3 |
4 | ## Examples
5 | - The world famous [Drunkflip](tests/src/test/java/run/amb/DrunkFlip.java) example.
6 |
7 | An example of some instrumented bytecode can be found [in this gist](https://gist.github.com/b-studios/28b9ed229369b962e0083989343d5ede). The source-file is [Simple.java](tests/src/test/java/run/Simple.java).
8 |
9 | ## Project Organization
10 | The project currently consists of the following subprojects:
11 |
12 | - [core](core) The runtime system of jvm-effekt -- contains necessary runtime classes (like Stack).
13 | - [instrumentation](instrumentation) The implementation of byte code instrumentation, depends on core.
14 | - [sbtplugin](sbtplugin) An sbtplugin for offline instrumentation of classfiles.
15 | - [tests](tests) A [separate](tests/build.sbt) project illustrating the use of Java Effekt.
16 |
17 | ## Dependencies
18 | The project currently depends on version `2.0.1` of the static analysis and bytecode generation framework [OPAL](https://bitbucket.org/delors/opal/overview).
19 |
20 | To run the tests, execute the following steps:
21 | ```
22 | # If you haven't already, clone the repo
23 | git clone git@github.com:b-studios/java-effekt.git
24 | cd java-effekt
25 |
26 | # clean all previous builds, if any to avoid sbt conflicts (only necessary once in a while ;) )
27 | rm -r **/target
28 | # Start sbt and publish the effekt instrumentation locally
29 | sbt publishLocal
30 |
31 | # now switch to the effekt core library and publish this locally
32 | cd core
33 | sbt publishLocal
34 |
35 | # switch to tests subproject and run tests
36 | cd ..
37 | git clone git@github.com:b-studios/java-effekt-tests.git
38 | cd java-effekt-tests
39 | sbt
40 | > test -- -oDF
41 | ```
42 |
43 | Running the tests will instrument the class files and dump them for inspection under `target/instrumented`.
44 | To inspect bytecode, I highly recommend the
45 | [OPAL - Bytecode Disassembler Plugin](https://atom.io/packages/java-bytecode-disassembler) for Atom.
46 |
47 | ## Background
48 | [Scala-Effekt](https://github.com/b-studios/scala-effekt) is a library based implementation of
49 | effect handlers and uses a monad to implement multiprompt delimited continuations (which are
50 | necessary for effect handlers).
51 | Similarly, some experiments in Java show that this is in fact a viable approach. However,
52 | the monadic style might impose usability as well as performance issues.
53 |
54 | This project implements stack manipulation (a la [DelimCC](http://okmij.org/ftp/continuations/implementations.html)) on the JVM by
55 | only using the JVM stack for pure (non effectful) operations. For effectful operations, instead a
56 | user-level [`Stack`](core/src/main/java/effekt/Effekt.java)
57 | is used. Before each effectful operation, the continuation is pushed as a first-class frame to this stack.
58 |
59 | These first-class frames are instances of [`Frame`](core/src/main/java/effekt/runtime/Frame.java)
60 | and exactly represent the continuation after the effectful call. The method state is stored in the closure of the frame and restored before the original body is executed.
61 |
62 | This is very similar to the bytecode instrumentation performed by [Quasar](http://docs.paralleluniverse.co/quasar/). In particular,
63 | we also use Java checked exceptions to mark "effectful" functions.
64 | However, Quasar does not have a notion of first-class frames and still uses the JVM-stack for effectful functions. Thus resuming a
65 | continuation takes O(n) in the depth of the JVM stack.
66 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | lazy val root = project
2 | .in(file("."))
3 | .settings(noPublishSettings)
4 | .aggregate(instrumentation, sbtplugin)
5 |
6 | lazy val effekt = "effekt"
7 | lazy val effektVersion = "0.1.2-SNAPSHOT"
8 |
9 | lazy val opalVersion = "2.0.1"
10 |
11 | lazy val effektSettings = Seq(
12 | scalaVersion := "2.12.4",
13 | organization := "de.b-studios",
14 | version := effektVersion
15 | )
16 |
17 | // the instrumentation component and java agent
18 | lazy val instrumentation = project
19 | .in(file("instrumentation"))
20 | .settings(effektSettings)
21 | .settings(instrumentationSettings)
22 | .settings(javaAgentSettings)
23 |
24 | // the ahead of time sbt plugin
25 | lazy val sbtplugin = project
26 | .in(file("sbtplugin"))
27 | .settings(effektSettings)
28 | .settings(sbtpluginSettings)
29 | .dependsOn(instrumentation)
30 |
31 |
32 | lazy val instrumentationSettings = Seq(
33 |
34 | moduleName := s"${effekt}-instrumentation",
35 | name := s"${effekt}-instrumentation",
36 |
37 | libraryDependencies ++= Seq(
38 | "de.opal-project" % "common_2.12" % opalVersion,
39 | "de.opal-project" % "bytecode-representation_2.12" % opalVersion,
40 | "de.opal-project" % "bytecode-creator_2.12" % opalVersion,
41 | "de.opal-project" % "bytecode-assembler_2.12" % opalVersion,
42 | "de.opal-project" % "abstract-interpretation-framework_2.12" % opalVersion
43 | )
44 | )
45 |
46 | lazy val sbtpluginSettings = Seq(
47 | sbtPlugin := true,
48 | moduleName := s"${effekt}-sbtplugin",
49 | name := s"${effekt}-sbtplugin"
50 | )
51 |
52 | lazy val noPublishSettings = Seq(
53 | publish := {},
54 | publishLocal := {},
55 | publishArtifact := false
56 | )
57 |
58 | lazy val javaAgentSettings = Seq(
59 | test in assembly := {},
60 | packageOptions in assembly += javaAgentManifest,
61 | packageOptions in ThisBuild += javaAgentManifest,
62 | packageOptions += javaAgentManifest
63 | )
64 |
65 | lazy val javaAgentManifest = Package.ManifestAttributes(
66 | "Premain-Class" -> "effekt.instrumentation.JavaAgent",
67 | "Agent-Class" -> "effekt.instrumentation.JavaAgent",
68 | "Can-Retransform-Classes" -> "true",
69 | "Can-Redefine-Classes" -> "true"
70 | )
71 |
--------------------------------------------------------------------------------
/core/build.sbt:
--------------------------------------------------------------------------------
1 | import effekt.plugin.EffektPlugin
2 |
3 | lazy val effektName = "effekt"
4 | lazy val effektVersion = "0.1.2-SNAPSHOT"
5 |
6 | // the Effekt library (incl. Runtime)
7 | // we apply the effekt instrumentation also for the core library
8 | // since Handler and StatefulHandler both need to be instrumented.
9 | lazy val root = project
10 | .in(file("."))
11 | .enablePlugins(EffektPlugin)
12 | .settings(EffektPlugin.effektDefaultSettings)
13 | .settings(EffektPlugin.instrumentAfterCompile)
14 | .settings(
15 | moduleName := s"${effektName}-core",
16 | name := s"${effektName}-core",
17 | organization := "de.b-studios",
18 | version := effektVersion,
19 | scalaVersion := "2.12.4"
20 | )
21 |
--------------------------------------------------------------------------------
/core/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.0.3
2 |
--------------------------------------------------------------------------------
/core/project/effekt.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("de.b-studios" % "effekt-sbtplugin" % "0.1.2-SNAPSHOT")
2 |
--------------------------------------------------------------------------------
/core/src/main/java/effekt/CPS.java:
--------------------------------------------------------------------------------
1 | package effekt;
2 |
3 | public interface CPS {
4 | B apply(Continuation k) throws Effects;
5 | }
--------------------------------------------------------------------------------
/core/src/main/java/effekt/Continuation.java:
--------------------------------------------------------------------------------
1 | package effekt;
2 |
3 | public interface Continuation extends Prog {
4 |
5 | B resume(A value) throws Effects;
6 |
7 | default B apply() throws Effects {
8 | return resume(null);
9 | }
10 | }
--------------------------------------------------------------------------------
/core/src/main/java/effekt/Effectful.java:
--------------------------------------------------------------------------------
1 | package effekt;
2 |
3 | /**
4 | * TODO add a family of interfaces:
5 | * Effectful () -> void
6 | * EffectfulIn A -> void
7 | * EffectfulOut () -> B
8 | * EffectfulInOut A -> B
9 | *
10 | * Same for CPS and StatefulCPS
11 | */
12 | public interface Effectful {
13 | B apply(A a) throws Effects;
14 | }
--------------------------------------------------------------------------------
/core/src/main/java/effekt/Effects.java:
--------------------------------------------------------------------------------
1 | package effekt;
2 |
3 | public class Effects extends Exception {}
--------------------------------------------------------------------------------
/core/src/main/java/effekt/Effekt.java:
--------------------------------------------------------------------------------
1 | package effekt;
2 |
3 | import effekt.instrumentation.annotations.DontInstrument;
4 | import effekt.runtime.Runtime;
5 |
6 | @DontInstrument
7 | public final class Effekt extends Runtime {
8 |
9 | private Effekt() {}
10 |
11 | // --- Operations used by the instrumentation ---
12 | public static int resultI() throws Throwable { return (Integer) result(); }
13 | public static long resultJ() throws Throwable { return (Long) result(); }
14 | public static float resultF() throws Throwable { return (Float) result(); }
15 | public static double resultD() throws Throwable { return (Double) result(); }
16 | public static void resultVoid() throws Throwable { result(); }
17 |
18 | public static void returnWith(long result) { returnWith(Long.valueOf(result)); }
19 | public static void returnWith(int result) { returnWith(Integer.valueOf(result)); }
20 | public static void returnWith(float result) { returnWith(Float.valueOf(result)); }
21 | public static void returnWith(double result) { returnWith(Double.valueOf(result)); }
22 | public static void returnVoid() { returnWith(null); }
23 | }
--------------------------------------------------------------------------------
/core/src/main/java/effekt/Handler.java:
--------------------------------------------------------------------------------
1 | package effekt;
2 |
3 | public interface Handler extends Prompt {
4 |
5 | Res pure(R r) throws Effects;
6 |
7 | default Res handle(Prog prog) throws Effects {
8 | return Effekt.pushPrompt(this, () -> pure(prog.apply()));
9 | }
10 |
11 | default A use(CPS body) throws Effects {
12 | return Effekt.withSubcontinuation(this, k ->
13 | body.apply(a ->
14 | Effekt.pushPrompt(this, () ->
15 | k.resume(a))));
16 | }
17 |
18 | // only for compatibility:
19 | default A useOnce(CPS body) throws Effects {
20 | return use(body);
21 | }
22 |
23 | default A discard(Prog body) throws Effects {
24 | return use(k -> body.apply());
25 | }
26 |
27 | static > E handle(H h, Effectful prog) throws Effects {
28 | return Effekt.pushPrompt(h, () -> h.pure(prog.apply(h)));
29 | }
30 | }
--------------------------------------------------------------------------------
/core/src/main/java/effekt/Prog.java:
--------------------------------------------------------------------------------
1 | package effekt;
2 |
3 | public interface Prog {
4 | A apply() throws Effects;
5 | }
--------------------------------------------------------------------------------
/core/src/main/java/effekt/Prompt.java:
--------------------------------------------------------------------------------
1 | package effekt;
2 |
3 |
4 | public interface Prompt {}
--------------------------------------------------------------------------------
/core/src/main/java/effekt/StatefulHandler.java:
--------------------------------------------------------------------------------
1 | package effekt;
2 |
3 | import effekt.stateful.Stateful;
4 |
5 | public interface StatefulHandler extends Handler, Stateful {
6 |
7 | Res pure(R r) throws Effects;
8 |
9 | default Res handle(Prog prog, State init) throws Effects {
10 | importState(init);
11 | return Effekt.pushPrompt(this, () -> pure(prog.apply()));
12 | }
13 |
14 | // on stateful handlers
15 | // use(k -> ... k.resume(a) ...) should be equivalent to
16 | // useStateful( (k, s) -> ... k.resume(a, s) ... )
17 | default A useStateful(effekt.stateful.CPS body) throws Effects {
18 | final State before = exportState();
19 | return Effekt.withSubcontinuation(this, k ->
20 | body.apply((a, s) -> Effekt.pushPrompt(this, () -> {
21 | importState(s);
22 | return k.resume(a);
23 | }),
24 | before));
25 | }
26 |
27 | default A use(CPS body) throws Effects {
28 | final State before = exportState();
29 | return Effekt.withSubcontinuation(this, k ->
30 | body.apply(a ->
31 | Effekt.pushPrompt(this, () -> {
32 | importState(before);
33 | return k.resume(a);
34 | })));
35 | }
36 |
37 | // only for compatibility:
38 | default A useOnce(effekt.stateful.CPS body) throws Effects {
39 | return useStateful(body);
40 | }
41 | }
--------------------------------------------------------------------------------
/core/src/main/java/effekt/instrumentation/annotations/AlreadyInstrumented.java:
--------------------------------------------------------------------------------
1 | package effekt.instrumentation.annotations;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Documented
6 | @Retention(RetentionPolicy.RUNTIME)
7 | @Target({ElementType.METHOD, ElementType.TYPE})
8 | public @interface AlreadyInstrumented {}
9 |
--------------------------------------------------------------------------------
/core/src/main/java/effekt/instrumentation/annotations/DontInstrument.java:
--------------------------------------------------------------------------------
1 | package effekt.instrumentation.annotations;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Documented
6 | @Retention(RetentionPolicy.RUNTIME)
7 | @Target({ElementType.METHOD, ElementType.TYPE})
8 | public @interface DontInstrument {}
--------------------------------------------------------------------------------
/core/src/main/java/effekt/instrumentation/annotations/Effectful.java:
--------------------------------------------------------------------------------
1 | package effekt.instrumentation.annotations;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Documented
6 | @Retention(RetentionPolicy.RUNTIME)
7 | @Target({ElementType.METHOD, ElementType.TYPE})
8 | public @interface Effectful {}
--------------------------------------------------------------------------------
/core/src/main/java/effekt/instrumentation/exceptions/NotInstrumented.java:
--------------------------------------------------------------------------------
1 | package effekt.instrumentation.exceptions;
2 |
3 | public class NotInstrumented extends RuntimeException {
4 | public static final NotInstrumented NOT_INSTRUMENTED = new NotInstrumented();
5 | }
--------------------------------------------------------------------------------
/core/src/main/java/effekt/runtime/Frame.java:
--------------------------------------------------------------------------------
1 | package effekt.runtime;
2 |
3 | import effekt.Effects;
4 | import effekt.instrumentation.annotations.DontInstrument;
5 |
6 | @DontInstrument
7 | public interface Frame {
8 | void enter() throws Effects, Throwable;
9 | }
--------------------------------------------------------------------------------
/core/src/main/java/effekt/runtime/Runtime.java:
--------------------------------------------------------------------------------
1 | package effekt.runtime;
2 |
3 | import effekt.*;
4 | import effekt.instrumentation.annotations.DontInstrument;
5 | import scala.Tuple2;
6 |
7 | // CPS Runtime
8 |
9 | @DontInstrument
10 | public class Runtime {
11 |
12 | private static SplitSeq stack = EmptyCont$.MODULE$;
13 | private static Object result = null;
14 |
15 | private static Throwable lastException = null;
16 |
17 | private static Frame lastFrame = null;
18 |
19 | public static void push(Frame frame) {
20 | pushLastFrame();
21 | lastFrame = frame;
22 | }
23 | public static void pop() { if (lastFrame != null) lastFrame = null; else stack = stack.tail(); }
24 |
25 | public static Object result() throws Throwable {
26 | if (lastException == null)
27 | return result;
28 | else
29 | throw lastException;
30 | }
31 |
32 | public static void returnWith(Object r) {
33 | result = r; lastException = null;
34 | }
35 |
36 | // clean up stack in case an effectful operation throws an exception
37 | public static void onThrow(Throwable t) throws Throwable {
38 | if (lastFrame != null) lastFrame = null;
39 | else stack = stack.tail();
40 | throw t;
41 | }
42 |
43 | public static A run(Prog prog) {
44 | stack = EmptyCont$.MODULE$;
45 | lastFrame = prog::apply;
46 | trampoline();
47 |
48 | if (lastException != null) {
49 | throw new RuntimeException("Exception during effectful execution", lastException);
50 | }
51 |
52 | return (A) result;
53 | }
54 |
55 |
56 | // Delimited Control Operators
57 |
58 | public static A pushPrompt(Prompt prompt, Prog prog) throws Effects {
59 | pushLastFrame();
60 | stack = stack.pushPrompt(prompt);
61 | return prog.apply();
62 | }
63 |
64 | private static void pushLastFrame() {
65 | if (lastFrame != null) {
66 | stack = stack.push(lastFrame);
67 | lastFrame = null;
68 | }
69 | }
70 |
71 | public static A withSubcontinuation(Prompt prompt, effekt.CPS body) throws Effects {
72 | pushLastFrame();
73 | final Tuple2 res = stack.splitAt(prompt);
74 | final Segment init = res._1;
75 | final SplitSeq rest = res._2;
76 |
77 | lastFrame = null;
78 | stack = rest;
79 | // The following only works with `Config.pushInitialEntrypoint`, since otherwise the callsite will
80 | // not typecheck since it still expects a value of type A.
81 | //
82 | // return (A) body.apply(new Subcont<>(init));
83 | body.apply(new Subcont<>(init));
84 | return null;
85 | }
86 |
87 | private static void trampoline() {
88 | while (lastFrame != null || stack.nonEmpty()) {
89 | try {
90 | if (lastFrame != null) {
91 | Frame f = lastFrame;
92 | lastFrame = null;
93 | f.enter();
94 | } else {
95 | final Tuple2 res = stack.pop();
96 | stack = res._2;
97 | res._1.enter();
98 | }
99 | }
100 | catch (Effects e) {}
101 | catch (Throwable t) {
102 | lastException = t;
103 | }
104 | }
105 | }
106 |
107 | @DontInstrument
108 | final static class Subcont implements Continuation {
109 | final Segment init;
110 | Subcont(Segment init) { this.init = init; }
111 |
112 | public R resume(A value) throws Effects {
113 | pushLastFrame();
114 | stack = init.prependTo(stack);
115 | Effekt.returnWith(value);
116 | return null;
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/core/src/main/java/effekt/stateful/CPS.java:
--------------------------------------------------------------------------------
1 | package effekt.stateful;
2 |
3 | import effekt.Effects;
4 |
5 | public interface CPS {
6 | B apply(Continuation k, S state) throws Effects;
7 | }
--------------------------------------------------------------------------------
/core/src/main/java/effekt/stateful/Continuation.java:
--------------------------------------------------------------------------------
1 | package effekt.stateful;
2 |
3 | import effekt.Effects;
4 |
5 | public interface Continuation {
6 | B resume(A value, S state) throws Effects;
7 | }
--------------------------------------------------------------------------------
/core/src/main/java/effekt/stateful/State.java:
--------------------------------------------------------------------------------
1 | package effekt.stateful;
2 |
3 | import scala.collection.immutable.Map;
4 | import scala.collection.immutable.Map$;
5 |
6 | public class State implements Stateful