Skip to content

Instantly share code, notes, and snippets.

@olpaw
Created March 9, 2022 16:44
Show Gist options
  • Save olpaw/32c11df542fbba28c7164cf8062f021d to your computer and use it in GitHub Desktop.
Save olpaw/32c11df542fbba28c7164cf8062f021d to your computer and use it in GitHub Desktop.

Module system implementation - state and implications

Currently supported module-related native-image options:

$ native-image --help

GraalVM native-image building tool

This tool can be used to generate an image that contains ahead-of-time compiled Java code.

Usage: native-image [options] class [imagename] [options]
(to build an image for a class)
or  native-image [options] -jar jarfile [imagename] [options]
(to build an image for a jar file)
or  native-image [options] -m <module>[/<mainclass>] [options]                                 πŸ‘ˆ MODULE SYSTEM OPTION
native-image [options] --module <module>[/<mainclass>] [options]                               πŸ‘ˆ MODULE SYSTEM OPTION
(to build an image for a module)

where options include:

    @argument files       one or more argument files containing options
    -cp <class search path of directories and zip/jar files>
    -classpath <class search path of directories and zip/jar files>
    --class-path <class search path of directories and zip/jar files>
                          A : separated list of directories, JAR archives,
                          and ZIP archives to search for class files.
    -p <module path> <<< MODULE SYSTEM OPTION
    --module-path <module path>...                                                             πŸ‘ˆ MODULE SYSTEM OPTION
                          A : separated list of directories, each directory
                          is a directory of modules.
    --add-modules <module name>[,<module name>...]                                             πŸ‘ˆ MODULE SYSTEM OPTION
                          root modules to resolve in addition to the initial module.
                          <module name> can also be ALL-DEFAULT, ALL-SYSTEM,
                          ALL-MODULE-PATH.
    -D<name>=<value>      set a system property
....

$ native-image --help-extra
Non-standard options help:

    --expert-options      lists image build options for experts
    --expert-options-all  lists all image build options for experts (use at your own risk).
                          Options marked with [Extra help available] contain help that can be
                          shown with --expert-options-detail
    --expert-options-detail
                          displays all available help for a comma-separated list of option names.
                          Pass * to show extra help for all options that contain it.

    --add-exports         value <module>/<package>=<target-module>(,<target-module>)*          πŸ‘ˆ MODULE SYSTEM OPTION
                          updates <module> to export <package> to <target-module>,
                          regardless of module declaration. <target-module> can be
                          ALL-UNNAMED to export to all unnamed modules.
    --add-opens           value <module>/<package>=<target-module>(,<target-module>)*          πŸ‘ˆ MODULE SYSTEM OPTION
                          updates <module> to open <package> to <target-module>, regardless
                          of module declaration.
    --add-reads           value <module>=<target-module>(,<target-module>)* updates <module>   πŸ‘ˆ MODULE SYSTEM OPTION
                          to read <target-module>, regardless of module declaration.
                          <target-module> can be ALL-UNNAMED to read all unnamed modules.

Currently unsupported options

From java --help

--upgrade-module-path <module path>...                                                         πŸ‘ˆ Would we ever need this?
              A : separated list of directories, each directory
              is a directory of modules that replace upgradeable
              modules in the runtime image

--enable-native-access <module name>[,<module name>...]                                        πŸ‘ˆ JEP 412 (JDK 17)
              modules that are permitted to perform restricted native operations.
              <module name> can also be ALL-UNNAMED.

From java --help-extra

--limit-modules <module name>[,<module name>...]
                  limit the universe of observable modules
--patch-module <module>=<file>(:<file>)*
                  override or augment a module with classes and resources
                  in JAR files or directories.

Remaining options will be added on demand (if the need arises)

Module-aware resource and resource bundle support

  • resource-config.json
    {
        "resources": {
            "includes": [
                {
                    "pattern": "^resource-file.txt$"
                }
            ]
        },
        "bundles": [
            {"name": "your.pkg.Bundle"}
        ]
    }
    

With module support we can use:

  • resource-config.json

    {
        "resources": {
            "includes": [
                {
                    "pattern": "library-module:^resource-file.txt$" πŸ‘ˆ 
                }
            ]
        },
        "bundles": [
            {"name": "main-module:your.pkg.Bundle"} πŸ‘ˆ 
        ]
    }
    
  • Include resource-file.txt only from Java module library-module. Even if other modules or classpath contains resources that would match pattern ^resource-file.txt$

  • Using <module-name>: ensures module is available at runtime (even if not referenced anywhere else)

    /* Get resource from module <module-name> */
    InputStream resource = ModuleLayer.boot().findModule(<module-name>).getResourceAsStream(resourcePath);
    
  • Resources / Resource Bundles are per module

    Suppose you have:

    public static final class MyFeature implements Feature {
        @Override
        public void beforeAnalysis(BeforeAnalysisAccess access) {
            ResourcesRegistry registry = ImageSingletons.lookup(ResourcesRegistry.class);
            for (Module module : ModuleLayer.boot().modules()) {
                registry.addResources(ConfigurationCondition.alwaysTrue(), module.getName() + ":" + "module-info.class");
            }
        }
    }
    

    Using the following at image runtime:

    Enumeration<URL> urlEnumeration = null;
    try {
    urlEnumeration = ClassLoader.getSystemResources("module-info.class"); πŸ‘ˆ 
    } catch (IOException e) {
    Assert.fail("IOException in ClassLoader.getSystemResources(\"module-info.class\"): " + e.getMessage());
    }
    

    The Enumeration<URL> contains all module-info.class resources registered via Feature at build-time!

Image builder running on module path vs. running on classpath

  • Previously the image builder was always running on classpath
    $ native-image -jar HelloMaven/target/app-1.0-SNAPSHOT.jar --verbose
    
    Executing [
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/bin/java \
    ...
    --add-exports=java.base/com.sun.crypto.provider=ALL-UNNAMED \
    --add-exports=java.base/jdk.internal.access.foreign=ALL-UNNAMED \
    --add-exports=java.base/jdk.internal.event=ALL-UNNAMED \
    --add-exports=java.base/jdk.internal.loader=ALL-UNNAMED \
    --add-exports=java.base/jdk.internal.logger=ALL-UNNAMED \
    --add-exports=java.base/jdk.internal.misc=ALL-UNNAMED \
    ...
    -cp \
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/svm.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/objectfile.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/pointsto.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/native-image-base.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/svm-enterprise.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/svm-llvm.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/llvm-wrapper-shadowed.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/javacpp-shadowed.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/llvm-platform-specific-shadowed.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/svm-enterprise-llvm.jar \
    --module-path \
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/truffle/truffle-api.jar \
    'com.oracle.svm.hosted.NativeImageGeneratorRunner$JDK9Plus' \
    ...
    -imagecp \
    /home/pwoegere/OLabs/issues/HelloMaven/target/app-1.0-SNAPSHOT.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/library-support.jar \
    ...
    ]
    
  • When -p / --module-path is used the builder runs on module-path
    $ native-image -p HelloMavenModule/target/app-1.0-SNAPSHOT.jar -m app.hello/paw.App --verbose
    
    Executing [
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/bin/java \
    ...
    --add-exports=java.base/com.sun.crypto.provider=org.graalvm.nativeimage.builder \                πŸ‘ˆ OPENING UP MODULES TOWARDS SPECIFIC TARGET-MODULES
    --add-exports=java.base/jdk.internal.access.foreign=org.graalvm.nativeimage.builder \
    --add-exports=java.base/jdk.internal.event=org.graalvm.nativeimage.builder \
    --add-exports=java.base/jdk.internal.loader=org.graalvm.nativeimage.builder \
    --add-exports=java.base/jdk.internal.logger=org.graalvm.nativeimage.builder \
    --add-exports=java.base/jdk.internal.misc=org.graalvm.nativeimage.builder \
    ...
    --module-path \                                                                                  πŸ‘ˆ BUILDER RUNS ON EXCLUSIVELY ON MODULE-PATH
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/truffle/truffle-api.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/svm.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/objectfile.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/pointsto.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/native-image-base.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/svm-enterprise.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/svm-llvm.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/llvm-wrapper-shadowed.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/javacpp-shadowed.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/llvm-platform-specific-shadowed.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/builder/svm-enterprise-llvm.jar \
    --module \
    org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner \               πŸ‘ˆ BUILDER ENTRY POINT IS MODULE/MAINCLASS
    ...
    -imagemp \                                                                                       πŸ‘ˆ MODULE TO BUILD AN IMAGE FROM IS PASSED TO BUILDER VIA -IMAGECP
    /home/pwoegere/OLabs/issues/HelloMavenModule/target/app-1.0-SNAPSHOT.jar:
    /home/pwoegere/.local/graalvm/graalvm-ee-java17-22.0.0.1/lib/svm/library-support.jar \
    ...
    ]
    
  • What to expect in the future (the current plan):
    • GraalVM 22.2: Switch to running builder on module-path per default.
    • GraalVM 22.3: Remove code needed to run builder on classpath.

Blockers for switching to running builder on module-path per default

  • Fix all module access issues when running builder on module-path

    e.g. mx native-unittest –builder-on-modulepath needs to work for every usecase

  • Build key-GraalVM components on module-path

    We already build all GraalVM release native-images coming from the substratevm-suite on module-path

    $ git grep -B 1 -A 1 use_modules
    
    mx.substratevm/mx_substratevm.py-868-        mx_sdk_vm.LauncherConfig(
    mx.substratevm/mx_substratevm.py:869:            use_modules='image',                              πŸ‘ˆ BUILD IMAGE AS JAVA-MODULE 
    mx.substratevm/mx_substratevm.py-870-            main_module="org.graalvm.nativeimage.driver",     πŸ‘ˆ DEFINE THE MAIN MODULE
    --
    mx.substratevm/mx_substratevm.py-879-        mx_sdk_vm.LibraryConfig(
    mx.substratevm/mx_substratevm.py:880:            use_modules='image',                              πŸ‘ˆ BUILD IMAGE AS JAVA-MODULE
    mx.substratevm/mx_substratevm.py-881-            destination="<lib:native-image-agent>",
    --
    mx.substratevm/mx_substratevm.py-893-        mx_sdk_vm.LibraryConfig(
    mx.substratevm/mx_substratevm.py:894:            use_modules='image',                              πŸ‘ˆ BUILD IMAGE AS JAVA-MODULE
    mx.substratevm/mx_substratevm.py-895-            destination="<lib:native-image-diagnostics-agent>",
    --
    mx.substratevm/mx_substratevm.py-1063-        mx_sdk_vm.LauncherConfig(
    mx.substratevm/mx_substratevm.py:1064:            use_modules='image',                             πŸ‘ˆ BUILD IMAGE AS JAVA-MODULE
    mx.substratevm/mx_substratevm.py-1065-            main_module="org.graalvm.nativeimage.configure", πŸ‘ˆ DEFINE THE MAIN MODULE
    
  • But:

    • Currently, libgraal cannot be built on module-path
    • Also truffle images cannot be built on module-path (except for js)

Module info at image-runtime

  • ModuleLayer.boot() at image runtime cannot simply return the same results as ModuleLayer.boot() at image buildtime

  • c.o.svm.hosted.ModuleLayerFeature takes care of that:

    • After analysis, we collect reachable modules via classes that are seen as reachable
    • We create corresponding runtime-modules and install them in image BootModuleLayer
    • As part of that module accessibility settings (--add-exports, --add-opens, ...) are translated from hosted modules to runtime modules (e.g. Resource bundle reflective instantiation)
  • Support for runtime module creation and dynamic visibility changes (even needed by JDK code itself – e.g. XSLT processor generates modules at runtime)

  • Running mx hellomodule tests this feature (among others)

  • Module aware stack-traces (WIP):

    Instead of:

    com.foo.bar.App.run(App.java:12)
    org.acme.Lib.test(Lib.java:80)
    

    We will have:

    com.foo.loader//com.foo.bar.App.run(App.java:12)                                                 πŸ‘ˆ DEFINED IN THE UNNAMED MODULE OF THE CLASS LOADER NAMED COM.FOO.LOADER 
    acme@2.1/org.acme.Lib.test(Lib.java:80)                                                          πŸ‘ˆ DEFINED IN ACME MODULE LOADED BY A BUILT-IN CLASS LOADER
    

    See PR: oracle/graal#4243

Current fully qualified classname limitation

  • Currently native-image only supports compiling one class for a given fully qualified classname
  • Full module-system support needs to get rid of this limitation (classes that are module-private can have identical fully qualified classnames)
  • This needs to be fixed at some point.

native-image -H:CompilerBackend=llvm

  • Building with the llvm backend does currently only work when the builder runs on classpath
  • Demo: Make llvm backend usable with module-path:
    native-image -H:CompilerBackend=llvm HelloWorld βœ…
    
    vs.
    native-image -H:CompilerBackend=llvm --macro:native-image-launcher πŸ”₯
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment