IBM Z and LinuxONE - IBM Z

IBM Z

The enterprise platform for mission-critical applications brings next-level data privacy, security, and resiliency to your hybrid multicloud.

 View Only

Introduction to Foreign Function Interface and Foreign memory access in OpenJDK on Linux on Z

By Sidraya Jayagond posted Wed December 13, 2023 03:35 AM

  

Introduction

Recently platform specific support for Foreign Function Interface (FFI) in OpenjDK on s390x platform is provided by OpenJDK team.

This  blog covers an introduction to FFI and provides an example to invoke C functions from Java program using FFI feature. 

Project Panama is a set of efforts within the Java Community Process (JCP) to improve the connection between the Java Virtual Machine (JVM) and native code.
The goal is to enhance the performance and productivity of Java applications by providing better support for non-Java code, particularly native code written in languages like C and C++.
More can be read about it here.
FFI feature in Java provides mechanisms to Java programs for seamless interaction with code written in other languages, such as C or C++. 
This allows developers to leverage existing native libraries or access low-level system functionalities. 
All this while, Java Native Interface (JNI) served as the means to invoke foreign functions from Java.
However, Project Panama addresses certain limitations of JNI by:
  • Eliminating the necessity to create intermediate native code wrappers in Java.
  • Substituting  ByteBuffer APIs with more future-proof Memory APIs.
  • Introducing a platform-agnostic, secure, and memory-efficient approach to invoke native code from Java.   

The Foreign Function & Memory APIs uses some key abstractions

  • Memory segment and its address - A set of APIs classes to work with native memory and pointer to it.
  • Memory layout and descriptors - APIs to model foreign types (structures, primitives) and function descriptors.  
  • Memory session - An abstraction to manage the lifecycle of one or more memory resources.
  • Linker and symbol lookup - A set of APIs classes to perform downcalls and upcalls.
Memory APIs allows efficient memory management and data exchange between Java and native code.
All memory segments provide strongly enforced spatial, temporal, and thread-confinement guarantees which make memory de-reference operation safe.
Read more about FFI and Foreign memory access APIs here.

Example

There are two way to use FFI APIs 

Jextract Tool

A simple command line tool which auto generates Java APIs from one or more native C headers. This tool is shipped with OpenJDK Panama builds.
This tool helps to get rid writing an intermediate native wrapper code.
For Example
  • To generate Java API for getpid()
jextract –source -t org.unix -I  /usr/include/unistd.h
    • Use generated Java API getpid() as below
class main { 
            Public static void main(String[] args) { 

              System.out.println(getpid()); 
       } 
} 

    2. FFI APIs invoked from Java Application program itself  

Java program written below passes a string "Hello World! Panama style" to printf() function of C library invoked from FFI API.
/**
 * Panama Hello World calling C functions.
 */
public class Hello_World{
  public static void main(String[] args) {
                try (var arena = Arena.ofConfined()) {
                        // MemorySegment C's printf using a C string
                        MemorySegment cString = arena.allocateFrom("Hello World! Panama Style\n");
 
                        /* A Linker serves as a connection between two binary interfaces: the Java Virtual Machine (JVM)
                         * and C/C++ native code, commonly referred to as the C Application Binary Interface (C ABI).
                         */
                        Linker linker = Linker.nativeLinker();
 
                        // C function printf()
                        // int printf(const char *format, ...);  a variadic function
                        var symbolName = "printf";
 
                        MemorySegment symbol = linker.defaultLookup()
                                .find(symbolName)
                                .or(() -> SymbolLookup.loaderLookup().find(symbolName))
                                .orElseThrow(() -> new RuntimeException("The symbol %s is not found".formatted(symbolName)));
 
                        /* downcallHandle: (Bind Native Function)
                         * Use the downcallHandle method to bind the native function from the loaded library.
                         * This creates a handle to the native function that can be used for making calls.
                         * FunctionDescriptor:
                         * We define a function signature using FunctionDescriptor that specifies the argument and return types of the native function.
                         * In this case, it's a function that takes a long as an argument and returns a long.
                         */
                        MethodHandle printfMH = linker.downcallHandle(
                                        symbol,
                                        FunctionDescriptor.of(JAVA_INT, ADDRESS));
                        /* Make the Function Call:
                         * Use the downcall method handle to make the actual function call.
                         * Provide the arguments as described in FunctionDescriptor.
                         */
                        printfMH.invoke(cString);
 
                } catch (Throwable e) {
                        throw new RuntimeException(e);
                }
        }
}
 

Steps to build and test above java program

 FFI feature on s390x got merged in jdk 22 version. If current jdk version is 22, skip first 2 steps.
 1. Download latest available Nightly build "Linux s390x" platform of type "JDK"  from here and untar.
Example
wget https://github.com/adoptium/temurin22-binaries/releases/download/jdk-22%2B24-ea-beta/OpenJDK-jdk_s390x_linux_hotspot_ea_22-0-24.tar.gz
 
tar -xvf OpenJDK-jdk_s390x_linux_hotspot_ea_22-0-24.tar.gz
 
2. set JAVA_HOME in Linux terminal and export JAVA_HOME to PATH environmental variable.
JAVA_HOME=/root/jdk-22+24
export PATH=$JAVA_HOME/bin:$PATH
3. Execute  basic Java Program with below commands.
javac  Hello_World.java
java --enable-native-access=ALL-UNNAMED --enable-preview --source 22 Hello_World
 
4. C printf() function from C library gets invoked and prints "Hello World! Panama Style" string as below.
Hello World! Panama style

Conclusion

The blog explained about the usage of FFI APIs to invoke non-java native library functions.  
Similarly other  shared C libraries can be created and invoked from  java code using FFI APIs as above. 

References

0 comments
30 views

Permalink