jcuda / jcuda-main

Summarizes the main JCuda libraries
MIT License
98 stars 20 forks source link

Make JCuda compatible with the JPMS (Java Platform Module System) #40

Open cfries opened 3 years ago

cfries commented 3 years ago

I am not 100% sure here, but it appears to me as if there is a fundamental issue with loading JCuda native libraries (as resources from the Jars), if JCuda is used within a Java 9 (or better) project that complies with the JPMS.

I am maintaining such a project - actually two - one using JCuda, the other using JOCL.

Under JPMS a named module cannot see an unnamed module. You can make classic jars - like JCuda - to be an automatically named module. Under JPMS there is an additional restriction: a named module can load resources only from its module and not from other module.

That said, I see the following issue:

For JOCL, this is no issue, because the native libraries are directly bundles in the jocl-<version>.jar.

The issue effects the method writeResourceToFile in JCuda LibUtils. Here, the problem is

            LibUtils.class.getResourceAsStream(resourceName);

if you have a look at the implementation of getResourceAsStream you find that it does a test if the module is a named module.

    public InputStream getResourceAsStream(String name) {
        name = resolveName(name);

        Module thisModule = getModule();
        if (thisModule.isNamed()) {

If a JPMS module likes to use JCuda, JCuda with become an automatically named module and this will be true. If you use a debugger and remove set the name of thisModule to null the ìf`-clause will be skipped and the library will be loaded successfully, because in that case, Java falls back to loading resources from the class path which contains all unnamed modules (the native library jars are unnamed modules).

A possible quick fix is to replace

  InputStream inputStream = LibUtils.class.getResourceAsStream(resourceName);

by the code used for unnamed modules, that is

  LibUtils.class.getClassLoader().getResourceAsStream(resourceName);

(I have not tested this, maybe I will - but it appears to be the code executed for unnamed modules).

I have tried to patch my project, but this is not so easy. I tried copy the code form LibUtils and load the native library with a patched version, but since the static initializer of JCuda crashes (due to the issue decribed here) I cannot retrieve getJCudaVersion. So this is another suggestion I have: allow using getJCudaVersion prior to loading and linking to native libraries.

PS: Under some situations, running maven unit tests, the library is loaded. I assume this is due to maven / junit making some modifications. That is, why I wrote "I am not sure". Maybe I overlooked some thing.

So before providing a pull request or digging deeper: is this issue known? am I overlooking something?

cfries commented 3 years ago

Let me know, if you require a MWE / sample project.

jcuda commented 3 years ago

Thanks for the detailed analysis! (I think there already was a hint somewhere about possible compatibility issues with the module system, but I didn't find it right now, and in any case, it was not as elaborate as your issue here)

I have to admit that I haven't looked into the details of the Java Module system yet. This specifically refers to the constraints (i.e. mutual "access rights") among modules. So the following is a bit simplified, for my limited understanding right now:

The the problem is, roughly speaking, that the jcuda.jar tries to access a resource from a jcuda-natives.jar. This worked until now, because the classpath handled all loaded libraries equally. But now, the module system restricts this, and says "The X.jar is by the default associated with module X, and cannot access resources from an Y.jar that by default belongs to module Y".

(Again, omitting some technical details, just to get the idea right)

If this is correct, then, according to my current understanding, there are possible solutions:

  1. Properly define modules for JCuda

I'll have to read more about that, but it would at least require some module-info.java files. And I assume that the native libraries may have one or the other caveat here. I'll have to closely examine the constraints that are imposed by getResourceAsStream implementation that you posted. Intuitively, I'd assume that one has to set up something like

module jcuda.natives {
    ...
    opens org.jcuda to jcuda;
}

or so - namely, to put the jcuda-natives into a dedicated module, and "expose" its contents (i.e. the native files) to the jcuda module.

(Or would it just work if jcuda and jcuda-natives were declared to both belong to a common jcuda module?)


  1. The "quick fix" that you suggested

I don't really understand why this might work. (It might have an effect that is similar to saying that "all jcuda... JARs belong to the same module", but I don't know the technical details...)


Since I don't know enough about the module system yet, I can only say: You probably have not overlooked something, but more likely found an issue that has to be resolved. It could be helpful to know whether the "quick fix" works. This could easily be added with little effort. Regardless of that, I'll have to consider to properly define the module structure of JCuda.


I cannot retrieve getJCudaVersion. So this is another suggestion I have: allow using getJCudaVersion prior to loading and linking to native libraries.

There certainly are some "legacy structures" involved in the process of loading the natives. (All this was started ~12 years ago - I was young and naive...). Transitioning this to the state where the binaries are contained in JARs that can be deployed to Maven Central and loaded transparently was pretty painful. Still, testing ~"whether CUDA is available" (if this is supposed to be deployed to a system that might not have an NVIDIA card in the first place) involves quirky things like

try {
    JCudaDriver.cuInit(0);
    return yepThatWorked;
} catch (Throwable t) {
    return noCudaApparently;
}

Specifically for your point: It certainly would make sense to move the getJCudaVersion function out of the JCuda class, and offer it as a single, dedicated function in a single, dedicated class...

class JCudaVersion {
    public static String getJCudaVersion() { ... }

    // This could be added here, wrapping the quirky code from above:
    public static boolean isCudaAvailable() { ... }
}

(to avoid interference with the static initializer). I'll definitely consider that. But the thing that I didn't understand yet: In how far would this help to solve this issue?

(Edit: An aside: Querying this "version string" is not part of the "public API", so to speak. It is only intended for finding the right native library name. So I wonder what you want to use it for...)


More generally: Pull requests or suggestions for improvements are always welcome. I recently haven't invested as much time in JCuda as I'd need to. There are some interrelated issues: There are some pending updates. But much of the newer CUDA functionality is ... *ehrm* ... hard use in general, and even harder to map to Java. JCuda might eventually be superseded by java-cpp. And after all, JCuda is a one-man, spare-time project, and maintaining it properly can be a lot of work.