Fork me on GitHub
#graalvm
<
2023-11-15
>
phronmophobic23:11:04

I'm getting a "the class was requested to be initialized at run time (from feature clj_easy.graal_build_time.InitClojureClasses.duringSetup with 'com.phronemophobic')" when calling a static method on a class. Are static methods not supported in clojure code compiled with native image? More info in 🧡 .

phronmophobic23:11:20

If I change (Structure/newInstance ...) to nil, it compiles just fine (obviously, that breaks the program).

phronmophobic23:11:49

The full error looks like:

Error: Classes that should be initialized at run time got initialized during image building:
 com.sun.jna.Structure was unintentionally initialized at build time. com.phronemophobic.llama.raw_gguf$ctx__GT_candidates$fn__14271 caused initialization of this class with the following trace: 
	at com.sun.jna.Structure.<clinit>(Structure.java:114)
	at java.lang.Class.forName0(Unknown Source)
	at java.lang.Class.forName(Class.java:495)
	at java.lang.Class.forName(Class.java:474)
	at clojure.lang.RT.classForName(RT.java:2209)
	at clojure.lang.RT.classForName(RT.java:2218)
	at com.phronemophobic.llama.raw_gguf$ctx__GT_candidates$fn__14271.<clinit>(raw_gguf.clj:460)

phronmophobic23:11:19

The enclosing function ctx->candidates isn't called until runtime.

borkdude09:11:11

I'd try to make a pure Java repro that calls Class/forName in a function body to see if you get the same behavior. If so, ask around in the graalvm slack - native-image channel + post an issue. If not, perhaps you can learn why this class gets initialized by inspecting the bytecode? Not sure...

littleli15:11:09

com.sun.jna.Structure 
looks like native interop using JNA. So my guess it's sniffing around how to map native structs to some java classes. This will be tough I'm afraid, but I don't want to be a messenger with a bad news.

phronmophobic19:11:04

It seems like the Class/forName call is from the clojure compiler when it's compiling the static method. It seems like there should be a way to compile the invocation of the static method without loading the class.

borkdude19:11:33

but all classes are processed through Class/forName... Perhaps you can work around this by placing this method in your own Java class and then calling that class

phronmophobic19:11:21

The idea being that calling a method on my own Java class will load that particular class, but not its dependencies?

phronmophobic19:11:32

ie. create my own java class MyStructWrapper with a static method that just calls Structure/newInstance. Calling MyStructWrapper/newInstance from clojure will load MyStructWrapper, but not Structure?

borkdude19:11:51

yes, that's the idea

phronmophobic19:11:08

That's worth a shot. Thanks for the idea!

borkdude19:11:15

but wait, isn't Clojure just doing Class/forName here because of reflection?

borkdude19:11:35

The clojure compiler isn't into play anymore once you have the AOT-ed jar

phronmophobic19:11:21

Oh, interesting. I checked with *warn-on-reflection* and it didn't get flagged. There's only a single overload of Structure/newInstance with 2 args.

borkdude19:11:40

hmm, don't know

phronmophobic19:11:04

maybe if I just wait on the new method thunk stuff to be released, my problem will go away πŸ™ clojure-spin

borkdude19:11:33

I guess you can test it now

phronmophobic19:11:47

Is there a release with Method thunk support already?

phronmophobic19:11:35

oh, weird. There's a second 2 arity overload for that method showing at the repl that doesn't appear in the javadocs.

borkdude19:11:39

I guess you could apply the patch, but yeah, that's maybe too experimental

borkdude19:11:07

and warn-on-reflection doesn't warn?

phronmophobic19:11:42

It doesn't seem to.

borkdude19:11:42

if there is only 1 method with that arg count no reflection will happen

phronmophobic19:11:20

Anyway, adding type hints fixed the problem and native-image now compiles.

phronmophobic19:11:24

I guess that makes sense. It's possible to convert a method to be public at runtime, so there's now way for the compiler to rule it out.

borkdude19:11:27

are you compiling the clojure using the same JVM as native-image?

πŸ‘ 1
phronmophobic19:11:06

I think the only issue is that *warn-on-reflection* doesn't flag it.

borkdude19:11:15

yeah, might be good to report it

πŸ‘ 1
phronmophobic19:11:29

I'll check ask.clojure to see if there's an existing question and add one if not.

borkdude19:11:33

perhaps the native image agent would have caught it (along with many other false positives)

borkdude19:11:57

(also see https://github.com/borkdude/refl, I don't use it much)

πŸ‘ 1
phronmophobic20:11:02

ok, I created a minimal repro and it seems the problem had nothing to do with the static method and it was calling Class/forName on the arg llama_token_dataByReference which subclasses Structure. :man-facepalming:

hiredman04:11:50

The compiler has to use reflection to find available methods, even if the generated code ends up being a non-reflective call

borkdude09:11:07

What I mean by is is the Compiler(.java) uses the reflector to detect methods at compile time to see what it should compile into. https://github.com/clojure/clojure/blob/08a2d9bdd013143a87e50fa82e9740e2ab4ee2c2/src/jvm/clojure/lang/Compiler.java#L977 This is visible in agents which detect reflection and cause false positives. I guess you can bypass this by first compiling to bytecode and then running that with an agent.