Fork me on GitHub
#clojure
<
2024-01-21
>
jeaye20:01:38

The Clojure JVM compiler will wrap a try form in a fnonce and then call it immediately, if the try is not in return position. Anyone know why? https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L2279-L2280

👀 1
p-himik21:01:49

Probably because of this (the date of the commit is the same as the date of the messages): https://groups.google.com/g/clojure/c/Y6fHOCjmcxk/m/YuIGPMpHMRAJ

jeaye21:01:25

Hm, nice digging. I'd also figure that's related.

jeaye21:01:56

It still doesn't quite explain what the problem would be. I'm suspecting it's JVM-specific, but I'll need to look into what a VerificationError is.

p-himik21:01:56

But no clue what VerificationError means, can't find it.

p-himik21:01:51

Maybe it's actually java.lang.VerifyError.

jeaye21:01:23

> You would get this error if the bytecode size of your method exceeds the 64kb limit; but you would probably have noticed that.

jeaye21:01:27

Could be related.

jeaye21:01:46

Either way, definitely something JVM-specific. I'll try without it in jank and see what we run into. I appreciate the help!

👍 1
p-himik21:01:57

Oh, that's fun! I got pre-1.0.0 Clojure up and running to reproduce it:

Clojure
user=> (def k)
#'user/k
user=> (. k dosomething (do (try :something (catch java.lang.Exception
e :something-else)) nil))
java.lang.VerifyError: (class: user/eval__3741, method: invoke signature: ()Ljava/lang/Object;) Inconsistent stack height 0 != 5
java.lang.VerifyError: (class: user/eval__3741, method: invoke signature: ()Ljava/lang/Object;) Inconsistent stack height 0 != 5
        at java.lang.Class.getDeclaredConstructors0(Native Method)
        at java.lang.Class.privateGetDeclaredConstructors(Class.java:2398)
        at java.lang.Class.getConstructor0(Class.java:2708)
        at java.lang.Class.newInstance0(Class.java:328)
        at java.lang.Class.newInstance(Class.java:310)
        at clojure.lang.Compiler$FnExpr.eval(Compiler.java:2984)
        at clojure.lang.Compiler.eval(Compiler.java:3843)
        at clojure.lang.Repl.main(Repl.java:75)

jeaye21:01:46

Huh. What does that error even mean?

hiredman21:01:10

The bytecode that try/catch compiled to doesn't really have expression semantics, the easiest way to make it an expression is to lift it into a method, and the easiest way to lift it into a method is to wrap it in a fn

jeaye21:01:36

I can get that. Early jank codegen (to C++) wrapped everything in lambdas for expression semantics as well. I suppose this is a limitation at the JVM bytecode level.

hiredman21:01:36

The compiler does it for a number of different forms try/catch loop and if

hiredman21:01:18

Yeah, the bytecode verifier imposes stricter requirements

hiredman21:01:48

(although funnily if I recall case doesn't get wrapped)

jeaye21:01:38

Gotcha. Thanks for the additional context. I'm happy to see the few cases where jank gets to be simpler than Clojure, since the vast majority of the cases the JVM is helping, not getting in the way. 😄

hiredman21:01:09

https://clojure.atlassian.net/browse/CLJ-701 an annoying thing is the wrapping loses type hints

jeaye21:01:44

Ahh, yes.

ghadi14:01:08

I talked to Rich about this once, and I think he mentioned that it was challenging to nest all the exception handlers in the bytecode

ghadi14:01:30

exception tables need bytecode ranges

p-himik14:01:22

If I wanted to educate myself on things like that (JVM implementation details, not necessarily how they're affecting Clojure), what would be good resources for that? I assume the spec, but maybe something else?