graalvm

phronmophobic 2023-11-15T23:35:04.981479Z

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 ๐Ÿงต .

phronmophobic 2023-11-15T23:36:20.617239Z

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

phronmophobic 2023-11-15T23:36:49.920339Z

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)

phronmophobic 2023-11-15T23:37:19.786959Z

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

borkdude 2023-11-16T09:51:11.498229Z

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...

littleli 2023-11-16T15:36:09.792309Z

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.

phronmophobic 2023-11-16T19:21:04.395349Z

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.

borkdude 2023-11-16T19:22:33.662679Z

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

phronmophobic 2023-11-16T19:23:21.339489Z

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

phronmophobic 2023-11-16T19:24:32.099819Z

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?

borkdude 2023-11-16T19:24:51.232259Z

yes, that's the idea

phronmophobic 2023-11-16T19:25:08.168329Z

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

borkdude 2023-11-16T19:25:15.471879Z

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

borkdude 2023-11-16T19:25:35.254059Z

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

phronmophobic 2023-11-16T19:26:21.386309Z

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.

borkdude 2023-11-16T19:26:40.320359Z

hmm, don't know

phronmophobic 2023-11-16T19:30:04.293989Z

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

borkdude 2023-11-16T19:30:24.545279Z

optimist

borkdude 2023-11-16T19:30:33.078249Z

I guess you can test it now

phronmophobic 2023-11-16T19:30:47.875859Z

Is there a release with Method thunk support already?

phronmophobic 2023-11-16T19:33:35.653279Z

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

borkdude 2023-11-16T19:33:39.785169Z

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

borkdude 2023-11-16T19:33:45.299039Z

aha

borkdude 2023-11-16T19:34:07.411279Z

and warn-on-reflection doesn't warn?

phronmophobic 2023-11-16T19:36:42.478079Z

It doesn't seem to.

borkdude 2023-11-16T19:38:42.130379Z

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

phronmophobic 2023-11-16T19:40:08.688489Z

hmm, there is an overload, but it's private: https://github.com/java-native-access/jna/blob/master/src/com/sun/jna/Structure.java#L1846

phronmophobic 2023-11-16T19:40:20.376799Z

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

borkdude 2023-11-16T19:40:55.663289Z

cool

phronmophobic 2023-11-16T19:41:24.593739Z

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.

borkdude 2023-11-16T19:41:27.024299Z

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

๐Ÿ‘ 1
phronmophobic 2023-11-16T19:42:06.359979Z

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

borkdude 2023-11-16T19:42:15.723159Z

yeah, might be good to report it

๐Ÿ‘ 1
phronmophobic 2023-11-16T19:42:29.567409Z

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

borkdude 2023-11-16T19:42:33.704169Z

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

borkdude 2023-11-16T19:42:57.744859Z

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

๐Ÿ‘ 1
phronmophobic 2023-11-16T20:31:02.298759Z

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. ๐Ÿคฆโ€โ™‚๏ธ

borkdude 2023-11-16T20:32:19.320829Z

๐Ÿ‘

borkdude 2023-11-27T09:58:07.269259Z

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.

2023-11-27T04:05:50.108169Z

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