Fork me on GitHub
#clojure-dev
<
2017-10-10
>
ragge20:10:41

I have a question regarding class loading and static initializers, related to https://dev.clojure.org/jira/browse/CLJ-1743

ragge20:10:46

Consider then following snippet:

ragge20:10:05

(ns javafx
  (:import (javafx.scene.control Cell))

(defn f [] Cell)

ragge20:10:49

Cell here is a JavaFX class that has a static initializer that requires the JavaFX platform to be initialized before it is executed

ragge20:10:13

so requiring or compiling this namespace fails with a CompilerException java.lang.ExceptionInInitializerError

ragge20:10:39

It seems like the bytecode for the static initialization of the f function does the following:

ragge20:10:41

0: ldc           #21                 // String javafx.scene.control.Cell
         2: invokestatic  #27                 // Method clojure/lang/RT.classForName:(Ljava/lang/String;)Ljava/lang/Class;
         5: putstatic     #15                 // Field const__0:Ljava/lang/Object;
         8: return

ragge20:10:21

ie. loads a constant string, and calls RT.classForName

ragge20:10:01

causing the load of the class and the call to the static initializer

ragge20:10:01

Java would instead use a Class entry in the constant pool, and just use that in the implementation of f:

ragge20:10:14

0: ldc           #2                  // class javafx/scene/control/Cell
         2: areturn

ragge20:10:36

where #2 is a Class entry in the constant pool

ragge20:10:32

My question is, is there a reason clojure could not do the same here, avoiding the RT.classForName call?

ragge20:10:12

I see the patch proposed for CLJ-1743 uses RT.classForNameNonLoading

hiredman20:10:55

yes, embedding the class in the constant pool is too static

ragge20:10:44

because of dynamic class loading?

ragge20:10:14

ok. in the snippet i showed, would it be reasonable to emit a RT.classForNameNonLoading for Cell?

hiredman20:10:39

I don't know

hiredman20:10:43

I would be more inclined to attempt to use reflection to work around jfx loading issues than anything else

ragge20:10:55

It's ok to workaround now, with JDK9 you can simply do a (Platform/startup #(compile 'javafx)) in this case

hiredman20:10:13

ugh, don't do that

hiredman20:10:51

compile is for aot compiling, aot compiling at runtime is silly

ragge20:10:03

not sure I follow, we AOT compile our app, so need to be able to compile the parts that use JavaFX classes too

ragge20:10:09

wouldn't make that call "at runtime", but as part of building our app

hiredman20:10:42

I would at the very least change it to require

hiredman20:10:53

but I guess it depends how as part of your build you are running (Platform/startup #(compile 'javafx)), e.g. if you just have it in the middle of a source file that is being compiled (in which case the compile will also attempt to run at runtime) or if you have it part of some build script that isn't being compiled

ragge20:10:24

so, a gross simplification of what we have is something like this:

ragge20:10:54

(defn aot
  []
  (binding [*compile-path* "build/classes"]
    (compile 'some-main-ns)))

(aot)

ragge20:10:14

that's in a .clj file that we invoke using clojure.main to AOT our main ns

ragge20:10:46

that doesn't work if 'some-main-ns transitively depends on something that imports a JavaFX class

ragge20:10:11

doing (Platform/startup #(aot)) works

ragge20:10:16

and is admittedly a hack