$30 off During Our Annual Pro Sale. View Details »

Panama, un pont vers le code natif (ParisJug, Nov 2021)

Panama, un pont vers le code natif (ParisJug, Nov 2021)

Depuis toujours, en Java, interagir avec le code natif est une nécessité. JNI est présent depuis les débuts de la JVM pour faire ce pont. Malgré ses défauts, c'est une technologie qui fonctionne bien. D’autres approches sont apparues pour combler ces défauts: JNA, puis JNR. Les ingénieurs de l’OpenJDK ont profité de l’arrivée des MethodHandles pour réviser cette interaction avec le monde natif pour plus de facilité et de sécurité. Ce travail est connu sous le nom de Project Panama et cette présentation vous introduira à ces nouvelles API.

Vidéo 🇫🇷

Brice Dutheil

November 10, 2021
Tweet

More Decks by Brice Dutheil

Other Decks in Technology

Transcript

  1. View Slide

  2. Panama
    Interconnecting Java with the native world.

    View Slide

  3. $ whoami
    Brice Dutheil
    $ jobs
    Datadog / Senior Software Engineer
    Blablacar / Senior Software Engineer
    Libon / Software Engineer
    :█

    View Slide

  4. https://www.oracle.com/a/ocom/img/obic-java-cup.svg
    https://upload.wikimedia.org/wikipedia/commons/1/18/OpenJDK_logo.svg
    17

    View Slide

  5. Le projet Panama atteint une nouvelle étape dans le JDK 17
    ...toujours en incubation pour le JDK 18
    ● JEP-412 Foreign Function & Memory API (Incubator)
    aka l’appel de fonctions étrangères au code Java
    ● JEP-414 Vector API (Second Incubator)
    aka l’API pour faire du calcul vectoriel

    View Slide

  6. Qui a déjà utilisé du code natif depuis la JVM ?

    View Slide

  7. Ce que je souhaite aborder ce soir
    ● Le projet panama, c’est quoi ?
    ● Appeler du code natif
    ○ Appels simple
    ○ Utiliser une API étrangère
    ● L’outil jextract
    ● Benchmark
    ● Pour finir

    View Slide

  8. Ce que je souhaite aborder ce soir
    ● Le projet panama, c’est quoi ?
    ● Appeler du code natif
    ○ Appels simple
    ○ Utiliser une API étrangère
    ● L’outil jextract
    ● Benchmark
    ● Pour finir

    View Slide

  9. Pourquoi Panama ?
    1. Les libraries “natives”
    ○ Omniprésente
    ○ Fonction de l’OS, device, file system, réseau, fonctionnalités tierces par d’autres libraries
    2. Interaction Java ⇔ Natif ardue
    ○ JNI, complexe à prendre en main,
    Possiblement: difficile à écrire du code correct et performant
    ○ Tant sur l’usage de l’API que du transfert de donnée
    À quoi doit-il succéder ?
    → JNI, JNA, JNR-FFI

    View Slide

  10. Les technos actuelles : JNI
    À l’origine JNI (Java Native Interface)
    ● JNI est présent dans la JVM dès la version 1.1
    ● Le code (vu de Java) ressemble à ça
    package q.r.s;
    class NativeBinding {
    static { System.load("{path/to/libNativeBinding.so}"); } // (1)
    public static native boolean isatty(int fileDescriptor); // (2)
    }

    View Slide

  11. Les technos actuelles : JNI
    À l’origine JNI (Java Native Interface)
    ● JNI est présent dans la JVM dès la version 1.1
    ● Le code (vu de Java) ressemble à ça
    package q.r.s;
    class NativeBinding {
    static { System.load("{path/to/libNativeBinding.so}"); } // (1)
    public static native boolean isatty(int fileDescriptor); // (2)
    }

    View Slide

  12. Les technos actuelles : JNI
    À l’origine JNI (Java Native Interface)
    ● JNI est présent dans la JVM dès la version 1.1
    ● Le code (vu de Java) ressemble à ça
    package q.r.s;
    class NativeBinding {
    static { System.load("{path/to/libNativeBinding.so}"); } // (1)
    public static native boolean isatty(int fileDescriptor); // (2)
    }

    View Slide

  13. Les technos actuelles : JNI
    Il faut extraire des headers C/C++ de la classe Java
    $ javac -d classes -cp src -h jni q/r/s/NativeBinding.java
    💡 Avant le JDK10 il faut
    utiliser javah.
    Voir JEP-313

    View Slide

  14. Les technos actuelles : JNI
    Il faut extraire des headers C/C++ de la classe Java
    Coté natif il faut écrire du code de glue
    #include "q_r_s_NativeBinding.h" // (1)
    #include
    JNIEXPORT jboolean JNICALL Java_q_r_s_NativeBinding_isatty //
    (2)
    (JNIEnv *env, jclass cls, jint fileDescriptor) {
    return isatty(fileDescriptor)? JNI_TRUE: JNI_FALSE;
    }
    $ javac -d classes -cp src -h jni q/r/s/NativeBinding.java
    💡 Avant le JDK10 il faut
    utiliser javah.
    Voir JEP-313

    View Slide

  15. Les technos actuelles : JNI
    Il faut extraire des headers C/C++ de la classe Java
    Coté natif il faut écrire du code de glue
    Il faut compiler séparément
    #include "q_r_s_NativeBinding.h" // (1)
    #include
    JNIEXPORT jboolean JNICALL Java_q_r_s_NativeBinding_isatty //
    (2)
    (JNIEnv *env, jclass cls, jint fileDescriptor) {
    return isatty(fileDescriptor)? JNI_TRUE: JNI_FALSE;
    }
    $ gcc \
    -I $JAVA_HOME/include \ # (1)
    -I $JAVA_HOME/include/linux \ # (2)
    -fPIC \ # (3)
    -shared \ # (4)
    -o NativeBinding.so \ # (5)
    NativeBinding.c
    $ javac -d classes -cp src -h jni q/r/s/NativeBinding.java
    💡 Avant le JDK10 il faut
    utiliser javah.
    Voir JEP-313

    View Slide

  16. Les technos actuelles : JNI
    À noter si le code utilise plutôt System.loadLibrary("NativeBinding")
    Alors ils faut indiquer ou chercher cette librairie
    $ java -Djava.library.path=path/to/jni-lib -cp classes q.r.s.NativeBinding

    View Slide

  17. Les technos actuelles : JNI
    Pour appeler du code natif, la JVM doit effectuer quelques opérations.
    ● Initialiser une stack C valide
    ● Initialiser les registres du CPU (et les remettre en place)
    ● Instructions de barrières de mémoire (fencing) sur le CPU
    ● Empêche certaines optimizations comme l'inlining
    ● JNI permet de définir des sections critiques (Get*Critical) empêchant
    l’action du GC (au minimum sur une région)
    ● Échanger des données (objets, tableaux) peut demander de la copie
    et/ou de la sérialisation

    View Slide

  18. Les technos actuelles : JNA
    ● Premier fragment montré dès 1999 et 1ère release en 2006
    ● Windows uniquement au début
    ● Inventé par la team ayant bossé sur JNI
    ● Brique omniprésente
    ● Souvent le choix retenu pour interagir avec les librairies natives

    View Slide

  19. Les technos actuelles : JNA
    JNA est facile à utiliser
    Il n’y a plus qu’à utiliser l’API
    public interface JNA_Library extends Library {
    JNA_Library INSTANCE = (JNA_Library) Native.loadLibrary(
    "c",
    JNA_Library.class
    ); // (1)
    boolean isatty(int fileDescriptor); // (2)
    }
    JNA_Library.INSTANCE.isattty(
    0);

    View Slide

  20. Les technos actuelles : JNA
    ● Support de types natif (wstring, structure, union, etc.)
    ● Beaucoup de reflection pour le ease-of-use
    ● Gère la sérialization, segments mémoire
    ● Repose tout de même sur JNI (invisible)
    ● Dispatch par la library native de JNA basé sur libffi
    libffi applique des routines écrites en assembleur à la main pour
    simuler un callsite C

    View Slide

  21. Les technos actuelles : JNR-FFI
    JNR-FFI est un projet open source débuté en 2011.
    Son but : être aussi simple que JNA, avec les performances en plus.
    The Java Native Runtime Project
    https://github.com/jnr
    public interface IsATTY_JNRFFI {
    boolean isatty(int fileDescriptor); // (1)
    }
    import jnr.ffi.LibraryLoader;
    public class JnrffiIsATTY {
    public static void main(String[] args) {
    IsATTY_JNRFFI c = LibraryLoader.create(IsATTY_JNRFFI.class).load(
    "c");
    System.out.printf("stdin : %s%n", c.isatty(0));
    System.out.printf("stdout: %s%n", c.isatty(1));
    System.out.printf("stderr: %s%n", c.isatty(3));
    }
    }

    View Slide

  22. Les technos actuelles : JNR-FFI
    ● Support de types natif (wstring, structure, union, etc.)
    ● Gestion des classloaders
    ● Génère du bytecode plutôt que de faire la reflection → nettement plus
    performant
    ● Tire parti également de libffi
    JNR-FFI est le projet qui inspirera le Projet Panama en 2014
    ● JEP-191 Foreign Function Interface
    Initité notamment par Charles Nutter de JRuby

    View Slide

  23. Projet Panama : Objectif monde natif
    Préserver la culture Java
    Opportunité : Tirer profit des MethodHandles
    Attention aux différences de :
    ● Syntaxe, sémantique
    ● Nommage, namespace
    ● Types de données
    ● Gestion de la mémoire
    ● Exceptions
    ● Performance
    ● Sécurité

    View Slide

  24. Projet Panama
    Plus facile, plus sécurisé, plus rapide
    ➔ Support des Fonctions étrangères
    et des types de données étrangers (FFI)
    ➔ Flexible dans le transfert de donnée
    ➔ Support de types natif moderne, comme les vecteurs
    Initité notamment par John Rose
    (bosse sur le JVM depuis la version 1, participe à beaucoup de sous système : sécurité, Unsafe, indy, valhala, java-on-java, …)

    View Slide

  25. JEP-412 Foreign Function & Memory API
    JEP-191 Foreign Function Interface
    JEP-338 Vector API (Incubator)
    JEP-370 Foreign Memory Access API (Incubator)
    JEP-383 Foreign Memory Access API (2nd Incubator)
    JEP-389 Foreign Linker API (Incubator)
    JEP-393 Foreign Memory Access API (3rd Incubator)
    JEP-414 Vector API (2nd Incubator)
    JEP-417 Vector API (3nd Incubator)

    View Slide

  26. ● Le projet panama, c’est quoi ?
    ● Appeler du code natif
    ○ Appels simple
    ○ Utiliser une API étrangère
    ● L’outil jextract
    ● Benchmark
    ● Pour finir

    View Slide

  27. Code natif : Appels simples

    View Slide

  28. libsodium
    Bibliothèque cryptographique (chiffrement, signatures, hashing, etc.)
    Écrite en C, multi-plateforme (OS, architectures)
    Usage facile et rapide

    View Slide

  29. Étape 0 : Configuration
    JDK 17

    View Slide

  30. Étape 0 : Configuration
    JDK 17
    Incubation ⇒ le code est livré dans un module, à activer manuellement
    $ javac --add-modules jdk.incubator.foreign ...
    $ java --add-modules jdk.incubator.foreign ...

    View Slide

  31. Étape 0 : Configuration
    JDK 17
    Incubation ⇒ le code est livré dans un module, à activer manuellement
    Pour exécuter des appels natif il faut ajouter
    $ javac --add-modules jdk.incubator.foreign ...
    $ java --add-modules jdk.incubator.foreign ...
    $ java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign ...
    Toujours en discussion

    View Slide

  32. Étape 0 : Configuration
    Faisable avec module-info.java
    module jmh.panama {
    requires jdk.incubator.foreign;
    // ...
    }

    View Slide

  33. Étape 0 : Configuration
    Faisable avec module-info.java
    module jmh.panama {
    requires jdk.incubator.foreign;
    // ...
    }
    Module path…
    Toute les dépendances ne sont
    pas prêtes

    View Slide

  34. Étape 1: Charger la bibliothèque
    1. Utiliser une fonction système (eg de la libc, etc.)
    CLinker.systemLookup()

    View Slide

  35. Étape 1: Charger la bibliothèque
    1. Utiliser une fonction système (eg de la libc, etc.)
    CLinker.systemLookup()
    .lookup("getpid").get() → Addresse mémoire de la fonction
    (pour ce process)

    View Slide

  36. Étape 1: Charger la bibliothèque
    System.load / System.loadLibrary
    monte la bibliothèque dans un segment
    mémoire
    Mémoire
    virtuelle du
    processus
    Java heap
    Stacks java
    Autres

    View Slide

  37. Étape 1: Charger la bibliothèque
    System.load / System.loadLibrary
    monte la bibliothèque dans un segment
    mémoire
    Mémoire
    virtuelle du
    processus
    Java heap
    Stacks java
    Autres
    libsodium
    Le mécanisme de lookup va rechercher
    les symboles C dans ce(s) segment(s).

    View Slide

  38. Étape 1: Charger la bibliothèque
    1. Utiliser une fonction système (eg de la libc, etc.)
    2. Charger une lib utilisateur
    a. En passant un chemin
    CLinker.systemLookup().lookup(
    "getpid").get()
    System.load("/usr/local/lib/libsodium.dylib
    ");
    SymbolLookup.loaderLookup().lookup(
    "crypto_box_sealbytes
    ").get();
    💡 lookup() retourne
    une adresse mémoire de
    la fonction native (pour ce
    process)

    View Slide

  39. Étape 1: Charger la bibliothèque
    1. Utiliser une fonction système (eg de la libc, etc.)
    2. Charger une lib utilisateur
    a. En passant un chemin
    b. Avec le nom de la bibliothèque
    (Doit être présente dans un des dossiers de java.library.path)
    CLinker.systemLookup().lookup(
    "getpid").get()
    System.loadLibrary("sodium");
    SymbolLookup.loaderLookup().lookup(
    "crypto_box_sealbytes
    ").get();
    System.load("/usr/local/lib/libsodium.dylib
    ");
    SymbolLookup.loaderLookup().lookup(
    "crypto_box_sealbytes
    ").get();
    💡 lookup() retourne
    une adresse mémoire de
    la fonction native (pour ce
    process)

    View Slide

  40. Ce qu’il se passe lors d’un downcallHandle
    *.lookup("getpid").get() ⇒ addresse
    lib
    Adresse de
    getpid

    View Slide

  41. Ce qu’il se passe lors d’un downcallHandle
    CLinker.getInstance()
    Linker spécifique pour OS,
    Architecture (amd64|x86_64|aarch64)
    *.lookup("getpid").get() ⇒ addresse
    lib
    Adresse de
    getpid

    View Slide

  42. Ce qu’il se passe lors d’un downcallHandle
    CLinker.getInstance()
    Linker spécifique pour OS,
    Architecture (amd64|x86_64|aarch64)
    *.lookup("getpid").get() ⇒ addresse
    Création de code exécutant la
    méthode native
    Code cache
    lib
    Adresse de
    getpid

    View Slide

  43. Ce qu’il se passe lors d’un downcallHandle
    CLinker.getInstance()
    Linker spécifique pour OS,
    Architecture (amd64|x86_64|aarch64)
    Calcul de la taille de la stack native
    Arg 1
    Arg 2
    Arg 2
    Stack
    0x00CAFE
    *.lookup("getpid").get() ⇒ addresse
    Création de code exécutant la
    méthode native
    Code cache
    lib
    Adresse de
    getpid

    View Slide

  44. Ce qu’il se passe lors d’un downcallHandle
    CLinker.getInstance()
    Linker spécifique pour OS,
    Architecture (amd64|x86_64|aarch64)
    Calcul de la taille de la stack native
    Copie des valeurs primitives dans
    la stack native Arg 1
    Arg 2
    Arg 2
    Stack
    0x00CAFE
    Java
    heap
    *.lookup("getpid").get() ⇒ addresse
    Création de code exécutant la
    méthode native
    Code cache
    lib
    Adresse de
    getpid

    View Slide

  45. Ce qu’il se passe lors d’un downcallHandle
    CLinker.getInstance()
    Linker spécifique pour OS,
    Architecture (amd64|x86_64|aarch64)
    Calcul de la taille de la stack native
    Copie des valeurs primitives dans
    la stack native Arg 1
    Arg 2
    Arg 2
    Stack
    0x00CAFE
    Java
    heap
    Invocation native via JNI
    *.lookup("getpid").get() ⇒ addresse
    Création de code exécutant la
    méthode native
    Code cache
    lib
    Adresse de
    getpid

    View Slide

  46. Ce qu’il se passe lors d’un downcallHandle
    CLinker.getInstance()
    Linker spécifique pour OS,
    Architecture (amd64|x86_64|aarch64)
    Calcul de la taille de la stack native
    Copie des valeurs primitives dans
    la stack native Arg 1
    Arg 2
    Arg 2
    Stack
    0x00CAFE
    Ret
    Stack
    0x00CAFE
    Copie des valeurs primitives dans
    la stack native
    Java
    heap
    Java
    heap
    Invocation native via JNI
    *.lookup("getpid").get() ⇒ addresse
    Création de code exécutant la
    méthode native
    Code cache
    lib
    Adresse de
    getpid

    View Slide

  47. Panama tire parti des MethodHandle pour appeler le code natif
    Dès Java 8, à la place de l’API de reflection
    Étape 2: Appel de méthode sans paramètre
    A method handle is a typed, directly executable reference to an underlying method,
    constructor, field, or similar low-level operation, with optional transformations of arguments
    or return values. These transformations are quite general, and include such patterns as
    conversion, insertion, deletion, and substitution.
    JDK 1.7
    MethodHandle mh = MethodHandles.lookup().findVirtual(
    TargetClass.class,
    "targetMetbod",
    MethodType.methodType(void.class, String.class)
    );
    mh.invokeExact(targetInstance, "...");

    View Slide

  48. Étape 2: Appel de méthode sans paramètre
    La génération de MethodHandle passe par CLinker
    Invocation classique de la référence (vers la méthode native)
    CLinker.getInstance().downcallHandle(
    methodAddress, // (1)
    MethodType.methodType(
    int.class), // (2)
    FunctionDescriptor.of(CLinker.C_INT) // (3)
    );
    int result = (int) crypto_box_sealbytes.invokeExact();

    View Slide

  49. Étape 3: Appel de méthode avec paramètres

    View Slide

  50. Étape 3: Appel de méthode avec paramètres
    Pour passer des arguments vers le
    monde natif, il faut les copier en dehors
    de la Heap Java.
    Mémoire
    virtuelle du
    processus
    libsodium
    segment
    off-heap
    Java heap

    View Slide

  51. Étape 3: Appel de méthode avec paramètres
    Pour passer des arguments vers le
    monde natif, il faut les copier en dehors
    de la Heap Java.
    Au retour de la méthode, il faut copier le
    résultat dans la Java Heap
    Mémoire
    virtuelle du
    processus
    libsodium
    segment
    off-heap
    Java heap

    View Slide

  52. Étape 3: Appel de méthode avec paramètres
    Exemple avec crypto_box_keypair
    Cette méthode prend deux pointeurs
    ● vers une zone mémoire off-heap
    ● le consommateur à la responsabilité d’initialiser ces zones
    unsigned char recipient_pk[crypto_box_PUBLICKEYBYTES];
    unsigned char recipient_sk[crypto_box_SECRETKEYBYTES];
    crypto_box_keypair(
    recipient_pk, recipient_sk);

    View Slide

  53. Étape 3: Appel de méthode avec paramètres
    MethodHandle crypto_box_keypair =
    CLinker.getInstance().downcallHandle(
    libsodiumLookup.lookup(
    "crypto_box_keypair"
    ).get(),
    MethodType.methodType(
    void.class,
    MemoryAddress.class, // pk
    MemoryAddress.class // sk
    ),
    FunctionDescriptor.ofVoid(C_POINTER, C_POINTER)
    );
    Représente les
    pointeurs vers pk et sk

    View Slide

  54. Étape 3: Appel de méthode avec paramètres
    MethodHandle crypto_box_keypair =
    CLinker.getInstance().downcallHandle(
    libsodiumLookup.lookup(
    "crypto_box_keypair"
    ).get(),
    MethodType.methodType(
    void.class,
    MemoryAddress.class, // pk
    MemoryAddress.class // sk
    ),
    FunctionDescriptor.ofVoid(C_POINTER, C_POINTER)
    );
    var recipientPublicKey = MemorySegment.allocateNative(crypto_box_publickeybytes(), scope);
    var recipientSecretKey = MemorySegment.allocateNative(crypto_box_secretkeybytes(), scope);
    crypto_box_keypair.invokeExact(
    recipientPublicKey.address(),
    recipientSecretKey.address());

    View Slide

  55. Étape 3: Appel de méthode avec paramètres
    MethodHandle crypto_box_keypair =
    CLinker.getInstance().downcallHandle(
    libsodiumLookup.lookup(
    "crypto_box_keypair"
    ).get(),
    MethodType.methodType(
    void.class,
    MemoryAddress.class, // pk
    MemoryAddress.class // sk
    ),
    FunctionDescriptor.ofVoid(C_POINTER, C_POINTER)
    );
    try (var scope = ResourceScope.newConfinedScope()) {
    var recipientPublicKey = MemorySegment.allocateNative(crypto_box_publickeybytes(),
    scope);
    var recipientSecretKey = MemorySegment.allocateNative(crypto_box_secretkeybytes(),
    scope);
    crypto_box_keypair.invokeExact(recipientPublicKey.address(),
    recipientSecretKey.address());
    return new CryptoBoxKeyPair(
    recipientPublicKey.toByteArray(),
    recipientSecretKey.toByteArray()
    );
    }

    View Slide

  56. Scope et allocation
    ResourceScope : Aide à la gestion du cycle de vie des segments mémoire
    Le scope est passé à la création de la ressource, eg. MemorySegment::allocateNative
    ● globalScope ⇒ scope implicite, pas de gestion automatique
    RSS

    View Slide

  57. Scope et allocation
    ResourceScope : Aide à la gestion du cycle de vie des segments mémoire
    Le scope est passé à la création de la ressource, eg. MemorySegment::allocateNative
    ● globalScope ⇒ scope implicite, pas de gestion automatique
    Pour une gestion explicite et déterministe
    (comprendre libère les segments à la sortie du try-with-resource)
    ● newConfinedScope ⇒ implique un restriction au thread en cours
    ● newSharedScope ⇒ pas de thread propriétaire
    RSS

    View Slide

  58. Scope et allocation
    MemorySegment::allocateNative ⇒ effectue un malloc (allocateur système)

    View Slide

  59. Scope et allocation
    MemorySegment::allocateNative ⇒ malloc (allocateur système)
    Stratégie d’allocation de la mémoire avec SegmentAllocator
    Interface avec 3 implémentations par défaut

    View Slide

  60. Scope et allocation
    MemorySegment::allocateNative ⇒ malloc (allocateur système)
    Stratégie d’allocation de la mémoire avec SegmentAllocator
    Interface avec 3 implémentations par défaut
    ● ofScope ⇒ effectue un malloc

    View Slide

  61. Scope et allocation
    MemorySegment::allocateNative ⇒ malloc (allocateur système)
    Stratégie d’allocation de la mémoire avec SegmentAllocator
    Interface avec 3 implémentations par défaut
    ● ofScope ⇒ effectue un malloc
    ● ofSegment ⇒ recycle le même segment

    View Slide

  62. Scope et allocation
    MemorySegment::allocateNative ⇒ malloc (allocateur système)
    Stratégie d’allocation de la mémoire avec SegmentAllocator
    Interface avec 3 implémentations par défaut
    ● ofScope ⇒ effectue un malloc
    ● ofSegment ⇒ recycle le même segment
    ● arenaAllocator ⇒ Gestion de la mémoire par arène (région)
    new
    freed

    View Slide

  63. Étape 3: Appel de méthode avec paramètres
    MethodHandle crypto_box_keypair =
    CLinker.getInstance().downcallHandle(
    libsodiumLookup.lookup(
    "crypto_box_keypair"
    ).get(),
    MethodType.methodType(
    void.class,
    MemoryAddress.class, // pk
    MemoryAddress.class // sk
    ),
    FunctionDescriptor.ofVoid(C_POINTER, C_POINTER)
    );
    try (var scope = ResourceScope.newConfinedScope()) {
    var allocator = SegmentAllocator.ofScope(scope);
    var recipientPublicKey = allocator.allocate(crypto_box_publickeybytes());
    var recipientSecretKey = allocator.allocate(crypto_box_secretkeybytes());
    crypto_box_keypair.invokeExact(recipientPublicKey.address(),
    recipientSecretKey.address());
    return new CryptoBoxKeyPair(
    recipientPublicKey.toByteArray(),
    recipientSecretKey.toByteArray()
    );
    }

    View Slide

  64. Étape 4: Appel de méthode avec paramètres
    Exemple avec crypto_box_seal
    Cette méthode
    ● prend trois pointeurs
    ○ le premier argument est le tableau ou sera stocké le message chiffré
    ● retourne un entier avec un status
    int crypto_box_seal(
    unsigned char *c, const unsigned char *m,
    unsigned long long mlen, const unsigned char *pk)

    View Slide

  65. Étape 4: Appel de méthode avec paramètres
    var crypto_box_seal = CLinker.getInstance().downcallHandle(
    libsodiumLookup.lookup(
    "crypto_box_seal
    ").get(),
    MethodType.methodType(
    int.class,
    MemoryAddress.class, // cipherText, output buffer
    MemoryAddress.class, // message
    long.class, // message length
    MemoryAddress.class // publicKey
    ),
    FunctionDescriptor.of(C_INT,
    C_POINTER,
    C_POINTER,
    C_LONG_LONG,
    C_POINTER)
    );

    View Slide

  66. Étape 4: Appel de méthode avec paramètres
    try (var scope = ResourceScope.newConfinedScope()) {
    var allocator = SegmentAllocator.ofScope(scope);
    var nativeMessage = CLinker.toCString(message, scope);
    var cipherText = allocator.allocate(crypto_box_sealbytes() +
    nativeMessage.byteSize());
    var ret = (int) crypto_box_seal.invokeExact(
    cipherText.address(),
    nativeMessage.address(),
    (long) nativeMessage.byteSize(),
    allocator.allocateArray(C_CHAR, publicKey).address());
    return cipherText.toByteArray();
    }

    View Slide

  67. Étape 4: Appel de méthode avec paramètres
    try (var scope = ResourceScope.newConfinedScope()) {
    var allocator = SegmentAllocator.ofScope(scope);
    var nativeMessage = CLinker.toCString(message, scope);
    var cipherText = allocator.allocate(crypto_box_sealbytes() +
    nativeMessage.byteSize());
    var ret = (int) crypto_box_seal.invokeExact(
    cipherText.address(),
    nativeMessage.address(),
    (long) nativeMessage.byteSize(),
    allocator.allocateArray(C_CHAR, publicKey).address());
    return cipherText.toByteArray();
    }

    View Slide

  68. Étape 4: Appel de méthode avec paramètres
    try (var scope = ResourceScope.newConfinedScope()) {
    var allocator = SegmentAllocator.ofScope(scope);
    var nativeMessage = CLinker.toCString(message, scope);
    var cipherText = allocator.allocate(crypto_box_sealbytes() +
    nativeMessage.byteSize());
    var ret = (int) crypto_box_seal.invokeExact(
    cipherText.address(),
    nativeMessage.address(),
    (long) nativeMessage.byteSize(),
    allocator.allocateArray(C_CHAR, publicKey).address());
    return cipherText.toByteArray();
    }

    View Slide

  69. Les classes essentielles
    ● CLinker
    ○ Définitions des types natifs char ⇒ C_CHAR, long long ⇒ C_LONG_LONG, pointeur ⇒
    C_POINTER, varargs
    ○ Factory pour de MethodHandle via downcallHandle
    ○ Conversion Java String ⇔ C String
    ● MemorySegment
    ○ Représente une zone mémoire off-heap
    ○ Méthodes pour associer la disposition (e.g tableau de double)
    ○ Peut représenter un fichier memory mappé
    ● MemoryAddress
    ○ Représente une adresse dans la mémoire virtuelle du processus.

    View Slide

  70. Les classes essentielles
    ● SegmentAllocator
    ○ Primitives de gestion de la mémoire
    ● ResourceScope
    ○ Supporte le périmètre et le cycle de vie de resources.
    ○ Imbricable
    Pour aller plus loin
    ● MemoryLayout
    ○ Outillage pour définir la disposition d’un segment de mémoire

    View Slide

  71. Ce que je souhaite aborder ce soir
    ● Le projet panama, c’est quoi ?
    ● Appeler du code natif
    ○ Appels simple
    ○ Utiliser une API étrangère
    ● L’outil jextract
    ● Benchmark
    ● Pour finir

    View Slide

  72. jextract
    jextract \
    -d src/main/java \
    -l sodium \
    --source \
    --target-package com.github.bric3.sodium \
    -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/ \
    -I $(brew --prefix)/include/sodium \
    @sodium.conf \
    $(brew --prefix)/include/sodium.h
    jextract @sodium-only.conf $(brew --prefix)/include/sodium.h
    jextract \
    -d src/main/java \
    -l sodium \
    --source \
    --target-package com.github.bric3.sodium \
    -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/
    \
    -I $(brew --prefix)/include/sodium \
    --dump-includes sodium.conf \
    $(brew --prefix)/include/sodium.h
    Build 17-panama+3-167
    https://jdk.java.net/panama/

    View Slide

  73. Ce que je souhaite aborder ce soir
    ● Le projet panama, c’est quoi ?
    ● Appeler du code natif
    ○ Appels simple
    ○ Utiliser une API étrangère
    ● L’outil jextract
    ● Benchmark
    ● Pour finir

    View Slide

  74. Demo
    REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
    why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
    experiments, perform baseline and negative tests that provide experimental control, make sure
    the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
    Do not assume the numbers tell you what you want them to tell.
    Benchmark Mode Cnt Score Error Units
    CryptoBoxSealBenchmark.jna avgt 50 160605.022 ± 1142.872 ns/op
    CryptoBoxSealBenchmark.jni avgt 50 150353.548 ± 750.460 ns/op
    CryptoBoxSealBenchmark.jnr avgt 50 213407.139 ± 26843.546 ns/op
    CryptoBoxSealBenchmark.panama avgt 50 151025.638 ± 637.727 ns/op

    View Slide

  75. Demo
    Benchmark Mode Cnt Score Error Units
    CryptoBoxSealBenchmark.jna avgt 50 160197.866 ± 1009.495 ns/op
    CryptoBoxSealBenchmark.jni avgt 50 151259.674 ± 1446.900 ns/op
    CryptoBoxSealBenchmark.jnr avgt 50 179482.479 ± 1735.433 ns/op
    CryptoBoxSealBenchmark.panama avgt 50 152633.815 ± 1247.167 ns/op
    CryptoBoxSealBenchmark.panama_off_heap avgt 50 151091.119 ± 917.583 ns/op

    View Slide

  76. Ce que je souhaite aborder ce soir
    ● Le projet panama, c’est quoi ?
    ● Appeler du code natif
    ○ Appels simple
    ○ Utiliser une API étrangère
    ● L’outil jextract
    ● Benchmark
    ● Pour finir

    View Slide

  77. Pour finir

    View Slide

  78. À voir aussi
    Appels natifs vers Java : upcallStub
    Memory mapping de fichiers
    MemoryAddress jm = CLinker.getInstance().upcallStub(
    MethodHandle, // the handle to the Java method
    FunctionDescriptor, // equivalent C signature
    ResourceScope // scope
    );
    c_method.invokeExact(
    ...,
    jm.address() // pointeur vers la méthode Java
    );
    MemorySegment.mapFile(path, // chemin du fichier
    0, // offset
    Files.size(path), // taille du mapping
    FileChannel.MapMode.READ_ONLY, // mode
    scope);

    View Slide

  79. À noter
    Changements à venir dans un prochain incubateur
    ● Ajustements autour des APIs de gestion de ressources, pooling, keep alive,

    ● Outillage en cours de finalisation
    Une fois finalisée, l’API sera intégrée dans les modules par défaut,
    e.g. possible ajustements de signature, voire déplacement de méthode.

    View Slide

  80. À noter
    Support de différents OS / architectures. Alignement…, etc.
    Support Android : Pas pour tout de suite
    ● Android SDK != JDK
    ● Déjà compliqué d’avoir un bon support de Java 8
    (API Level 26 ajoute java.time, java.nio.file et java.lang.invoke)

    View Slide

  81. Questions ?
    https://inside.java/
    https://mail.openjdk.java.net/pipermail/panama-dev/
    https://jdk.java.net/panama/
    https://blog.arkey.fr/
    @BriceDutheil

    View Slide