Skip to content

Commit

Permalink
WIP: Graal native-image build of Java Schnorr Example
Browse files Browse the repository at this point in the history
To build native image use:

./gradlew secp256k1-examples-java:nativeCompile

Build currently fails on macOS with:

Error: Support for the Foreign Function and Memory API is currently available only on the AMD64 architecture

Note: the Arena.ofConfined() change should probably not be merged to `master`,
but is currently necessary to work with native-image.
  • Loading branch information
msgilligan committed Apr 15, 2024
1 parent 4304b81 commit 0cd5fae
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 1 deletion.
14 changes: 14 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ Run the script:

* `./secp256k1-examples-java/build/install/secp256k1-examples-java/bin/secp256k1-examples-java`

== Build and run a native image (AMD64-only with Graal JDK 22)

To build using GraalVM `native-image`:

. Make sure you have GraalVM 22 or later installed
. Make sure `GRAALVM_HOME` points to the Graal JDK 22 installation
. `./gradlew secp256k1-examples-java:nativeCompile`

To run the compiled, native executable:

. `export LD_LIBRARY_PATH="$HOME/.nix-profile/lib:$LD_LIBRARY_PATH"`
. `./secp256k1-examples-java/build/schnorr`
. Don't blink!

== Building with Nix

NOTE:: We currently only support setting up a development environment with Nix. In the future we hope to support a full Nix build.
Expand Down
38 changes: 38 additions & 0 deletions secp256k1-examples-java/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ dependencies {
implementation project(':secp256k1-api')
runtimeOnly project(':secp256k1-bouncy')
runtimeOnly project(':secp256k1-foreign')

// This is only needed for ForeignRegistrationFeature and the native-image build
implementation group: 'org.graalvm.sdk', name: 'nativeimage', version: '24.0.0'
}

jar {
Expand All @@ -34,3 +37,38 @@ run {
systemProperty "java.library.path", findProperty("javaPath") ?: "${userHome}/.nix-profile/lib"
jvmArgs += '--enable-native-access=org.bitcoinj.secp256k1.foreign'
}

configurations {
nativeToolImplementation.extendsFrom implementation
}

def mainClassName = "org.bitcoinj.secp256k1.examples.Schnorr"

jar {
manifest {
attributes 'Implementation-Title': 'Schnorr Signature Example',
'Main-Class': mainClassName,
'Implementation-Version': archiveVersion.get()
}
}

// Compile a native image using GraalVM's native-image tool
// Graal must be installed at $GRAALVM_HOME
tasks.register('nativeCompile', Exec) {
dependsOn jar
workingDir = projectDir
executable = "${System.env.GRAALVM_HOME}/bin/native-image"
args = ['--verbose',
'--no-fallback',
'-cp', "${-> configurations.nativeToolImplementation.asPath}", // Lazy configuration resolution
'-jar', jar.archiveFile.get(),
'-H:Path=build',
'-H:Name=schnorr',
'-H:+ForeignAPISupport',
'--features=org.bitcoinj.secp256k1.examples.ForeignRegistrationFeature',
'--enable-native-access=ALL-UNNAMED',
'-H:+ReportUnsupportedElementsAtRuntime',
'-H:+ReportExceptionStackTraces'
]
}

1 change: 1 addition & 0 deletions secp256k1-examples-java/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
*/
module org.bitcoinj.secp256k1.examples {
requires org.bitcoinj.secp256k1.api;
requires org.graalvm.nativeimage;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2023-2024 secp256k1-jdk Developers.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoinj.secp256k1.examples;

import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeForeignAccess;

import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;

import static java.lang.foreign.ValueLayout.*;

/**
*
*/
public class ForeignRegistrationFeature implements Feature {
public void duringSetup(Feature.DuringSetupAccess access) {
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.ofVoid());
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT));
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.of(JAVA_LONG, JAVA_INT));
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT, JAVA_INT));
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_LONG));
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_LONG, JAVA_LONG, JAVA_LONG));
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_LONG, JAVA_LONG, JAVA_LONG, JAVA_LONG));
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_LONG, JAVA_LONG, JAVA_INT));
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_LONG));
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.ofVoid(JAVA_LONG, JAVA_LONG));
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.ofVoid(JAVA_LONG));
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.ofVoid(JAVA_INT, JAVA_INT));
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.of(ADDRESS, JAVA_INT, JAVA_INT), Linker.Option.firstVariadicArg(1));
RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.ofVoid(JAVA_INT), Linker.Option.captureCallState("errno"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public Secp256k1Foreign(int flags) {
}

public Secp256k1Foreign(int flags, boolean randomize) {
arena = Arena.ofShared();
arena = Arena.ofConfined(); // Changed from `ofShared` for use in Graal native-image tools
/* Before we can call actual API functions, we need to create a "context". */
ctx = secp256k1_h.secp256k1_context_create(flags);

Expand Down

0 comments on commit 0cd5fae

Please sign in to comment.