Hello
The lwjgl demo build and run just fine using the GraalSDK found on the website, but it fail to generate the native executable with native-image
Here is the log and command used:
$GRAAL_HOME/bin/native-image -H:Kind=EXECUTABLE -H:Name=test -H:Path=$PWD/target -J-XstartOnFirstThread -jar lwjgl3-demos.jar
https://hastebin.com/yibulevuqi.bash
Here is the repo of the demo: https://github.com/LWJGL/lwjgl3-demos
The demo tested was: org.lwjgl.demo.bgfx.Cubes
OS: macOS 10.3.3
GraalSDK: (0.31)
I have issues with lwjgl too. What is your log ? (I can't access https://hastebin.com/yibulevuqi.bash)
I can't get my lwjgl (2.9.2) application to compile too. Here's my command line:
./native-image -Djava.library.path=path-to-folder-containing-lwjgl.{dll,so} -jar ~/my-libgdx-game.jar --report-unsupported-elements-at-runtime
native-image fails as follows (full log here: https://pastebin.com/9Wd9LNJc):
Error: Bytecode parsing error: java.lang.NoClassDefFoundError: Could not initialize class org.lwjgl.opengl.Display
...
Original exception that caused the problem: java.lang.NoClassDefFoundError: Could not initialize class org.lwjgl.opengl.Display
This should not be caused by missing .so files (I'm on 64bits Linux) because they are in the folder specified in -Djava.library.path.
Any help appreciated and I'm available for further debugging if needed
The NoClassDefFoundError is probably caused by a JAR that is missing on a classpath.
But the issues in the first comment require introducing custom substitutions for these libraries. For now, if in hurry, you can try and figure out how we use substitutions: feel free to ask questions here.
In the short-term future, we will provide documentation that will guide you on how to support such features.
@vjovanov> Thanks for your answer. I'm puzzled because my jar (uploaded here (an ascii game): http://dl.free.fr/sCWlTe9i1) is standalone. I.e. it starts fine with java -jar my-libgdx-game.jar (no -cp needed). And if I unzip the jar, the class concerned by NoClassDefFoundError is there.
I tried unzipping the jar an giving it to native-image's -cp argument, but to no avail.
Strange, I can investigate only next week.
If you have the SubstrateVM sources you could debug by running native-image with the --debug-attach flag.
My bad, I noticed that the error message changed after running native-image --server-shutdown-all and rerunning I saw that the root of the error was liblwjgl64.so already loaded in another classloader which is fixed by running a fresh server every time.
Now it fails with (full log here: https://pastebin.com/2k6zrxeQ):
Original error: com.oracle.svm.hosted.analysis.flow.SVMMethodTypeFlowBuilder$UnsafeOffsetError: Field AnalysisField<MemoryUtilSun$AccessorUnsafe.address accessed: false reads: true written: false> is used as an offset in an unsafe operation, but no value recomputation found.
Wrapped field: HotSpotField<org.lwjgl.MemoryUtilSun$AccessorUnsafe.address long:16>
which happens in this class of lwjgl: https://github.com/LWJGL/lwjgl/blob/master/src/java/org/lwjgl/MemoryUtilSun.java
Thanks for noticing, we need to fix this for the server as it should work regardless of what happens with class loaders. Could you please open an issue for that with steps to reproduce so I can fix it?
The problem here is that the static analysis can't figure out what address points to so it is asking to supply necessary information by using the RecomputeFieldValue annotatiton.
@cstancu why did analysis miss this case? Also, do you have advice on how to use RecomputeFieldValue here?
I was able to run your dm-18-03-31.jar demo and replicate the issue.
The automatic recomputation of field offsets is limited, i.e., we look for specific, common code patterns. In the case that you reported the code pattern that we use is: static final long fieldOffset = Unsafe.getUnsafe().objectFieldOffset(X.class.getDeclaredField("f"));, so if you access the field through another method it won't work. If you can rewrite the code to access the field directly i.e., not through a call to MemoryUtil.getAddressField(), it should work.
In lwjgl MemoryUtilSun.AccessorUnsafe the call to MemoryUtil.getAddressField() always returns the java.nio.Buffer.address field so you could replace the call to MemoryUtil.getAddressField() with java.nio.Buffer.getDeclaredField("address") like so address = unsafe.objectFieldOffset(java.nio.Buffer.getDeclaredField("address")).
The other option, somewhat more complicated but which avoids modifying the source code of imported libraries, is to use @RecomputeFieldValue. In your case you would need a substitution that looks like this:
@TargetClass(className = "org.lwjgl.MemoryUtilSun$AccessorUnsafe")
final class Target_org_lwjgl_MemoryUtilSun_AccessorUnsafe {
@Alias @RecomputeFieldValue(kind = Kind.FieldOffset, declClass = java.nio.Buffer.class, name = "address") long address;
}
You need to add this class to the native-image classpath to be discovered during image building.
Concretely:
import org.lwjgl.*;
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.annotate.RecomputeFieldValue;
import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind;
import com.oracle.svm.core.annotate.Alias;
@TargetClass(className = "org.lwjgl.MemoryUtilSun$AccessorUnsafe")
final class Target_org_lwjgl_MemoryUtilSun_AccessorUnsafe {
@Alias @RecomputeFieldValue(kind = Kind.FieldOffset, declClass = java.nio.Buffer.class, name = "address") long address;
}
/** Dummy class to have a class with the file's name. */
public class MySubstitutions {
}
subst subdirectory, compile it with:> javac -cp $GRAAL_DIR/jre/lib/svm/builder/svm.jar:../mydir/dm-18-03-31.jar MySubstitutions.java
> $GRAAL_DIR/bin/native-image -Djava.library.path=mydir/dm-18-03-31 -cp subst -jar mydir/dm-18-03-31.jar --no-server
Using this method the field offset is properly recomputed. However the build later fails with a NPE in one of your classes when it tries to initialize the class:
Caused by: java.lang.NullPointerException
at com.hgames.gdx.closed.panel.ConcreteTextMenu.<clinit>(ConcreteTextMenu.java:43)
... 26 more
fatal error: java.lang.NoClassDefFoundError: Could not initialize class com.hgames.gdx.closed.panel.ConcreteTextMenu
at sun.misc.Unsafe.ensureClassInitialized(Native Method)
at jdk.vm.ci.hotspot.HotSpotResolvedObjectTypeImpl.initialize(HotSpotResolvedObjectTypeImpl.java:381)
Thank you very much for this detailed help @cstancu! I was able to reproduce the next error you mentioned and correct it (I could remove my static initializer, which was indeed depending on some platform-dependent stuff being setuped; a bad idea).
Now I'm stuck with an error in libgdx, which is also too eager to access platform-dependent stuff statically. I believe I could however skip this static code (a single Object allocation) altogether. Is it possible to declare this to graal (It's a simple static T instance = new T(); initializer) ?, without modifying the sources (I'm working against release libgdx jars).
Thanks for your help and for this framework in general, it'd very valuable for Java gamedevs to get this working!
You could use @Alias and @RecomputeFieldValue to force set that field to null if the value is not used:
@Alias @RecomputeFieldValue(kind = Kind.FromAlias)
static XClass instance = null;
or simply reset it to null:
@Alias @RecomputeFieldValue(kind = Kind.Reset)
static XClass instance;
or provide an alternative implementation:
@Alias @RecomputeFieldValue(kind = Kind.FromAlias)
private static final XClass instance = new YourXSubclass();
Alternatively you can force lazy initialization on first access using @InjectAccessors. Look at the Target_sun_security_provider_NativePRNG.INSTANCE here for an example.
Thanks @cstancu. I've been trying your first suggestion, because I don't need the field's value, but I can't get it to work. It's as if I haven't been specifying anything. Here's what I've added to the MySubstitutions.java file:
@TargetClass(className = "com.badlogic.gdx.utils.Timer")
final class Target_com_badlogic_gdx_utils_Timer {
@Alias @RecomputeFieldValue(kind = Kind.FromAlias)
static com.badlogic.gdx.utils.Timer instance = null; // Specify that field should be left to null
// required because com.badlogic.gdx.utils.Timer.instance's initialization uses Gdx.app (See TimerThread::resume)
}
The field I want to filter is "static Timer instance = new Timer()" in this file: Timer.java on libgdx github repo
I've made sure I recompiled MySubstitutions.java and have no error when doing so; but then compiling with native-image still fails on this field, as if I had changed nothing. Am I doing something wrong ?
You say "compiling with native-image still fails on this field". What is the error message you get?
If you just want to make com.badlogic.gdx.utils.Timer.instance have the value null in the generated image, the simplest way is to use the second suggestion from @cstancu, e.g.,
@TargetClass(com.badlogic.gdx.utils.Timer.class)
final class Target_com_badlogic_gdx_utils_Timer {
@Alias @RecomputeFieldValue(kind = Kind.Reset)
static com.badlogic.gdx.utils.Timer instance;
}
(and you might not need the fully-qualified type in the declaration of instance depending on your import statements.)
@smelc I might have misunderstood your problem. If you get a NPE (or any other exception) in a static initializer the substitution won't help you. Substitutions can be only used to patch code that ends up in the image. For example in the ligbdx code example it would allow you to re-initialize the Timer instance field at runtime, regardless of its statically initialized value during image build time, as I described before. This is useful for example when a static initializer opens a file on the build machine but you need to open that file at runtime on the target machine. The static initializer is run by HotSpot during image building when the class if first loaded. Then we capture the static state and embed it in the image. However we don't intercept the code before HotSpot runs the static initializer. Does your code run with HotSpot, i.e., java -jar libgdx-game.jar, or do you get the same problem?
@cstancu and @Peter-B-Kessler > Indeed the problem is that libgdx's Timer static initializer crashes _during the call to native-image_ because platform-dependent stuff (Gdx.app) isn't yet initialized.I thought I could tell native-image to interpret the bytecode differently _when loading it during compilation_, but I get the difference now.
My code runs with HotSpot, but that's because my game (like all libgdx games) first hands off control to libgdx's initialization (which takes care of performing static initialization in the right order) and then the game loops start. So this problem applies to all libgdx games. libgdx developers are aware that depending on global static field is brittle, but it's the "less worst" method for this platform-dependent stuff. The Timer class has recently changed in libgdx (and the change will fix the static initializer crash), so I'll give this process another try when I update my version of libgdx.
Any news on this ?
I'm getting a similar problem with a libGDX game using LWJGL3. Looks like it tries to initialize openAL statically. Here's the error log, and the problematic file on Github.
Does anyone have any ideas besides editing LWJGL source?
Any news on LibGDX/LWJGL3 support? Any other libraries we could use for rendering with GraalVM?
i think for libgdx it is better to ditch completly LWJGL, and create a new backend with simpler opengl/glfw bindings
Most helpful comment
I'm getting a similar problem with a libGDX game using LWJGL3. Looks like it tries to initialize openAL statically. Here's the error log, and the problematic file on Github.
Does anyone have any ideas besides editing LWJGL source?