Clojure support
H! I've been trying to use coroutines for Clojure. I found that your library is promising. But Clojure's compiler makes slightly different bytecode than Java does.
(defn x []
(let [my-coroutine (reify Coroutine
(run [_ c]
(.suspend c)))
r (CoroutineRunner. my-coroutine)]
(doto r
(.execute))))
The compiler produces this bytecode
public void run(com.offbynull.coroutines.user.Continuation) throws java.lang.Exception;
Code:
0: aload_1
1: aconst_null
2: astore_1
3: checkcast #30 // class com/offbynull/coroutines/user/Continuation
6: invokevirtual #33 // Method com/offbynull/coroutines/user/Continuation.suspend:()V
9: aconst_null
10: pop
11: return
Java Decompiler decompiles it to the following Java code
public void run(Continuation c) throws Exception {
c = null; // wrong decompilation
c.suspend();
null;
}
I think that the decompiler is wrong because every Java method call in Clojure has the same bytecode. So the bytecode is correct.
But when I run the code I've got a NullPointerException on the (.suspend c) line.
Maybe this library has the same mistake?
Hi,
I generally don't have the time to maintain this project anymore, but I may be able to take a look if you can attach the class file both before and after instrumentation.
I suspect that the clojure compiler doesn't maintain structure between source code and compiled class the same way that the Java compiler does. Some other JVM languages are the same way. If that's the case, this library won't work. Your best bet would be to just extend the language itself to bake in coroutines.
On Sat, Aug 29, 2020, 8:49 AM Mikhail Kuzmin, [email protected] wrote:
H! I've been trying to use coroutines for Clojure. I found that your library is promising. But Clojure's compiler makes slightly different bytecode than Java does.
(defn x [] (let [my-coroutine (reify Coroutine (run [_ c] (.suspend c))) r (CoroutineRunner. my-coroutine)] (doto r (.execute))))
The compiler produces this bytecode
public void run(com.offbynull.coroutines.user.Continuation) throws java.lang.Exception; Code: 0: aload_1 1: aconst_null 2: astore_1 3: checkcast #30 // class com/offbynull/coroutines/user/Continuation 6: invokevirtual #33 // Method com/offbynull/coroutines/user/Continuation.suspend:()V 9: aconst_null 10: pop 11: return
Java Decompiler http://java-decompiler.github.io/ decompiles it to the following Java code
public void run(Continuation c) throws Exception { c = null; // wrong decompilation c.suspend(); null; }
I think that the decompiler is wrong because every Java method call in Clojure has the same bytecode. So the bytecode is correct.
But when I run the code I've got a NullPointerException on the (.suspend c) line. Maybe this library has the same mistake?
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/offbynull/coroutines/issues/92, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABQPQIW54Y6Q772ORJIBTD3SDEPR3ANCNFSM4QPB4EOA .
It's the class before the transformation. core$x$reify__12279.class.zip
I use the Java agent, so I don't have a transformed class file. If you tell me how I can save transformed class I would send it.
In the class you posted, I don't see the CoroutineRunner.execute() invocation anywhere? where is it?
Also, do you have a stacktrace for the NPE?
CoroutineRunner creates the Continuation object, which gets passed to the run method in the class you posted. The byte code transformations should remove the call to suspend() and inject a bunch of code that messes around with the Continuation object as well as the control flow of your run method.
Here is the full example. offbynull-coroutines.zip
./classes are not in the classpath. They are only for demonstration. Clojure stores compiled sources in memory.
;; src/example/core.clj
(ns example.core
(:import
[com.offbynull.coroutines.user Continuation Coroutine CoroutineRunner]))
(set! *warn-on-reflection* true)
;; core$_main.class
(defn -main []
(let [my-coroutine (reify Coroutine ;;core$_main$reify__<>.class
(run [_ c]
(.suspend c)))
runner (CoroutineRunner. my-coroutine)]
(.execute runner)))
(comment
(-main))
// decompiled by java decompiler
// the anonymous class was generated by the reify form
package example;
import clojure.lang.IObj;
import clojure.lang.IPersistentMap;
import com.offbynull.coroutines.user.Continuation;
import com.offbynull.coroutines.user.Coroutine;
public final class core$_main$reify__12306 implements Coroutine, IObj {
final IPersistentMap __meta;
public core$_main$reify__12306(IPersistentMap paramIPersistentMap) {
this.__meta = paramIPersistentMap;
}
public core$_main$reify__12306() {
this(null);
}
public IPersistentMap meta() {
return this.__meta;
}
public IObj withMeta(IPersistentMap paramIPersistentMap) {
return new core$_main$reify__12306(paramIPersistentMap);
}
public void run(Continuation c) throws Exception {
c = null;
c
.suspend();
null;
}
}
// decompiled main function
package example;
import clojure.lang.AFn;
import clojure.lang.AFunction;
import clojure.lang.IPersistentMap;
import clojure.lang.RT;
import com.offbynull.coroutines.user.Coroutine;
import com.offbynull.coroutines.user.CoroutineRunner;
public final class core$_main extends AFunction {
public static final AFn const__4 = (AFn)RT.map(new Object[] { RT.keyword(null, "line"), Integer.valueOf(9), RT.keyword(null, "column"), Integer.valueOf(22) });
public static Object invokeStatic() {
Object my_coroutine = (new core$_main$reify__12306(null)).withMeta((IPersistentMap)const__4); // anonymous class instance
my_coroutine = null;
Object runner = new CoroutineRunner((Coroutine)my_coroutine);
runner = null;
return
((CoroutineRunner)runner).execute() ? Boolean.TRUE : Boolean.FALSE; // <= runner invocation
}
public Object invoke() {
return invokeStatic();
}
}
Execution error (NullPointerException) at example.core$_main$reify__148/run (core.clj:11).
null
Full report at:
/var/folders/j2/wjykw1xn5sz1c09j5zf_9ffc0000gn/T/clojure-17279943102503115240.edn
mikhail@iMac-Mihail offbynull-coroutines % open .
mikhail@iMac-Mihail offbynull-coroutines % cat /var/folders/j2/wjykw1xn5sz1c09j5zf_9ffc0000gn/T/clojure-3027632169497577500.edn
{:clojure.main/message
"Execution error (NullPointerException) at example.core$_main$reify__148/run (core.clj:11).\nnull\n",
:clojure.main/triage
{:clojure.error/class java.lang.NullPointerException,
:clojure.error/line 11,
:clojure.error/symbol example.core$_main$reify__148/run,
:clojure.error/source "core.clj",
:clojure.error/phase :execution},
:clojure.main/trace
{:via
[{:type com.offbynull.coroutines.user.CoroutineException,
:message "Exception thrown during execution",
:at
[com.offbynull.coroutines.user.CoroutineRunner
execute
"CoroutineRunner.java"
69]}
{:type java.lang.NullPointerException,
:at [example.core$_main$reify__148 run "core.clj" 11]}],
:trace
[[example.core$_main$reify__148 run "core.clj" 11]
[com.offbynull.coroutines.user.CoroutineRunner
execute
"CoroutineRunner.java"
65]
[example.core$_main invokeStatic "core.clj" 13]
[example.core$_main invoke "core.clj" 8]
[clojure.lang.AFn applyToHelper "AFn.java" 152]
[clojure.lang.AFn applyTo "AFn.java" 144]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.core$apply invokeStatic "core.clj" 665]
[clojure.main$main_opt invokeStatic "main.clj" 514]
[clojure.main$main_opt invoke "main.clj" 510]
[clojure.main$main invokeStatic "main.clj" 664]
[clojure.main$main doInvoke "main.clj" 616]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.main main "main.java" 40]]}}
You can reproduce this
- download clojure CLI - https://clojure.org/guides/getting_started
- unzip archive
- cd offbynull-coroutines
- clojure -Adev -m example.core
-Adev setups the agent
;; deps.edn
{:deps {}
:aliases {:dev {:jvm-opts ["-javaagent:java-agent-1.5.3-shaded.jar"]}}}