-
Notifications
You must be signed in to change notification settings - Fork 10
FFM #54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FFM #54
Changes from 14 commits
9aee541
0d37138
6e0c6c0
d901156
fdd18ff
a39b78e
f231fc6
901a3e3
e6b90e9
2414cfc
c12ff57
7ac5b23
ba42cc6
8a7bedc
a0c0339
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,12 +4,14 @@ Generate [UniFFI](https://github.com/mozilla/uniffi-rs) bindings for Java. | |
|
|
||
| Official Kotlin bindings already exist, which can be used by any JVM language including Java. The Java specific bindings use Java-native types where possible for a more ergonomic interface, for example the Java bindings use `CompletableFutures` instead of `kotlinx.coroutines`. | ||
|
|
||
| Generated bindings use Java's [Foreign Function & Memory API](https://docs.oracle.com/en/java/javase/21/core/foreign-function-and-memory-api.html) (Project Panama) instead of JNA. See [benches/](benches/) for performance comparisons with Kotlin, Python, and Swift. | ||
|
|
||
| We highly reccommend you use [UniFFI's proc-macro definition](https://mozilla.github.io/uniffi-rs/latest/proc_macro/index.html) instead of UDL where possible. | ||
|
|
||
| ## Requirements | ||
|
|
||
| * Java 20+: `javac`, and `jar` | ||
| * The [Java Native Access](https://github.com/java-native-access/jna#download) JAR downloaded and its path added to your `$CLASSPATH` environment variable. | ||
| * Java 21+: `javac`, and `jar` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this needs to be 22
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the current shape of the FFM API is actually available in 21+, it left preview in 22+. PanamaPort lists 22+ for the Android support part though, so might as well list that as well. |
||
| * At runtime, the JVM must be allowed to use the Foreign Function & Memory API. For classpath-based applications, pass `--enable-native-access=ALL-UNNAMED` to `java`. For JPMS modules, use `--enable-native-access=your.module.name`. See [Java's documentation](https://docs.oracle.com/en/java/javase/25/core/restricted-methods.html#GUID-080FE2FA-F96A-4987-B4E1-A9F089D11B54__GUID-70A202F4-46C0-4D4D-8CD0-9D147854F776) for more information. | ||
|
|
||
| ## Installation | ||
|
|
||
|
|
@@ -115,7 +117,7 @@ Arguments: | |
|
|
||
| ## Integrating Bindings | ||
|
|
||
| After generation you'll have an `--out-dir` full of Java files. Package those into a `.jar` using your build tools of choice, and the result can be imported and used as per normal in any Java project with the `JNA` dependency available. | ||
| After generation you'll have an `--out-dir` full of Java files. Package those into a `.jar` using your build tools of choice, and the result can be imported and used as per normal in any Java project. The generated code uses the Foreign Function & Memory API (no external dependencies like JNA are required). | ||
|
|
||
| Any top level functions in the Rust library will be static methods in a class named after the crate. | ||
|
|
||
|
|
@@ -131,8 +133,7 @@ The generated Java can be configured using a `uniffi.toml` configuration file. | |
| | `custom_types` | | A map which controls how custom types are exposed to Java. See the [custom types section of the UniFFI manual](https://mozilla.github.io/uniffi-rs/latest/udl/custom_types.html#custom-types-in-the-bindings-code) | | ||
| | `external_packages` | | A map of packages to be used for the specified external crates. The key is the Rust crate name, the value is the Java package which will be used referring to types in that crate. See the [external types section of the manual](https://mozilla.github.io/uniffi-rs/latest/udl/ext_types_external.html#kotlin) | | ||
| | `rename` | | A map to rename types, functions, methods, and their members in the generated Java bindings. See the [renaming section](https://mozilla.github.io/uniffi-rs/latest/renaming.html). | | ||
| | `android` | `false` | Used to toggle on Android specific optimizations (warning: not well tested yet) | | ||
| | `android_cleaner` | `android` | Use the `android.system.SystemCleaner` instead of `java.lang.ref.Cleaner`. Fallback in both instances is the one shipped with JNA. | | ||
| | `android` | `false` | Generate [PanamaPort](https://github.com/vova7878/PanamaPort)-compatible code for Android. Replaces `java.lang.foreign.*` with `com.v7878.foreign.*` and `java.lang.invoke.VarHandle` with `com.v7878.invoke.VarHandle`. Requires PanamaPort `io.github.vova7878.panama:Core` as a runtime dependency and Android API 26+. | | ||
| | `omit_checksums` | `false` | Whether to omit checking the library checksums as the library is initialized. Changing this will shoot yourself in the foot if you mixup your build pipeline in any way, but might speed up initialization. | | ||
|
|
||
| ### Example | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| #!/usr/bin/env python3 | ||
| """Parse Criterion benchmark output files and produce comparison tables. | ||
|
|
||
| Usage: | ||
| python3 bench_compare.py "Column Title" path/to/bench.txt ["Title2" path2.txt ...] | ||
| python3 bench_compare.py --speedup "Faster" file1.txt "Slower" file2.txt | ||
| nix shell nixpkgs#python3 -c python3 bench_compare.py ... | ||
|
|
||
| The language prefix (e.g. "java-") is extracted from bench names and appended | ||
| to the column title automatically. | ||
|
|
||
| With --speedup, exactly two columns are expected and the output is a speedup | ||
| table showing how much faster the first column is vs the second (ratio > 1 | ||
| means first is faster, < 1 means second is faster). | ||
| """ | ||
|
|
||
| import re | ||
| import sys | ||
| from collections import OrderedDict | ||
|
|
||
| UNIT_TO_NS = {'ns': 1, 'µs': 1e3, 'us': 1e3, 'ms': 1e6, 's': 1e9} | ||
|
|
||
|
|
||
| def parse_bench_file(path): | ||
| """Parse a Criterion benchmark output file. | ||
|
|
||
| Returns (language, results) where results maps | ||
| (category, bench_name) -> nanoseconds as float. | ||
| """ | ||
| results = {} | ||
| current_bench = None | ||
| language = None | ||
|
|
||
| name_re = re.compile(r'^(\S+?)/(\w+?)-(\S+?)(?:\s+time:|\s*$)') | ||
| time_re = re.compile(r'time:\s+\[[\d.]+ \S+ ([\d.]+) (ns|µs|us|ms|s) [\d.]+ \S+\]') | ||
|
|
||
| with open(path) as f: | ||
| for line in f: | ||
| stripped = line.strip() | ||
|
|
||
| name_m = name_re.match(stripped) | ||
| if name_m: | ||
| language = language or name_m.group(2) | ||
| current_bench = (name_m.group(1), name_m.group(3)) | ||
|
|
||
| time_m = time_re.search(line) | ||
| if time_m and current_bench: | ||
| ns = float(time_m.group(1)) * UNIT_TO_NS[time_m.group(2)] | ||
| results[current_bench] = ns | ||
| current_bench = None | ||
|
|
||
| return language, results | ||
|
|
||
|
|
||
| def format_time(ns): | ||
| if ns < 1000: | ||
| return f"{ns:.2f} ns" if ns >= 10 else f"{ns:.1f} ns" | ||
| elif ns < 1e6: | ||
| return f"{ns / 1e3:.2f} us" | ||
| elif ns < 1e9: | ||
| return f"{ns / 1e6:.2f} ms" | ||
| else: | ||
| return f"{ns / 1e9:.2f} s" | ||
|
|
||
|
|
||
| def format_speedup(ratio): | ||
| if ratio >= 10: | ||
| return f"{ratio:.0f}x" | ||
| else: | ||
| return f"{ratio:.1f}x" | ||
|
|
||
|
|
||
| def collect_categories(all_results): | ||
| categories = OrderedDict() | ||
| for results in all_results: | ||
| for cat, bench in results: | ||
| if cat not in categories: | ||
| categories[cat] = OrderedDict() | ||
| if bench not in categories[cat]: | ||
| categories[cat][bench] = True | ||
| return categories | ||
|
|
||
|
|
||
| def print_comparison(columns, all_results): | ||
| categories = collect_categories(all_results) | ||
|
|
||
| for cat, benches in categories.items(): | ||
| print(f"### {cat}\n") | ||
| header = "Test Case | " + " | ".join(columns) | ||
| sep = "-- | " + " | ".join("--" for _ in columns) | ||
| print(header) | ||
| print(sep) | ||
|
|
||
| for bench in benches: | ||
| values = [] | ||
| for results in all_results: | ||
| ns = results.get((cat, bench)) | ||
| values.append(format_time(ns) if ns is not None else "") | ||
| print(f"{bench} | " + " | ".join(values)) | ||
|
|
||
| print() | ||
|
|
||
|
|
||
| def print_speedup(columns, all_results): | ||
| categories = collect_categories(all_results) | ||
| a_results, b_results = all_results | ||
| title = f"{columns[0]} vs {columns[1]} Speedup" | ||
|
|
||
| cat_names = list(categories.keys()) | ||
|
|
||
| print(f"### {title}\n") | ||
| header = "Test Case | " + " | ".join(f"{cat} Speedup" for cat in cat_names) | ||
| sep = "-- | " + " | ".join("--" for _ in cat_names) | ||
| print(header) | ||
| print(sep) | ||
|
|
||
| all_benches = OrderedDict() | ||
| for benches in categories.values(): | ||
| for b in benches: | ||
| all_benches[b] = True | ||
|
|
||
| for bench in all_benches: | ||
| values = [] | ||
| for cat in cat_names: | ||
| a_ns = a_results.get((cat, bench)) | ||
| b_ns = b_results.get((cat, bench)) | ||
| if a_ns is not None and b_ns is not None and a_ns > 0: | ||
| values.append(format_speedup(b_ns / a_ns)) | ||
| else: | ||
| values.append("") | ||
| print(f"{bench} | " + " | ".join(values)) | ||
|
|
||
| print() | ||
|
|
||
|
|
||
| def main(): | ||
| args = sys.argv[1:] | ||
|
|
||
| speedup = False | ||
| if "--speedup" in args: | ||
| speedup = True | ||
| args.remove("--speedup") | ||
|
|
||
| if len(args) < 2 or len(args) % 2 != 0: | ||
| print("Usage: bench_compare.py [--speedup] \"Title1\" file1.txt [\"Title2\" file2.txt ...]", | ||
| file=sys.stderr) | ||
| sys.exit(1) | ||
|
|
||
| if speedup and len(args) != 4: | ||
| print("--speedup requires exactly two title/file pairs", file=sys.stderr) | ||
| sys.exit(1) | ||
|
|
||
| columns = [] | ||
| all_results = [] | ||
| for i in range(0, len(args), 2): | ||
| title = args[i] | ||
| path = args[i + 1] | ||
| lang, results = parse_bench_file(path) | ||
| if lang: | ||
| title = f"{title} ({lang.capitalize()})" | ||
| columns.append(title) | ||
| all_results.append(results) | ||
|
|
||
| if speedup: | ||
| print_speedup(columns, all_results) | ||
| else: | ||
| print_comparison(columns, all_results) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
Uh oh!
There was an error while loading. Please reload this page.