Basically, fastFFI has three components:
- FFI: DSL and API used to develop FFI applications.
- Annotation Processor: the code generator for FFI.
- LLVM4JNI: LLVM4JNI has two submodules:
- LLVM4JNI: a tool that translates LLVM bitcode into Java bytecode.
- LLVM4JNI Runtime: the runtime component used by generated bytecode.
An FFI application must include ffi
and llvm4jni-runtime
in its class path as runtime dependency.
-
Checkout source code
git clone <path-to-fastffi> fastffi
-
Prepare building environment
export LLVM11_HOME=<path-to-llvm-11>
LLVM11_HOME
should point to the home of LLVM 11. In Ubuntu, it is at/usr/lib/llvm-11
. Basically, the build procedure the following binary:$LLVM11_HOME/bin/clang++
$LLVM11_HOME/bin/ld.lld
$LLVM11_HOME/lib/cmake/llvm
-
Use fastFFI with Maven.
<properties> <fastffi.revision>0.1.2</fastffi.revision> </properties> <dependencies> <!-- The FFI annotation --> <dependency> <groupId>com.alibaba.fastffi</groupId> <artifactId>ffi</artifactId> <version>${fastffi.revision}</version> </dependency> <!-- The FFI annotation processor for code generation --> <dependency> <groupId>com.alibaba.fastffi</groupId> <artifactId>annotation-processor</artifactId> <version>${fastffi.revision}</version> </dependency> <!-- The runtime component of LLVM4JNI --> <dependency> <groupId>com.alibaba.fastffi</groupId> <artifactId>llvm4jni</artifactId> <version>${fastffi.revision}</version> <classifier>${os.detected.classifier}</classifier> </dependency> <dependency> <groupId>com.alibaba.fastffi</groupId> <artifactId>llvm4jni-runtime</artifactId> <version>${fastffi.revision}</version> </dependency> </dependencies> <plugins> <plugin> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.7.0</version> <executions> <execution> <phase>initialize</phase> <goals> <goal>detect</goal> </goals> </execution> </executions> </plugin> </plugins>
-
Use maven to build your applications.
The generated code, including Java and C++ code, is available in
<project.dir>/target/generated-source/annotations
A Java programming language compiler must support standard options in the format -Akey[=value]
. fastFFI provides the following options:
fastffi.handleException
: whether generating code to handle C++ exceptions- default value:
false
- default value:
fastffi.manualBoxing
: usingnew Integer()
ornew Long()
to box a primitive integer.- default value:
true
- Auto boxing uses
Integer.valueOf
orLong.valueOf
, which cannot be properly handled by the escape analysis of C2 compiler.
- default value:
fastffi.strictTypeCheck
- default value:
false
- default value:
fastffi.nullReturnValueCheck
- default value:
true
- insert additional null check for native pointers
- default value:
fastffi.cxxOutputLocation
- default value:
CLASS_OUTPUT
- accept values:
CLASS_OUTPUT
,SOURCE_OUTPUT
,NATIVE_HEADER_OUTPUT
.
- default value:
fastffi.traceJNICalls
- default value:
false
- generate stuffs to trace the invocations of JNI wrappers
- default value:
fastffi.compactFFINames
- default value:
true
- generate compact FFI wrapper type names, non-compact names will benefit debugging, but increase the binary size
- default value:
Usage:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<compilerVersion>${javac.target}</compilerVersion>
<source>${javac.target}</source>
<target>${javac.target}</target>
<compilerArgs>
<arg>-Afastffi.strictTypeCheck=true</arg>
</compilerArgs>
</configuration>
</plugin>
-
Install a JDK (JDK 8 and 11)
-
Install LLVM 11, Maven and CMake
brew install llvm@11 cmake maven
-
Set ENV
export LLVM11_HOME=/usr/local/opt/llvm@11
In Ubuntu:
sudo apt-get install llvm-11 clang-11 lld-11 libclang-11-dev libz-dev -y
Or use this script with LLVM11_HOME=/usr/lib/llvm-11
and LLVM_VAR=11.0.0
installing from source.
You need to write a CMakeLists.txt because the auto-generated C++ code depends on JNI library and other library you need.
By collecting what C++ code depend, you can provide a JNI dynamic-link library, so that the auto-generated C++ code can only rely on the JNI dynamic-link library.
First of all, the basic way to link dynamic library is:
try {
System.loadLibrary("lib-name");
}
There are some higher level way to link dynamic library by wrapping the basic way.
One way is use @FFIGen(library = "lib-name")
For example, if the original C++ code belongs to a third-party library, and you have linked this library in your JNI library, so you need specify the library as your JNI library like @FFIGen(library = "jni-lib")
.
@FFIGen(library = "jni-lib")
@FFITypeAlias("ns::Clz")
@CXXHead("xxx.h")
public interface Clz extends CXXPointer {}
An other way is using @FFIApplication(jniLibrary = "lib-name")
create a package-info.java
file in your package, telling needed library like:
@FFIApplication(jniLibrary = "jni-lib")
package com.alibaba.fastffi.demo.ffi;
import com.alibaba.fastffi.FFIApplication;
So that you don't need to specify library for @FFIGen
in the same package. Sample
You may use FFILibrary
designed for functions defined in a namespace. For any C function, it could be viewed as defined in the global empty namespace. Here is an example:
@FFIGen
@FFILibrary(value = "cmath", namespace = "")
@CXXHead("math.h")
public interface C {
double fabs(double v);
double pow(double x, double y);
}
Field value
in the @FFILibrary
is simply a key used to register an instance of the FFILibrary
in FFITypeFactory
.
To use the interface C
, we need to obtain an instance of the FFILibrary
via FFITypeFactory
.
import com.alibaba.fastffi.FFITypeFactory;
public class Main {
public static void main(String[]args) {
C clib = FFITypeFactory.getLibrary(C.class);
double a = 1.234;
double b = 2.345;
double c = -3.456;
System.out.println(clib.fabs(a));
System.out.println(clib.fabs(b));
System.out.println(clib.fabs(c));
System.out.println(clib.pow(a, b));
System.out.println(Math.pow(a, b));
}
}
For example, you need to use std::map
which fastffi.stdcxx
didn't provide. So you write StdMap like this:
@FFIGen
@CXXHead(
value = {"stdint.h"},
system = {"map"})
@FFITypeAlias("std::map")
@CXXTemplate(
cxx = {"std::string", "value_class"},
java = {"StdString", "ValueClass"})
public interface StdMap<K, V> extends FFIPointer {
@CXXOperator("[]")
@CXXReference V get(@CXXReference K key);
@FFIFactory
interface Factory<K, V> {
StdMap<K, V> create();
}
}
Unfortunately, you well meet error below:
stdcxx/StdMap.java:[28,8] java.lang.IllegalArgumentException: Cannot find type 'StdString' in the context of stdcxx.StdMap
To solve this, you must implement all stdcxx you need manually.
Beside, template in C++ is not same as generic in Java, so you need use @CXXTemplate
to specific needed class manually.
For example, there is a public string member in C++ called name
without set and get function.
Use @FFISetter
and @FFIGetter
like this:
@FFIGetter
@FFINameAlias("name")
@CXXValue
StdString getName();
@FFISetter
@FFINameAlias("name")
void setName(@CXXValue StdString name);
Pass C++ full name to method FFITypeFactory.getFactory
like this:
Factory<Long> LONG_FACTORY = FFITypeFactory.getFactory(StdVector.class, "std::vector<int64_t>");
In common case(enum's value is from 0 to n), you only need to implement getValue
method with ordinal
method like this:
@Override
public int getValue() {
return ordinal();
}
But for special C++ enum like this:
enum class AdjListType : std::uint8_t {
/// collection of edges by source, but unordered, can represent COO format
unordered_by_source = 0b00000001,
/// collection of edges by destination, but unordered, can represent COO
/// format
unordered_by_dest = 0b00000010,
/// collection of edges by source, ordered by source, can represent CSR format
ordered_by_source = 0b00000100,
/// collection of edges by destination, ordered by destination, can represent
/// CSC format
ordered_by_dest = 0b00001000,
};
It's obviously that enum's numbers are not continuous, so you need also pass value like this rather than from 0 to 3.
Overriding getValue
method is key of this problem, so you'd like write enum like this:
@FFITypeAlias("GraphArchive::AdjListType")
@FFITypeRefiner("com.alibaba.graphar.types.AdjListType.get")
public enum AdjListType implements CXXEnum {
// collection of edges by source, but unordered, can represent COO format
unordered_by_source((byte)0b00000001),
// collection of edges by destination, but unordered, can represent COO
// format
unordered_by_dest((byte)0b00000010),
// collection of edges by source, ordered by source, can represent CSR format
ordered_by_source((byte)0b00000100),
// collection of edges by destination, ordered by destination, can represent
// CSC format
ordered_by_dest((byte)0b00001000);
private final byte binaryNum;
AdjListType(byte binaryNum) {
this.binaryNum = binaryNum;
}
@Override
public int getValue() {
return binaryNum;
}
}
In auto-generate Java code, method witch accept enum parameter will pass enumObject.getvalue()
to native function like this:
@FFINameAlias("AddAdjList")
@CXXValue
public Status addAdjList(@CXXValue AdjListType adjListType, @CXXValue FileType fileType) {
long ret$ = nativeAddAdjList0(address, com.alibaba.fastffi.CXXValueScope.allocate(com.alibaba.graphar.utils.Status_cxx_0x42f3f706.SIZE),
adjListType.getValue(), fileType.getValue());
return (new com.alibaba.graphar.utils.Status_cxx_0x42f3f706(ret$));
}
By this way, value will be passed correctly.