diff --git a/.github/workflows/build_publish.yaml b/.github/workflows/build_publish.yaml new file mode 100644 index 0000000..fb993cd --- /dev/null +++ b/.github/workflows/build_publish.yaml @@ -0,0 +1,134 @@ +# This workflow compiles the native wasmer_jni lib into various OS and architectures using dynamic_libs_reusable.yaml. +# Then it builds and publishes the JAR based on the GITHUB_REF that calls the workflow. There are 2 options: +# 1. The workflow is executed from a branch. In that case the JAR is available in the GitHub action as an artifact. +# 2. The workflow is executed from a tag. In that case the JAR is published in the corresponding GitHub release. +name: Build and publish + +on: + push: + branches: + - '*' # Trigger on push to any branch + tags: + - '*' # Trigger on push to any tag + +jobs: + upload_dynamic_libs: + strategy: + matrix: + platform: + - os: 'macos-latest' + target: 'aarch64-apple-darwin' + artifact: 'darwin-arm64' + lib_name: 'libwasmer_jni.dylib' + + - os: 'macos-latest' + target: 'x86_64-apple-darwin' + artifact: 'darwin-amd64' + lib_name: 'libwasmer_jni.dylib' + + - os: 'ubuntu-latest' + target: 'x86_64-unknown-linux-gnu' + artifact: 'linux-amd64' + lib_name: 'libwasmer_jni.so' + + - os: 'windows-latest' + target: 'x86_64-pc-windows-msvc' + artifact: 'windows-amd64' + lib_name: 'wasmer_jni.dll' + uses: ./.github/workflows/dynamic_libs_reusable.yaml + with: + platform_os: ${{ matrix.platform.os }} + platform_target: ${{ matrix.platform.target }} + platform_artifact: ${{ matrix.platform.artifact }} + platform_lib_name: ${{ matrix.platform.lib_name }} + + publish_jar: + name: Publish the JARs + runs-on: ubuntu-latest + needs: [upload_dynamic_libs] + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Set up Java 21 + uses: actions/setup-java@v1 + with: + java-version: 21 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true + override: true + + - name: Cache Cargo registry + uses: actions/cache@v3 + with: + path: ~/.cargo/registry + key: cargo-registry-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache Cargo bin + uses: actions/cache@v3 + with: + path: ~/.cargo/bin + key: cargo-bin-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache Cargo build + uses: actions/cache@v3 + with: + path: target + key: cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + + - name: Setup wasmer-jni artifacts dir + shell: bash + run: | + echo "EXT_ARTIFACTS_DIR=$(mktemp -d)" >> $GITHUB_ENV + + - name: Download wasmer_jni artifacts + uses: actions/download-artifact@v4 + with: + path: ${{ env.EXT_ARTIFACTS_DIR }} + + - name: Display structure of downloaded files + run: ls -R $EXT_ARTIFACTS_DIR + + - name: Run all the tests + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + make test + + - name: Create the JAR + id: create_jar + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + make package + + - name: Upload JAR as workflow artifact + if: startsWith(github.ref, 'refs/heads/') + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.create_jar.outputs.name }} + path: ${{ steps.create_jar.outputs.path }} + retention-days: 1 + + - name: Get release info + id: get_release_info + if: startsWith(github.ref, 'refs/tags/') + uses: bruceadams/get-release@v1.3.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload JAR as Github pre-release asset + if: startsWith(github.ref, 'refs/tags/') + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release_info.outputs.upload_url }} + asset_path: ${{ steps.create_jar.outputs.path }} + asset_name: ${{ steps.create_jar.outputs.name }} + asset_content_type: application/java-archive diff --git a/.github/workflows/dynamic_libs_reusable.yaml b/.github/workflows/dynamic_libs_reusable.yaml new file mode 100644 index 0000000..fe5b6f9 --- /dev/null +++ b/.github/workflows/dynamic_libs_reusable.yaml @@ -0,0 +1,53 @@ +# This workflow cross compiles a rust library into a native based on a provided target. +# The compiled library is then uploaded as a GitHub artifact. +name: upload_dynamic_libs + +on: + workflow_call: + inputs: + platform_os: + description: Operating system that the runner will use. + type: string + required: true + platform_target: + description: The architecture that rust will compile the libs for. + type: string + required: true + platform_artifact: + description: The name of the artifact that will be uploaded. + type: string + required: true + platform_lib_name: + description: Name of the native library to be uploaded in the artifact. + type: string + required: true + + +jobs: + publish_dlibs: + name: Publish the dynamic libraries + runs-on: ${{ inputs.platform_os }} + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true + override: true + + - name: Create the dynamic libs + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + rustup target add ${{ inputs.platform_target }} + cargo build --release --target=${{ inputs.platform_target }} + + - name: Upload the dynamic libs + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.platform_artifact }} + path: ./target/${{ inputs.platform_target }}/release/${{ inputs.platform_lib_name }} + retention-days: 1 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index f77f0a5..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,130 +0,0 @@ -name: Release - -on: - push: - tags: - - '**' - -jobs: - create_pre_release: - name: Create pre-release - - runs-on: ubuntu-latest - - steps: - # The pre-release must be created only once, hence the split - # into multiple jobs with different `strategy`. - - name: Create a Github pre-release - id: create_pre_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: ${{ github.ref }} - draft: false - prerelease: true - - - name: Output `release_url` into a temporary file - run: echo "${{ steps.create_pre_release.outputs.upload_url }}" > release_url.txt - - - name: Save the `release_url` temporary file - uses: actions/upload-artifact@v1 - with: - name: release_url - path: release_url.txt - - publish_jar: - name: Publish the JARs - - needs: [create_pre_release] - - strategy: - matrix: - # The job runs on 3 different OS. - os: [ubuntu-latest, macos-latest, windows-latest] - # The job runs on different Java versions (LTS). - java: [8] - # As soon as one job fails in the matrix, all the other - # in-progress jobs are canceled. - fail-fast: true - - runs-on: ${{ matrix.os }} - - steps: - - name: Check out code - uses: actions/checkout@v2 - - - name: Set up Java ${{ matrix.version }} - uses: actions/setup-java@v1 - with: - java-version: ${{ matrix.java }} - - - name: Set up Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - default: true - override: true - - - name: Cache Cargo registry - uses: actions/cache@v1 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache Cargo bin - uses: actions/cache@v1 - with: - path: ~/.cargo/bin - key: ${{ runner.os }}-cargo-bin-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache Cargo build - uses: actions/cache@v1 - with: - path: target - key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - - name: Run all the tests - shell: bash - run: | - export PATH="$HOME/.cargo/bin:$PATH" - make test - - - name: Create the JAR - id: create_jar - shell: bash - run: | - export PATH="$HOME/.cargo/bin:$PATH" - make package - - - name: Load the `release_url` from the temporary file - uses: actions/download-artifact@v1 - with: - name: release_url - - - name: Read the `release_url` temporary file - id: get_release_info - shell: bash - run: | - value=$(cat release_url/release_url.txt) - echo ::set-output name=upload_url::$value - - - name: Upload JAR as Github pre-release asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.get_release_info.outputs.upload_url }} - asset_path: ${{ steps.create_jar.outputs.path }} - asset_name: ${{ steps.create_jar.outputs.name }} - asset_content_type: application/java-archive - - - name: Upload JAR to Bintray (JCenter) - env: - BINTRAY_USER: ${{ secrets.BINTRAY_USER }} - BINTRAY_API_KEY: ${{ secrets.BINTRAY_API_KEY }} - shell: bash - run: | - export PATH="$HOME/.cargo/bin:$PATH" - make publish diff --git a/.github/workflows/test.yml b/.github/workflows/test.yaml similarity index 91% rename from .github/workflows/test.yml rename to .github/workflows/test.yaml index 87591e8..7b3095f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yaml @@ -10,9 +10,9 @@ jobs: strategy: matrix: # The job runs on 3 different OS. - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-13, windows-latest] # The job runs on different Java versions (LTS). - java: [8] + java: [21] # As soon as one job fails in the matrix, all the other # in-progress jobs are canceled. fail-fast: true diff --git a/.gitignore b/.gitignore index d590423..726eb70 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ /build /include/org/ /target -/.idea/ \ No newline at end of file +/.idea/ +.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0d27064..bb9595b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -1157,7 +1157,7 @@ dependencies = [ [[package]] name = "wasmer-jni" -version = "0.3.0" +version = "1.1.1" dependencies = [ "jni", "wasmer", diff --git a/Cargo.toml b/Cargo.toml index 8fdb281..7cbbedd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,8 @@ [package] publish = false name = "wasmer-jni" -version = "0.3.0" -authors = ["Ivan Enderlin "] -edition = "2018" +version = "1.1.1" +edition = "2021" [lib] crate-type = ["cdylib"] diff --git a/Makefile b/Makefile index 8e14e63..8affd2a 100644 --- a/Makefile +++ b/Makefile @@ -13,11 +13,12 @@ else endif ARCH := $(shell uname -m) - ifeq ($(ARCH),x86_64) build_arch = amd64 + else ifeq ($(ARCH),arm64) + build_arch = arm64 else - $(error Architecture not supported yet) + $(error Architecture not supported yet) endif endif @@ -30,7 +31,7 @@ build: build-headers build-rust build-java build-rust: build-rust-$(build_arch)-$(build_os) # Compile the Rust part. -build-rust-all-targets: build-rust-amd64-darwin build-rust-amd64-linux build-rust-amd64-windows +build-rust-all-targets: build-rust-amd64-darwin build-rust-arm64-darwin build-rust-amd64-linux build-rust-amd64-windows build-rust-amd64-darwin: rustup target add x86_64-apple-darwin @@ -40,6 +41,14 @@ build-rust-amd64-darwin: install_name_tool -id "@rpath/libwasmer_jni.dylib" ./artifacts/darwin-amd64/libwasmer_jni.dylib test -h target/current || ln -s x86_64-apple-darwin/release target/current +build-rust-arm64-darwin: + rustup target add aarch64-apple-darwin + cargo build --release --target=aarch64-apple-darwin + mkdir -p artifacts/darwin-arm64 + cp target/aarch64-apple-darwin/release/libwasmer_jni.dylib artifacts/darwin-arm64 + install_name_tool -id "@rpath/libwasmer_jni.dylib" ./artifacts/darwin-arm64/libwasmer_jni.dylib + test -h target/current || ln -s aarch64-apple-darwin/release target/current + build-rust-amd64-linux: rustup target add x86_64-unknown-linux-gnu cargo build --release --target=x86_64-unknown-linux-gnu @@ -72,6 +81,9 @@ test-rust: test-rust-$(build_arch)-$(build_os) test-rust-amd64-darwin: cargo test --lib --release --target=x86_64-apple-darwin +test-rust-arm64-darwin: + cargo test --lib --release --target=aarch64-apple-darwin + test-rust-amd64-linux: cargo test --lib --release --target=x86_64-unknown-linux-gnu @@ -85,7 +97,7 @@ test-java: # Test the examples. test-examples: @for example in $(shell find examples -name "*Example.java") ; do \ - example=$${example#examples/}; \ + example=$${example%examples/}; \ example=$${example%Example.java}; \ echo "Testing $${example}"; \ make run-example EXAMPLE=$${example}; \ @@ -100,10 +112,6 @@ javadoc: package: "./gradlew" --info jar -# Publish the package artifact to a public repository -publish: - "./gradlew" --info uploadToBintray - # Run a specific example, with `make run-example EXAMPLE=Simple` for instance. run-example: $(eval JAR := $(shell find ./build/libs/ -name "wasmer-jni-*.jar")) @@ -111,6 +119,13 @@ run-example: javac -classpath "../${JAR}" ${EXAMPLE}Example.java; \ java -Djava.library.path=$(CURDIR)/artifacts/$(build_os)-$(build_arch) -classpath ".:../${JAR}" -enableassertions ${EXAMPLE}Example +# Runs the main class, cd to java is necessary because java.org.wasmer is a "Prohibited package name" +run-main: + $(eval JAR := $(shell find ./build/libs/ -name "wasmer-jni-*.jar")) + @cd src; \ + javac -sourcepath ./java -classpath "./${JAR}" java/org/wasmer/Main.java; \ + cd java && java -Djava.library.path=$(CURDIR)/artifacts/$(build_os)-$(build_arch) -classpath ".:../${JAR}" org/wasmer/Main; \ + find org -type f -name "*.class" -delete # Clean clean: cargo clean diff --git a/README.md b/README.md index 8fcf567..0d05d7c 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,6 @@ License - - Go Package - API Documentation @@ -46,17 +43,13 @@ Features: # Install -The Wasmer package is published in [Bintray](https://bintray.com/) on [the -`wasmer/wasmer-jni` -repository](https://bintray.com/wasmer/wasmer-jni/wasmer-jni). - The JAR files are named as follows: -`wasmer-jni-$(architecture)-$(os)-$(version).jar`. Thus, to include +`wasmer-jni-$(version).jar`. Thus, to include Wasmer JNI as a dependency, write for instance: ```gradle dependencies { - implementation "org.wasmer:wasmer-jni-amd64-linux:0.3.0" + implementation "org.wasmer:wasmer-jni:1.1.1" } ``` @@ -66,9 +59,13 @@ releases page](https://github.com/wasmerio/wasmer-java/releases)! architecture, see [the Development Section](#development) to learn more. +# Main + +To compile and execute the main class located at `src/java/org/wasmer/Main.java` run the following command: `make run-main` + # Example -There is a toy program in `java/src/test/resources/simple.rs`, written +There is a toy program in `test/resources/simple.rs`, written in Rust (or any other language that compiles to WebAssembly): ```rust @@ -108,8 +105,8 @@ class Example { } ``` -There is more examples in the `examples/` directory. Run them with the -`Makefile`, such as: `make run-example EXAMPLE=Simple` to run the +There are more examples in the `examples/` directory. Run them with the +`Makefile`, such as: `make run-example EXAMPLE=Import` to run the `ImportExample` example. # API of the `wasmer` library @@ -305,61 +302,13 @@ To build the JAR package: $ make package ``` -This will generate the file `build/libs/wasmer-jni-$(architecture)-$(os)-0.3.0.jar`. - -#### Automatic dependencies per architecture and platform - -
-It is possible to infer the archive appendix automatically, see how. - -According the [Gradle Jar -API](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html#org.gradle.api.tasks.bundling.Jar:appendix), -the `$(architecture)-$(os)` part is called the _archive prefix_. To -infer that appendix automatically to configure your dependencies, you -can use the following `inferWasmerJarAppendix` function: - -```gradle -String inferWasmerJarAppendix() { - def nativePlatform = new org.gradle.nativeplatform.platform.internal.DefaultNativePlatform("current") - def arch = nativePlatform.architecture - def os = nativePlatform.operatingSystem - - def arch_name - - switch (arch.getName()) { - case ["x86_64", "x64", "x86-64"]: - arch_name = "amd64" - break; - - default: - throw new RuntimeException("`wasmer-jni` has no pre-compiled archive for the architecture " + arch.getName()) - } - - def os_name - - if (os.isMacOsX()) { - os_name = "darwin" - } else if (os.isLinux()) { - os_name = "linux" - } else if (os.isWindows()) { - os_name = "windows" - } else { - throw new RuntimeException("`wasmer-jni` has no pre-compiled archive for the platform " + os.getName()) - } - - return arch_name + "-" + os_name -} -``` - -Finally, you can configure your dependencies such as: +This will generate the file `build/libs/wasmer-jni-1.1.1.jar`. -```gradle -dependencies { - implementation "org.wasmer:wasmer-jni-" + inferWasmerJarAppendix() + ":0.3.0" -} -``` +### Native `wasmer-jni` inclusion flow. -
+You can export the environment variable `EXT_ARTIFACTS_DIR` in the terminal that you are building the project from. +It should point to the directory where you've put your `wasmer-jni` native libs. If the environment variable is missing +gradle will copy the contents of the `artifacts` folder, which is generated by the `make build-rust` command. ### Testing diff --git a/build.gradle b/build.gradle index b3903ae..c4b4469 100644 --- a/build.gradle +++ b/build.gradle @@ -2,19 +2,18 @@ plugins { id "java" // As Wasmer is a package, we need tools to build the JARs and so id "java-library" - id "com.jfrog.bintray" version "1.8.5" } allprojects { group "org.wasmer" - version "0.3.0" + version "1.1.1" } // This is needed for the Java plugin to make sure // the generated class files are compatible with // old versions of Java. -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +sourceCompatibility = 21 +targetCompatibility = 21 sourceSets { main { @@ -66,41 +65,6 @@ jar { attributes("Implementation-Title": project.name, "Implementation-Version": project.version) } - - // The JAR name is defined as - // `${baseName}-${appendix}-${version}-${classifier}.${extension}`. - archiveAppendix = inferWasmerJarAppendix() -} - -String inferWasmerJarAppendix() { - def nativePlatform = new org.gradle.nativeplatform.platform.internal.DefaultNativePlatform("current") - def arch = nativePlatform.architecture - def os = nativePlatform.operatingSystem - - def arch_name - - switch (arch.getName()) { - case ["x86_64", "x64", "x86-64"]: - arch_name = "amd64" - break; - - default: - throw new RuntimeException("Architecture " + arch.getName() + " is not supported.") - } - - def os_name - - if (os.isMacOsX()) { - os_name = "darwin" - } else if (os.isLinux()) { - os_name = "linux" - } else if (os.isWindows()) { - os_name = "windows" - } else { - throw new RuntimeException("Platform " + os.getName() + " is not supported.") - } - - return arch_name + "-" + os_name } task generateJniHeaders(type: JavaCompile) { @@ -126,10 +90,17 @@ task buildRust(type: Exec) { task copyAllArtifacts(type: Copy) { dependsOn buildRust - description "Copy build artifacts to the `build/` directory." + description "Copy artifacts to the `build/` directory. If a non-empty directory exists at the value of " + + "'EXT_ARTIFACTS_DIR' env variable it copies its contents, otherwise it copies from the artifacts folder." + + def extArtifactsDir = System.getenv('EXT_ARTIFACTS_DIR') + def sourceDir = extArtifactsDir != null && !extArtifactsDir.isBlank() + ? extArtifactsDir + : 'artifacts' + + from(sourceDir) + include '**/*' - from "artifacts" - include "**/*" into "$buildDir/toArtifact/org/wasmer/native/" } @@ -148,7 +119,7 @@ tasks.withType(Test) { jar.doLast() { // Display specific “action outputs” for Github Actions. - def jar_name = project.archivesBaseName + "-" + inferWasmerJarAppendix() + "-" + project.version + ".jar" + def jar_name = project.archivesBaseName + "-" + project.version + ".jar" println(jar_name) println("::set-output name=path::./build/libs/" + jar_name) println("::set-output name=name::" + jar_name) diff --git a/examples/ImportExample.java b/examples/ImportExample.java index f0dc14c..a8345cf 100644 --- a/examples/ImportExample.java +++ b/examples/ImportExample.java @@ -1,8 +1,9 @@ +import org.wasmer.ImportObject; import org.wasmer.Imports; import org.wasmer.Instance; -import org.wasmer.MemoryType; import org.wasmer.Module; import org.wasmer.Type; +import org.wasmer.Memory; import java.io.IOException; import java.nio.file.Files; @@ -18,22 +19,22 @@ public static void main(String[] args) throws IOException { Module module = new Module(bytes); System.out.println("Creating import object"); Imports imports = Imports.from(Arrays.asList( - new Imports.Spec("env", "ext_storage_set_version_1", argv -> { + new ImportObject.FuncImport("env", "ext_storage_set_version_1", argv -> { System.out.println("Message printed in the body of 'ext_storage_set_version_1'"); return argv; }, Arrays.asList(Type.I64, Type.I64), Collections.emptyList()), - new Imports.Spec("env", "ext_storage_get_version_1", argv -> { + new ImportObject.FuncImport("env", "ext_storage_get_version_1", argv -> { System.out.println("Message printed in the body of 'ext_storage_get_version_1'"); return argv; - }, - Collections.singletonList(Type.I64), Collections.singletonList(Type.I64))), - new MemoryType("env", "memory", false, 45), module); + }, Collections.singletonList(Type.I64), Collections.singletonList(Type.I64)), + new ImportObject.MemoryImport("env", 20, false)), module); System.out.println("Instantiating module"); Instance instance = module.instantiate(imports); System.out.println("Calling exported function 'Core_initialize_block' as it calls both of the imported functions"); instance.exports.getFunction("Core_initialize_block").apply(1,2); + Memory memory = instance.exports.getMemory("memory"); instance.close(); } } diff --git a/examples/polkadot_runtime-v9360.compact.compressed.wasm b/examples/polkadot_runtime-v9360.compact.compressed.wasm deleted file mode 100644 index 7925d43..0000000 Binary files a/examples/polkadot_runtime-v9360.compact.compressed.wasm and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f42e62f..28f5fcf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/include/org_wasmer_Imports.h b/include/org_wasmer_Imports.h index c359c8b..96d1820 100644 --- a/include/org_wasmer_Imports.h +++ b/include/org_wasmer_Imports.h @@ -10,10 +10,10 @@ extern "C" { /* * Class: org_wasmer_Imports * Method: nativeImportsInstantiate - * Signature: (Ljava/util/List;Lorg/wasmer/MemoryType;J)J + * Signature: (Ljava/util/List;J)J */ JNIEXPORT jlong JNICALL Java_org_wasmer_Imports_nativeImportsInstantiate - (JNIEnv *, jclass, jobject, jobject, jlong); + (JNIEnv *, jclass, jobject, jlong); /* * Class: org_wasmer_Imports @@ -31,14 +31,6 @@ JNIEXPORT jlong JNICALL Java_org_wasmer_Imports_nativeImportsChain JNIEXPORT jlong JNICALL Java_org_wasmer_Imports_nativeImportsWasi (JNIEnv *, jclass, jlong); -/* - * Class: org_wasmer_Imports - * Method: nativeDrop - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_org_wasmer_Imports_nativeDrop - (JNIEnv *, jclass, jlong); - #ifdef __cplusplus } #endif diff --git a/include/org_wasmer_Instance.h b/include/org_wasmer_Instance.h index 3747295..747048c 100644 --- a/include/org_wasmer_Instance.h +++ b/include/org_wasmer_Instance.h @@ -47,6 +47,14 @@ JNIEXPORT void JNICALL Java_org_wasmer_Instance_nativeInitializeExportedFunction JNIEXPORT void JNICALL Java_org_wasmer_Instance_nativeInitializeExportedMemories (JNIEnv *, jclass, jlong); +/* + * Class: org_wasmer_Instance + * Method: nativeInitializeExportedGlobals + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_wasmer_Instance_nativeInitializeExportedGlobals + (JNIEnv *, jclass, jlong); + #ifdef __cplusplus } #endif diff --git a/include/org_wasmer_Util.h b/include/org_wasmer_Util.h new file mode 100644 index 0000000..72ed6cf --- /dev/null +++ b/include/org_wasmer_Util.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_wasmer_Util */ + +#ifndef _Included_org_wasmer_Util +#define _Included_org_wasmer_Util +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_wasmer_Util + * Method: nativePanic + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_org_wasmer_Util_nativePanic + (JNIEnv *, jclass, jstring); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/exception.rs b/src/exception.rs index 5874eba..f57028b 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -1,22 +1,56 @@ +use std::num::TryFromIntError; +use std::str::Utf8Error; use jni::JNIEnv; use std::thread; use jni::errors::Error as JNIError; +use wasmer::MemoryError; +use wasmer_wasi::{WasiError, WasiStateCreationError}; #[derive(Debug)] pub enum Error { JNIError(JNIError), Message(String), + WasiError(WasiError), + WasiStateCreationError(WasiStateCreationError), + MemoryError(MemoryError), + TryFromIntError(TryFromIntError), + Utf8Error(Utf8Error) } impl From for Error { fn from(err: JNIError) -> Self { Self::JNIError(err) } } +impl From for Error { + fn from(err: WasiError) -> Self { Self::WasiError(err) } +} + +impl From for Error { + fn from(err: WasiStateCreationError) -> Self { Self::WasiStateCreationError(err) } +} + +impl From for Error { + fn from(err: MemoryError) -> Self { Self::MemoryError(err) } +} + +impl From for Error { + fn from(err: TryFromIntError) -> Self { Self::TryFromIntError(err) } +} + +impl From for Error { + fn from(err: Utf8Error) -> Self { Self::Utf8Error(err) } +} + impl std::fmt::Display for Error { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { match self { Error::JNIError(err) => err.fmt(fmt), Error::Message(msg) => msg.fmt(fmt), + Error::WasiError(err) => err.fmt(fmt), + Error::WasiStateCreationError(err) => err.fmt(fmt), + Error::MemoryError(err) => err.fmt(fmt), + Error::TryFromIntError(err) => err.fmt(fmt), + Error::Utf8Error(err) => err.fmt(fmt), } } } @@ -47,7 +81,7 @@ pub fn joption_or_throw(env: &JNIEnv, result: thread::Result Err(error) => { if !env.exception_check().unwrap() { env.throw_new("java/lang/RuntimeException", &error.to_string()) - .expect("Cannot throw an `java/lang/RuntimeException` exception."); + .expect("Cannot throw a `java/lang/RuntimeException` exception."); } JOption::None @@ -55,7 +89,7 @@ pub fn joption_or_throw(env: &JNIEnv, result: thread::Result }, Err(ref error) => { env.throw_new("java/lang/RuntimeException", format!("{:?}", error)) - .expect("Cannot throw an `java/lang/RuntimeException` exception."); + .expect("Cannot throw a `java/lang/RuntimeException` exception."); JOption::None } diff --git a/src/imports.rs b/src/imports.rs index 48cf4d0..dd8dcc5 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -10,8 +10,10 @@ use jni::{ }; use std::{collections::HashMap, panic}; use std::convert::TryFrom; +use std::sync::Arc; use wasmer::{ImportObject, NamedResolver, ChainableNamedResolver, Exports, Function, FunctionType, Type, Value, Memory, MemoryType}; use wasmer_wasi::WasiState; +use crate::memory::{IMPORTED_MEMORY, Memory as MemoryWrapper}; pub struct Imports { pub(crate) import_object: Box, @@ -31,8 +33,7 @@ fn array2vec<'a, T: TypeArray>(array: &'a AutoArray) -> Vec<&'a T> { pub extern "system" fn Java_org_wasmer_Imports_nativeImportsInstantiate( env: JNIEnv, _class: JClass, - spec: JObject, - memory: JObject, + imports: JObject, module: jptr, ) -> jptr { let output = panic::catch_unwind(|| { @@ -40,77 +41,76 @@ pub extern "system" fn Java_org_wasmer_Imports_nativeImportsInstantiate( let mut import_object = ImportObject::new(); let module: &Module = Into::>::into(module).borrow(); let store = module.module.store(); - let spec = env.get_list(spec).unwrap(); - - let memory_type = if !memory.is_null() { - let min_pages = env.get_field(memory, "minPages", "I").unwrap().i().unwrap(); - MemoryType::new(u32::try_from(min_pages).unwrap(), None, false) - } else { - unimplemented!(); - }; - let memory_namespace = env.get_field(memory, "namespace", "Ljava/lang/String;").unwrap().l().unwrap(); - let memory_namespace = env.get_string(memory_namespace.into()).unwrap().to_str().unwrap().to_owned(); - let memory_name = env.get_field(memory, "name", "Ljava/lang/String;").unwrap().l().unwrap(); - let memory_name = env.get_string(memory_name.into()).unwrap().to_str().unwrap().to_owned(); - - for import in spec.iter().unwrap() { - let namespace = env.get_field(import, "namespace", "Ljava/lang/String;").unwrap().l().unwrap(); - let namespace = env.get_string(namespace.into()).unwrap().to_str().unwrap().to_string(); - let name = env.get_field(import, "name", "Ljava/lang/String;").unwrap().l().unwrap(); - let name = env.get_string(name.into()).unwrap().to_str().unwrap().to_string(); - let function = env.get_field(import, "function", "Ljava/util/function/Function;").unwrap().l().unwrap(); - let params = env.get_field(import, "argTypesInt", "[I").unwrap().l().unwrap(); - let returns = env.get_field(import, "retTypesInt", "[I").unwrap().l().unwrap(); - let params = env.get_int_array_elements(*params, ReleaseMode::NoCopyBack).unwrap(); - let returns = env.get_int_array_elements(*returns, ReleaseMode::NoCopyBack).unwrap(); - let i2t = |i: &i32| match i { 1 => Type::I32, 2 => Type::I64, 3 => Type::F32, 4 => Type::F64, _ => unreachable!("Unknown {}", i)}; - let params = array2vec(¶ms).into_iter().map(i2t).collect::>(); - let returns = array2vec(&returns).into_iter().map(i2t).collect::>(); - let sig = FunctionType::new(params.clone(), returns.clone()); - let function = env.new_global_ref(function).unwrap(); - let jvm = env.get_java_vm().unwrap(); - namespaces.entry(namespace).or_insert_with(|| Exports::new()).insert(name, Function::new(store, sig, move |argv| { - // There is many ways of transferring the args from wasm to java, JList being the cleanes, - // but probably also slowest by far (two JNI calls per argument). Benchmark? - let env = jvm.get_env().unwrap(); - env.ensure_local_capacity(argv.len() as i32 + 2).ok(); - let jargv = env.new_long_array(argv.len() as i32).unwrap(); - let argv = argv.into_iter().enumerate().map(|(i, arg)| match arg { - Value::I32(arg) => { assert!(params[i] == Type::I32); *arg as i64 }, - Value::I64(arg) => { assert!(params[i] == Type::I64); *arg as i64 }, - Value::F32(arg) => { assert!(params[i] == Type::F32); arg.to_bits() as i64 }, - Value::F64(arg) => { assert!(params[i] == Type::F64); arg.to_bits() as i64 }, - _ => panic!("Argument of unsupported type {:?}", arg) - }).collect::>(); - env.set_long_array_region(jargv, 0, &argv).unwrap(); - let jret = env.call_method(function.as_obj(), "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", &[jargv.into()]).unwrap().l().unwrap(); - let ret = match returns.len() { - 0 => vec![], - len => { - let mut ret = vec![0; len]; - env.get_long_array_region(*jret, 0, &mut ret).unwrap(); - ret.into_iter().enumerate().map(|(i, ret)| match returns[i] { - Type::I32 => Value::I32(ret as i32), - Type::I64 => Value::I64(ret as i64), - Type::F32 => Value::F32(f32::from_bits(ret as u32)), - Type::F64 => Value::F64(f64::from_bits(ret as u64)), - t => panic!("Return of unsupported type {:?}", t) - }).collect() - } + let imports = env.get_list(imports)?; + + for import in imports.iter()? { + let namespace = env.get_field(import, "namespace", "Ljava/lang/String;")?.l()?; + let namespace = env.get_string(namespace.into())?.to_str()?.to_string(); + let name = env.get_field(import, "name", "Ljava/lang/String;")?.l()?; + let name = env.get_string(name.into())?.to_str()?.to_string(); + + if name == "memory" { + let min_pages = env.get_field(import, "minPages", "I")?.i()?; + let max_pages = env.get_field(import, "maxPages", "Ljava/lang/Integer;")?.l()?; + let max_pages = if max_pages.is_null() { + None + } else { + //have to get the field again if not null as it cannot be cast to int + let max_pages = env.get_field(import, "maxPages", "I")?.i()?; + Some(u32::try_from(max_pages)?) }; - // TODO: Error handling - Ok(ret) - })); + let shared = env.get_field(import, "shared", "Z")?.z()?; + let memory_type = MemoryType::new(u32::try_from(min_pages)?, max_pages, shared); + let memory = Memory::new(&store, memory_type)?; + IMPORTED_MEMORY.lock().unwrap().replace(MemoryWrapper::new(Arc::new(memory.clone()))); + namespaces.entry(namespace).or_insert_with(|| Exports::new()).insert(name, memory) + } else { + let function = env.get_field(import, "function", "Ljava/util/function/Function;")?.l()?; + let params = env.get_field(import, "argTypesInt", "[I")?.l()?; + let returns = env.get_field(import, "retTypesInt", "[I")?.l()?; + let params = env.get_int_array_elements(*params, ReleaseMode::NoCopyBack)?; + let returns = env.get_int_array_elements(*returns, ReleaseMode::NoCopyBack)?; + let i2t = |i: &i32| match i { 1 => Type::I32, 2 => Type::I64, 3 => Type::F32, 4 => Type::F64, _ => unreachable!("Unknown {}", i)}; + let params = array2vec(¶ms).into_iter().map(i2t).collect::>(); + let returns = array2vec(&returns).into_iter().map(i2t).collect::>(); + let sig = FunctionType::new(params.clone(), returns.clone()); + let function = env.new_global_ref(function)?; + let jvm = env.get_java_vm()?; + namespaces.entry(namespace).or_insert_with(|| Exports::new()).insert(name, Function::new(store, sig, move |argv| { + // There are many ways of transferring the args from wasm to java, JList being the cleanest, + // but probably also slowest by far (two JNI calls per argument). Benchmark? + let env = jvm.get_env().expect("Couldn't get JNIEnv"); + env.ensure_local_capacity(argv.len() as i32 + 2).ok(); + let jargv = env.new_long_array(argv.len() as i32).expect("Couldn't create array"); + let argv = argv.into_iter().enumerate().map(|(i, arg)| match arg { + Value::I32(arg) => { assert_eq!(params[i], Type::I32); *arg as i64 }, + Value::I64(arg) => { assert_eq!(params[i], Type::I64); *arg as i64 }, + Value::F32(arg) => { assert_eq!(params[i], Type::F32); arg.to_bits() as i64 }, + Value::F64(arg) => { assert_eq!(params[i], Type::F64); arg.to_bits() as i64 }, + _ => panic!("Argument of unsupported type {:?}", arg) + }).collect::>(); + env.set_long_array_region(jargv, 0, &argv).expect("Couldn't set array region"); + let jret = env.call_method(function.as_obj(), "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", &[jargv.into()]) + .expect("Couldn't call 'apply' function").l().expect("Failed to unwrap object"); + let ret = match returns.len() { + 0 => vec![], + len => { + let mut ret = vec![0; len]; + env.get_long_array_region(*jret, 0, &mut ret).expect("Couldn't get array region"); + ret.into_iter().enumerate().map(|(i, ret)| match returns[i] { + Type::I32 => Value::I32(ret as i32), + Type::I64 => Value::I64(ret as i64), + Type::F32 => Value::F32(f32::from_bits(ret as u32)), + Type::F64 => Value::F64(f64::from_bits(ret as u64)), + t => panic!("Return of unsupported type {:?}", t) + }).collect() + } + }; + Ok(ret) + })); + } } - if namespaces.contains_key(memory_namespace.as_str()) { - let env_namespace = namespaces.entry(memory_namespace); - env_namespace.or_insert_with(|| Exports::new()).insert(memory_name, Memory::new(&store, memory_type).unwrap()); - } else { - let mut exports = Exports::new(); - exports.insert(memory_name, Memory::new(&store, memory_type).unwrap()); - namespaces.insert(memory_namespace, exports); - } for (namespace, exports) in namespaces.into_iter() { import_object.register(namespace, exports); } @@ -129,10 +129,9 @@ pub extern "system" fn Java_org_wasmer_Imports_nativeImportsWasi( module: jptr, ) -> jptr { let output = panic::catch_unwind(|| { - // TODO: When getting serious about this, one might have to expose the wasi builder... :/ let module: &Module = Into::>::into(module).borrow(); - let mut wasi = WasiState::new("").finalize().unwrap(); - let import_object = wasi.import_object(&module.module).unwrap(); + let mut wasi = WasiState::new("").finalize()?; + let import_object = wasi.import_object(&module.module)?; let import_object = Box::new(import_object); Ok(Pointer::new(Imports { import_object }).into()) @@ -149,7 +148,6 @@ pub extern "system" fn Java_org_wasmer_Imports_nativeImportsChain( front: jptr, ) -> jptr { let output = panic::catch_unwind(|| { - let back: &Imports = Into::>::into(back).borrow(); let front: &Imports = Into::>::into(front).borrow(); let import_object = Box::new((&back.import_object).chain_front(&front.import_object)); @@ -158,14 +156,4 @@ pub extern "system" fn Java_org_wasmer_Imports_nativeImportsChain( }); joption_or_throw(&env, output).unwrap_or(0) - -} - -#[no_mangle] -pub extern "system" fn Java_org_wasmer_Imports_nativeDrop( - _env: JNIEnv, - _class: JClass, - imports_pointer: jptr, -) { - let _: Pointer = imports_pointer.into(); } diff --git a/src/instance.rs b/src/instance.rs index 43cb5f7..b680c6d 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -11,7 +11,8 @@ use jni::{ JNIEnv, }; use std::{collections::HashMap, convert::TryFrom, panic, rc::Rc}; -use wasmer::{imports, Extern, Value as WasmValue, Function}; +use std::sync::Arc; +use wasmer::{imports, Extern, Value as WasmValue, Function, Exportable, Global, Type}; use wasmer as core; use wasmer_compiler_cranelift::Cranelift; use wasmer_engine_universal::Universal as UniversalEngine; @@ -43,7 +44,7 @@ impl Instance { .exports .iter() .filter_map(|(export_name, export)| match export { - Extern::Memory(memory) => Some((export_name.to_string(), Memory::new(Rc::new(memory.clone())))), + Extern::Memory(memory) => Some((export_name.to_string(), Memory::new(Arc::new(memory.clone())))), _ => None, }) .collect(); @@ -224,3 +225,66 @@ pub extern "system" fn Java_org_wasmer_Instance_nativeInitializeExportedMemories joption_or_throw(&env, output).unwrap_or(()) } + +#[no_mangle] +pub extern "system" fn Java_org_wasmer_Instance_nativeInitializeExportedGlobals( + env: JNIEnv, + _class: JClass, + instance_pointer: jptr, +) { + let output = panic::catch_unwind(|| { + let instance: &Instance = Into::>::into(instance_pointer).borrow(); + + // Get the `org.wasmer.Global` class. + let global_class = env.find_class("org/wasmer/Global")?; + let exports_object: JObject = env + .get_field( + instance.java_instance_object.as_obj(), + "exports", + "Lorg/wasmer/Exports;", + )? + .l()?; + + for (export_name, export) in instance.instance.exports.iter() { + if let Extern::Global { .. } = export { + let global = Global::get_self_from_extern(export).unwrap(); + let name = env.new_string(export_name)?; + let global_object = env.new_object(global_class, "()V", &[])?; + + // Sets the two fields of the Global class - value and type + match global.get().ty() { + Type::I32 => { + env.set_field(global_object, "value", "Ljava/lang/String;", env.new_string(global.get().unwrap_i32().to_string())?.into())?; + env.set_field(global_object, "type", "Ljava/lang/String;", env.new_string("i32")?.into())? + }, + Type::I64 => { + env.set_field(global_object, "value", "Ljava/lang/String;", env.new_string(global.get().unwrap_i64().to_string())?.into())?; + env.set_field(global_object, "type", "Ljava/lang/String;", env.new_string("i64")?.into())? + }, + Type::F32 => { + env.set_field(global_object, "value", "Ljava/lang/String;", env.new_string(global.get().unwrap_f32().to_string())?.into())?; + env.set_field(global_object, "type", "Ljava/lang/String;", env.new_string("f32")?.into())? + }, + Type::F64 => { + env.set_field(global_object, "value", "Ljava/lang/String;", env.new_string(global.get().unwrap_f64().to_string())?.into())?; + env.set_field(global_object, "type", "Ljava/lang/String;", env.new_string("f64")?.into())? + }, + _ => println!("No current support for globals of type {}", global.get().ty()) + } + + env.call_method( + exports_object, + "addGlobal", + "(Ljava/lang/String;Lorg/wasmer/Global;)V", + &[ + JObject::from(name).into(), + global_object.into() + ], + )?; + } + } + Ok(()) + }); + + joption_or_throw(&env, output).unwrap_or(()) +} diff --git a/src/java/org/wasmer/Exports.java b/src/java/org/wasmer/Exports.java index 5e456d9..b6129f0 100644 --- a/src/java/org/wasmer/Exports.java +++ b/src/java/org/wasmer/Exports.java @@ -6,10 +6,11 @@ import java.lang.ClassCastException; import java.util.HashMap; import java.util.Map; +import java.util.logging.Logger; /** * `Exports` is a Java class that represents the set of WebAssembly exports. - * + *

* Example: *

{@code
  * Instance instance = new Instance(wasmBytes);
@@ -22,8 +23,18 @@
  * Object[] result = ((Function) sum).apply(1, 2);
  * }
*/ +// Used in Rust +@SuppressWarnings("unused") public class Exports { - private Map inner; + private static final Logger logger = Logger.getLogger(Exports.class.getName()); + + /** + * Lambda expression for currying. + * This takes a function name and returns the function to call WebAssembly function. + */ + private java.util.function.Function functionWrapperGenerator = + functionName -> arguments -> this.instance.nativeCallExportedFunction(this.instance.instancePointer, functionName, arguments); + private final Map inner; private Instance instance; /** @@ -42,6 +53,7 @@ protected Exports(Instance instance) { * @param name Name of the export to return. */ public Export get(String name) { + logger.fine("Called get with arg: " + name); return this.inner.get(name); } @@ -51,6 +63,7 @@ public Export get(String name) { * @param name Name of the exported function. */ public Function getFunction(String name) throws ClassCastException { + logger.fine("Called getFunction with arg: " + name); return (Function) this.inner.get(name); } @@ -60,9 +73,20 @@ public Function getFunction(String name) throws ClassCastException { * @param name Name of the exported memory. */ public Memory getMemory(String name) throws ClassCastException { + logger.fine("Called getMemory with arg: " + name); return (Memory) this.inner.get(name); } + /** + * Return the export with the name `name` as an exported global. + * + * @param name Name of the exported global. + */ + public Global getGlobal(String name) throws ClassCastException { + logger.fine("Called getGlobal with arg: " + name); + return (Global) this.inner.get(name); + } + /** * Called by Rust to add a new exported function. */ @@ -78,11 +102,11 @@ private void addMemory(String name, Memory memory) { } /** - * Lambda expression for currying. - * This takes a function name and returns the function to call WebAssembly function. + * Called by Rust to add a new exported global. */ - private java.util.function.Function functionWrapperGenerator = - functionName -> arguments -> this.instance.nativeCallExportedFunction(this.instance.instancePointer, functionName, arguments); + private void addGlobal(String name, Global global) { + this.inner.put(name, global); + } /** * Generate the exported function wrapper. diff --git a/src/java/org/wasmer/Global.java b/src/java/org/wasmer/Global.java new file mode 100644 index 0000000..7d2451c --- /dev/null +++ b/src/java/org/wasmer/Global.java @@ -0,0 +1,46 @@ +package org.wasmer; + +import org.wasmer.exports.Export; + +// Used in Rust +@SuppressWarnings("unused") +public class Global implements Export { + private String value; + private String type; + + private Global() { + // This object is instantiated by Rust. + } + + public String getType() { + return this.type; + } + + public int getIntValue() { + if (!type.equals("i32")) { + throw new RuntimeException("Type mismatch, wanted type is i32 but global type is " + this.type); + } + return Integer.parseInt(this.value); + } + + public long getLongValue() { + if (!type.equals("i64")) { + throw new RuntimeException("Type mismatch, wanted type is i64 but global type is " + this.type); + } + return Long.parseLong(this.value); + } + + public float getFloatValue() { + if (!type.equals("f32")) { + throw new RuntimeException("Type mismatch, wanted type is f32 but global type is " + this.type); + } + return Float.parseFloat(this.value); + } + + public double getDoubleValue() { + if (!type.equals("f64")) { + throw new RuntimeException("Type mismatch, wanted type is f64 but global type is " + this.type); + } + return Double.parseDouble(this.value); + } +} diff --git a/src/java/org/wasmer/ImportObject.java b/src/java/org/wasmer/ImportObject.java new file mode 100644 index 0000000..abda2c6 --- /dev/null +++ b/src/java/org/wasmer/ImportObject.java @@ -0,0 +1,93 @@ +package org.wasmer; + +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +// The fields producing these warnings are accessed in Rust and would +// break Rust code if changed +@SuppressWarnings({"FieldCanBeLocal", "unused"}) +public class ImportObject { + private static final Logger logger = Logger.getLogger(ImportObject.class.getName()); + static { + if (!Native.LOADED_EMBEDDED_LIBRARY) { + System.loadLibrary(Native.DYNAMIC_LIBRARY_NAME_SHORT); + } + } + + private final String namespace; + private final String name; + + public ImportObject(String namespace, String name) { + logger.finer(String.format("Initializing import %s with namespace %s", name, namespace)); + this.name = name; + this.namespace = namespace; + } + + public static class FuncImport extends ImportObject { + private Function function; + private final List argTypes; + private final List retTypes; + private final int[] argTypesInt; + private final int[] retTypesInt; + + public FuncImport(String namespace, String name, Function, List> function, List argTypes, List retTypes) { + super(namespace, name); + logger.fine(String.format("Initialized function import %s with namespace %s", name, namespace)); + this.function = (long[] argv) -> { + List lret = function.apply(IntStream.range(0, argTypes.size()).mapToObj((int i) -> switch (argTypes.get(i)) { + case I32 -> (int) argv[i]; + case I64 -> argv[i]; + case F32 -> Float.intBitsToFloat((int) argv[i]); + case F64 -> Double.longBitsToDouble(argv[i]); + }).collect(Collectors.toList())); + long[] ret = argv.length >= retTypes.size() ? argv : new long[retTypes.size()]; + for (int i = 0; i < retTypes.size(); i++) + switch (retTypes.get(i)) { + case I32: + case I64: + ret[i] = lret.get(i).longValue(); + break; + case F32: + ret[i] = Float.floatToRawIntBits(lret.get(i).floatValue()); + break; + case F64: + ret[i] = Double.doubleToRawLongBits(lret.get(i).doubleValue()); + break; + default: + throw new RuntimeException("Unreachable (return type)"); + } + return ret; + }; + this.argTypesInt = argTypes.stream().mapToInt(t -> t.i).toArray(); + this.retTypesInt = retTypes.stream().mapToInt(t -> t.i).toArray(); + this.argTypes = Collections.unmodifiableList(argTypes); + this.retTypes = Collections.unmodifiableList(retTypes); + } + } + + public static class MemoryImport extends ImportObject { + private int minPages; + private Integer maxPages; + private boolean shared; + + public MemoryImport(String namespace, int minPages, Integer maxPages, boolean shared) { + super(namespace, "memory"); + logger.fine(String.format("Initialized memory import with namespace %s", namespace)); + this.minPages = minPages; + this.maxPages = maxPages; + this.shared = shared; + } + + public MemoryImport(String namespace, int minPages, boolean shared) { + super(namespace, "memory"); + logger.fine(String.format("Initialized memory import with namespace %s", namespace)); + this.minPages = minPages; + this.maxPages = null; + this.shared = shared; + } + } +} diff --git a/src/java/org/wasmer/Imports.java b/src/java/org/wasmer/Imports.java index d458f3b..3e9fbf6 100644 --- a/src/java/org/wasmer/Imports.java +++ b/src/java/org/wasmer/Imports.java @@ -1,95 +1,28 @@ package org.wasmer; -import java.util.Collections; import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.IntStream; +// Used in Rust +@SuppressWarnings("unused") public class Imports { static { if (!Native.LOADED_EMBEDDED_LIBRARY) { - System.loadLibrary("wasmer_jni"); + System.loadLibrary(Native.DYNAMIC_LIBRARY_NAME_SHORT); } } - private static native long nativeImportsInstantiate(List imports, MemoryType memory, long modulePointer) throws RuntimeException; + private static native long nativeImportsInstantiate(List imports, long modulePointer) throws RuntimeException; private static native long nativeImportsChain(long back, long front) throws RuntimeException; private static native long nativeImportsWasi(long modulePointer) throws RuntimeException; - private static native void nativeDrop(long nativePointer); final long importsPointer; -// public static class ImportsContainer { -// private final List imports; -// private final Memory memory; -// -// public ImportsContainer(List imports, Memory memory) { -// this.imports = imports; -// this.memory = memory; -// } -// } - - public static class Spec { - public final String namespace; - public final String name; - final Function function; - public final List argTypes; - public final List retTypes; - final int[] argTypesInt; - final int[] retTypesInt; - - public Spec(String namespace, String name, Function, List> function, List argTypes, List retTypes) { - this.namespace = namespace; - this.name = name; - this.function = (long[] argv) -> { - List lret = function.apply(IntStream.range(0, argTypes.size()).mapToObj((int i) -> { - switch (argTypes.get(i)) { - case I32: - return (int) argv[i]; - case I64: - return argv[i]; - case F32: - return Float.intBitsToFloat((int) argv[i]); - case F64: - return Double.longBitsToDouble(argv[i]); - default: - throw new RuntimeException("Unreachable (argument type)"); - } - }).collect(Collectors.toList())); - long[] ret = argv.length >= retTypes.size() ? argv : new long[retTypes.size()]; - for (int i = 0; i < retTypes.size(); i++) - switch (retTypes.get(i)) { - case I32: - ret[i] = lret.get(i).longValue(); - break; - case I64: - ret[i] = lret.get(i).longValue(); - break; - case F32: - ret[i] = Float.floatToRawIntBits(lret.get(i).floatValue()); - break; - case F64: - ret[i] = Double.doubleToRawLongBits(lret.get(i).doubleValue()); - break; - default: - throw new RuntimeException("Unreachable (return type)"); - } - return ret; - }; - this.argTypesInt = argTypes.stream().mapToInt(t -> t.i).toArray(); - this.retTypesInt = retTypes.stream().mapToInt(t -> t.i).toArray(); - this.argTypes = Collections.unmodifiableList(argTypes); - this.retTypes = Collections.unmodifiableList(retTypes); - } - } - private Imports(long importsPointer) { this.importsPointer = importsPointer; } - public static Imports from(List imports, MemoryType memory, Module module) throws RuntimeException { - return new Imports(nativeImportsInstantiate(imports, memory, module.modulePointer)); + public static Imports from(List imports, Module module) throws RuntimeException { + return new Imports(nativeImportsInstantiate(imports, module.modulePointer)); } public static Imports chain(Imports back, Imports front) { @@ -99,9 +32,4 @@ public static Imports chain(Imports back, Imports front) { public static Imports wasi(Module module) { return new Imports(nativeImportsWasi(module.modulePointer)); } - - protected void finalize() { - nativeDrop(importsPointer); - // TODO allow memory-safe user invocation - } } diff --git a/src/java/org/wasmer/Instance.java b/src/java/org/wasmer/Instance.java index 9672a05..04467a9 100644 --- a/src/java/org/wasmer/Instance.java +++ b/src/java/org/wasmer/Instance.java @@ -2,26 +2,25 @@ /** * `Instance` is a Java class that represents a WebAssembly instance. - * + *

* Example: *

{@code
  * Instance instance = new Instance(wasmBytes);
  * }
*/ public class Instance { - /** - * Native bindings. - */ static { if (!Native.LOADED_EMBEDDED_LIBRARY) { - System.loadLibrary("wasmer_jni"); + System.loadLibrary(Native.DYNAMIC_LIBRARY_NAME_SHORT); } } + private native long nativeInstantiate(Instance self, byte[] moduleBytes) throws RuntimeException; private native void nativeDrop(long instancePointer); protected native Object[] nativeCallExportedFunction(long instancePointer, String exportName, Object[] arguments) throws RuntimeException; protected static native void nativeInitializeExportedFunctions(long instancePointer); protected static native void nativeInitializeExportedMemories(long instancePointer); + protected static native void nativeInitializeExportedGlobals(long instancePointer); /** * All WebAssembly exports. @@ -47,6 +46,7 @@ public Instance(byte[] moduleBytes) throws RuntimeException { nativeInitializeExportedFunctions(instancePointer); nativeInitializeExportedMemories(instancePointer); + nativeInitializeExportedGlobals(instancePointer); } protected Instance() { @@ -58,9 +58,9 @@ protected Instance() { */ public void close() { // To avoid duplicate native dropping - if(this.instancePointer != 0l) { + if (this.instancePointer != 0L) { this.nativeDrop(this.instancePointer); - this.instancePointer = 0l; + this.instancePointer = 0L; } } diff --git a/src/java/org/wasmer/Main.java b/src/java/org/wasmer/Main.java new file mode 100644 index 0000000..f6c24f8 --- /dev/null +++ b/src/java/org/wasmer/Main.java @@ -0,0 +1,38 @@ +package org.wasmer; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; + +public class Main { + public static void main(String[] args) throws IOException { + System.out.println("Reading wasm bytes"); + byte[] bytes = Files.readAllBytes(Paths.get("../../examples/runtime.wasm")); + Module module = new Module(bytes); + System.out.println("Creating import object"); + Imports imports = Imports.from(Arrays.asList( + new ImportObject.FuncImport("env", "ext_storage_set_version_1", argv -> { + System.out.println("Message printed in the body of 'ext_storage_set_version_1'"); + return argv; + }, Arrays.asList(Type.I64, Type.I64), Collections.emptyList()), + new ImportObject.FuncImport("env", "ext_storage_get_version_1", argv -> { + System.out.println("Message printed in the body of 'ext_storage_get_version_1'"); + return argv; + }, Collections.singletonList(Type.I64), Collections.singletonList(Type.I64)), + new ImportObject.MemoryImport("env", 20, false)), module); + System.out.println("Instantiating module"); + Instance instance = module.instantiate(imports); + + System.out.println("Calling exported function 'Core_initialize_block' as it calls both of the imported functions"); + instance.exports.getFunction("Core_initialize_block").apply(1,2); + + Global heapBase = instance.exports.getGlobal("__heap_base"); + System.out.println(heapBase.getIntValue()); + + Memory memory = instance.exports.getMemory("memory"); + System.out.println(memory.buffer()); + instance.close(); + } +} diff --git a/src/java/org/wasmer/MemoryType.java b/src/java/org/wasmer/MemoryType.java deleted file mode 100644 index dca6d60..0000000 --- a/src/java/org/wasmer/MemoryType.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.wasmer; - -public class MemoryType { - private String namespace; - private String name; - private boolean shared; - private int minPages; - - public MemoryType(String namespace, String name, boolean shared, int minPages) { - this.namespace = namespace; - this.name = name; - this.shared = shared; - this.minPages = minPages; - } -} diff --git a/src/java/org/wasmer/Module.java b/src/java/org/wasmer/Module.java index 226a489..6e113f3 100644 --- a/src/java/org/wasmer/Module.java +++ b/src/java/org/wasmer/Module.java @@ -1,10 +1,8 @@ package org.wasmer; -import java.util.Collections; - /** * `Module` is a Java class that represents a WebAssembly module. - * + *

* Example: *

{@code
  * boolean isValid = Module.validate(wasmBytes);
@@ -13,15 +11,15 @@
  * Instance instance = module.instantiate();
  * }
*/ +// Used in Rust +@SuppressWarnings("unused") public class Module { - /** - * Native bindings. - */ static { if (!Native.LOADED_EMBEDDED_LIBRARY) { - System.loadLibrary("wasmer_jni"); + System.loadLibrary(Native.DYNAMIC_LIBRARY_NAME_SHORT); } } + private native long nativeModuleInstantiate(Module self, byte[] moduleBytes) throws RuntimeException; private native void nativeDrop(long modulePointer); private native long nativeInstantiate(long modulePointer, Instance instance, long importsPointer); @@ -49,8 +47,7 @@ public static boolean validate(byte[] moduleBytes) { * @param moduleBytes WebAssembly bytes. */ public Module(byte[] moduleBytes) throws RuntimeException { - long modulePointer = this.nativeModuleInstantiate(this, moduleBytes); - this.modulePointer = modulePointer; + this.modulePointer = this.nativeModuleInstantiate(this, moduleBytes); } private Module() {} @@ -60,9 +57,9 @@ private Module() {} */ public void close() { // To avoid duplicate native dropping - if (this.modulePointer != 0l) { + if (this.modulePointer != 0L) { this.nativeDrop(this.modulePointer); - this.modulePointer = 0l; + this.modulePointer = 0L; } } @@ -74,9 +71,6 @@ public void finalize() { this.close(); } - public Instance instantiate() { - return instantiate(Imports.from(Collections.emptyList(), null, this)); - } /** * Create an instance object based on a module object. * @@ -88,8 +82,9 @@ public Instance instantiate(Imports imports) { long instancePointer = this.nativeInstantiate(this.modulePointer, instance, imports.importsPointer); instance.instancePointer = instancePointer; - instance.nativeInitializeExportedFunctions(instancePointer); - instance.nativeInitializeExportedMemories(instancePointer); + Instance.nativeInitializeExportedFunctions(instancePointer); + Instance.nativeInitializeExportedMemories(instancePointer); + Instance.nativeInitializeExportedGlobals(instancePointer); return instance; } @@ -109,8 +104,7 @@ public byte[] serialize() { */ public static Module deserialize(byte[] serializedBytes) { Module module = new Module(); - long modulePointer = Module.nativeDeserialize(module, serializedBytes); - module.modulePointer = modulePointer; + module.modulePointer = Module.nativeDeserialize(module, serializedBytes); return module; } } diff --git a/src/java/org/wasmer/Native.java b/src/java/org/wasmer/Native.java index 749c396..cb0cf33 100644 --- a/src/java/org/wasmer/Native.java +++ b/src/java/org/wasmer/Native.java @@ -5,23 +5,29 @@ package org.wasmer; -import java.io.*; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; +import java.nio.file.Files; +import java.util.logging.Level; +import java.util.logging.Logger; public class Native { public static final boolean LOADED_EMBEDDED_LIBRARY; + public static final String DYNAMIC_LIBRARY_NAME_SHORT = "wasmer_jni"; + private static final Logger logger = Logger.getLogger(Native.class.getName()); static { LOADED_EMBEDDED_LIBRARY = loadEmbeddedLibrary(); } - private Native() {} + private Native() { + } public static String getCurrentPlatformIdentifier() { String osName = System.getProperty("os.name").toLowerCase(); @@ -30,10 +36,21 @@ public static String getCurrentPlatformIdentifier() { osName = "windows"; } else if (osName.contains("mac os x")) { osName = "darwin"; + String[] args = new String[]{"/bin/bash", "-c", "uname -m"}; + try { + Process proc = new ProcessBuilder(args).start(); + String arch = new BufferedReader(new InputStreamReader(proc.getInputStream())) + .readLine().equals("x86_64") + ? "amd64" + : "arm64"; + + return osName + "-" + arch; + } catch (IOException e) { + throw new RuntimeException(e); + } } else { osName = osName.replaceAll("\\s+", "_"); } - return osName + "-" + System.getProperty("os.arch"); } @@ -56,10 +73,9 @@ private static boolean loadEmbeddedLibrary() { url.append(getCurrentPlatformIdentifier()).append("/"); URL nativeLibraryUrl = null; - // loop through extensions, stopping after finding first one - for (String lib: libs) { - nativeLibraryUrl = Module.class.getResource(url.toString() + lib); + for (String lib : libs) { + nativeLibraryUrl = Module.class.getResource(url + lib); if (nativeLibraryUrl != null) { break; @@ -69,13 +85,13 @@ private static boolean loadEmbeddedLibrary() { if (nativeLibraryUrl != null) { // native library found within JAR, extract and load try { - final File libfile = File.createTempFile("wasmer_jni", ".lib"); + final File libfile = File.createTempFile(DYNAMIC_LIBRARY_NAME_SHORT, ".lib"); libfile.deleteOnExit(); // just in case final InputStream in = nativeLibraryUrl.openStream(); - final OutputStream out = new BufferedOutputStream(new FileOutputStream(libfile)); + final OutputStream out = new BufferedOutputStream(Files.newOutputStream(libfile.toPath())); - int len = 0; + int len; byte[] buffer = new byte[8192]; while ((len = in.read(buffer)) > -1) { @@ -88,9 +104,8 @@ private static boolean loadEmbeddedLibrary() { usingEmbedded = true; } catch (IOException x) { - // mission failed, do nothing + logger.log(Level.SEVERE, "Failed to load native library", x); } - } return usingEmbedded; diff --git a/src/java/org/wasmer/Type.java b/src/java/org/wasmer/Type.java index 2eb9c0c..32d21d1 100644 --- a/src/java/org/wasmer/Type.java +++ b/src/java/org/wasmer/Type.java @@ -6,7 +6,7 @@ public enum Type { F32((byte)3), F64((byte)4); - byte i; + final byte i; Type(byte i) { this.i = i; diff --git a/src/java/org/wasmer/Util.java b/src/java/org/wasmer/Util.java new file mode 100644 index 0000000..4cec267 --- /dev/null +++ b/src/java/org/wasmer/Util.java @@ -0,0 +1,11 @@ +package org.wasmer; + +public class Util { + static { + if (!Native.LOADED_EMBEDDED_LIBRARY) { + System.loadLibrary(Native.DYNAMIC_LIBRARY_NAME_SHORT); + } + } + + public static native void nativePanic(String message); +} diff --git a/src/java/org/wasmer/exports/Function.java b/src/java/org/wasmer/exports/Function.java index 5f87522..599886c 100644 --- a/src/java/org/wasmer/exports/Function.java +++ b/src/java/org/wasmer/exports/Function.java @@ -1,17 +1,14 @@ package org.wasmer.exports; -import org.wasmer.exports.Export; - /** * Functional interface for WebAssembly exported functions, i.e. it * creates a new type for a closure that mimics a WebAssembly exported * function. - * + *

* The apply method takes an arbitrary number of arguments and returns * an output. */ @FunctionalInterface public interface Function extends Export { - @SuppressWarnings("unchecked") - public Object[] apply(Object... inputs); + Object[] apply(Object... inputs); } diff --git a/src/lib.rs b/src/lib.rs index a7a362a..a03255b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,4 @@ mod module; mod types; mod value; mod imports; +mod util; diff --git a/src/memory.rs b/src/memory.rs index 4ffc07e..63dc5dc 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -7,18 +7,24 @@ use jni::{ sys::jint, JNIEnv, }; -use std::{cell::Cell, panic, rc::Rc, slice}; +use std::{cell::Cell, panic, slice}; +use std::sync::{Arc, Mutex}; use wasmer::MemoryView; use wasmer::Pages; use wasmer::Memory as WasmMemory; +/// A static variable holding the imported memory if there's such +/// +/// Note that `Memory` here refers to the local struct and not `wasmer::Memory` +pub static IMPORTED_MEMORY: Mutex> = Mutex::new(None); + #[derive(Clone)] pub struct Memory { - pub memory: Rc, + pub memory: Arc, } impl Memory { - pub fn new(memory: Rc) -> Self { + pub fn new(memory: Arc) -> Self { Self { memory } } @@ -108,6 +114,7 @@ pub mod java { types::{jptr, Pointer}, }; use jni::{objects::JObject, JNIEnv}; + use crate::memory::IMPORTED_MEMORY; pub fn initialize_memories(env: &JNIEnv, instance: &Instance) -> Result<(), Error> { let exports_object: JObject = env @@ -120,6 +127,26 @@ pub mod java { // Get the `org.wasmer.Memory` class. let memory_class = env.find_class("org/wasmer/Memory")?; + let memory_lock = IMPORTED_MEMORY.lock().unwrap(); + if memory_lock.is_some() { + let memory_object = env.new_object(memory_class, "()V", &[])?; + + // Try to set the memory pointer to the field `org.wasmer.Memory.memoryPointer`. + let memory_pointer: jptr = Pointer::new(memory_lock.clone().unwrap()).into(); + env.set_field(memory_object, "memoryPointer", "J", memory_pointer.into())?; + + // Add the newly created `org.wasmer.Memory` in the + // `org.wasmer.Exports` collection. + env.call_method( + exports_object, + "addMemory", + "(Ljava/lang/String;Lorg/wasmer/Memory;)V", + &[ + JObject::from(env.new_string("memory")?).into(), + memory_object.into(), + ], + )?; + } for (memory_name, memory) in &instance.memories { // Instantiate the `Memory` class. diff --git a/src/module.rs b/src/module.rs index 4b7e540..25042d0 100644 --- a/src/module.rs +++ b/src/module.rs @@ -11,6 +11,7 @@ use jni::{ JNIEnv, }; use std::{collections::HashMap, panic, rc::Rc}; +use std::sync::Arc; use wasmer::{self as runtime, Extern, Engine as _}; use wasmer_compiler_cranelift::Cranelift; use wasmer_engine_universal::Universal as UniversalEngine; @@ -26,7 +27,7 @@ impl Module { let store = runtime::Store::new(&UniversalEngine::new(Cranelift::default()).engine()); let module_bytes = module_bytes.as_slice(); let module = runtime::Module::new(&store, module_bytes) - .map_err(|e| runtime_error(format!("Failed to compile the module: {}", e)))?; + .map_err(|e| runtime_error(format!("Failed to compile the module: {:?}", e)))?; Ok(Self { java_module_object, @@ -106,12 +107,11 @@ pub extern "system" fn Java_org_wasmer_Module_nativeInstantiate( let instance = runtime::Instance::new(&module.module, &imports.import_object).map_err(|e| { runtime_error(format!("Failed to instantiate a WebAssembly module: {}", e)) })?; - let memories: HashMap = instance .exports .iter() .filter_map(|(export_name, export)| match export { - Extern::Memory(memory) => Some((export_name.to_string(), Memory::new(Rc::new(memory.clone())))), + Extern::Memory(memory) => Some((export_name.to_string(), Memory::new(Arc::new(memory.clone())))), _ => None, }) .collect(); diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..b952384 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,14 @@ +use jni::{ + objects::{JClass, JString}, + JNIEnv, +}; + +#[no_mangle] +pub extern "system" fn Java_org_wasmer_Util_nativePanic( + env: JNIEnv, + _class: JClass, + message: JString, +) { + let message: String = env.get_string(message).unwrap().into(); + panic!("{}", message) +} \ No newline at end of file diff --git a/test/org/wasmer/ImportsTest.java b/test/org/wasmer/ImportsTest.java index 3820c48..4a5e4da 100644 --- a/test/org/wasmer/ImportsTest.java +++ b/test/org/wasmer/ImportsTest.java @@ -44,7 +44,7 @@ void simple() throws Exception { AtomicReference arg2r = new AtomicReference<>(); AtomicReference ret1r = new AtomicReference<>(); Imports imports = Imports.from(Collections.singletonList( - new Imports.Spec("env", "mul_from_java", argv -> { + new ImportObject.FuncImport("env", "mul_from_java", argv -> { arg1r.set(argv.get(0)); arg2r.set(argv.get(1)); int arg1 = argv.get(0).intValue(); @@ -54,7 +54,7 @@ void simple() throws Exception { ret1r.set(argv.get(0)); return argv; }, Arrays.asList(Type.I32, Type.I32), Collections.singletonList(Type.I32)) - ), new MemoryType("env", "memory", false, 45), module); + ), module); Instance instance = module.instantiate(imports); Object[] ret = instance.exports.getFunction("double_each_arg_then_mul").apply(2, 3); @@ -80,7 +80,7 @@ void accessMemory() throws IOException,Exception { Module module = new Module(getBytes("import_accessmemory.wasm")); AtomicReference arInstance = new AtomicReference<>(); Imports imports = Imports.from(Arrays.asList( - new Imports.Spec("env", "get_greet_msg_len_from_java", argv -> { + new ImportObject.FuncImport("env", "get_greet_msg_len_from_java", argv -> { Memory memory = arInstance.get().exports.getMemory("memory"); int msgKeyPtr = argv.get(0).intValue(); ByteBuffer mbf = memory.buffer(); @@ -88,7 +88,7 @@ void accessMemory() throws IOException,Exception { argv.set(0, msgKey.length()); return argv; }, Collections.singletonList(Type.I32), Collections.singletonList(Type.I32)), - new Imports.Spec("env", "greet_from_java", argv -> { + new ImportObject.FuncImport("env", "greet_from_java", argv -> { Memory memory = arInstance.get().exports.getMemory("memory"); int msgKeyPtr = argv.get(0).intValue(); ByteBuffer mbf = memory.buffer(); @@ -100,7 +100,7 @@ void accessMemory() throws IOException,Exception { mbf.put(msgValueBytes); return argv; }, Arrays.asList(Type.I32, Type.I32), Collections.singletonList(Type.I32)) - ), new MemoryType("env", "memory", false, 45), module); + ), module); Instance instance = module.instantiate(imports); arInstance.set(instance); diff --git a/test/org/wasmer/ModuleTest.java b/test/org/wasmer/ModuleTest.java index 2fda0eb..aa6d0f9 100644 --- a/test/org/wasmer/ModuleTest.java +++ b/test/org/wasmer/ModuleTest.java @@ -1,11 +1,9 @@ package org.wasmer; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertFalse; import java.lang.RuntimeException; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -13,33 +11,33 @@ import org.junit.jupiter.api.Test; class ModuleTest { - private byte[] getBytes(String filename) throws IOException,Exception { + private byte[] getBytes(String filename) throws Exception { Path modulePath = Paths.get(getClass().getClassLoader().getResource(filename).toURI()); return Files.readAllBytes(modulePath); } @Test - void validate() throws IOException,Exception { + void validate() throws Exception { assertTrue(Module.validate(getBytes("tests.wasm"))); } @Test - void invalidate() throws IOException,Exception { + void invalidate() throws Exception { assertFalse(Module.validate(getBytes("invalid.wasm"))); } @Test - void compile() throws IOException,Exception { + void compile() throws Exception { assertTrue(new Module(getBytes("tests.wasm")) instanceof Module); } @Test - void failedToCompile() throws IOException,Exception { + void failedToCompile() { Exception exception = Assertions.assertThrows(RuntimeException.class, () -> { Module module = new Module(getBytes("invalid.wasm")); }); - String expected = "Failed to compile the module: Validation error: invalid leading byte in type definition"; + String expected = "Failed to compile the module: Validate(\"invalid leading byte in type definition"; assertTrue(exception.getMessage().startsWith(expected)); } @@ -55,7 +53,7 @@ void failedToCompile() throws IOException,Exception { // } @Test - void serialize() throws IOException,Exception { + void serialize() throws Exception { Module module = new Module(getBytes("tests.wasm")); assertTrue(module.serialize() instanceof byte[]); module.close();