├── .github └── workflows │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── pom.xml ├── src ├── main │ └── java │ │ └── net │ │ └── bluejekyll │ │ └── wasmtime │ │ ├── AbstractOpaquePtr.java │ │ ├── WasmEngine.java │ │ ├── WasmFunction.java │ │ ├── WasmInstance.java │ │ ├── WasmLinker.java │ │ ├── WasmModule.java │ │ ├── WasmStore.java │ │ ├── Wasmtime.java │ │ ├── WasmtimeException.java │ │ ├── proxy │ │ ├── WasmExport.java │ │ ├── WasmExportable.java │ │ ├── WasmFunctionDef.java │ │ ├── WasmImport.java │ │ ├── WasmImportProxy.java │ │ ├── WasmImportable.java │ │ └── WasmModule.java │ │ └── ty │ │ ├── ExternRef.java │ │ ├── F32.java │ │ ├── F64.java │ │ ├── FuncRef.java │ │ ├── I32.java │ │ ├── I64.java │ │ ├── V128.java │ │ ├── ValType.java │ │ ├── WasmType.java │ │ ├── WasmTypeUtil.java │ │ └── WasmVoid.java └── test │ └── java │ └── net │ └── bluejekyll │ └── wasmtime │ ├── TestUtil.java │ ├── WasmEngineTest.java │ ├── WasmFunctionTest.java │ ├── WasmInstanceTest.java │ ├── WasmLinkerTest.java │ ├── WasmModuleTest.java │ ├── WasmStoreTest.java │ ├── WasmtimeExceptionTest.java │ ├── WasmtimeTest.java │ ├── proxy │ ├── ExportTests.java │ ├── ImportTests.java │ ├── TestExport.java │ └── TestImportProxy.java │ └── tests │ ├── MathTests.java │ ├── MathWitTests.java │ ├── SliceTests.java │ └── StringTests.java ├── tests ├── math-wit │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── math.wit ├── math │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── slices │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── strings │ ├── Cargo.toml │ └── src │ └── lib.rs ├── wasmtime-jni-exports ├── Cargo.toml └── src │ └── lib.rs └── wasmtime-jni ├── Cargo.lock ├── Cargo.toml └── src ├── lib.rs ├── opaque_ptr.rs ├── ty ├── byte_slice.rs ├── complex_ty.rs ├── mod.rs ├── str.rs └── wasm_alloc.rs ├── wasm_engine.rs ├── wasm_exception.rs ├── wasm_function.rs ├── wasm_instance.rs ├── wasm_linker.rs ├── wasm_module.rs ├── wasm_state.rs ├── wasm_store.rs ├── wasm_value.rs └── wasmtime.rs /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release/** 8 | pull_request: 9 | branches: 10 | - main 11 | - release/** 12 | schedule: 13 | - cron: '0 3 * * 4' 14 | 15 | env: 16 | CARGO_MAKE_VERSION: '0.27.0' 17 | 18 | jobs: 19 | ## Run all default oriented feature sets across all platforms. 20 | platform-matrix: 21 | name: platform 22 | runs-on: ${{ matrix.os }} 23 | strategy: 24 | matrix: 25 | #os: [ubuntu-latest, macos-latest, windows-latest] # TODO: need someone on Windows to help with fixing the windows build 26 | os: [ubuntu-latest, macos-latest] 27 | steps: 28 | - uses: actions/checkout@v2 29 | 30 | - uses: actions/setup-java@v1 31 | with: 32 | java-version: 1.11 33 | 34 | - uses: actions-rs/toolchain@v1 35 | with: 36 | profile: minimal 37 | toolchain: stable 38 | override: true 39 | 40 | - name: make init 41 | run: make init 42 | 43 | - name: make test 44 | run: make test 45 | 46 | ## Execute the clippy checks 47 | cleanliness: 48 | name: cleanliness 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@v2 52 | 53 | - uses: actions-rs/toolchain@v1 54 | with: 55 | profile: minimal 56 | toolchain: stable 57 | components: rustfmt, clippy 58 | override: true 59 | 60 | # Clippy 61 | - name: cargo clippy 62 | run: cargo clippy 63 | # Rustfmt 64 | - name: cargo fmt 65 | run: cargo fmt -- --check 66 | # Audit 67 | #- name: cargo audit 68 | # run: cargo make audit 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .idea/ 3 | **/*.iml 4 | 5 | *.swp 6 | 7 | # Compiled files 8 | *.o 9 | *.so 10 | *.rlib 11 | *.dll 12 | .DS_Store 13 | 14 | # Executables 15 | *.exe 16 | 17 | # Generated by Cargo 18 | /target 19 | **/target 20 | 21 | *.rs.bk 22 | .rls.toml 23 | rls/** 24 | *.log 25 | 26 | .settings/** 27 | .classpath 28 | .project 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [profile.release] 2 | debug = true 3 | 4 | [profile.production] 5 | inherits = "release" 6 | lto = true 7 | 8 | [workspace] 9 | members = ["wasmtime-jni", "wasmtime-jni-exports", "tests/math", "tests/math-wit", "tests/slices", "tests/strings"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq (${OS}, Windows_NT) 2 | PLATFORM = Windows 3 | ifeq ($(PROCESSOR_ARCHITEW6432), AMD64) 4 | ARCH = x86_64 5 | else 6 | ifeq ($(PROCESSOR_ARCHITECTURE), AMD64) 7 | ARCH = x86_64 8 | endif 9 | ifeq ($(PROCESSOR_ARCHITECTURE), x86) 10 | ARCH = i386 11 | endif 12 | endif 13 | else 14 | PLATFORM = $(shell uname -s) 15 | ARCH = $(shell uname -m) 16 | endif 17 | 18 | ifeq (${PLATFORM}, Windows) 19 | DYLIB_EXT = dll 20 | endif 21 | 22 | ifeq (${PLATFORM}, Darwin) 23 | DYLIB_EXT = dylib 24 | endif 25 | 26 | ifeq (${PLATFORM}, Linux) 27 | DYLIB_EXT = so 28 | endif 29 | 30 | ifeq (${RELEASE}, --release) 31 | DYLIB_PATH=release 32 | else 33 | DYLIB_PATH=debug 34 | endif 35 | 36 | WASMTIME_TARGET_DIR := ${PWD}/target 37 | NATIVE_TARGET_DIR := ${PWD}/target/native/${PLATFORM}/${ARCH} 38 | WASM_TESTS := $(wildcard tests/*/Cargo.toml) 39 | RUSTV := "+stable" 40 | 41 | 42 | ## This can be changed to the different wasm targets 43 | # WASM_TARGET := wasm32-unknown-unknown 44 | WASM_TARGET := wasm32-wasi 45 | 46 | .PHONY: init 47 | init: 48 | @echo "====> Testing for all tools" 49 | @mvn -version || (echo maven is required, e.g. 'brew install maven' && mvn -version) 50 | @cargo ${RUSTV} --version || (echo rust is required, e.g. 'curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh' && cargo --version) 51 | rustup target add ${WASM_TARGET} 52 | 53 | .PHONY: clean 54 | clean: 55 | @echo "====> Cleaning" 56 | cd wasmtime-jni && cargo clean 57 | mvn clean 58 | 59 | target/native: 60 | @echo "====> Building wasmtime-jni for ${PLATFORM} arch ${ARCH}" 61 | cd wasmtime-jni && cargo ${RUSTV} build ${RELEASE} --lib 62 | @mkdir -p ${NATIVE_TARGET_DIR} 63 | @cp -rpf ${WASMTIME_TARGET_DIR}/${DYLIB_PATH}/*.${DYLIB_EXT} ${NATIVE_TARGET_DIR}/ 64 | 65 | .PHONY: build 66 | build: 67 | @echo "====> Building" 68 | cd wasmtime-jni && cargo ${RUSTV} build ${RELEASE} --lib 69 | $(MAKE) ${WASM_TESTS} 70 | 71 | rm -rf ${PWD}/target/native 72 | $(MAKE) mvn-compile 73 | 74 | .PHONY: ${WASM_TESTS} 75 | ${WASM_TESTS}: 76 | @echo "====> Building $(dir $@)" 77 | cd $(dir $@) && cargo ${RUSTV} build --target ${WASM_TARGET} 78 | 79 | .PHONY: test 80 | test: build 81 | @echo "====> Testing" 82 | cd wasmtime-jni && cargo ${RUSTV} test ${RELEASE} 83 | $(MAKE) mvn-test 84 | 85 | .PHONY: mvn-test 86 | mvn-test: target/native 87 | PLATFORM=${PLATFORM} ARCH=${ARCH} mvn verify 88 | 89 | .PHONY: mvn-compile 90 | mvn-compile: target/native 91 | PLATFORM=${PLATFORM} ARCH=${ARCH} mvn compile 92 | 93 | .PHONY: package 94 | package: build 95 | PLATFORM=${PLATFORM} ARCH=${ARCH} mvn package 96 | 97 | .PHONY: install 98 | install: 99 | RELEASE=--release make package 100 | PLATFORM=${PLATFORM} ARCH=${ARCH} mvn install 101 | 102 | .PHONY: cleanliness 103 | cleanliness: 104 | cargo ${RUSTV} clean -p wasmtime-jni -p wasmtime-jni-exports -p math -p slices -p strings 105 | cargo ${RUSTV} clippy -- -D warnings 106 | cargo ${RUSTV} fmt -- --check 107 | cargo ${RUSTV} audit 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![test](https://github.com/bluejekyll/wasmtime-java/workflows/test/badge.svg?branch=main)](https://github.com/bluejekyll/wasmtime-java/actions?query=workflow%3Atest) 2 | 3 | # Java bindings to the Wasmtime runtime engine 4 | 5 | WASM, web-assembly, is a low-level stack execution engine. This library gives a JNI based binding to the Wasmtime WASM runtime. 6 | 7 | For more information, please see [github.com/bytecodealliance/wasmtime](https://github.com/bytecodealliance/wasmtime). 8 | 9 | This is very early stages. All interfaces are subject to change. Supported platforms, x86_64 for Linux and macOS. Windows should also be supported, but currently not due to lack of resources. 10 | 11 | ## Building 12 | 13 | Use the provided Makefile. You need Java 11 (not 17), Maven and Rustup (the error messages will prompt you to install that if you don't have it). 14 | 15 | Ensure tools are installed: 16 | 17 | ```shell 18 | > make init 19 | ``` 20 | 21 | Run tests: 22 | 23 | ```shell 24 | > make test 25 | ``` 26 | 27 | Install the maven packages locally: 28 | 29 | ```shell 30 | > make install 31 | ``` 32 | 33 | ## Using in Java 34 | 35 | Maven co-ordinates for the installed artifacts: `net.bluejekyll:wasmtime-java:1.0-SNAPSHOT`. 36 | 37 | ### Initializing the Wasmtime runtime 38 | 39 | Initialize the WASM runtime. Much of this is not threadsafe, so be aware... 40 | 41 | ```java 42 | // Initiale the Wasmtime JNI bindings (this will happen once per ClassLoader) 43 | Wasmtime wasmtime = new Wasmtime(); 44 | // Get a new Engine (one per thread) 45 | WasmEngine engine = wasmtime.newWasmEngine(); 46 | // Compile The module, this can be reused, i.e. is cacheable for multiple executions 47 | WasmModule module = engine.newModule(${PATH_TO_MODULE}); 48 | ``` 49 | 50 | Once the runtime is initialized, create a new instance. This requires a linker, and exported functions if the module has imports that need to be met. 51 | 52 | ```java 53 | // create a new store and linker 54 | WasmStore store = engine.newStore(); 55 | WasmLinker linker = store.newLinker(); 56 | 57 | // This will link in exports from Java to the WASM module, if that module has imports, see below 58 | linker.defineFunctions(this.store, new TestExport()); 59 | ``` 60 | 61 | ### Exporting functions to Webassembly 62 | 63 | To export functions into WASM, that are bound to the WASM module's imports, a class must implement the `WasmExportable` interface. Each method to export and be bound to functions the corresponding function in WASM must be annotated with `WasmExport`. 64 | 65 | See [`TestExport`](https://github.com/bluejekyll/wasmtime-java/blob/fd075fe88ba106409d10a1c985f8573f2d7936e2/src/test/java/net/bluejekyll/wasmtime/proxy/TestExport.java) for example: 66 | 67 | ```java 68 | // Specify the module name that we need to export functions into (imports in the WASM) 69 | @WasmModule(name = "test") 70 | public class TestExport implements WasmExportable { 71 | 72 | // Export the function to WASM and give the name that corresponds to the import in WASM 73 | @WasmExport(name = "reverse_bytes_java") 74 | public final byte[] reverseBytesJava(ByteBuffer buffer) { 75 | ByteBuffer toReverse = buffer.duplicate(); 76 | byte[] bytes = new byte[toReverse.remaining()]; 77 | 78 | for (int i = bytes.length - 1; i >= 0; i--) { 79 | bytes[i] = toReverse.get(); 80 | } 81 | 82 | return bytes; 83 | } 84 | 85 | } 86 | ``` 87 | 88 | After Wasmtime is initiated and the module compiled, then it can be linked and an instance created: 89 | 90 | ```java 91 | linker.defineFunctions(this.store, new TestExport()); 92 | WasmInstance instance = linker.instantiate(module); 93 | ``` 94 | 95 | ### Importing functions implemented in Webassembly 96 | 97 | `WasmImportProxy` To work with the Proxy builder, an `interface` must extend the `WasmImportable` interface. It should be annotated to specify the WASM module being bound to. 98 | 99 | See [`TestImportProxy`](https://github.com/bluejekyll/wasmtime-java/blob/fd075fe88ba106409d10a1c985f8573f2d7936e2/src/test/java/net/bluejekyll/wasmtime/proxy/TestImportProxy.java) for example: 100 | 101 | 102 | ```java 103 | // This annotation allows for the WASM module name to be specified 104 | @WasmModule(name = "test") 105 | public interface TestImportProxy extends WasmImportable { 106 | // This annotation allows for the function name to be set as it is defined in WASM 107 | @WasmImport(name = "add_i32") 108 | int addInteger(int a, int b); 109 | } 110 | ``` 111 | 112 | This can now be used with `WasmImportProxy` to create a proxy that will call into the specified functions in WASM, see [`ImportTests`](https://github.com/bluejekyll/wasmtime-java/blob/fd075fe88ba106409d10a1c985f8573f2d7936e2/src/test/java/net/bluejekyll/wasmtime/proxy/ImportTests.java): 113 | 114 | ```java 115 | WasmInstance instance = linker.instantiate(this.module); 116 | WasmImportProxy importProxy = new WasmImportProxy(instance); 117 | TestImportProxy proxy = importProxy.newWasmProxy(TestImportProxy.class); 118 | 119 | int ret = proxy.addInteger(3, 2); 120 | ``` 121 | 122 | ## Structure 123 | 124 | The Java is meant to be as minimal as possible. All Wasmtime object references are stored in Java objects as opaque pointers (longs). The safety in this area has not yet been proven. Thread safety in particular is non-existent. The WasmEngine should be safe to share across threads, though we should most likely introduce a custom clone method for this purpose. 125 | 126 | ### Adding new native methods 127 | 128 | The Java compiler will automatically output the headers for the JNI bindings based on the native methods defined in the various classes. While the headers generated by the Java compiler aren't directly used during the Rust JNI compilation, they are useful for seeing the C signature that the Rust needs to export. The files can be found in `target/generated-sources/*.h`. 129 | 130 | ## Debugging 131 | 132 | The tests should all run regardless of platform. Windows hasn't been fully tested due to lack of resources. If tests fail to run, there are a few different environments at play which will make discovering which component failed and why difficult. At the moment, all output from Java is captured in `txt` files at in the `target/surfire-reports/{CLASS_NAME}-output.txt`. All output from the JNI bindings is currently captured in `target/wasm-logs/java_{DATE}.log`, this may be combined into the same place in the future. In the `pom.xml` Maven project file, the `surfire` test configuration has `RUST_LOG` set to `debug` by default. This can be set to any other value to increase or decrease logging output. 133 | 134 | ## libc like support with WASI 135 | 136 | [WASI](https://wasi.dev/) is supported for things like printing to stdout. This is supplied during linking in the Java bindings. It is not required, in Rust this can be targeted with `cargo build --target wasm32-wasi`, that target must be installed with `rustup` before hand. 137 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | net.bluejekyll 8 | wasmtime-java 9 | 1.0-SNAPSHOT 10 | 11 | wasmtime-java 12 | https://github.com/bluejekyll/wasmtime-java 13 | 14 | 15 | UTF-8 16 | 11 17 | 11 18 | 19 | ${project.basedir}/target/native 20 | ${project.build.outputDirectory}/NATIVE 21 | 22 | 23 | 24 | 25 | 26 | junit 27 | junit 28 | 4.11 29 | test 30 | 31 | 32 | com.google.code.findbugs 33 | jsr305 34 | 2.0.1 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | maven-clean-plugin 44 | 3.1.0 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | maven-resources-plugin 123 | 3.1.0 124 | 125 | 126 | copy-resources 127 | 128 | process-resources 129 | 130 | copy-resources 131 | 132 | 133 | ${native.dir} 134 | 135 | 136 | ${native.build.dir} 137 | 138 | **/*.dll 139 | **/*.dylib 140 | **/*.so 141 | 142 | false 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | org.apache.maven.plugins 151 | maven-antrun-plugin 152 | 1.8 153 | 154 | 155 | chmod-dylibs 156 | process-resources 157 | 158 | run 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | maven-compiler-plugin 176 | 3.8.1 177 | 178 | 179 | -h 180 | ${project.build.directory}/generated-sources 181 | 182 | 183 | 184 | 185 | com.github.spotbugs 186 | spotbugs-maven-plugin 187 | 4.1.3 188 | 189 | 190 | check 191 | verify 192 | 193 | check 194 | 195 | 196 | 197 | 198 | 199 | 200 | com.github.spotbugs 201 | spotbugs 202 | 4.1.4 203 | 204 | 205 | 206 | 207 | maven-surefire-plugin 208 | 3.0.0-M5 209 | 210 | 1 211 | true 212 | true 213 | 214 | 215 | -Djava.library.path=${native.dir}/${env.PLATFORM}/${env.ARCH} 216 | 217 | 218 | 219 | wasmtime=debug,wasmtime-jni=debug 220 | full 221 | 222 | 223 | 224 | 225 | maven-jar-plugin 226 | 3.2.0 227 | 228 | 229 | maven-install-plugin 230 | 2.5.2 231 | 232 | 233 | maven-deploy-plugin 234 | 2.8.2 235 | 236 | 237 | 238 | maven-site-plugin 239 | 3.9.1 240 | 241 | 242 | maven-project-info-reports-plugin 243 | 3.1.1 244 | 245 | 246 | 247 | 248 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/AbstractOpaquePtr.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import javax.annotation.concurrent.NotThreadSafe; 4 | import java.lang.ref.Cleaner; 5 | import java.util.function.Consumer; 6 | 7 | @NotThreadSafe 8 | public abstract class AbstractOpaquePtr implements AutoCloseable { 9 | private static final Cleaner cleaner = Cleaner.create(); 10 | 11 | private final long ptr; 12 | private final Cleaner.Cleanable cleanable; 13 | 14 | /** 15 | * @param ptr a valid, non-null pointer to the underlying native type 16 | * @param free a function to free the pointer, this must be a static method 17 | */ 18 | protected AbstractOpaquePtr(long ptr, Consumer free) { 19 | this.ptr = ptr; 20 | this.cleanable = cleaner.register(this, new Freedom(ptr, free)); 21 | 22 | if (this.ptr == 0) { 23 | throw new NullPointerException( 24 | String.format("Null pointer for %s(%d)", this.getClass().getName(), this.ptr)); 25 | } 26 | } 27 | 28 | private static class Freedom implements Runnable { 29 | private final long ptr; 30 | private final Consumer free; 31 | 32 | Freedom(long ptr, Consumer free) { 33 | this.ptr = ptr; 34 | this.free = free; 35 | } 36 | 37 | public void run() { 38 | this.free.accept(this.ptr); 39 | } 40 | } 41 | 42 | protected long getPtr() { 43 | if (this.ptr == 0) { 44 | throw new NullPointerException( 45 | String.format("Null pointer for %s(%d)", this.getClass().getName(), this.ptr)); 46 | } 47 | 48 | return this.ptr; 49 | } 50 | 51 | @Override 52 | public void close() { 53 | this.cleanable.clean(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/WasmEngine.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import javax.annotation.concurrent.NotThreadSafe; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.nio.ByteBuffer; 10 | 11 | // TODO implement cloneable and clone the underlying engine between threads 12 | @NotThreadSafe 13 | public class WasmEngine extends AbstractOpaquePtr implements Cloneable { 14 | WasmEngine(long ptr) { 15 | super(ptr, WasmEngine::freeEngine); 16 | } 17 | 18 | private static native void freeEngine(long ptr); 19 | 20 | private static native long newStoreNtv(long engine_ptr); 21 | 22 | private static native long newModuleNtv(long engine_ptr, ByteBuffer wasm_bytes) throws WasmtimeException; 23 | 24 | // takes a pointer to an engine 25 | private static native long newLinker(long engine_ptr) throws WasmtimeException; 26 | 27 | public WasmStore newStore() { 28 | long storePtr = newStoreNtv(super.getPtr()); 29 | 30 | System.err.printf("Java Store Pointer: %d%n", storePtr); 31 | 32 | return new WasmStore(storePtr); 33 | } 34 | 35 | public WasmModule newModule(ByteBuffer wasm_bytes) throws WasmtimeException { 36 | if (!wasm_bytes.isDirect()) 37 | throw new WasmtimeException("passed in buffer must be direct"); 38 | 39 | return new WasmModule(newModuleNtv(super.getPtr(), wasm_bytes.asReadOnlyBuffer())); 40 | } 41 | 42 | public WasmModule newModule(byte[] wasm_bytes) throws WasmtimeException { 43 | // ByteBuffers must be direct 44 | ByteBuffer buf = ByteBuffer.allocateDirect(wasm_bytes.length); 45 | buf.put(wasm_bytes); 46 | return new WasmModule(newModuleNtv(super.getPtr(), buf)); 47 | } 48 | 49 | public WasmModule newModule(File wasm_file) throws WasmtimeException, IOException { 50 | try (InputStream in = new FileInputStream(wasm_file)) { 51 | return this.newModule(in.readAllBytes()); 52 | } 53 | } 54 | 55 | public WasmLinker newLinker() throws WasmtimeException { 56 | long ptr = newLinker(this.getPtr()); 57 | return new WasmLinker(ptr); 58 | } 59 | 60 | /** Need to support clone for safe copies being used across threads */ 61 | @Override 62 | protected Object clone() throws CloneNotSupportedException { 63 | // TODO Auto-generated method stub 64 | return super.clone(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/WasmFunction.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import javax.annotation.concurrent.NotThreadSafe; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.Parameter; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import net.bluejekyll.wasmtime.ty.*; 9 | 10 | @NotThreadSafe 11 | public class WasmFunction extends AbstractOpaquePtr { 12 | WasmFunction(long ptr) { 13 | super(ptr, WasmFunction::freeFunc); 14 | } 15 | 16 | private static native void freeFunc(long ptr); 17 | 18 | private static native long createFunc(long store_ptr, Method method, Object obj, 19 | Class returnType, 20 | List> paramTypes) throws WasmtimeException; 21 | 22 | private static native WasmType callNtv(long func_ptr, long instance_pointer, long store_ptr, 23 | Class returnType, WasmType... args) 24 | throws WasmtimeException; 25 | 26 | public static WasmFunction newFunc(WasmStore store, Object target, String methodName, 27 | Class... args) 28 | throws WasmtimeException, NoSuchMethodException { 29 | Method method = target.getClass().getMethod(methodName, args); 30 | return WasmFunction.newFunc(store, method, target); 31 | } 32 | 33 | public static WasmFunction newFunc(WasmStore store, Method method, Object obj) throws WasmtimeException { 34 | List> parameters = new ArrayList<>(5); 35 | for (Parameter param : method.getParameters()) { 36 | Class paramType = param.getType(); 37 | if (!WasmType.class.isAssignableFrom(paramType)) 38 | throw new RuntimeException( 39 | String.format("Only WasmType parameters supported: %s", paramType.getName())); 40 | Class ty = (Class) paramType; 41 | 42 | parameters.add(ty); 43 | } 44 | 45 | // validate that the type is something we support 46 | final Class returnType; 47 | Class javaReturnType = method.getReturnType(); 48 | if (WasmType.class.isAssignableFrom(javaReturnType)) { 49 | returnType = (Class) javaReturnType; 50 | } else if (Void.TYPE.isAssignableFrom(javaReturnType) 51 | || Void.class.isAssignableFrom(javaReturnType)) { 52 | // We'll allow standard void and Void as well. 53 | returnType = WasmVoid.class; 54 | } else { 55 | throw new RuntimeException( 56 | String.format("Only WasmType return values supported: %s", 57 | javaReturnType.getName())); 58 | } 59 | 60 | long ptr = createFunc(store.getPtr(), method, obj, returnType, parameters); 61 | return new WasmFunction(ptr); 62 | } 63 | 64 | /** 65 | * 66 | * @param instance the linked and compiled instance to call this function 67 | * agains 68 | * @param returnType the class of the return type, Void for no return 69 | * @param args list of arguments for the function, must match those of the 70 | * "wrapped" function 71 | * @param return type matching the wrapped functions return type 72 | * @return If there is a return value for the function, otherwise Void 73 | * @throws WasmtimeException If any exception is thrown byt the underlying 74 | * function 75 | */ 76 | @SuppressWarnings("unchecked") 77 | public T call(WasmInstance instance, WasmStore store, Class returnType, 78 | WasmType... args) 79 | throws WasmtimeException { 80 | return (T) callNtv(this.getPtr(), instance.getPtr(), store.getPtr(), returnType, args); 81 | } 82 | 83 | /** 84 | * 85 | * @param instance the linked and compiled instance to call this function agains 86 | * @param args list of arguments for the function, must match those of the 87 | * "wrapped" function 88 | * @return If there is a return value for the function, otherwise Void 89 | * @throws WasmtimeException If any exception is thrown byt the underlying 90 | * function 91 | */ 92 | @SuppressWarnings("unchecked") 93 | public void call(WasmInstance instance, WasmStore store, WasmType... args) throws WasmtimeException { 94 | callNtv(this.getPtr(), instance.getPtr(), store.getPtr(), WasmVoid.class, args); 95 | } 96 | 97 | /** 98 | * WARNING: this is really only useful in tests, Instance will be null in the 99 | * native call, which is bad for any non-native types, like Strings arrays or 100 | * ByteBuffers. 101 | */ 102 | @SuppressWarnings("unchecked") 103 | T call_for_tests(WasmStore store, Class returnType, WasmType... args) 104 | throws WasmtimeException { 105 | return (T) callNtv(this.getPtr(), 0, store.getPtr(), returnType, args); 106 | } 107 | 108 | /** 109 | * WARNING: this is really only useful in tests, Instance will be null in the 110 | * native call, which is bad for any non-native types, like Strings arrays or 111 | * ByteBuffers. 112 | */ 113 | void call_for_tests(WasmStore store, WasmType... args) throws WasmtimeException { 114 | callNtv(this.getPtr(), 0, store.getPtr(), WasmVoid.class, args); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/WasmInstance.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import java.util.Optional; 4 | 5 | public class WasmInstance extends AbstractOpaquePtr { 6 | WasmInstance(long ptr) { 7 | super(ptr, WasmInstance::freeInstance); 8 | } 9 | 10 | private static native void freeInstance(long ptr); 11 | 12 | private static native long getFunctionNtv(long ptr, long store_ptr, String name); 13 | 14 | public Optional getFunction(WasmStore store, String name) { 15 | long func = WasmInstance.getFunctionNtv(this.getPtr(), store.getPtr(), name); 16 | if (func == 0) { 17 | return Optional.empty(); 18 | } else { 19 | return Optional.of(new WasmFunction(func)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/WasmLinker.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import java.util.List; 4 | 5 | import net.bluejekyll.wasmtime.proxy.WasmExportable; 6 | import net.bluejekyll.wasmtime.proxy.WasmFunctionDef; 7 | 8 | public class WasmLinker extends AbstractOpaquePtr { 9 | WasmLinker(long ptr) { 10 | super(ptr, WasmLinker::freeLinker); 11 | } 12 | 13 | private static native void freeLinker(long ptr); 14 | 15 | private static native void defineFunc(long ptr, String module, String name, long func_ptr); 16 | 17 | private static native long instantiateNtv(long linker_ptr, long store_ptr, long module_ptr) 18 | throws WasmtimeException; 19 | 20 | /** 21 | * @param module name of the module in which this function should be defined 22 | * (like a class) 23 | * @param name for the function to use 24 | */ 25 | public void defineFunction(String module, String name, WasmFunction function) throws WasmtimeException { 26 | WasmLinker.defineFunc(this.getPtr(), module, name, function.getPtr()); 27 | } 28 | 29 | public void defineFunctions(WasmStore store, WasmExportable exportable) throws WasmtimeException { 30 | List functions = exportable.defineWasmFunctions(store); 31 | 32 | for (WasmFunctionDef function : functions) { 33 | this.defineFunction(function.getModuleName(), function.getFunctionName(), function.getFunction()); 34 | } 35 | } 36 | 37 | public WasmInstance instantiate(WasmStore store, WasmModule module) throws WasmtimeException { 38 | return new WasmInstance(WasmLinker.instantiateNtv(this.getPtr(), store.getPtr(), module.getPtr())); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/WasmModule.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | public class WasmModule extends AbstractOpaquePtr { 4 | WasmModule(long ptr) { 5 | super(ptr, WasmModule::freeModule); 6 | } 7 | 8 | private static native void freeModule(long ptr); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/WasmStore.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | public class WasmStore extends AbstractOpaquePtr { 4 | // Store is !Send and !Sync in Rust, we will enforce that with a ThreadLocal 5 | 6 | WasmStore(long ptr) { 7 | super(ptr, WasmStore::freeStore); 8 | } 9 | 10 | private static native void freeStore(long ptr); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/Wasmtime.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | 8 | /** 9 | * Wasmtime 10 | *

11 | * A wrapper over the FFI of the Rust wasmtime library, which uses Wasmtime for 12 | * the WASM runtime 13 | */ 14 | public class Wasmtime { 15 | private static final String NATIVE_LIB = "wasmtime_jni"; 16 | private static volatile boolean libraryLoaded = false; 17 | 18 | private static void loadNative() { 19 | if (Wasmtime.libraryLoaded) { 20 | return; 21 | } 22 | 23 | try { 24 | System.loadLibrary(NATIVE_LIB); 25 | System.out.printf("loadLibrary succeeded for %s%n", NATIVE_LIB); 26 | libraryLoaded = true; 27 | return; 28 | } catch (UnsatisfiedLinkError e) { 29 | System.out.printf("Failed to loadLibrary %s, will try from classpath%n", NATIVE_LIB); 30 | } 31 | 32 | String osName = System.getProperty("os.name"); 33 | String osArch = System.getProperty("os.arch"); 34 | 35 | if (osName.contains("Mac OS X")) { 36 | osName = "Darwin"; 37 | } 38 | if (osArch.contains("amd64")) { 39 | osArch = "x86_64"; 40 | } 41 | 42 | final String libName = System.mapLibraryName(NATIVE_LIB); 43 | final String libPath = String.format("NATIVE/%s/%s/%s", osName, osArch, libName); 44 | 45 | // open a temporary file for the native_lib 46 | final String tmpDir = System.getProperty("java.io.tmpdir"); 47 | 48 | if (tmpDir == null) { 49 | throw new RuntimeException("java.io.tmpdir is null?"); 50 | } 51 | 52 | final File libFile = new File(tmpDir, libName); 53 | final String path = libFile.getAbsolutePath(); 54 | 55 | // FIXME: add back... 56 | // if (libFile.exists()) { 57 | // System.out.printf("Temporary library already exists %s, did not replace%n", 58 | // libFile); 59 | // loadLibrary(path); 60 | // return; 61 | // } 62 | 63 | try (OutputStream os = new FileOutputStream(libFile, false); 64 | InputStream in = ClassLoader.getSystemResourceAsStream(libPath);) { 65 | if (in == null) 66 | throw new RuntimeException(String.format("could not find %s in classpath", libPath)); 67 | long length = in.transferTo(os); 68 | System.out.printf("Created temporary library sized %d at %s%n", length, libFile); 69 | os.flush(); 70 | } catch (Exception e) { 71 | System.err.printf("Failed to write %s to %s library: %s%n", libPath, libFile, e.getMessage()); 72 | libraryLoaded = false; 73 | throw new RuntimeException(e); 74 | } 75 | 76 | loadLibrary(path); 77 | return; 78 | } 79 | 80 | private static void loadLibrary(String path) { 81 | try { 82 | System.load(path); 83 | System.out.printf("Load succeeded for %s%n", path); 84 | libraryLoaded = true; 85 | } catch (UnsatisfiedLinkError e) { 86 | System.out.printf("Failed to load %s%n", path); 87 | libraryLoaded = false; 88 | throw e; 89 | } 90 | } 91 | 92 | public Wasmtime() throws WasmtimeException { 93 | try { 94 | loadNative(); 95 | } catch (Exception e) { 96 | throw new WasmtimeException("Failed to load native library for Wasmtime", e); 97 | } 98 | } 99 | 100 | private static native long newWasmEngineNtv(); 101 | 102 | public WasmEngine newWasmEngine() { 103 | return new WasmEngine(newWasmEngineNtv()); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/WasmtimeException.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | public class WasmtimeException extends Exception { 4 | public WasmtimeException(String msg) { 5 | super(msg); 6 | } 7 | 8 | public WasmtimeException(String msg, Throwable e) { 9 | super(msg, e); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/proxy/WasmExport.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.proxy; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Use to annotate methods that will be exported for use in WebAssembly from 10 | * Java. 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.METHOD) 14 | public @interface WasmExport { 15 | /** Name in the Webassembly ABI */ 16 | public String name() default ""; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/proxy/WasmExportable.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.proxy; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Method; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import net.bluejekyll.wasmtime.WasmFunction; 9 | import net.bluejekyll.wasmtime.WasmStore; 10 | import net.bluejekyll.wasmtime.WasmtimeException; 11 | 12 | public interface WasmExportable { 13 | public default List defineWasmFunctions(WasmStore store) throws WasmtimeException { 14 | Class clazz = this.getClass(); 15 | 16 | // name will default to the class name if none is offered 17 | String moduleName = ""; 18 | WasmModule module = clazz.getAnnotation(WasmModule.class); 19 | if (module != null) { 20 | moduleName = module.name(); 21 | } 22 | 23 | if (moduleName.equals("")) { 24 | // TODO: convert to WASM convention 25 | moduleName = clazz.getName(); 26 | } 27 | 28 | // list the methods annotated with WasmExport 29 | Method[] methods = clazz.getMethods(); 30 | ArrayList functions = new ArrayList<>(methods.length); 31 | 32 | for (Method method : methods) { 33 | WasmExport export = method.getAnnotation(WasmExport.class); 34 | 35 | // if there was no annotation, we will skip... 36 | if (export == null) 37 | continue; 38 | 39 | String exportName = export.name(); 40 | if (exportName.equals("")) { 41 | exportName = method.getName(); 42 | } 43 | 44 | // get parameters... 45 | WasmFunction function = WasmFunction.newFunc(store, method, this); 46 | functions.add(new WasmFunctionDef(moduleName, exportName, function)); 47 | } 48 | 49 | functions.trimToSize(); 50 | return functions; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/proxy/WasmFunctionDef.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.proxy; 2 | 3 | import net.bluejekyll.wasmtime.WasmFunction; 4 | 5 | public class WasmFunctionDef { 6 | private final String moduleName; 7 | private final String functionName; 8 | private final WasmFunction function; 9 | 10 | public WasmFunctionDef(String moduleName, String functionName, WasmFunction function) { 11 | this.moduleName = moduleName; 12 | this.functionName = functionName; 13 | this.function = function; 14 | } 15 | 16 | public String getModuleName() { 17 | return this.moduleName; 18 | } 19 | 20 | public String getFunctionName() { 21 | return this.functionName; 22 | } 23 | 24 | public WasmFunction getFunction() { 25 | return this.function; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/proxy/WasmImport.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.proxy; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Use to annotate methods that will be exported for use in WebAssembly from 10 | * Java. 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.METHOD) 14 | public @interface WasmImport { 15 | /** Name in the Webassembly ABI */ 16 | public String name() default ""; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/proxy/WasmImportProxy.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.proxy; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.lang.reflect.Method; 6 | import java.lang.reflect.Proxy; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.Optional; 10 | 11 | import javax.annotation.concurrent.NotThreadSafe; 12 | 13 | import net.bluejekyll.wasmtime.WasmFunction; 14 | import net.bluejekyll.wasmtime.WasmInstance; 15 | import net.bluejekyll.wasmtime.WasmStore; 16 | import net.bluejekyll.wasmtime.WasmtimeException; 17 | import net.bluejekyll.wasmtime.ty.WasmType; 18 | 19 | @NotThreadSafe 20 | public class WasmImportProxy { 21 | private WasmImportProxy() { 22 | } 23 | 24 | @NotThreadSafe 25 | private static class WasmInvocationHandler implements InvocationHandler { 26 | final WasmInstance instance; 27 | final WasmStore store; 28 | final Map functions; 29 | 30 | WasmInvocationHandler(WasmInstance instance, WasmStore store, Map functions) { 31 | this.instance = instance; 32 | this.store = store; 33 | this.functions = functions; 34 | } 35 | 36 | @Override 37 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 38 | WasmFunction function = functions.get(method.getName()); 39 | 40 | if (function != null) { 41 | WasmType[] wasmArgs = (WasmType[]) args; 42 | return function.call(this.instance, this.store, (Class) method.getReturnType(), wasmArgs); 43 | } 44 | 45 | // TODO: add this back with Java 1.16 46 | // if (method.isDefault()) { 47 | // return InvocationHandler.invokeDefault​(proxy, method, args); 48 | // } 49 | 50 | throw new RuntimeException(String.format("Method is not defined for WASM module: %s", method.getName())); 51 | } 52 | 53 | } 54 | 55 | public static T proxyWasm(WasmInstance instance, WasmStore store, Class proxyClass) 56 | throws IllegalArgumentException, WasmtimeException { 57 | final Method[] methods = proxyClass.getMethods(); 58 | 59 | final HashMap functions = new HashMap<>(); 60 | 61 | for (Method method : methods) { 62 | final WasmImport wasmImport = method.getAnnotation(WasmImport.class); 63 | final String methodName = method.getName(); 64 | 65 | // we will only support annotated methods 66 | if (wasmImport == null) 67 | continue; 68 | 69 | String functionName = wasmImport.name(); 70 | if (functionName.isEmpty()) { 71 | functionName = methodName; 72 | } 73 | 74 | Optional func = instance.getFunction(store, functionName); 75 | 76 | if (!func.isPresent()) { 77 | throw new WasmtimeException(String.format("Function not present in WASM Module: %s", functionName)); 78 | } 79 | 80 | // we use the Java method name here because that's what will be passed into the 81 | // invocation handler. 82 | WasmFunction existing = functions.get(methodName); 83 | if (existing != null) { 84 | throw new WasmtimeException( 85 | String.format("Function %s already has method %s bound", functionName, methodName)); 86 | } 87 | 88 | functions.put(methodName, func.get()); 89 | } 90 | 91 | WasmInvocationHandler invocationHandler = new WasmInvocationHandler(instance, store, functions); 92 | return (T) Proxy.newProxyInstance(proxyClass.getClassLoader(), new Class[] { proxyClass }, 93 | invocationHandler); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/proxy/WasmImportable.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.proxy; 2 | 3 | public interface WasmImportable { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/proxy/WasmModule.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.proxy; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Use this to indicate the module of class or interface being abstracted over 10 | * Webassembly for Java 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.TYPE) 14 | public @interface WasmModule { 15 | /** 16 | * Name in the Webassembly ABI. 17 | * 18 | * This is unnecessary, and ignored, for import proxies. 19 | */ 20 | public String name() default ""; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/ty/ExternRef.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.ty; 2 | 3 | /** 4 | * This is only a placeholder for now, to be implemented in the future 5 | */ 6 | public class ExternRef implements WasmType { 7 | private ExternRef() { 8 | throw new UnsupportedOperationException(); 9 | } 10 | 11 | @Override 12 | public Integer getField() { 13 | throw new UnsupportedOperationException(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/ty/F32.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.ty; 2 | 3 | public class F32 implements WasmType { 4 | public final float field; 5 | 6 | public F32(float field) { 7 | this.field = field; 8 | } 9 | 10 | @Override 11 | public Float getField() { 12 | return this.field; 13 | } 14 | 15 | public float floatValue() { 16 | return this.field; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/ty/F64.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.ty; 2 | 3 | public class F64 implements WasmType { 4 | public final double field; 5 | 6 | public F64(double field) { 7 | this.field = field; 8 | } 9 | 10 | @Override 11 | public Double getField() { 12 | return this.field; 13 | } 14 | 15 | public double doubleValue() { 16 | return this.field; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/ty/FuncRef.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.ty; 2 | 3 | /** 4 | * This is only here as a placeholder, it's not usable yet 5 | */ 6 | public class FuncRef implements WasmType { 7 | private FuncRef() { 8 | throw new UnsupportedOperationException(); 9 | } 10 | 11 | @Override 12 | public Integer getField() { 13 | throw new UnsupportedOperationException(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/ty/I32.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.ty; 2 | 3 | public class I32 implements WasmType { 4 | public final int field; 5 | 6 | public I32(int field) { 7 | this.field = field; 8 | } 9 | 10 | @Override 11 | public Integer getField() { 12 | return this.field; 13 | } 14 | 15 | public int intValue() { 16 | return this.field; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/ty/I64.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.ty; 2 | 3 | public class I64 implements WasmType { 4 | public final long field; 5 | 6 | public I64(long field) { 7 | this.field = field; 8 | } 9 | 10 | @Override 11 | public Long getField() { 12 | return this.field; 13 | } 14 | 15 | public long longValue() { 16 | return field; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/ty/V128.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.ty; 2 | 3 | import java.math.BigInteger; 4 | 5 | public class V128 implements WasmType { 6 | // TODO: maybe this should be an array of bytes... 7 | public final BigInteger field; 8 | 9 | public V128(BigInteger field) { 10 | this.field = field; 11 | } 12 | 13 | @Override 14 | public BigInteger getField() { 15 | return this.field; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/ty/ValType.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.ty; 2 | 3 | public enum ValType { 4 | I32, 5 | I64, 6 | F32, 7 | F64, 8 | V128, 9 | FuncRef, 10 | ExternRef; 11 | 12 | public boolean is_num() { 13 | switch (this) { 14 | case I32: 15 | ; 16 | case I64: 17 | ; 18 | case F32: 19 | ; 20 | case V128: 21 | return true; 22 | default: 23 | return false; 24 | } 25 | } 26 | 27 | public boolean is_ref() { 28 | switch (this) { 29 | case FuncRef: 30 | ; 31 | case ExternRef: 32 | return true; 33 | default: 34 | return false; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/ty/WasmType.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.ty; 2 | 3 | public interface WasmType { 4 | Object getField(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/ty/WasmTypeUtil.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.ty; 2 | 3 | import java.math.BigInteger; 4 | 5 | public class WasmTypeUtil { 6 | public static I32 i32(int val) { 7 | return new I32(val); 8 | }; 9 | 10 | public static I64 i64(long val) { 11 | return new I64(val); 12 | }; 13 | 14 | public static F32 f32(float val) { 15 | return new F32(val); 16 | }; 17 | 18 | public static F64 f64(double val) { 19 | return new F64(val); 20 | }; 21 | 22 | public static V128 v128(BigInteger val) { 23 | return new V128(val); 24 | }; 25 | 26 | // FuncRef, 27 | // ExternRef; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/bluejekyll/wasmtime/ty/WasmVoid.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.ty; 2 | 3 | public class WasmVoid implements WasmType { 4 | public final java.lang.Void field = null; 5 | 6 | @Override 7 | public java.lang.Void getField() { 8 | return this.field; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/TestUtil.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import java.io.File; 4 | 5 | public class TestUtil { 6 | public final static File WASM_TARGET_DIR = new File("target/wasm32-wasi/debug"); 7 | public final static File MATH_PATH = new File(WASM_TARGET_DIR, "math.wasm"); 8 | public final static File MATH_WIT_PATH = new File(WASM_TARGET_DIR, "math_wit.wasm"); 9 | public final static File SLICES_PATH = new File(WASM_TARGET_DIR, "slices.wasm"); 10 | public final static File STRINGS_PATH = new File(WASM_TARGET_DIR, "strings.wasm"); 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/WasmEngineTest.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import static org.junit.Assert.fail; 4 | 5 | import org.junit.Test; 6 | 7 | public class WasmEngineTest { 8 | @Test 9 | public void testNewWasmEngine() throws Exception { 10 | Wasmtime wasm = new Wasmtime(); 11 | try (WasmEngine engine = wasm.newWasmEngine()) { 12 | System.out.println("new engine succeeded"); 13 | } catch (Exception e) { 14 | fail(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/WasmFunctionTest.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import java.io.UnsupportedEncodingException; 8 | import java.lang.reflect.Method; 9 | import java.nio.ByteBuffer; 10 | import java.util.Optional; 11 | 12 | import org.junit.Test; 13 | 14 | import net.bluejekyll.wasmtime.ty.*; 15 | 16 | import static net.bluejekyll.wasmtime.ty.WasmTypeUtil.*; 17 | 18 | public class WasmFunctionTest { 19 | public final void helloWorld() { 20 | System.out.println("Hello World"); 21 | } 22 | 23 | @Test 24 | public void testFunction() throws Exception { 25 | Wasmtime wasm = new Wasmtime(); 26 | try (WasmEngine engine = wasm.newWasmEngine(); 27 | WasmStore store = engine.newStore()) { 28 | 29 | Method method = this.getClass().getMethod("helloWorld"); 30 | WasmFunction func = WasmFunction.newFunc(store, method, this); 31 | 32 | try (func) { 33 | System.out.println("new store succeeded"); 34 | func.call_for_tests(store); 35 | assertNotNull(func); 36 | } 37 | } 38 | } 39 | 40 | public final I32 addI32s(I32 a, I32 b) { 41 | return i32(a.field + b.field); 42 | } 43 | 44 | @Test 45 | public void testParamsAndReturn() throws Exception { 46 | Wasmtime wasm = new Wasmtime(); 47 | try (WasmEngine engine = wasm.newWasmEngine(); 48 | WasmStore store = engine.newStore()) { 49 | 50 | Method method = this.getClass().getMethod("addI32s", I32.class, I32.class); 51 | WasmFunction func = WasmFunction.newFunc(store, method, this); 52 | 53 | try (func) { 54 | System.out.println("running function"); 55 | I32 val = func.call_for_tests(store, I32.class, i32(1), i32(2)); 56 | assertEquals(3, val.field); 57 | } 58 | } 59 | } 60 | 61 | public final I64 addI64s(I64 a, I64 b) { 62 | return i64(a.field + b.field); 63 | } 64 | 65 | @Test 66 | public void testLongParamsAndReturn() throws Exception { 67 | Wasmtime wasm = new Wasmtime(); 68 | try (WasmEngine engine = wasm.newWasmEngine(); WasmStore store = engine.newStore()) { 69 | 70 | Method method = this.getClass().getMethod("addI64s", I64.class, I64.class); 71 | WasmFunction func = WasmFunction.newFunc(store, method, this); 72 | 73 | try (func) { 74 | System.out.println("running function"); 75 | I64 val = func.call_for_tests(store, I64.class, i64(1), i64(2)); 76 | assertEquals(3, val.field); 77 | } 78 | } 79 | } 80 | 81 | public final F32 addF32s(F32 a, F32 b) { 82 | return f32(a.field + b.field); 83 | } 84 | 85 | @Test 86 | public void testFloatParamsAndReturn() throws Exception { 87 | Wasmtime wasm = new Wasmtime(); 88 | try (WasmEngine engine = wasm.newWasmEngine(); WasmStore store = engine.newStore()) { 89 | 90 | Method method = this.getClass().getMethod("addF32s", F32.class, F32.class); 91 | WasmFunction func = WasmFunction.newFunc(store, method, this); 92 | 93 | try (func) { 94 | System.out.println("running function"); 95 | F32 val = func.call_for_tests(store, F32.class, f32((float) 1.1), f32((float) 1.2)); 96 | assertTrue(2.29 < val.field); 97 | assertTrue(2.31 > val.field); 98 | } 99 | } 100 | } 101 | 102 | public final F64 addF64(F64 a, F64 b) { 103 | return f64(a.field + b.field); 104 | } 105 | 106 | @Test 107 | public void testDoubleParamsAndReturn() throws Exception { 108 | Wasmtime wasm = new Wasmtime(); 109 | try (WasmEngine engine = wasm.newWasmEngine(); WasmStore store = engine.newStore()) { 110 | 111 | Method method = this.getClass().getMethod("addF64", F64.class, F64.class); 112 | WasmFunction func = WasmFunction.newFunc(store, method, this); 113 | 114 | try (func) { 115 | System.out.println("running function"); 116 | F64 val = func.call_for_tests(store, F64.class, f64((double) 1.1), f64((double) 1.2)); 117 | assertTrue(2.29 < val.field); 118 | assertTrue(2.31 > val.field); 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/WasmInstanceTest.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import org.junit.Test; 4 | 5 | public class WasmInstanceTest { 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/WasmLinkerTest.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.Optional; 7 | 8 | import org.junit.Test; 9 | 10 | public class WasmLinkerTest { 11 | public final void helloWorld() { 12 | System.out.println("Hello World"); 13 | } 14 | 15 | @Test 16 | public void testLinking() throws Exception { 17 | String call_hello_world = "(module\n" + " (import \"hello\" \"world\" (func $host_hello))\n" 18 | + " (func (export \"hello\")\n" + " call $host_hello)\n" + " )"; 19 | 20 | Wasmtime wasm = new Wasmtime(); 21 | try (WasmEngine engine = wasm.newWasmEngine(); 22 | WasmStore store = engine.newStore(); 23 | WasmLinker linker = engine.newLinker();) { 24 | 25 | // define the Java hello world function 26 | Method method = this.getClass().getMethod("helloWorld"); 27 | WasmFunction func = WasmFunction.newFunc(store, method, this); 28 | 29 | // add it to the linker 30 | linker.defineFunction("hello", "world", func); 31 | 32 | // compile the calling module and then link it 33 | WasmModule module = engine.newModule(call_hello_world.getBytes()); 34 | WasmInstance instance = linker.instantiate(store, module); 35 | Optional function = instance.getFunction(store, "hello"); 36 | 37 | assertTrue(function.isPresent()); 38 | 39 | function.ifPresent(f -> { 40 | try { 41 | f.call(instance, store); 42 | } catch (Exception e) { 43 | throw new RuntimeException(e); 44 | } 45 | }); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/WasmModuleTest.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import static org.junit.Assert.assertNotNull; 4 | import static org.junit.Assert.fail; 5 | 6 | import java.io.File; 7 | 8 | import org.junit.Test; 9 | 10 | public class WasmModuleTest { 11 | @Test 12 | public void testNewWasmModule() throws Exception { 13 | String good = "(module\n" + " (import \"\" \"\" (func $host_hello (param i32)))\n" + "\n" 14 | + " (func (export \"hello\")\n" + " i32.const 3\n" + " call $host_hello)\n" + " )"; 15 | 16 | Wasmtime wasm = new Wasmtime(); 17 | try (WasmEngine engine = wasm.newWasmEngine(); WasmModule module = engine.newModule(good.getBytes())) { 18 | System.out.println("module compiled"); 19 | assertNotNull(module); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/WasmStoreTest.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import static org.junit.Assert.fail; 4 | 5 | import org.junit.Test; 6 | 7 | public class WasmStoreTest { 8 | @Test 9 | public void testNewWasmStore() throws Exception { 10 | Wasmtime wasm = new Wasmtime(); 11 | try (WasmEngine engine = wasm.newWasmEngine(); WasmStore store = engine.newStore()) { 12 | System.out.println("new store succeeded"); 13 | } catch (Exception e) { 14 | fail(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/WasmtimeExceptionTest.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | import static org.junit.Assert.assertNull; 7 | import static org.junit.Assert.assertTrue; 8 | import static org.junit.Assert.fail; 9 | 10 | import java.lang.reflect.Method; 11 | 12 | public class WasmtimeExceptionTest { 13 | @Test(expected = WasmtimeException.class) 14 | public void testNewWasmBadModule() throws Exception { 15 | byte[] bad = { 0, 1, 2 }; 16 | 17 | Wasmtime wasm = new Wasmtime(); 18 | try (WasmEngine engine = wasm.newWasmEngine(); WasmModule module = engine.newModule(bad)) { 19 | System.out.println("bad, module should have failed"); 20 | } 21 | } 22 | 23 | @Test 24 | public void testWasmWorksAfterException() throws Exception { 25 | byte[] bad = { 0, 1, 2 }; 26 | 27 | Wasmtime wasm = new Wasmtime(); 28 | try (WasmEngine engine = wasm.newWasmEngine()) { 29 | try (WasmModule module = engine.newModule(bad)) { 30 | fail("bad, module should have failed"); 31 | } catch (WasmtimeException e) { 32 | // cool 33 | } 34 | 35 | // check that things are still working 36 | try (WasmStore store = engine.newStore()) { 37 | System.out.println("engine still functions"); 38 | } 39 | } 40 | } 41 | 42 | public void iThrowForFun() { 43 | System.out.println("I throw for fun!"); 44 | throw new RuntimeException("I throw for fun!"); 45 | } 46 | 47 | public void iWork() { 48 | System.out.println("I work!"); 49 | } 50 | 51 | @Test 52 | public void testExceptionInJavaFunc() throws Exception { 53 | Wasmtime wasm = new Wasmtime(); 54 | try (WasmEngine engine = wasm.newWasmEngine(); WasmStore store = engine.newStore()) { 55 | 56 | Method method = this.getClass().getMethod("iThrowForFun"); 57 | WasmFunction func = WasmFunction.newFunc(store, method, this); 58 | 59 | try (func) { 60 | System.out.println("running function"); 61 | func.call_for_tests(store); 62 | } catch (Exception e) { 63 | // TODO: we eventually want to look for the RuntimeException 64 | assertTrue(e.getMessage().contains("InvocationTargetException")); 65 | } 66 | 67 | // double check the exception is cleared... 68 | Method method2 = this.getClass().getMethod("iWork"); 69 | WasmFunction func2 = WasmFunction.newFunc(store, method2, this); 70 | 71 | try (func2) { 72 | func2.call_for_tests(store); 73 | assertTrue(true); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/WasmtimeTest.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime; 2 | 3 | import java.lang.reflect.Method; 4 | import java.nio.ByteBuffer; 5 | import java.util.Optional; 6 | import java.util.function.Function; 7 | 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Unit test for simple App. 15 | */ 16 | public class WasmtimeTest { 17 | @Test 18 | public void testWasmtimeLibraryLoads() throws Exception { 19 | new Wasmtime(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/proxy/ExportTests.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.proxy; 2 | 3 | import static org.junit.Assert.assertArrayEquals; 4 | import static org.junit.Assert.assertEquals; 5 | import static org.junit.Assert.assertNotNull; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | import java.io.IOException; 9 | import java.util.Optional; 10 | 11 | import org.junit.After; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | 15 | import net.bluejekyll.wasmtime.TestUtil; 16 | import net.bluejekyll.wasmtime.WasmEngine; 17 | import net.bluejekyll.wasmtime.WasmFunction; 18 | import net.bluejekyll.wasmtime.WasmInstance; 19 | import net.bluejekyll.wasmtime.WasmLinker; 20 | import net.bluejekyll.wasmtime.WasmStore; 21 | import net.bluejekyll.wasmtime.WasmModule; 22 | import net.bluejekyll.wasmtime.Wasmtime; 23 | import net.bluejekyll.wasmtime.WasmtimeException; 24 | 25 | /** 26 | * Tests for validating Class (module) exports to WASM 27 | */ 28 | public class ExportTests { 29 | private Wasmtime wasmtime; 30 | private WasmEngine engine; 31 | private WasmModule module; 32 | private WasmStore store; 33 | private WasmLinker linker; 34 | 35 | // @Before 36 | // public void setup() throws WasmtimeException, IOException { 37 | // this.wasmtime = new Wasmtime(); 38 | // this.engine = wasmtime.newWasmEngine(); 39 | // this.module = engine.newModule(TestUtil.SLICES_PATH); 40 | // System.out.println("slices compiled"); 41 | // assertNotNull(this.module); 42 | 43 | // this.store = engine.newStore(); 44 | // this.linker = engine.newLinker(); 45 | 46 | // // this could be a test instead... 47 | // this.linker.defineFunctions(this.store, new TestExport()); 48 | // } 49 | 50 | // @After 51 | // public void tearDown() { 52 | // this.linker.close(); 53 | // this.store.close(); 54 | // this.module.close(); 55 | // this.engine.close(); 56 | // } 57 | 58 | // @Test 59 | // public void testHelloToJavaWasmModule() throws Exception { 60 | // WasmInstance instance = linker.instantiate(store, module); 61 | // Optional func = instance.getFunction(store, 62 | // "say_hello_to_java"); 63 | 64 | // assertTrue("say_hello_to_java isn't present in the module", 65 | // func.isPresent()); 66 | // WasmFunction function = func.get(); 67 | 68 | // function.call(instance, store); 69 | 70 | // } 71 | 72 | // @Test 73 | // public void testSlicesWasmModule() throws Exception { 74 | // WasmInstance instance = linker.instantiate(store, module); 75 | // Optional func = instance.getFunction(store, "print_bytes"); 76 | 77 | // assertTrue("print_bytes isn't present in the module", func.isPresent()); 78 | // WasmFunction function = func.get(); 79 | 80 | // byte[] bytes = new byte[] { 0, 1, 2, 3 }; 81 | 82 | // function.call(instance, store, bytes); 83 | // } 84 | 85 | // @Test 86 | // public void testReverseBytes() throws Exception { 87 | // WasmInstance instance = linker.instantiate(store, module); 88 | // Optional func = instance.getFunction(store, "reverse_bytes"); 89 | 90 | // assertTrue("print_bytes isn't present in the module", func.isPresent()); 91 | // WasmFunction function = func.get(); 92 | 93 | // byte[] bytes = new byte[] { 0, 1, 2, 3 }; 94 | 95 | // byte[] ret = function.call(instance, store, byte[].class, bytes); 96 | // assertNotNull(ret); 97 | // assertEquals(bytes.length, ret.length); 98 | 99 | // assertArrayEquals(ret, new byte[] { 3, 2, 1, 0 }); 100 | // } 101 | 102 | // @Test 103 | // public void testReverseBytesInJava() throws Exception { 104 | // WasmInstance instance = linker.instantiate(store, module); 105 | // Optional func = instance.getFunction(store, 106 | // "reverse_bytes_in_java"); 107 | 108 | // assertTrue("print_bytes isn't present in the module", func.isPresent()); 109 | // WasmFunction function = func.get(); 110 | 111 | // byte[] bytes = new byte[] { 0, 1, 2, 3 }; 112 | 113 | // byte[] ret = function.call(instance, store, byte[].class, bytes); 114 | // assertNotNull(ret); 115 | // assertEquals(bytes.length, ret.length); 116 | 117 | // assertArrayEquals(ret, new byte[] { 3, 2, 1, 0 }); 118 | 119 | // } 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/proxy/ImportTests.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.proxy; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | 6 | import java.io.IOException; 7 | 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import net.bluejekyll.wasmtime.TestUtil; 13 | import net.bluejekyll.wasmtime.WasmEngine; 14 | import net.bluejekyll.wasmtime.WasmInstance; 15 | import net.bluejekyll.wasmtime.WasmLinker; 16 | import net.bluejekyll.wasmtime.WasmStore; 17 | import net.bluejekyll.wasmtime.WasmModule; 18 | import net.bluejekyll.wasmtime.Wasmtime; 19 | import net.bluejekyll.wasmtime.WasmtimeException; 20 | 21 | public class ImportTests { 22 | private Wasmtime wasmtime; 23 | private WasmEngine engine; 24 | private WasmModule module; 25 | private WasmStore store; 26 | private WasmLinker linker; 27 | 28 | // @Before 29 | // public void setup() throws WasmtimeException, IOException { 30 | // this.wasmtime = new Wasmtime(); 31 | // this.engine = wasmtime.newWasmEngine(); 32 | // this.module = engine.newModule(TestUtil.MATH_PATH); 33 | // System.out.println("slices compiled"); 34 | // assertNotNull(this.module); 35 | 36 | // this.store = engine.newStore(); 37 | // this.linker = engine.newLinker(); 38 | // } 39 | 40 | // @After 41 | // public void tearDown() { 42 | // this.linker.close(); 43 | // this.store.close(); 44 | // this.module.close(); 45 | // this.engine.close(); 46 | // } 47 | 48 | // @Test 49 | // public void testAddIntegers() throws Exception { 50 | // WasmInstance instance = linker.instantiate(store, module); 51 | // TestImportProxy proxy = WasmImportProxy.proxyWasm(instance, store, 52 | // TestImportProxy.class); 53 | 54 | // int ret = proxy.addInteger(3, 2); 55 | // assertEquals(ret, 5); 56 | // } 57 | 58 | // @Test 59 | // public void testAddFloats() throws Exception { 60 | // WasmInstance instance = linker.instantiate(store, module); 61 | // TestImportProxy proxy = WasmImportProxy.proxyWasm(instance, store, 62 | // TestImportProxy.class); 63 | 64 | // float ret = proxy.addFloats((float) 1.1, (float) 2.2); 65 | // assertEquals(ret, (float) 3.3, 0.1); 66 | // } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/proxy/TestExport.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.proxy; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.io.UnsupportedEncodingException; 6 | 7 | @WasmModule(name = "test") 8 | public class TestExport implements WasmExportable { 9 | 10 | @WasmExport(name = "hello_to_java") 11 | public void helloToJava(byte[] hello_bytes) { 12 | final String hello = "Hello Java!"; 13 | 14 | // System.out.printf("Hello length: %d%n", hello_bytes.length); 15 | 16 | final String from_wasm; 17 | try { 18 | from_wasm = new String(hello_bytes, "UTF-8"); 19 | } catch (UnsupportedEncodingException e) { 20 | // this should never happen for UTF-8 21 | throw new RuntimeException(e); 22 | } 23 | 24 | // System.out.printf("Hello: %s%n", from_wasm); 25 | assertEquals(hello, from_wasm); 26 | } 27 | 28 | @WasmExport(name = "reverse_bytes_java") 29 | public final byte[] reverseBytesJava(byte[] buffer) { 30 | // System.out.printf("reversingBytesJava len: %d%n", buffer.length); 31 | 32 | byte[] bytes = new byte[buffer.length]; 33 | 34 | for (int i = bytes.length - 1; i >= 0; i--) { 35 | bytes[i] = buffer[buffer.length - 1 - i]; 36 | } 37 | 38 | return bytes; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/proxy/TestImportProxy.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.proxy; 2 | 3 | @WasmModule 4 | public interface TestImportProxy extends WasmImportable { 5 | @WasmImport(name = "add_i32") 6 | int addInteger(int a, int b); 7 | 8 | @WasmImport(name = "add_f32") 9 | float addFloats(float a, float b); 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/tests/MathTests.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.tests; 2 | 3 | import net.bluejekyll.wasmtime.*; 4 | import net.bluejekyll.wasmtime.ty.*; 5 | 6 | import org.junit.Test; 7 | 8 | import java.io.UnsupportedEncodingException; 9 | import java.nio.ByteBuffer; 10 | import java.util.Optional; 11 | 12 | import static org.junit.Assert.*; 13 | import static net.bluejekyll.wasmtime.ty.WasmTypeUtil.*; 14 | 15 | /** 16 | * Tests corresponding to the Rust based WASM programs in /tests/math 17 | */ 18 | public class MathTests { 19 | @Test 20 | public void testAddI32() throws Exception { 21 | Wasmtime wasm = new Wasmtime(); 22 | try (WasmEngine engine = wasm.newWasmEngine(); 23 | WasmModule module = engine.newModule(TestUtil.MATH_PATH); 24 | WasmStore store = engine.newStore(); 25 | WasmLinker linker = engine.newLinker()) { 26 | System.out.println("slices compiled"); 27 | assertNotNull(module); 28 | 29 | WasmInstance instance = linker.instantiate(store, module); 30 | Optional func = instance.getFunction(store, "add_i32"); 31 | 32 | assertTrue("add_i32 isn't present in the module", func.isPresent()); 33 | WasmFunction function = func.get(); 34 | 35 | I32 ret = function.call(instance, store, I32.class, i32(3), i32(2)); 36 | assertEquals(ret.field, 5); 37 | } 38 | } 39 | 40 | @Test 41 | public void testAddI64() throws Exception { 42 | Wasmtime wasm = new Wasmtime(); 43 | try (WasmEngine engine = wasm.newWasmEngine(); 44 | WasmModule module = engine.newModule(TestUtil.MATH_PATH); 45 | WasmStore store = engine.newStore(); 46 | WasmLinker linker = engine.newLinker()) { 47 | System.out.println("slices compiled"); 48 | assertNotNull(module); 49 | 50 | WasmInstance instance = linker.instantiate(store, module); 51 | Optional func = instance.getFunction(store, "add_i64"); 52 | 53 | assertTrue("add_i64 isn't present in the module", func.isPresent()); 54 | WasmFunction function = func.get(); 55 | 56 | I64 ret = function.call(instance, store, I64.class, i64(3), i64(2)); 57 | assertEquals(ret.field, 5); 58 | } 59 | } 60 | 61 | @Test 62 | public void testAddF32() throws Exception { 63 | Wasmtime wasm = new Wasmtime(); 64 | try (WasmEngine engine = wasm.newWasmEngine(); 65 | WasmModule module = engine.newModule(TestUtil.MATH_PATH); 66 | WasmStore store = engine.newStore(); 67 | WasmLinker linker = engine.newLinker()) { 68 | System.out.println("slices compiled"); 69 | assertNotNull(module); 70 | 71 | WasmInstance instance = linker.instantiate(store, module); 72 | Optional func = instance.getFunction(store, "add_f32"); 73 | 74 | assertTrue("add_f32 isn't present in the module", func.isPresent()); 75 | WasmFunction function = func.get(); 76 | 77 | F32 ret = function.call(instance, store, F32.class, f32((float) 1.1), f32((float) 2.2)); 78 | assertEquals(ret.field, (float) 3.3, 0.1); 79 | } 80 | } 81 | 82 | @Test 83 | public void testAddF64() throws Exception { 84 | Wasmtime wasm = new Wasmtime(); 85 | try (WasmEngine engine = wasm.newWasmEngine(); 86 | WasmModule module = engine.newModule(TestUtil.MATH_PATH); 87 | WasmStore store = engine.newStore(); 88 | WasmLinker linker = engine.newLinker()) { 89 | System.out.println("slices compiled"); 90 | assertNotNull(module); 91 | 92 | WasmInstance instance = linker.instantiate(store, module); 93 | Optional func = instance.getFunction(store, "add_f64"); 94 | 95 | assertTrue("add_f64 isn't present in the module", func.isPresent()); 96 | WasmFunction function = func.get(); 97 | 98 | F64 ret = function.call(instance, store, F64.class, f64((double) 1.1), f64((double) 2.2)); 99 | assertEquals(ret.field, (double) 3.3, 0.1); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/tests/MathWitTests.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.tests; 2 | 3 | import net.bluejekyll.wasmtime.*; 4 | import net.bluejekyll.wasmtime.ty.*; 5 | 6 | import org.junit.Test; 7 | 8 | import java.io.UnsupportedEncodingException; 9 | import java.nio.ByteBuffer; 10 | import java.util.Optional; 11 | 12 | import static org.junit.Assert.*; 13 | import static net.bluejekyll.wasmtime.ty.WasmTypeUtil.*; 14 | 15 | /** 16 | * Tests corresponding to the Rust based WASM programs in /tests/math 17 | */ 18 | public class MathWitTests { 19 | @Test 20 | public void testAddI32() throws Exception { 21 | Wasmtime wasm = new Wasmtime(); 22 | try (WasmEngine engine = wasm.newWasmEngine(); 23 | WasmModule module = engine.newModule(TestUtil.MATH_WIT_PATH); 24 | WasmStore store = engine.newStore(); 25 | WasmLinker linker = engine.newLinker()) { 26 | System.out.println("slices compiled"); 27 | assertNotNull(module); 28 | 29 | WasmInstance instance = linker.instantiate(store, module); 30 | Optional func = instance.getFunction(store, "add-i32"); 31 | 32 | assertTrue("add_i32 isn't present in the module", func.isPresent()); 33 | WasmFunction function = func.get(); 34 | 35 | I32 ret = function.call(instance, store, I32.class, i32(3), i32(2)); 36 | assertEquals(ret.field, 5); 37 | } 38 | } 39 | 40 | @Test 41 | public void testAddI64() throws Exception { 42 | Wasmtime wasm = new Wasmtime(); 43 | try (WasmEngine engine = wasm.newWasmEngine(); 44 | WasmModule module = engine.newModule(TestUtil.MATH_WIT_PATH); 45 | WasmStore store = engine.newStore(); 46 | WasmLinker linker = engine.newLinker()) { 47 | System.out.println("slices compiled"); 48 | assertNotNull(module); 49 | 50 | WasmInstance instance = linker.instantiate(store, module); 51 | Optional func = instance.getFunction(store, "add-i64"); 52 | 53 | assertTrue("add_i64 isn't present in the module", func.isPresent()); 54 | WasmFunction function = func.get(); 55 | 56 | I64 ret = function.call(instance, store, I64.class, i64(3), i64(2)); 57 | assertEquals(ret.field, 5); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/tests/SliceTests.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.tests; 2 | 3 | import net.bluejekyll.wasmtime.*; 4 | import org.junit.Test; 5 | 6 | import java.io.UnsupportedEncodingException; 7 | import java.nio.ByteBuffer; 8 | import java.util.Optional; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Tests corresponding to the Rust based WASM programs in /tests/slices 14 | */ 15 | public class SliceTests { 16 | // public void hello_to_java(byte[] hello_bytes) { 17 | // final String hello = "Hello Java!"; 18 | 19 | // System.out.printf("Hello length: %d%n", hello_bytes.length); 20 | 21 | // final String from_wasm; 22 | // try { 23 | // from_wasm = new String(hello_bytes, "UTF-8"); 24 | // } catch (UnsupportedEncodingException e) { 25 | // // this should never happen for UTF-8 26 | // throw new RuntimeException(e); 27 | // } 28 | 29 | // System.out.printf("Hello: %s%n", from_wasm); 30 | // assertEquals(hello, from_wasm); 31 | // } 32 | 33 | // public final byte[] reverse_bytes_java(byte[] buffer) { 34 | // byte[] bytes = new byte[buffer.length]; 35 | 36 | // for (int i = bytes.length - 1; i >= 0; i--) { 37 | // bytes[i] = buffer[buffer.length - 1 - i]; 38 | // } 39 | 40 | // return bytes; 41 | // } 42 | 43 | // public void link(WasmStore store, WasmLinker linker) throws 44 | // WasmtimeException, NoSuchMethodException { 45 | // WasmFunction hello_to_java = WasmFunction.newFunc(store, this, 46 | // "hello_to_java", byte[].class); 47 | // linker.defineFunction("test", "hello_to_java", hello_to_java); 48 | 49 | // WasmFunction reverse_bytes_java = WasmFunction.newFunc(store, this, 50 | // "reverse_bytes_java", byte[].class); 51 | // linker.defineFunction("test", "reverse_bytes_java", reverse_bytes_java); 52 | // } 53 | 54 | // @Test 55 | // public void testHelloToJavaWasmModule() throws Exception { 56 | // Wasmtime wasm = new Wasmtime(); 57 | // try (WasmEngine engine = wasm.newWasmEngine(); 58 | // WasmModule module = engine.newModule(TestUtil.SLICES_PATH); 59 | // WasmStore store = engine.newStore(); 60 | // WasmLinker linker = engine.newLinker()) { 61 | // System.out.println("slices compiled"); 62 | // assertNotNull(module); 63 | 64 | // link(store, linker); 65 | 66 | // WasmInstance instance = linker.instantiate(store, module); 67 | // Optional func = instance.getFunction(store, 68 | // "say_hello_to_java"); 69 | 70 | // assertTrue("say_hello_to_java isn't present in the module", 71 | // func.isPresent()); 72 | // WasmFunction function = func.get(); 73 | 74 | // function.call(instance, store); 75 | // } 76 | // } 77 | 78 | // @Test 79 | // public void testSlicesWasmModule() throws Exception { 80 | // Wasmtime wasm = new Wasmtime(); 81 | // try (WasmEngine engine = wasm.newWasmEngine(); 82 | // WasmModule module = engine.newModule(TestUtil.SLICES_PATH); 83 | // WasmStore store = engine.newStore(); 84 | // WasmLinker linker = engine.newLinker()) { 85 | // System.out.println("slices compiled"); 86 | // assertNotNull(module); 87 | 88 | // link(store, linker); 89 | 90 | // WasmInstance instance = linker.instantiate(store, module); 91 | // Optional func = instance.getFunction(store, "print_bytes"); 92 | 93 | // assertTrue("print_bytes isn't present in the module", func.isPresent()); 94 | // WasmFunction function = func.get(); 95 | 96 | // byte[] bytes = new byte[] { 0, 1, 2, 3 }; 97 | // function.call(instance, store, bytes); 98 | // } 99 | // } 100 | 101 | // @Test 102 | // public void testReverseBytes() throws Exception { 103 | // Wasmtime wasm = new Wasmtime(); 104 | // try (WasmEngine engine = wasm.newWasmEngine(); 105 | // WasmModule module = engine.newModule(TestUtil.SLICES_PATH); 106 | // WasmStore store = engine.newStore(); 107 | // WasmLinker linker = engine.newLinker()) { 108 | // System.out.println("slices compiled"); 109 | // assertNotNull(module); 110 | 111 | // link(store, linker); 112 | 113 | // WasmInstance instance = linker.instantiate(store, module); 114 | // Optional func = instance.getFunction(store, "reverse_bytes"); 115 | 116 | // assertTrue("print_bytes isn't present in the module", func.isPresent()); 117 | // WasmFunction function = func.get(); 118 | 119 | // byte[] bytes = new byte[] { 0, 1, 2, 3 }; 120 | // byte[] ret = function.call(instance, store, byte[].class, bytes); 121 | // assertNotNull(ret); 122 | // assertEquals(bytes.length, ret.length); 123 | 124 | // assertArrayEquals(ret, new byte[] { 3, 2, 1, 0 }); 125 | // } 126 | // } 127 | 128 | // @Test 129 | // public void testReverseBytesInJava() throws Exception { 130 | // Wasmtime wasm = new Wasmtime(); 131 | // try (WasmEngine engine = wasm.newWasmEngine(); 132 | // WasmModule module = engine.newModule(TestUtil.SLICES_PATH); 133 | // WasmStore store = engine.newStore(); 134 | // WasmLinker linker = engine.newLinker()) { 135 | // System.out.println("slices compiled"); 136 | // assertNotNull(module); 137 | 138 | // link(store, linker); 139 | 140 | // WasmInstance instance = linker.instantiate(store, module); 141 | // Optional func = instance.getFunction(store, 142 | // "reverse_bytes_in_java"); 143 | 144 | // assertTrue("print_bytes isn't present in the module", func.isPresent()); 145 | // WasmFunction function = func.get(); 146 | 147 | // byte[] bytes = new byte[] { 0, 1, 2, 3 }; 148 | // byte[] ret = function.call(instance, store, byte[].class, bytes); 149 | // assertNotNull(ret); 150 | // assertEquals(bytes.length, ret.length); 151 | 152 | // assertArrayEquals(ret, new byte[] { 3, 2, 1, 0 }); 153 | // } 154 | // } 155 | } 156 | -------------------------------------------------------------------------------- /src/test/java/net/bluejekyll/wasmtime/tests/StringTests.java: -------------------------------------------------------------------------------- 1 | package net.bluejekyll.wasmtime.tests; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import java.util.Optional; 8 | 9 | import org.junit.Test; 10 | 11 | import net.bluejekyll.wasmtime.*; 12 | 13 | /** 14 | * Tests corresponding to the Rust based WASM programs in /tests/strings 15 | */ 16 | public class StringTests { 17 | // public final String say_hello_to_java(String name) { 18 | // return String.format("Hello, %s!", name); 19 | // } 20 | 21 | // public void link(WasmStore store, WasmLinker linker) throws 22 | // WasmtimeException, NoSuchMethodException { 23 | // WasmFunction hello_to_java = WasmFunction.newFunc(store, this, 24 | // "say_hello_to_java", String.class); 25 | // linker.defineFunction("test", "say_hello_to_java", hello_to_java); 26 | // } 27 | 28 | // @Test 29 | // public void testHello() throws Exception { 30 | // Wasmtime wasm = new Wasmtime(); 31 | // try (WasmEngine engine = wasm.newWasmEngine(); 32 | // WasmModule module = engine.newModule(TestUtil.STRINGS_PATH); 33 | // WasmStore store = engine.newStore(); 34 | // WasmLinker linker = engine.newLinker()) { 35 | // System.out.println("slices compiled"); 36 | // assertNotNull(module); 37 | 38 | // link(store, linker); 39 | // WasmInstance instance = linker.instantiate(store, module); 40 | // Optional func = instance.getFunction(store, "say_hello_to"); 41 | 42 | // assertTrue("say_hello_to isn't present in the module", func.isPresent()); 43 | // WasmFunction function = func.get(); 44 | 45 | // String name = this.getClass().getName(); 46 | // String ret = function.call(instance, store, String.class, name); 47 | // assertNotNull(ret); 48 | 49 | // String expected = String.format("Hello, %s!", name); 50 | // assertEquals(expected, ret); 51 | // } 52 | // } 53 | 54 | // @Test 55 | // public void testHelloToJava() throws Exception { 56 | // Wasmtime wasm = new Wasmtime(); 57 | // try (WasmEngine engine = wasm.newWasmEngine(); 58 | // WasmModule module = engine.newModule(TestUtil.STRINGS_PATH); 59 | // WasmStore store = engine.newStore(); 60 | // WasmLinker linker = engine.newLinker()) { 61 | // System.out.println("slices compiled"); 62 | // assertNotNull(module); 63 | 64 | // link(store, linker); 65 | // WasmInstance instance = linker.instantiate(store, module); 66 | 67 | // Optional func = instance.getFunction(store, 68 | // "say_hello_in_java"); 69 | 70 | // assertTrue("say_hello_to isn't present in the module", func.isPresent()); 71 | // WasmFunction function = func.get(); 72 | 73 | // String name = this.getClass().getName(); 74 | // String ret = function.call(instance, store, String.class, name); 75 | // assertNotNull(ret); 76 | 77 | // String expected = String.format("Hello, %s!", name); 78 | // assertEquals(expected, ret); 79 | // } 80 | // } 81 | } 82 | -------------------------------------------------------------------------------- /tests/math-wit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "math-wit" 3 | version = "0.1.0" 4 | authors = ["Benjamin Fry "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ['cdylib'] 9 | 10 | [dependencies] 11 | wit-bindgen-rust = { git = "https://github.com/bluejekyll/wit-bindgen" } 12 | 13 | -------------------------------------------------------------------------------- /tests/math-wit/src/lib.rs: -------------------------------------------------------------------------------- 1 | wit_bindgen_rust::export!("tests/math-wit/src/math.wit"); 2 | 3 | struct Math; 4 | 5 | pub use math::Math as MathExport; 6 | 7 | impl MathExport for Math { 8 | fn add_i32(a: i32, b: i32) -> i32 { 9 | a + b 10 | } 11 | 12 | fn add_u32(a: u32, b: u32) -> u32 { 13 | a + b 14 | } 15 | 16 | fn add_i64(a: i64, b: i64) -> i64 { 17 | a + b 18 | } 19 | 20 | fn add_u64(a: u64, b: u64) -> u64 { 21 | a + b 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | 29 | #[test] 30 | fn test_add_i32() { 31 | assert_eq!(Math::add_i32(1, 2), 3); 32 | } 33 | 34 | #[test] 35 | fn test_add_u32() { 36 | assert_eq!(Math::add_u32(1, 2), 3); 37 | } 38 | 39 | #[test] 40 | fn test_add_i64() { 41 | assert_eq!(Math::add_i64(1, 2), 3); 42 | } 43 | 44 | #[test] 45 | fn test_add_u64() { 46 | assert_eq!(Math::add_u64(1, 2), 3); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/math-wit/src/math.wit: -------------------------------------------------------------------------------- 1 | add-i32: function(a: s32, b: s32) -> s32 2 | add-u32: function(a: u32, b: u32) -> u32 3 | add-i64: function(a: s64, b: s64) -> s64 4 | add-u64: function(a: u64, b: u64) -> u64 5 | -------------------------------------------------------------------------------- /tests/math/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "math" 3 | version = "0.1.0" 4 | authors = ["Benjamin Fry "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ['cdylib'] 9 | 10 | [dependencies] 11 | 12 | -------------------------------------------------------------------------------- /tests/math/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[no_mangle] 2 | pub extern "C" fn add_i32(a: i32, b: i32) -> i32 { 3 | a + b 4 | } 5 | 6 | #[no_mangle] 7 | pub extern "C" fn add_u32(a: u32, b: u32) -> u32 { 8 | a + b 9 | } 10 | 11 | #[no_mangle] 12 | pub extern "C" fn add_i64(a: i64, b: i64) -> i64 { 13 | a + b 14 | } 15 | 16 | #[no_mangle] 17 | pub extern "C" fn add_u64(a: u64, b: u64) -> u64 { 18 | a + b 19 | } 20 | 21 | #[no_mangle] 22 | pub extern "C" fn add_f32(a: f32, b: f32) -> f32 { 23 | a + b 24 | } 25 | 26 | #[no_mangle] 27 | pub extern "C" fn add_f64(a: f64, b: f64) -> f64 { 28 | a + b 29 | } 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use super::*; 34 | 35 | #[test] 36 | fn test_add_i32() { 37 | assert_eq!(add_i32(1, 2), 3); 38 | } 39 | 40 | #[test] 41 | fn test_add_u32() { 42 | assert_eq!(add_u32(1, 2), 3); 43 | } 44 | 45 | #[test] 46 | fn test_add_i64() { 47 | assert_eq!(add_i64(1, 2), 3); 48 | } 49 | 50 | #[test] 51 | fn test_add_u64() { 52 | assert_eq!(add_u64(1, 2), 3); 53 | } 54 | 55 | #[test] 56 | fn test_add_f32() { 57 | assert!(add_f32(1.1, 2.2) > 3.2); 58 | assert!(add_f32(1.1, 2.2) < 3.4); 59 | } 60 | 61 | #[test] 62 | fn test_add_f64() { 63 | assert!(add_f64(1.1, 2.2) > 3.2); 64 | assert!(add_f64(1.1, 2.2) < 3.4); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/slices/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "slices" 3 | version = "0.1.0" 4 | authors = ["Benjamin Fry "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ['cdylib'] 9 | 10 | [dependencies] 11 | wasmtime-jni-exports = { path = "../../wasmtime-jni-exports" } -------------------------------------------------------------------------------- /tests/slices/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasmtime_jni_exports::{Borrowed, Owned, WasmAllocated, WasmSlice}; 2 | 3 | // needed for exports to wasmtime-jni 4 | pub use wasmtime_jni_exports; 5 | 6 | // These functions are declared in Java and use the Linker to associate them to the Module Instance. 7 | #[link(wasm_import_module = "test")] 8 | extern "C" { 9 | fn hello_to_java(data_ptr: i32, data_len: i32); 10 | fn reverse_bytes_java(data_ptr: i32, data_len: i32, result: &mut Owned); 11 | } 12 | 13 | #[no_mangle] 14 | pub extern "C" fn say_hello_to_java() { 15 | let hello: Borrowed = "Hello Java!".into(); 16 | 17 | unsafe { hello_to_java(hello.ptr(), hello.len()) } 18 | } 19 | 20 | /// # Safety 21 | /// 22 | /// This relies on an external method having properly allocated the WasmSlice before calling this method. 23 | #[no_mangle] 24 | pub unsafe extern "C" fn print_bytes(slice_ptr: i32, slice_len: i32) { 25 | let slice = WasmSlice::borrowed(slice_ptr, &slice_len); 26 | println!( 27 | "slices::print_bytes: ptr: {:x?} len: {}", 28 | slice_ptr, slice_len 29 | ); 30 | 31 | let data: &[u8] = slice.as_bytes(); 32 | println!("slices::print_bytes: received bytes {:x?}", data); 33 | } 34 | 35 | /// # Safety 36 | /// 37 | /// This relies on an external method having properly allocated the WasmSlice before calling this method. 38 | #[no_mangle] 39 | pub unsafe extern "C" fn reverse_bytes( 40 | slice_ptr: i32, 41 | slice_len: i32, 42 | slice_ref: &mut Owned, 43 | ) { 44 | let slice = WasmSlice::borrowed(slice_ptr, &slice_len); 45 | println!( 46 | "slices::reverse_bytes: ptr: {:x?} len: {}", 47 | slice_ptr, slice_len 48 | ); 49 | 50 | let data: &[u8] = slice.as_bytes(); 51 | println!("slices::reverse_bytes: received bytes {:x?}", data); 52 | 53 | let mut reversed: Vec = Vec::with_capacity(data.len()); 54 | for b in data.iter().rev() { 55 | reversed.push(*b); 56 | } 57 | 58 | let reversed = reversed.into_boxed_slice(); 59 | let reversed = Owned::::from(reversed); 60 | 61 | // assign the return value 62 | slice_ref.replace(reversed); 63 | } 64 | 65 | /// # Safety 66 | /// Assumes that the data input is properly allocated slice, and the result has an allocated WasmSlice object at the pointer. 67 | #[no_mangle] 68 | pub unsafe extern "C" fn reverse_bytes_in_java( 69 | data_ptr: i32, 70 | data_len: i32, 71 | result: &mut Owned, 72 | ) { 73 | let data = WasmSlice::borrowed(data_ptr, &data_len); 74 | println!("slices::reverse_bytes_in_java: {:?}", *data); 75 | reverse_bytes_java(data_ptr, data_len, result); 76 | } 77 | 78 | // #[cfg(test)] 79 | // mod tests { 80 | // use super::*; 81 | 82 | // #[test] 83 | // fn test_print_bytes() { 84 | // let bytes = &[0u8, 1, 2] as &[u8]; 85 | // unsafe { print_bytes(bytes.as_ptr() as i32, bytes.len() as i32) }; 86 | // } 87 | // } 88 | -------------------------------------------------------------------------------- /tests/strings/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "strings" 3 | version = "0.1.0" 4 | authors = ["Benjamin Fry "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ['cdylib'] 9 | 10 | [dependencies] 11 | wasmtime-jni-exports = { path = "../../wasmtime-jni-exports" } 12 | 13 | -------------------------------------------------------------------------------- /tests/strings/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasmtime_jni_exports::{Owned, WasmAllocated, WasmSlice}; 2 | 3 | /// test imports from Java 4 | #[link(wasm_import_module = "test")] 5 | extern "C" { 6 | // Ownership, the response should be freed by the caller 7 | fn say_hello_to_java(data_ptr: i32, data_len: i32, response: &mut Owned); 8 | } 9 | 10 | /// Greetings 11 | /// 12 | /// # Safety 13 | /// Passed in WasmSlice is owned by caller 14 | #[no_mangle] 15 | pub unsafe extern "C" fn greet(name_ptr: i32, name_len: i32) { 16 | let name = WasmSlice::borrowed(name_ptr, &name_len); 17 | let name = name.from_utf8_lossy(); 18 | 19 | println!("Hello, {}!", name); 20 | } 21 | 22 | /// # Safety 23 | /// Passed in WasmSlice is owned by caller 24 | #[no_mangle] 25 | pub unsafe extern "C" fn say_hello_to( 26 | name_ptr: i32, 27 | name_len: i32, 28 | response: &mut Owned, 29 | ) { 30 | let name = WasmSlice::borrowed(name_ptr, &name_len); 31 | let name = name.from_utf8_lossy(); 32 | 33 | let hello_to = format!("Hello, {}!", name); 34 | println!("{}", hello_to); 35 | let hello_to: Owned = hello_to.into(); // make this a heap allocated str (this makes capacity == len) 36 | 37 | assert_eq!( 38 | hello_to.ptr() as *mut u8, 39 | hello_to.as_bytes() as *const [u8] as *mut u8 40 | ); 41 | 42 | response.replace(hello_to); 43 | } 44 | 45 | /// # Safety 46 | /// Passed in WasmSlice is owned by caller 47 | #[no_mangle] 48 | pub unsafe extern "C" fn say_hello_in_java( 49 | data_ptr: i32, 50 | data_len: i32, 51 | response: &mut Owned, 52 | ) { 53 | // Technically after this call we "own" the response, but we pass that directly into the next method 54 | // which will then own freeing the memory (which is technically handled by the wasmtime-jni bindings) 55 | say_hello_to_java(data_ptr, data_len, response) 56 | } 57 | -------------------------------------------------------------------------------- /wasmtime-jni-exports/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmtime-jni-exports" 3 | version = "0.1.0" 4 | authors = ["Benjamin Fry "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /wasmtime-jni-exports/src/lib.rs: -------------------------------------------------------------------------------- 1 | //#[cfg(target_arch = "wasm32")] 2 | use std::{ 3 | alloc::{self, Layout}, 4 | convert::TryFrom, 5 | marker::PhantomData, 6 | ops::{Deref, DerefMut}, 7 | }; 8 | use std::{borrow::Cow, slice}; 9 | 10 | pub const MEMORY_EXPORT: &str = "memory"; 11 | pub const ALLOC_EXPORT: &str = "__alloc_bytes"; 12 | pub const DEALLOC_EXPORT: &str = "__dealloc_bytes"; 13 | 14 | /// Allocates size in bytes of `memory`, offset to area returned. 15 | /// 16 | /// # Returns 17 | /// 18 | /// Offset from start of `memory` export in WASM to the region, or 0 if unable to allocate. 19 | /// 20 | /// # Safety 21 | /// 22 | /// This will allocate a byte array in the WASM module. To refer to this memory externally, there is an 23 | /// exported memory section. These bytes are only valid for the life of the Store or until the Memory is 24 | /// resized. 25 | #[no_mangle] 26 | //#[cfg(target_arch = "wasm32")] 27 | pub unsafe extern "C" fn __alloc_bytes(size: u32) -> i32 { 28 | let layout = Layout::array::(size as usize).expect("u8 should definitely have a layout"); 29 | let ptr = alloc::alloc(layout) as i32; 30 | 31 | debug_assert_ne!(0, ptr); 32 | // useful for debugging 33 | //println!("allocated {} at {}", size, ptr); 34 | ptr 35 | } 36 | 37 | /// Frees ptr from `memory` in WASM 38 | /// 39 | /// # Safety 40 | /// 41 | /// Must be a pointer to data allocated with the __alloc_bytes 42 | #[no_mangle] 43 | //#[cfg(target_arch = "wasm32")] 44 | pub unsafe extern "C" fn __dealloc_bytes(ptr: u32, size: u32) { 45 | if ptr != 0 && size != 0 { 46 | let layout = 47 | Layout::array::(size as usize).expect("u8 should definitely have a layout"); 48 | alloc::dealloc(ptr as *mut u8, layout); 49 | } 50 | } 51 | 52 | /// Data that was allocated inside a WASM module 53 | pub trait WasmAllocated: Sized { 54 | /// Return the WASM offset pointer 55 | /// 56 | /// # Safety 57 | /// 58 | /// This pointer is only valid given the context, i.e. Borrowed data should treat this as a `*const u8` ~= `&u8` 59 | fn ptr(&self) -> i32; 60 | 61 | /// Return the WASM offset pointer 62 | fn len(&self) -> i32; 63 | 64 | /// Swap current WasmAllocated version with other data 65 | //#[cfg(target_arch = "wasm32")] 66 | fn replace(&mut self, other: Owned); 67 | 68 | fn is_empty(&self) -> bool { 69 | self.len() == 0 70 | } 71 | } 72 | 73 | /// A WasmSlice is an offset into the local `memory` of the WASM module instance. 74 | /// 75 | /// It is only valid in the context of a `memory` contiguous region and a module's associated `Store` 76 | #[repr(C)] 77 | #[derive(Clone, Copy, Debug)] 78 | pub struct WasmSlice { 79 | ptr: i32, 80 | len: i32, 81 | } 82 | 83 | impl WasmSlice { 84 | /// Danger, danger, you probable want `owned` or `borrowed`. This will produce something that is neither. 85 | /// 86 | /// # Safety 87 | /// 88 | /// This constructs a new Slice from WASM data. ptr must be a valid offset in memory and the length must be allocated 89 | /// at that offset. 90 | //#[cfg(not(target_arch = "wasm32"))] 91 | pub unsafe fn new(ptr: i32, len: i32) -> Self { 92 | Self { ptr, len } 93 | } 94 | 95 | /// Create an owned reference to a byte slice to the memory at `ptr` with a length of `len`. 96 | /// 97 | /// This will be dropped and freed at the end of usaged 98 | /// 99 | /// # Safety 100 | /// 101 | /// The `ptr` must be a valid offset in the WASM module for the slice of the specified length. It must abide by the same rules as a `&[u8]`. 102 | //#[cfg(target_arch = "wasm32")] 103 | pub unsafe fn owned(ptr: i32, len: i32) -> Owned { 104 | Owned(Self { ptr, len }) 105 | } 106 | 107 | /// Create an borrowed reference to a byte slice to the memory at `ptr` with a length of `len`. 108 | /// 109 | /// This will *not* be dropped or freed at the end of usaged. 110 | /// 111 | /// # Safety 112 | /// 113 | /// The `ptr` must be a valid offset in the WASM module for the slice of the specified length. It must abide by the same rules as a `&[u8]`. 114 | //#[cfg(target_arch = "wasm32")] 115 | pub unsafe fn borrowed(ptr: i32, len: &i32) -> Borrowed<'_, Self> { 116 | Borrowed { 117 | val: WasmSlice { ptr, len: *len }, 118 | ghost: PhantomData, 119 | } 120 | } 121 | 122 | /// # Safety 123 | /// This relies on the ptr and len being accurate for the current memory environment. Inside a WASM runtime for example. 124 | #[inline] 125 | pub fn as_bytes(&self) -> &[u8] { 126 | unsafe { 127 | let ptr = self.ptr as *const u8; 128 | slice::from_raw_parts(ptr, self.len as usize) 129 | } 130 | } 131 | 132 | /// Return the content as a str of utf8 or allocate and replace as necessary. 133 | /// 134 | /// See [`std::string::String::from_utf8_lossy`] 135 | pub fn from_utf8_lossy(&self) -> Cow<'_, str> { 136 | let string = self.as_bytes(); 137 | String::from_utf8_lossy(string) 138 | } 139 | } 140 | 141 | impl WasmAllocated for WasmSlice { 142 | /// Return the WASM offset pointer 143 | fn ptr(&self) -> i32 { 144 | self.ptr 145 | } 146 | 147 | /// Return the WASM offset pointer 148 | fn len(&self) -> i32 { 149 | self.len 150 | } 151 | 152 | /// Swap current WasmAllocated version with other data 153 | //#[cfg(target_arch = "wasm32")] 154 | fn replace(&mut self, mut other: Owned) { 155 | unsafe { __dealloc_bytes(self.ptr as u32, self.len as u32) }; 156 | 157 | self.ptr = other.ptr; 158 | self.len = other.len; 159 | 160 | other.ptr = 0; 161 | other.len = 0; 162 | } 163 | } 164 | 165 | //#[cfg(target_arch = "wasm32")] 166 | impl From> for Owned { 167 | #[inline] 168 | fn from(bytes: Vec) -> Self { 169 | let boxed_slice = bytes.into_boxed_slice(); 170 | Owned::::from(boxed_slice) 171 | } 172 | } 173 | 174 | //#[cfg(target_arch = "wasm32")] 175 | impl From> for Owned { 176 | #[inline] 177 | fn from(bytes: Box<[u8]>) -> Self { 178 | let len = i32::try_from(bytes.len()).expect("length outside WASM bounds"); 179 | let ptr = i32::try_from(Box::into_raw(bytes) as *mut u8 as usize) 180 | .expect("pointer outside WASM bounds"); 181 | 182 | // helpful for debugging 183 | // println!("storing Box<[u8]> at {} len {}", ptr, len); 184 | 185 | unsafe { WasmSlice::owned(ptr, len) } 186 | } 187 | } 188 | 189 | //#[cfg(target_arch = "wasm32")] 190 | impl From for Owned { 191 | #[inline] 192 | fn from(s: String) -> Self { 193 | let bytes = s.into_bytes(); 194 | Owned::::from(bytes) 195 | } 196 | } 197 | 198 | // #[cfg(target_arch = "wasm32")] 199 | impl From<&str> for Borrowed<'_, WasmSlice> { 200 | #[inline] 201 | fn from(s: &str) -> Self { 202 | assert_eq!(s.as_ptr(), s.as_bytes() as *const [u8] as *const u8); 203 | 204 | let len = s.len(); 205 | let ptr = s.as_bytes().as_ptr(); 206 | 207 | let len = i32::try_from(len).expect("size in excess of max WASM length"); 208 | let ptr = i32::try_from(ptr as usize).expect("pointer outside WASM range"); 209 | 210 | Borrowed { 211 | val: WasmSlice { ptr, len }, 212 | ghost: PhantomData, 213 | } 214 | } 215 | } 216 | 217 | /// An Owned item will be dropped 218 | #[repr(transparent)] 219 | //#[cfg(target_arch = "wasm32")] 220 | pub struct Owned(T); 221 | 222 | #[cfg(target_arch = "wasm32")] 223 | impl Drop for Owned { 224 | fn drop(&mut self) { 225 | unsafe { __dealloc_bytes(self.0.ptr() as u32, self.0.len() as u32) }; 226 | println!("dropping: ptr({}), len({})", self.0.ptr(), self.0.len()); 227 | } 228 | } 229 | 230 | //#[cfg(target_arch = "wasm32")] 231 | impl Deref for Owned { 232 | type Target = T; 233 | 234 | #[must_use] 235 | fn deref(&self) -> &Self::Target { 236 | &self.0 237 | } 238 | } 239 | 240 | //#[cfg(target_arch = "wasm32")] 241 | impl DerefMut for Owned { 242 | #[must_use] 243 | fn deref_mut(&mut self) -> &mut Self::Target { 244 | &mut self.0 245 | } 246 | } 247 | 248 | /// A Borrowed item will not be dropped 249 | #[repr(transparent)] 250 | //#[cfg(target_arch = "wasm32")] 251 | pub struct Borrowed<'a, T: WasmAllocated> { 252 | val: T, 253 | ghost: PhantomData<&'a T>, 254 | } 255 | 256 | //#[cfg(target_arch = "wasm32")] 257 | impl<'a, T: WasmAllocated> Deref for Borrowed<'a, T> { 258 | type Target = T; 259 | 260 | #[must_use] 261 | fn deref(&self) -> &Self::Target { 262 | &self.val 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /wasmtime-jni/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmtime-jni" 3 | version = "0.1.0" 4 | authors = ["Benjamin Fry "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | name = "wasmtime_jni" 11 | crate_type = ["cdylib"] 12 | 13 | [dependencies] 14 | anyhow = "1.0.32" 15 | flexi_logger = "0.17.1" 16 | #env_logger = "0.7.1" 17 | jni = "0.19.0" 18 | log = "0.4.11" 19 | wasi-common = "0.31.0" 20 | wasmtime = { version = "0.31", features=["jitdump", "wat", "cache"] } 21 | wasmtime-jni-exports = { path = "../wasmtime-jni-exports" } 22 | wasmtime-wasi = "0.31.0" -------------------------------------------------------------------------------- /wasmtime-jni/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod opaque_ptr; 2 | mod ty; 3 | mod wasm_engine; 4 | mod wasm_exception; 5 | mod wasm_function; 6 | mod wasm_instance; 7 | mod wasm_linker; 8 | mod wasm_module; 9 | mod wasm_state; 10 | mod wasm_store; 11 | mod wasm_value; 12 | mod wasmtime; 13 | -------------------------------------------------------------------------------- /wasmtime-jni/src/opaque_ptr.rs: -------------------------------------------------------------------------------- 1 | use std::any; 2 | use std::fmt; 3 | use std::marker::PhantomData; 4 | use std::ops::{Deref, DerefMut}; 5 | 6 | use jni::sys::jlong; 7 | use log::{debug, trace}; 8 | 9 | /// List of Opaque types that we support for passing to and from Java 10 | pub(crate) trait Opaqueable {} 11 | 12 | impl Opaqueable for wasmtime::Engine {} 13 | impl Opaqueable for wasmtime::Func {} 14 | impl Opaqueable for wasmtime::Instance {} 15 | impl Opaqueable for wasmtime::Linker {} 16 | impl Opaqueable for wasmtime::Module {} 17 | impl Opaqueable for wasmtime::Store {} 18 | 19 | // TODO: add methods to extract from a passed in Object to have better ownership semantics in Java. 20 | /// This borrows the pointer stored at jlong, not taking ownership 21 | /// 22 | /// This should only be used with [`make_opaque`] 23 | #[repr(transparent)] 24 | pub struct OpaquePtr<'a, T> { 25 | ptr: jlong, 26 | ty: PhantomData<&'a T>, 27 | } 28 | 29 | impl<'a, T> OpaquePtr<'a, T> { 30 | pub(crate) fn from(val: T) -> Self 31 | where 32 | T: Sized + Opaqueable, 33 | { 34 | let ptr: jlong = Box::into_raw(Box::new(val)) as jlong; 35 | 36 | let this = Self { 37 | ptr, 38 | ty: PhantomData, 39 | }; 40 | 41 | debug!("{:?}::from", this); 42 | 43 | this 44 | } 45 | 46 | #[track_caller] 47 | pub fn as_ref(&self) -> &'a T { 48 | trace!("{:?}::as_ref", self); 49 | assert_ne!( 50 | self.ptr, 51 | 0, 52 | "cannot deref null for &{}", 53 | any::type_name::() 54 | ); 55 | let obj = self.ptr as *const T; 56 | 57 | unsafe { &*obj } 58 | } 59 | 60 | #[track_caller] 61 | pub fn as_mut(&mut self) -> &mut T { 62 | trace!("{:?}::as_mut", self); 63 | assert_ne!( 64 | self.ptr, 65 | 0, 66 | "cannot deref null for &{}", 67 | any::type_name::() 68 | ); 69 | let obj = self.ptr as *mut T; 70 | 71 | unsafe { &mut *obj } 72 | } 73 | 74 | /// This takes ownership of the pointer stored at jlong. 75 | /// 76 | /// It is undefined behavior to reference the ptr in any other context after this. 77 | #[track_caller] 78 | pub fn take(self) -> Box { 79 | trace!("{:?}::take", self); 80 | assert_ne!( 81 | self.ptr, 82 | 0, 83 | "cannot deref null for &{}", 84 | any::type_name::() 85 | ); 86 | let obj = self.ptr as *mut T; 87 | 88 | unsafe { Box::from_raw(obj) } 89 | } 90 | 91 | /// Take ownership of a Rust type and return an opaque pointer as a jlong for future usage 92 | pub fn make_opaque(self) -> jlong { 93 | self.ptr 94 | } 95 | 96 | /// Returns true if the backing ptr is == 0 97 | #[allow(unused)] 98 | pub fn is_null(&self) -> bool { 99 | self.ptr == 0 100 | } 101 | } 102 | 103 | impl<'a, T> Deref for OpaquePtr<'a, T> { 104 | type Target = T; 105 | 106 | #[track_caller] 107 | fn deref(&self) -> &Self::Target { 108 | self.as_ref() 109 | } 110 | } 111 | 112 | impl<'a, T> DerefMut for OpaquePtr<'a, T> { 113 | #[track_caller] 114 | fn deref_mut(&mut self) -> &mut Self::Target { 115 | self.as_mut() 116 | } 117 | } 118 | 119 | impl<'a, T> fmt::Debug for OpaquePtr<'a, T> { 120 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 121 | write!(f, "OpaquePtr<{}>({})", std::any::type_name::(), self.ptr) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /wasmtime-jni/src/ty/byte_slice.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use anyhow::{anyhow, ensure, Error}; 4 | use log::debug; 5 | use wasmtime::{Store, Val, ValType}; 6 | pub use wasmtime_jni_exports::{WasmAllocated, WasmSlice}; 7 | 8 | use crate::{ 9 | ty::{Abi, ComplexTy, ReturnAbi, WasmAlloc, WasmSliceWrapper}, 10 | wasm_state::JavaState, 11 | }; 12 | 13 | // pub fn greet(name: &str) -> String { 14 | // format!("Hello, {}!", name) 15 | // } 16 | 17 | // #[allow(non_snake_case)] 18 | // #[cfg_attr( 19 | // all(target_arch = "wasm32", not(target_os = "emscripten")), 20 | // export_name = "greet" 21 | // )] 22 | // #[allow(clippy::all)] 23 | // pub extern "C" fn __wasm_bindgen_generated_greet( 24 | // arg0: ::Abi, 25 | // ) -> ::Abi { 26 | // let _ret = { 27 | // let arg0 = unsafe { ::ref_from_abi(arg0) }; 28 | // let arg0 = &*arg0; 29 | // greet(arg0) 30 | // }; 31 | // ::return_abi(_ret) 32 | // } 33 | 34 | // #[no_mangle] 35 | // #[allow(non_snake_case)] 36 | // #[doc(hidden)] 37 | // #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] 38 | // #[allow(clippy::all)] 39 | // pub extern "C" fn __wbindgen_describe_greet() { 40 | // use wasm_bindgen::describe::*; 41 | 42 | // wasm_bindgen::__rt::link_mem_intrinsics(); 43 | 44 | // inform(FUNCTION); 45 | // inform(0); 46 | // inform(1u32); 47 | 48 | // <&str as WasmDescribe>::describe(); 49 | // ::describe(); 50 | // } 51 | 52 | // #[allow(non_upper_case_globals)] 53 | // #[cfg(target_arch = "wasm32")] 54 | // #[link_section = "__wasm_bindgen_unstable"] 55 | // #[doc(hidden)] 56 | // #[allow(clippy::all)] 57 | // pub static __WASM_BINDGEN_GENERATED_85872804fca1fa32: [u8; 107usize] = { 58 | // static _INCLUDED_FILES: &[&str] = &[]; 59 | // * 60 | // b".\x00\x00\x00{\"schema_version\":\"0.2.68\",\"version\":\"0.2.68\"}5\x00\x00\x00\x01\x00\x00\x00\x01\x04name\x05greet\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x18strings-841a3553a8e33c06\x00" 61 | // }; 62 | 63 | #[derive(Debug)] 64 | #[repr(transparent)] 65 | pub struct ByteSlice([u8]); 66 | 67 | impl ByteSlice { 68 | // pub fn new<'a>(bytes: &'a [u8]) -> &'a Self { 69 | // unsafe { &*(bytes as *const [u8] as *const Self) } 70 | // } 71 | } 72 | 73 | impl Deref for ByteSlice { 74 | type Target = [u8]; 75 | 76 | fn deref(&self) -> &Self::Target { 77 | &self.0 78 | } 79 | } 80 | 81 | impl ComplexTy for ByteSlice { 82 | type Abi = WasmSlice; 83 | 84 | #[inline] 85 | fn compatible_with_store(&self, _store: &Store) -> bool { 86 | true 87 | } 88 | } 89 | 90 | impl Abi for WasmSlice { 91 | fn push_arg_tys(args: &mut Vec) { 92 | args.push(ValType::I32); // offset into memory 93 | args.push(ValType::I32); // length 94 | } 95 | 96 | fn store_to_args(self, args: &mut Vec) { 97 | let ptr = self.ptr(); 98 | let len = self.len(); 99 | args.push(Val::from(ptr as i32)); 100 | args.push(Val::from(len as i32)); 101 | } 102 | 103 | fn load_from_args(mut args: impl Iterator) -> Result { 104 | let ptr = args 105 | .next() 106 | .ok_or_else(|| anyhow!("missing ptr arg"))? 107 | .i32() 108 | .ok_or_else(|| anyhow!("ptr not i32"))?; 109 | 110 | let len = args 111 | .next() 112 | .ok_or_else(|| anyhow!("missing ptr arg"))? 113 | .i32() 114 | .ok_or_else(|| anyhow!("ptr not i32"))?; 115 | 116 | unsafe { Ok(WasmSlice::new(ptr, len)) } 117 | } 118 | 119 | fn matches_arg_tys(mut tys: impl Iterator) -> anyhow::Result<()> { 120 | // offset 121 | let next = tys.next(); 122 | ensure!( 123 | next == Some(ValType::I32), 124 | "Expected offset for memory: {:?}", 125 | next 126 | ); 127 | 128 | // length 129 | let next = tys.next(); 130 | ensure!( 131 | next == Some(ValType::I32), 132 | "Expected length for memory: {:?}", 133 | next 134 | ); 135 | 136 | Ok(()) 137 | } 138 | } 139 | 140 | impl ReturnAbi for WasmSlice { 141 | /// Place the necessary type signature in the type list 142 | #[allow(unused)] 143 | fn return_or_push_arg_tys(args: &mut Vec) -> Option { 144 | // For slice returns, we need a pointer to WasmSlice in the final parameter position 145 | args.push(ValType::I32); 146 | 147 | None 148 | } 149 | 150 | /// Place the values in the argument list, if there was an allocation, the pointer is returned 151 | #[allow(unused)] 152 | fn return_or_store_to_arg<'w>( 153 | args: &mut Vec, 154 | wasm_alloc: Option<&'w WasmAlloc>, 155 | store: &mut Store, 156 | ) -> Result>, Error> { 157 | // create a place in memory for the slice to be returned 158 | let slice = wasm_alloc 159 | .ok_or_else(|| anyhow!("WasmAlloc not supplied"))? 160 | .alloc::(store)?; 161 | 162 | args.push(Val::from(slice.ptr())); 163 | Ok(Some(slice)) 164 | } 165 | 166 | fn get_return_by_ref_arg(mut args: impl Iterator) -> Option { 167 | args.next().as_ref().and_then(Val::i32) 168 | } 169 | 170 | /// Load from the returned value, or from the passed in pointer to the return by ref parameter 171 | fn return_or_load_or_from_args( 172 | _ret: Option<&Val>, 173 | mut ret_by_ref_ptr: Option>, 174 | _wasm_alloc: Option<&WasmAlloc>, 175 | store: &mut Store, 176 | ) -> Result { 177 | let ptr = ret_by_ref_ptr 178 | .take() 179 | .ok_or_else(|| anyhow!("No pointer was supplied"))?; 180 | let wasm_slice = unsafe { ptr.obj_as_mut(store) }; 181 | 182 | debug!("read {:?}", wasm_slice); 183 | Ok(*wasm_slice) 184 | } 185 | 186 | /// matches the arg tys 187 | fn matches_return_or_arg_tys( 188 | _ret: Option, 189 | mut tys: impl Iterator, 190 | ) -> Result<(), Error> { 191 | let ty = tys.next(); 192 | 193 | // is a pointer type 194 | ensure!( 195 | ty == Some(ValType::I32), 196 | "Expected ptr for return by ref: {:?}", 197 | ty 198 | ); 199 | 200 | Ok(()) 201 | } 202 | } 203 | 204 | // impl<'b> IntoAbi for &'b ByteSlice { 205 | // type Abi = WasmSlice; 206 | 207 | // fn into_abi<'a>(self) -> Self::Abi { 208 | // let ptr = self.0.as_ptr() as i32; 209 | // let len = self.len() as i32; 210 | 211 | // WasmSlice { ptr, len } 212 | // } 213 | // } 214 | 215 | // impl<'b> FromAbi for &'b ByteSlice { 216 | // unsafe fn from_abi<'a>(abi: WasmSlice) -> Self { 217 | // let WasmSlice { ptr, len } = abi; 218 | 219 | // let ptr = ptr as *const u8; 220 | // ByteSlice::new(slice::from_raw_parts(ptr as *const _, len as usize)) 221 | // } 222 | // } 223 | -------------------------------------------------------------------------------- /wasmtime-jni/src/ty/complex_ty.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, ensure, Error}; 2 | use wasmtime::{Store, Val, ValType}; 3 | 4 | use crate::{ 5 | ty::{WasmAlloc, WasmSliceWrapper}, 6 | wasm_state::JavaState, 7 | }; 8 | 9 | pub(crate) trait ComplexTy { 10 | type Abi: Abi; 11 | 12 | fn compatible_with_store(&self, _store: &Store) -> bool; 13 | } 14 | 15 | pub(crate) trait Abi: Copy { 16 | /// Place the necessary type signature in the type list 17 | fn push_arg_tys(args: &mut Vec); 18 | 19 | /// Place the values in the argument list 20 | fn store_to_args(self, args: &mut Vec); 21 | 22 | /// Load from the argument list 23 | fn load_from_args(args: impl Iterator) -> Result; 24 | 25 | /// matches the arg tys 26 | fn matches_arg_tys(tys: impl Iterator) -> anyhow::Result<()>; 27 | } 28 | 29 | pub(crate) trait ReturnAbi: Abi { 30 | /// Place the necessary type signature in the type list 31 | #[allow(unused)] 32 | fn return_or_push_arg_tys(args: &mut Vec) -> Option; 33 | 34 | /// Matches the return type or the arg tys 35 | fn matches_return_or_arg_tys( 36 | ret: Option, 37 | arg_tys: impl Iterator, 38 | ) -> Result<(), anyhow::Error>; 39 | 40 | fn get_return_by_ref_arg(args: impl Iterator) -> Option; 41 | 42 | /// Place the values in the argument list 43 | #[allow(unused)] 44 | fn return_or_store_to_arg<'w>( 45 | args: &mut Vec, 46 | wasm_alloc: Option<&'w WasmAlloc>, 47 | store: &mut Store, 48 | ) -> Result>, Error>; 49 | 50 | /// Load from the argument list 51 | fn return_or_load_or_from_args( 52 | ret: Option<&Val>, 53 | ret_by_ref_ptr: Option>, 54 | wasm_alloc: Option<&WasmAlloc>, 55 | store: &mut Store, 56 | ) -> Result; 57 | } 58 | 59 | impl ReturnAbi for T { 60 | /// Place the necessary type signature in the type list 61 | #[allow(unused)] 62 | fn return_or_push_arg_tys(args: &mut Vec) -> Option { 63 | Some(Self::into_val_type()) 64 | } 65 | 66 | /// Place the values in the argument list 67 | #[allow(unused)] 68 | fn return_or_store_to_arg<'w>( 69 | args: &mut Vec, 70 | wasm_alloc: Option<&'w WasmAlloc>, 71 | store: &mut Store, 72 | ) -> Result>, Error> { 73 | Ok(None) 74 | } 75 | 76 | fn get_return_by_ref_arg(_args: impl Iterator) -> Option { 77 | None 78 | } 79 | 80 | /// Load from the argument list 81 | fn return_or_load_or_from_args( 82 | mut ret: Option<&Val>, 83 | _ret_by_ref_ptr: Option>, 84 | _wasm_alloc: Option<&WasmAlloc>, 85 | _store: &mut Store, 86 | ) -> Result { 87 | ret.take() 88 | .cloned() 89 | .map(Self::from_val) 90 | .ok_or_else(|| anyhow!("Return Val not present")) 91 | } 92 | 93 | /// matches the arg tys 94 | fn matches_return_or_arg_tys( 95 | mut ret: Option, 96 | _arg_tys: impl Iterator, 97 | ) -> Result<(), anyhow::Error> { 98 | let ty = ret 99 | .take() 100 | .ok_or_else(|| anyhow!("expected return type to compare"))?; 101 | 102 | ensure!( 103 | Self::matches_val_type(ty.clone()), 104 | "Expected {} but was {:?}", 105 | std::any::type_name::(), 106 | ty 107 | ); 108 | 109 | Ok(()) 110 | } 111 | } 112 | 113 | pub(crate) trait IntoValType { 114 | fn into_val_type() -> ValType; 115 | } 116 | 117 | pub(crate) trait FromVal: Sized { 118 | fn from_val(v: Val) -> Self; 119 | } 120 | 121 | pub(crate) trait IntoAbi { 122 | type Abi: Abi; 123 | 124 | fn into_abi(self) -> Self::Abi; 125 | } 126 | 127 | pub(crate) trait FromAbi { 128 | unsafe fn from_abi(abi: A) -> Self; 129 | } 130 | 131 | pub(crate) trait MatchesValType { 132 | fn matches_val_type(ty: ValType) -> bool; 133 | } 134 | 135 | macro_rules! direct_complex_ty { 136 | ($t:ident, $v:path) => { 137 | impl Abi for $t { 138 | fn push_arg_tys(args: &mut Vec) { 139 | args.push($v); 140 | } 141 | 142 | fn store_to_args(self, args: &mut Vec) { 143 | args.push(Val::from(self)); 144 | } 145 | 146 | fn load_from_args(mut args: impl Iterator) -> Result { 147 | let val = args 148 | .next() 149 | .ok_or_else(|| anyhow!("next argument missing, expected: {:?}", $v))?; 150 | val.$t().ok_or_else(|| { 151 | anyhow!( 152 | "incorrect value for argument, expected: {:?}, got: {:?}", 153 | $v, 154 | val 155 | ) 156 | }) 157 | } 158 | 159 | fn matches_arg_tys(mut tys: impl Iterator) -> anyhow::Result<()> { 160 | let next = tys.next(); 161 | ensure!( 162 | next == Some($v), 163 | "Expected ty for next arg: {:?} got: {:?}", 164 | $v, 165 | next 166 | ); 167 | 168 | Ok(()) 169 | } 170 | } 171 | 172 | impl IntoAbi for $t { 173 | type Abi = Self; 174 | 175 | fn into_abi<'a>(self) -> Self::Abi { 176 | self 177 | } 178 | } 179 | 180 | impl FromAbi<$t> for $t { 181 | unsafe fn from_abi<'a>(abi: Self) -> Self { 182 | abi 183 | } 184 | } 185 | 186 | impl IntoValType for $t { 187 | fn into_val_type() -> ValType { 188 | $v 189 | } 190 | } 191 | 192 | impl FromVal for $t { 193 | fn from_val(val: Val) -> Self { 194 | val.$t().expect("Should always succeed") 195 | } 196 | } 197 | 198 | impl MatchesValType for $t { 199 | fn matches_val_type(ty: ValType) -> bool { 200 | ty == $v 201 | } 202 | } 203 | 204 | impl ComplexTy for $t { 205 | type Abi = Self; 206 | 207 | fn compatible_with_store(&self, _store: &Store) -> bool { 208 | true 209 | } 210 | } 211 | }; 212 | } 213 | 214 | direct_complex_ty!(i32, ValType::I32); 215 | direct_complex_ty!(i64, ValType::I64); 216 | direct_complex_ty!(f32, ValType::F32); 217 | direct_complex_ty!(f64, ValType::F64); 218 | -------------------------------------------------------------------------------- /wasmtime-jni/src/ty/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod byte_slice; 2 | pub(crate) mod complex_ty; 3 | mod wasm_alloc; 4 | 5 | pub(crate) use byte_slice::{WasmAllocated, WasmSlice}; 6 | pub(crate) use complex_ty::{Abi, ComplexTy, ReturnAbi}; 7 | pub(crate) use wasm_alloc::{WasmAlloc, WasmSliceWrapper}; 8 | -------------------------------------------------------------------------------- /wasmtime-jni/src/ty/str.rs: -------------------------------------------------------------------------------- 1 | use jni::objects::JString; 2 | use jni::JNIEnv; 3 | use wasm_bindgen::convert::WasmSlice; 4 | use wasmtime::WasmTy; 5 | 6 | pub struct WasmJString<'j> { 7 | env: JNIEnv<'j>, 8 | string: JString<'j>, 9 | } 10 | 11 | impl<'j> WasmTy for WasmJString<'j> { 12 | type Abi = WasmSlice; 13 | 14 | #[inline] 15 | fn compatible_with_store(&self, _store: &Store) -> bool { 16 | true 17 | } 18 | 19 | #[inline] 20 | fn into_abi(self, store: &Store) -> Self::Abi { 21 | let jstr = self 22 | .env 23 | .get_string(&self.string) 24 | .expect("oh man, it's not a JString"); 25 | 26 | let str = Cow::from(jstr); 27 | 28 | unimplemented!(); 29 | // WasmSlice { 30 | // pub ptr: u32, 31 | // pub len: u32, 32 | // } 33 | } 34 | 35 | #[inline] 36 | unsafe fn from_abi<'a>(abi: Self::Abi, _store: &Store) -> Self { 37 | unimplemented!(); 38 | } 39 | 40 | fn push(dst: &mut Vec) { 41 | dst.push(ValType::I32); // offset into memory 42 | dst.push(ValType::I32); // length 43 | } 44 | 45 | fn matches(mut tys: impl Iterator) -> anyhow::Result<()> { 46 | // offset 47 | let next = tys.next(); 48 | ensure!( 49 | next == Some(ValType::I32), 50 | "Expected offset for memory: {:?}" 51 | next 52 | ); 53 | 54 | // length 55 | let next = tys.next(); 56 | ensure!( 57 | next == Some(ValType::I32), 58 | "Expected length for memory: {:?}" 59 | next 60 | ); 61 | } 62 | 63 | unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi { 64 | unimplemented!(); 65 | } 66 | 67 | unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) { 68 | unimplemented!(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /wasmtime-jni/src/ty/wasm_alloc.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::mem; 3 | use std::ops::Deref; 4 | 5 | use anyhow::{anyhow, ensure, Context, Error}; 6 | use log::debug; 7 | use wasmtime::{AsContextMut, Caller, Extern, Func, Instance, Memory, Store, Val}; 8 | use wasmtime_jni_exports::{ALLOC_EXPORT, DEALLOC_EXPORT, MEMORY_EXPORT}; 9 | 10 | use crate::{ 11 | ty::{WasmAllocated, WasmSlice}, 12 | wasm_state::JavaState, 13 | }; 14 | 15 | const MEM_SEGMENT_SIZE: usize = 64 * 1024; 16 | 17 | /// Allocator that can allocate and deallocate to and from a WASM module. 18 | /// 19 | /// This assumes the existence of `memory` Memory as well as `__alloc_bytes` and `__dealloc_bytes` Funcs 20 | /// are exported from the module. 21 | pub(crate) struct WasmAlloc { 22 | memory: Memory, 23 | alloc: Func, 24 | dealloc: Func, 25 | } 26 | 27 | impl WasmAlloc { 28 | pub fn from_caller(caller: &mut Caller) -> Option { 29 | let memory = caller 30 | .get_export(MEMORY_EXPORT) 31 | .and_then(Extern::into_memory); 32 | let alloc = caller.get_export(ALLOC_EXPORT).and_then(Extern::into_func); 33 | let dealloc = caller 34 | .get_export(DEALLOC_EXPORT) 35 | .and_then(Extern::into_func); 36 | 37 | Self::from(memory, alloc, dealloc) 38 | } 39 | 40 | pub fn from_instance(instance: &Instance, mut store: impl AsContextMut) -> Option { 41 | Self::from( 42 | instance.get_memory(&mut store, MEMORY_EXPORT), 43 | instance.get_func(&mut store, ALLOC_EXPORT), 44 | instance.get_func(&mut store, DEALLOC_EXPORT), 45 | ) 46 | } 47 | 48 | fn from(memory: Option, alloc: Option, dealloc: Option) -> Option { 49 | Some(Self { 50 | memory: memory?, 51 | alloc: alloc?, 52 | dealloc: dealloc?, 53 | }) 54 | } 55 | 56 | /// Safety, the returned array is uninitialized 57 | #[allow(clippy::mut_from_ref)] 58 | pub unsafe fn as_mut<'a>( 59 | &self, 60 | wasm_slice: WasmSlice, 61 | store: &'a mut impl AsContextMut, 62 | ) -> &'a mut [u8] { 63 | debug!("data ptr: {}", wasm_slice.ptr()); 64 | 65 | &mut self.memory.data_mut(store.as_context_mut())[wasm_slice.ptr() as usize..] 66 | [..wasm_slice.len() as usize] 67 | } 68 | 69 | /// Allocates size bytes in the Wasm Memory context, returns the offset into the Memory region 70 | pub unsafe fn alloc_size( 71 | &self, 72 | size: usize, 73 | store: impl AsContextMut, 74 | ) -> Result, Error> { 75 | let len = size as i32; 76 | let mut ptr = [Val::null(); 1]; 77 | self.alloc.call(store, &[Val::I32(len)], &mut ptr)?; 78 | 79 | let ptr = ptr 80 | .get(0) 81 | .and_then(|v| v.i32()) 82 | .ok_or_else(|| anyhow!("i32 was not returned from the alloc"))?; 83 | 84 | debug!("Allocated offset {} len {}", ptr, len); 85 | 86 | let wasm_slice = WasmSlice::new(ptr, len); 87 | Ok(WasmSliceWrapper::new(self, wasm_slice)) 88 | } 89 | 90 | /// Allocates the bytes from the src bytes 91 | pub fn alloc_bytes( 92 | &self, 93 | src: &[u8], 94 | mut store: impl AsContextMut, 95 | ) -> Result, Error> { 96 | let mem_base = self.memory.data_ptr(&mut store) as usize; 97 | let mem_size = self.memory.size(&mut store) as usize * MEM_SEGMENT_SIZE; 98 | 99 | // TODO: check if we need to grow the underlying memory with `grow`, etc. 100 | ensure!( 101 | mem_size > src.len(), 102 | "memory is {} need {} more", 103 | mem_size, 104 | src.len() 105 | ); 106 | 107 | // get target memory location and then copy into the function 108 | let wasm_slice = unsafe { self.alloc_size(src.len(), &mut store)? }; 109 | let mem_bytes = unsafe { self.as_mut(wasm_slice.wasm_slice, &mut store) }; 110 | mem_bytes.copy_from_slice(src); 111 | 112 | debug!( 113 | "copied bytes into mem: {:x?}, mem_base: {:x?} mem_bytes: {:x?}", 114 | mem_bytes, 115 | mem_base, 116 | mem_bytes.as_ptr(), 117 | ); 118 | 119 | Ok(wasm_slice) 120 | } 121 | 122 | /// Deallocate the WasmSlice 123 | pub fn dealloc_bytes( 124 | &self, 125 | slice: WasmSlice, 126 | store: &mut Store, 127 | ) -> Result<(), Error> { 128 | let ptr = slice.ptr(); 129 | let len = slice.len(); 130 | let mut no_result = [Val::null(); 0]; 131 | self.dealloc 132 | .call(store, &[Val::I32(ptr), Val::I32(len)], &mut no_result) 133 | .with_context(|| anyhow!("failed to deallocate bytes"))?; 134 | 135 | debug!("Deallocated offset {} len {}", ptr, len); 136 | Ok(()) 137 | } 138 | 139 | pub fn alloc( 140 | &self, 141 | store: &mut Store, 142 | ) -> Result, Error> { 143 | let wasm_slice = unsafe { self.alloc_size(mem::size_of::(), &mut *store)? }; 144 | 145 | // zero out the memory... 146 | for b in unsafe { wasm_slice.as_mut(&mut *store) } { 147 | *b = 0; 148 | } 149 | 150 | debug!( 151 | "stored {} at {:x?}", 152 | std::any::type_name::(), 153 | wasm_slice.wasm_slice().ptr() 154 | ); 155 | Ok(wasm_slice) 156 | } 157 | 158 | #[allow(clippy::mut_from_ref)] 159 | pub unsafe fn obj_as_mut(&self, ptr: i32, store: S) -> &mut T { 160 | debug_assert!(ptr > 0); 161 | let ptr_to_mem = self.memory.data_ptr(store).add(ptr as usize); 162 | debug!("dereffing {:x?} from offset {:x?}", ptr_to_mem, ptr); 163 | 164 | &mut *(ptr_to_mem as *mut T) 165 | } 166 | 167 | #[allow(unused)] 168 | pub unsafe fn dealloc( 169 | &self, 170 | ptr: i32, 171 | store: &mut Store, 172 | ) -> Result<(), Error> { 173 | debug_assert!(ptr > 0); 174 | let len = i32::try_from(mem::size_of::())?; 175 | let wasm_slice = WasmSlice::new(ptr, len); 176 | 177 | self.dealloc_bytes(wasm_slice, store) 178 | } 179 | } 180 | 181 | /// This is use to free memory after a function call 182 | pub(crate) struct WasmSliceWrapper<'w> { 183 | wasm_alloc: &'w WasmAlloc, 184 | wasm_slice: WasmSlice, 185 | } 186 | 187 | impl<'w> WasmSliceWrapper<'w> { 188 | pub fn new(wasm_alloc: &'w WasmAlloc, wasm_slice: WasmSlice) -> Self { 189 | Self { 190 | wasm_alloc, 191 | wasm_slice, 192 | } 193 | } 194 | 195 | /// Safety, the returned array is uninitialized 196 | #[allow(clippy::mut_from_ref)] 197 | pub unsafe fn as_mut<'a>(&'a self, store: &'a mut Store) -> &'a mut [u8] { 198 | self.wasm_alloc.as_mut(self.wasm_slice, store) 199 | } 200 | 201 | #[allow(clippy::mut_from_ref)] 202 | pub unsafe fn obj_as_mut(&self, store: &mut Store) -> &mut T { 203 | self.wasm_alloc.obj_as_mut(self.wasm_slice.ptr(), store) 204 | } 205 | 206 | /// Copy out the WasmSlice, careful, the lifetime of this is really tied to the memory lifetime backing the WasmAlloc 207 | pub fn wasm_slice(&self) -> WasmSlice { 208 | self.wasm_slice 209 | } 210 | } 211 | 212 | impl<'w> Deref for WasmSliceWrapper<'w> { 213 | type Target = WasmSlice; 214 | 215 | fn deref(&self) -> &WasmSlice { 216 | &self.wasm_slice 217 | } 218 | } 219 | 220 | impl<'w> Drop for WasmSliceWrapper<'w> { 221 | fn drop(&mut self) { 222 | eprintln!("leaked wasm slice in WASM") 223 | 224 | //if let Err(err) = self.wasm_alloc.dealloc_bytes(self.wasm_slice, self.store) { 225 | // warn!("Error deallocating bytes: {}", err); 226 | //} 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /wasmtime-jni/src/wasm_engine.rs: -------------------------------------------------------------------------------- 1 | use jni::objects::{JByteBuffer, JClass}; 2 | use jni::sys::jlong; 3 | use jni::JNIEnv; 4 | use log::{debug, warn}; 5 | use wasmtime::{Engine, Linker, Module, Store}; 6 | 7 | use crate::opaque_ptr::OpaquePtr; 8 | use crate::wasm_exception; 9 | use crate::wasm_state::JavaState; 10 | 11 | /// /* 12 | /// * Class: net_bluejekyll_wasmtime_WasmEngine 13 | /// * Method: freeEngine 14 | /// * Signature: (J)V 15 | /// */ 16 | /// JNIEXPORT void JNICALL Java_net_bluejekyll_wasmtime_WasmEngine_freeEngine 17 | /// (JNIEnv *, jclass, jlong); 18 | #[no_mangle] 19 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmEngine_freeEngine<'j>( 20 | _env: JNIEnv<'j>, 21 | _class: JClass<'j>, 22 | engine: OpaquePtr<'j, Engine>, 23 | ) { 24 | drop(engine.take()); 25 | } 26 | 27 | /// /* 28 | /// * Class: net_bluejekyll_wasmtime_WasmEngine 29 | /// * Method: newStoreNtv 30 | /// * Signature: ()J 31 | /// */ 32 | /// JNIEXPORT jlong JNICALL Java_net_bluejekyll_wasmtime_WasmEngine_newStoreNtv 33 | /// (JNIEnv *, jclass, jlong); 34 | #[no_mangle] 35 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmEngine_newStoreNtv<'j>( 36 | env: JNIEnv<'j>, 37 | _class: JClass<'j>, 38 | engine: OpaquePtr<'j, Engine>, 39 | ) -> jlong { 40 | let ptr = wasm_exception::attempt(&env, |_env| { 41 | let store: Store = Store::new(&engine, JavaState::new(env)?); 42 | Ok(OpaquePtr::from(store).make_opaque()) 43 | }); 44 | 45 | ptr 46 | } 47 | 48 | /// /* 49 | /// * Class: net_bluejekyll_wasmtime_WasmEngine 50 | /// * Method: newModuleNtv 51 | /// * Signature: (JLjava/nio/ByteBuffer;)J 52 | /// */ 53 | /// JNIEXPORT jlong JNICALL Java_net_bluejekyll_wasmtime_WasmEngine_newModuleNtv 54 | /// (JNIEnv *, jclass, jlong, jobject); 55 | #[no_mangle] 56 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmEngine_newModuleNtv( 57 | env: JNIEnv, 58 | _class: JClass, 59 | engine: OpaquePtr, 60 | wat: JByteBuffer, 61 | ) -> jlong { 62 | let wat_bytes = match env.get_direct_buffer_address(wat) { 63 | Err(err) => { 64 | warn!("Error accessing byte buffer: {}", err); 65 | env.throw_new("net/bluejekyll/wasmtime/WasmtimeException", err.to_string()) 66 | .expect("failed to throw exception"); 67 | return 0; 68 | } 69 | Ok(ok) => ok, 70 | }; 71 | 72 | debug!("compiling wasm module from bytes: {}", wat_bytes.len()); 73 | let module = match Module::new(&engine, wat_bytes) { 74 | Err(err) => { 75 | env.throw_new("net/bluejekyll/wasmtime/WasmtimeException", err.to_string()) 76 | .expect("failed to throw exception"); 77 | return 0; 78 | } 79 | Ok(ok) => ok, 80 | }; 81 | 82 | if log::log_enabled!(log::Level::Debug) { 83 | for import in module.imports() { 84 | debug!("Import module: {:?}", import); 85 | } 86 | 87 | for export in module.exports() { 88 | debug!("Export module: {:?}", export); 89 | } 90 | } 91 | 92 | OpaquePtr::from(module).make_opaque() 93 | } 94 | 95 | /// /* 96 | /// * Class: net_bluejekyll_wasmtime_WasmEngine 97 | /// * Method: newLinker 98 | /// * Signature: (J)J 99 | /// */ 100 | /// JNIEXPORT jlong JNICALL Java_net_bluejekyll_wasmtime_WasmEngine_newLinker 101 | /// (JNIEnv *, jclass, jlong); 102 | #[no_mangle] 103 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmEngine_newLinker<'j>( 104 | env: JNIEnv<'j>, 105 | _class: JClass<'j>, 106 | engine: OpaquePtr<'j, Engine>, 107 | ) -> jlong { 108 | wasm_exception::attempt(&env, |_env| { 109 | let mut linker = Linker::::new(&engine); 110 | wasmtime_wasi::add_to_linker(&mut linker, |s| s.wasi_mut())?; 111 | 112 | linker.allow_shadowing(false); 113 | 114 | Ok(OpaquePtr::from(linker).make_opaque()) 115 | }) 116 | } 117 | -------------------------------------------------------------------------------- /wasmtime-jni/src/wasm_exception.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fmt; 3 | 4 | use anyhow::{anyhow, Context, Error}; 5 | use jni::objects::{JObject, JString, JThrowable}; 6 | use jni::strings::JavaStr; 7 | use jni::sys::jarray; 8 | use jni::JNIEnv; 9 | use log::warn; 10 | 11 | use crate::wasm_value; 12 | 13 | #[track_caller] 14 | pub fn attempt(env: &JNIEnv, f: F) -> R 15 | where 16 | R: Default, 17 | F: FnOnce(&JNIEnv) -> Result, 18 | { 19 | attempt_or_else(env, R::default, f) 20 | } 21 | 22 | #[track_caller] 23 | pub fn attempt_or_else(env: &JNIEnv, or: D, f: F) -> R 24 | where 25 | D: FnOnce() -> R, 26 | F: FnOnce(&JNIEnv) -> Result, 27 | { 28 | let r = f(env); 29 | 30 | match r { 31 | Ok(ok) => ok, 32 | Err(err) => { 33 | let msg = format!("Error in WASM Binding: {:?}", err); 34 | warn!("{}", msg); 35 | env.throw_new("net/bluejekyll/wasmtime/WasmtimeException", msg) 36 | .expect("failed to throw exception"); 37 | or() 38 | } 39 | } 40 | } 41 | 42 | pub fn exception_to_err<'j>(env: &JNIEnv<'j>, throwable: JThrowable<'j>) -> Error { 43 | let reporter = ReportJThrowable { env, throwable }; 44 | 45 | warn!("WASM caught an exception: {}", reporter); 46 | anyhow!("WASM caught an exception: {}", reporter) 47 | } 48 | 49 | struct ReportJThrowable<'l, 'j: 'l> { 50 | env: &'l JNIEnv<'j>, 51 | throwable: JThrowable<'j>, 52 | } 53 | 54 | impl<'l, 'j: 'l> ReportJThrowable<'l, 'j> { 55 | fn from(env: &'l JNIEnv<'j>, throwable: JThrowable<'j>) -> Self { 56 | Self { env, throwable } 57 | } 58 | } 59 | 60 | impl<'l, 'j: 'l> fmt::Display for ReportJThrowable<'l, 'j> { 61 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 62 | // just in case... 63 | if self.throwable.is_null() { 64 | write!(f, "null throwable thrown")?; 65 | return Ok(()); 66 | } 67 | 68 | let cause = self 69 | .env 70 | .call_method(self.throwable, "getCause", "()Ljava/lang/Throwable;", &[]) 71 | .map_err(|_| fmt::Error)?; 72 | 73 | let cause = cause.l().map_err(|_| fmt::Error)?; 74 | if !cause.is_null() { 75 | let reporter = ReportJThrowable::from(self.env, JThrowable::from(cause)); 76 | write!(f, "cause: {}", reporter)?; 77 | } 78 | 79 | let clazz = wasm_value::get_class_name_obj(self.env, self.throwable.into()) 80 | .map_err(|_| fmt::Error)?; 81 | 82 | let message = 83 | call_string_method(self.env, &self.throwable, "getMessage").map_err(|_| fmt::Error)?; 84 | 85 | if let Some(message) = message { 86 | writeln!(f, "{}: {}", clazz, Cow::from(&message))?; 87 | } else { 88 | writeln!(f, "{}", clazz)?; 89 | }; 90 | 91 | let trace = self 92 | .env 93 | .call_method( 94 | self.throwable, 95 | "getStackTrace", 96 | "()[Ljava/lang/StackTraceElement;", 97 | &[], 98 | ) 99 | .map_err(|_| fmt::Error)? 100 | .l() 101 | .map_err(|_| fmt::Error)?; 102 | 103 | if !trace.is_null() { 104 | let trace = *trace as jarray; 105 | let len = self.env.get_array_length(trace).map_err(|_| fmt::Error)?; 106 | 107 | for i in 0..len as usize { 108 | let stack_element = self 109 | .env 110 | .get_object_array_element(trace, i as i32) 111 | .map_err(|_| fmt::Error)?; 112 | 113 | let stack_str = call_string_method(self.env, &stack_element, "toString") 114 | .map_err(|_| fmt::Error)?; 115 | 116 | if let Some(stack_str) = stack_str { 117 | writeln!(f, "\t{}", Cow::from(&stack_str))?; 118 | } 119 | } 120 | } 121 | 122 | Ok(()) 123 | } 124 | } 125 | 126 | fn call_string_method<'j: 'l, 'l>( 127 | env: &'l JNIEnv<'j>, 128 | obj: &JObject<'j>, 129 | method: &str, 130 | ) -> Result>, anyhow::Error> { 131 | let jstring = env 132 | .call_method(*obj, method, "()Ljava/lang/String;", &[]) 133 | .map_err(|_| fmt::Error)? 134 | .l() 135 | .map_err(|_| fmt::Error) 136 | .map(JString::from)?; 137 | 138 | if jstring.is_null() { 139 | return Ok(None); 140 | } 141 | 142 | env.get_string(jstring) 143 | .with_context(|| format!("Failed to get_string for Exception: {:?}", obj)) 144 | .map(Some) 145 | } 146 | -------------------------------------------------------------------------------- /wasmtime-jni/src/wasm_function.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::convert::TryFrom; 3 | use std::slice; 4 | 5 | use anyhow::{anyhow, Context, Error}; 6 | use jni::objects::{JClass, JMethodID, JObject, JString, JValue, ReleaseMode}; 7 | use jni::signature::JavaType; 8 | use jni::sys::{jlong, jobject, jobjectArray}; 9 | use jni::JNIEnv; 10 | use log::debug; 11 | use log::warn; 12 | use wasmtime::{AsContextMut, Caller, Func, FuncType, Instance, Store, Trap, Val, ValType}; 13 | 14 | use crate::opaque_ptr::OpaquePtr; 15 | use crate::ty::{WasmAlloc, WasmSlice}; 16 | use crate::wasm_exception; 17 | use crate::wasm_state::JavaState; 18 | use crate::wasm_value::{self, WasmTy, WasmVal}; 19 | 20 | /// Take ths src_bytes and copy into the location at ret_by_ref_ptr as a reference. 21 | /// This will forget the allocation, as it is expected that the function accepting the reference will own the 22 | /// data afterward. 23 | /// 24 | /// # Safety 25 | /// 26 | /// A pointer with allocated memory for a type of (i32,i32) length is valid and exists at `ret_by_ref_ptr` in the WASM module 27 | unsafe fn pass_bytes_to_wasm_by_ref( 28 | wasm_alloc: WasmAlloc, 29 | ret_by_ref_ptr: i32, 30 | src_bytes: &[u8], 31 | mut store: impl AsContextMut, 32 | ) -> Result<(), Trap> { 33 | let bytes = wasm_alloc.alloc_bytes(src_bytes, &mut store)?; 34 | 35 | // get mutable reference to the return by ref pointer and then store 36 | let ret_by_ref_loc = wasm_alloc.obj_as_mut::(ret_by_ref_ptr, store); 37 | *ret_by_ref_loc = bytes.wasm_slice(); 38 | 39 | // This memory is owned by the WASM code after this... 40 | std::mem::forget(bytes); 41 | Ok(()) 42 | } 43 | 44 | /// /* 45 | /// * Class: net_bluejekyll_wasmtime_WasmFunction 46 | /// * Method: createFunc 47 | /// * Signature: (JLjava/lang/reflect/Method;Ljava/lang/Object;Ljava/lang/Class;Ljava/util/List;)J 48 | /// */ 49 | /// JNIEXPORT jlong JNICALL Java_net_bluejekyll_wasmtime_WasmFunction_createFunc 50 | /// (JNIEnv *, jclass, jlong, jobject, jobject, jclass, jobject); 51 | #[no_mangle] 52 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmFunction_createFunc<'j>( 53 | env: JNIEnv<'j>, 54 | _class: JClass<'j>, 55 | mut store: OpaquePtr<'j, Store>, 56 | method: JObject<'j>, 57 | obj: JObject<'j>, 58 | return_ty: JClass<'j>, 59 | param_tys: JObject<'j>, 60 | ) -> jlong { 61 | // Read this to help understand String and array types https://github.com/rustwasm/wasm-bindgen/blob/d54340e5a220953651555f45f90061499dc0ac92/guide/src/contributing/design/exporting-rust.md 62 | 63 | wasm_exception::attempt(&env, move |env| { 64 | let method = env.new_global_ref(method)?; 65 | let obj = env.new_global_ref(obj)?; 66 | let jvm = env.get_java_vm()?; 67 | 68 | let method_name = get_method_name(env, &method)?; 69 | debug!("building WASM function from method: \"{}\"", method_name); 70 | 71 | // collect all the arguments from 72 | let param_list = env.get_list(param_tys)?; 73 | let mut wasm_args: Vec = Vec::with_capacity(param_list.size()? as usize); 74 | let mut java_args: Vec = Vec::with_capacity(wasm_args.len()); 75 | 76 | for class in param_list.iter()? { 77 | // this is a list of classes 78 | let val = wasm_value::from_java_class(env, class.into(), false) 79 | .context("error converting type to wasm")?; 80 | let val = val.ok_or_else(|| anyhow!("Null parameters not allowed"))?; 81 | debug!( 82 | "Mapping parameter from {:?} to {:?}", 83 | wasm_value::get_class_name(env, class.into())?, 84 | val 85 | ); 86 | 87 | val.push_arg_tys(&mut wasm_args); 88 | java_args.push(val); 89 | } 90 | 91 | // determine the return type 92 | let java_ret = wasm_value::from_java_class(env, return_ty, true) 93 | .context("error converting type to wasm")?; 94 | debug!( 95 | "Mapping return value from {:?} to {:?}", 96 | wasm_value::get_class_name(env, return_ty)?, 97 | java_ret 98 | ); 99 | 100 | let wasm_ret = if let Some(java_ret) = &java_ret { 101 | java_ret.return_or_push_arg_tys(&mut wasm_args) 102 | } else { 103 | None 104 | }; 105 | 106 | let wasm_ret: Vec = wasm_ret.map_or_else(Vec::new, |v| vec![v]); 107 | 108 | // This defines the lambda that will be called by the Wasmtime engine from the WASM module. 109 | // all params need to be converted to Java equivalent params, and return types need to be bound 110 | // correctly. 111 | let func = move |mut caller: Caller, 112 | inputs: &[Val], 113 | outputs: &mut [Val]| 114 | -> Result<(), Trap> { 115 | let java_args = &java_args; 116 | let java_ret = &java_ret; 117 | let wasm_alloc = WasmAlloc::from_caller(&mut caller); 118 | 119 | debug!( 120 | "Calling Java method args {} and return {} with WASM {} inputs and {} outputs", 121 | java_args.len(), 122 | java_ret.as_ref().as_ref().map_or(0, |_| 1), 123 | inputs.len(), 124 | outputs.len() 125 | ); 126 | 127 | // validate the parameters 128 | let mut input_ty_iter = inputs.iter().map(|v| v.ty()); 129 | for java_arg in java_args.iter() { 130 | java_arg 131 | .matches_arg_tys(&mut input_ty_iter) 132 | .with_context(|| { 133 | format!("Expected arguments to line up with java arg: {}", java_arg) 134 | })?; 135 | } 136 | 137 | // TODO: this validation fails, b/c the ty from WASM is ExternRef... not sure why? 138 | // validate the return 139 | // if let Some(java_ret) = java_ret { 140 | // java_ret 141 | // .matches_return_or_arg_tys(outputs.get(0).map(Val::ty), &mut input_ty_iter)?; 142 | // } 143 | 144 | let env = jvm 145 | .get_env() 146 | .map_err(Error::from) 147 | .context("Error accessing JNIEnv in WASM")?; 148 | 149 | // get the invoke method on the method, the result is always Object 150 | let ret = JavaType::Object(String::from("java/lang/Object")); 151 | let method_id: JMethodID = match env.get_method_id( 152 | "java/lang/reflect/Method", 153 | "invoke", 154 | "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", 155 | ) { 156 | Err(err) => { 157 | let warning = format!("Error accessing byte buffer: {}", err); 158 | warn!("{}", warning); 159 | return Err(Trap::new(warning)); 160 | } 161 | Ok(ok) => ok, 162 | }; 163 | 164 | // build up parameters 165 | let arg_class = env 166 | .find_class("java/lang/Object") 167 | .map_err(Error::from) 168 | .context("Could not find Object?")?; 169 | 170 | let method_args = env 171 | .new_object_array(java_args.len() as i32, arg_class, JObject::null()) 172 | .map_err(Error::from) 173 | .context("Could not create empty array?")?; 174 | 175 | // set the parameters 176 | let mut input_iter = inputs.iter().cloned(); 177 | for (i, java_arg) in java_args.iter().enumerate() { 178 | let jvalue = unsafe { 179 | java_arg 180 | .load_from_args(&env, &mut input_iter, wasm_alloc.as_ref(), &mut caller) 181 | .with_context(|| format!("Failed to get Java arg from: {}", java_arg))? 182 | }; 183 | 184 | debug!( 185 | "Setting parameter {}: {:?} as {:?}", 186 | i, 187 | java_arg, 188 | wasm_value::get_class_name_obj(&env, jvalue) 189 | ); 190 | 191 | env.set_object_array_element(method_args, i as i32, jvalue) 192 | .map_err(Error::from) 193 | .context("Failed to add value to array?")?; 194 | } 195 | 196 | // get the optional pointer to the arg to store a byte return by ref 197 | let ret_by_ref_ptr = java_ret 198 | .as_ref() 199 | .and_then(|v| v.get_return_by_ref_arg(input_iter)); 200 | 201 | debug!("Calling Java method"); 202 | 203 | // setup the arguments for the call 204 | let method_args = JObject::from(method_args); 205 | let val = env 206 | .call_method_unchecked( 207 | method.as_obj(), 208 | method_id, 209 | ret, 210 | &[JValue::Object(obj.as_obj()), JValue::Object(method_args)], 211 | ) 212 | .map_err(Error::from) 213 | .context("Call to Java method failed!"); 214 | 215 | // Check if Java threw an exception. 216 | if val.is_err() 217 | && env 218 | .exception_check() 219 | .context("Failed to check for exception")? 220 | { 221 | // get the exception 222 | let exception = env 223 | .exception_occurred() 224 | .context("Failed to get exception")?; 225 | // clear the exception so that we can make additional java calls 226 | env.exception_clear().context("Failed to clear exception")?; 227 | 228 | let err = wasm_exception::exception_to_err(&env, exception); 229 | return Err(err.into()); 230 | } 231 | 232 | // unwrap the exception 233 | let val = val?; 234 | 235 | // Now get the return value 236 | let val = wasm_value::from_jvalue(&env, val)?; 237 | let result = outputs.get_mut(0); 238 | 239 | // TODO: much of this logic is duplicitive with that in wasm_value::WasmVal::store_to_args 240 | match (val, result) { 241 | (Some(WasmVal::Val(val)), Some(result)) => { 242 | debug!("associating {:?} with result", val); 243 | *result = val; 244 | } 245 | // (Some(WasmVal::ByteBuffer(val)), None) => { 246 | // debug!("allocating space and associating bytes for return by ref"); 247 | // let ptr = ret_by_ref_ptr 248 | // .ok_or_else(|| anyhow!("expected return by ref argument pointer"))?; 249 | // let wasm_alloc = wasm_alloc.ok_or_else(|| anyhow!("WasmAlloc is required"))?; 250 | 251 | // let bytes = env 252 | // .get_direct_buffer_address(val) 253 | // .context("could not get bytes from address")?; 254 | 255 | // // get mutable reference to the return by ref pointer and then store 256 | // unsafe { pass_bytes_to_wasm_by_ref(wasm_alloc, ptr, byte_array)? }; 257 | 258 | // } 259 | (Some(WasmVal::ByteArray { jarray, .. }), None) => { 260 | debug!("allocating space and associating bytes for return by ref"); 261 | let ptr = ret_by_ref_ptr 262 | .ok_or_else(|| anyhow!("expected return by ref argument pointer"))?; 263 | let wasm_alloc = wasm_alloc.ok_or_else(|| anyhow!("WasmAlloc is required"))?; 264 | 265 | let len = env 266 | .get_array_length(jarray) 267 | .context("failed to get Java array length")?; 268 | let jbytes = env 269 | .get_byte_array_elements(jarray, ReleaseMode::CopyBack) 270 | .context("failed to get java array elements")?; 271 | let byte_array: &[u8] = unsafe { 272 | slice::from_raw_parts(jbytes.as_ptr() as *const u8, len as usize) 273 | }; 274 | 275 | // get mutable reference to the return by ref pointer and then store 276 | unsafe { pass_bytes_to_wasm_by_ref(wasm_alloc, ptr, byte_array, &mut caller)? }; 277 | } 278 | (Some(WasmVal::String(string)), None) => { 279 | debug!("allocating space and associating string for return by ref"); 280 | let ptr = ret_by_ref_ptr 281 | .ok_or_else(|| anyhow!("expected return by ref argument pointer"))?; 282 | let wasm_alloc = wasm_alloc.ok_or_else(|| anyhow!("WasmAlloc is required"))?; 283 | 284 | let jstr = env 285 | .get_string(string) 286 | .context("failed to get Java String")?; 287 | let cow = Cow::from(&jstr); 288 | debug!("String from Java for pass_by_ref: {}", cow); 289 | 290 | let cow_bytes = cow.as_bytes(); 291 | 292 | // get mutable reference to the return by ref pointer and then store 293 | unsafe { pass_bytes_to_wasm_by_ref(wasm_alloc, ptr, cow_bytes, &mut caller)? }; 294 | } 295 | // (Some(WasmVal::ByteBuffer(_val)), Some(_result)) => { 296 | // return Err(anyhow!( 297 | // "Unexpected WASM return value, should have been return by reference" 298 | // ) 299 | // .into()); 300 | // } 301 | (Some(WasmVal::ByteArray { .. }), Some(_result)) => { 302 | return Err(anyhow!( 303 | "Unexpected WASM return value, should have been return by reference" 304 | ) 305 | .into()); 306 | } 307 | (Some(WasmVal::String(_)), Some(_result)) => { 308 | return Err(anyhow!( 309 | "Unexpected WASM return value, should have been return by reference" 310 | ) 311 | .into()); 312 | } 313 | (None, Some(result)) => { 314 | debug!("associating null with result"); 315 | *result = Val::null(); 316 | } 317 | (Some(val), None) => { 318 | warn!("WASM expected no result, but Java supplied: {:?}", val); 319 | } 320 | (None, None) => { 321 | debug!("returning no result"); 322 | } 323 | } 324 | 325 | Ok(()) 326 | }; 327 | 328 | let func_type = FuncType::new(wasm_args, wasm_ret); 329 | debug!( 330 | "method \"{}\" as function in WASM: {:?}", 331 | method_name, func_type 332 | ); 333 | 334 | let func = Func::new(&mut *store, func_type, func); 335 | 336 | Ok(OpaquePtr::from(func).make_opaque()) 337 | }) 338 | } 339 | 340 | /// /* 341 | /// * Class: net_bluejekyll_wasmtime_WasmFunction 342 | /// * Method: freeFunc 343 | /// * Signature: (J)V 344 | /// */ 345 | /// JNIEXPORT void JNICALL Java_net_bluejekyll_wasmtime_WasmFunction_freeFunc 346 | /// (JNIEnv *, jclass, jlong); 347 | #[no_mangle] 348 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmFunction_freeFunc<'j>( 349 | _env: JNIEnv<'j>, 350 | _class: JClass<'j>, 351 | func: OpaquePtr<'j, Func>, 352 | ) { 353 | drop(func.take()); 354 | } 355 | 356 | /// /* 357 | /// * Class: net_bluejekyll_wasmtime_WasmFunction 358 | /// * Method: callNtv 359 | /// * Signature: (JJJLjava/lang/Class;[Ljava/lang/Object;)Ljava/lang/Object; 360 | /// */ 361 | /// JNIEXPORT jobject JNICALL Java_net_bluejekyll_wasmtime_WasmFunction_callNtv 362 | /// (JNIEnv *, jclass, jlong, jlong, jlong, jclass, jobjectArray); 363 | #[no_mangle] 364 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmFunction_callNtv<'j>( 365 | env: JNIEnv<'j>, 366 | _class: JClass<'j>, 367 | func: OpaquePtr<'j, Func>, 368 | instance: OpaquePtr<'j, Instance>, 369 | mut store: OpaquePtr<'j, Store>, 370 | return_type: JClass<'j>, 371 | args: jobjectArray, 372 | ) -> jobject { 373 | // Read this to help understand String and array types https://github.com/rustwasm/wasm-bindgen/blob/d54340e5a220953651555f45f90061499dc0ac92/guide/src/contributing/design/exporting-rust.md 374 | 375 | wasm_exception::attempt_or_else( 376 | &env, 377 | || JObject::null().into_inner(), 378 | move |env| { 379 | let len = env.get_array_length(args)?; 380 | let len = usize::try_from(len)?; 381 | let mut wasm_args = Vec::with_capacity(len); 382 | 383 | let wasm_alloc = if !instance.is_null() { 384 | WasmAlloc::from_instance(&instance, &mut *store) 385 | } else { 386 | None 387 | }; 388 | 389 | // let droppers will cleanup allocated memory in the WASM module after the function call 390 | // or should the callee drop?? hmm... 391 | let mut wasm_droppers = Vec::with_capacity(len); 392 | 393 | // we need to convert all the parameters to WASM vals for the call 394 | debug!("got {} args for function", len); 395 | for i in 0..len { 396 | let obj = env 397 | .get_object_array_element(args, i32::try_from(i)?) 398 | .with_context(|| format!("could not get array index: {} len: {}", i, len))?; 399 | 400 | let val = wasm_value::from_java(env, obj) 401 | .with_context(|| format!("failed to convert argument at index: {}", i))?; 402 | 403 | debug!("adding arg: {}", val.ty()); 404 | if let Some(dropper) = 405 | val.store_to_args(env, &mut wasm_args, wasm_alloc.as_ref(), &mut *store)? 406 | { 407 | wasm_droppers.push(dropper); 408 | } 409 | } 410 | 411 | // now we may need to add a return_by_ref parameter 412 | let wasm_return_ty = wasm_value::from_java_class(env, return_type, true)?; 413 | debug!("return ty: {:?}", wasm_return_ty); 414 | 415 | let maybe_ret_by_ref = if let Some(wasm_return_ty) = &wasm_return_ty { 416 | wasm_return_ty.clone().return_or_store_to_arg( 417 | &mut wasm_args, 418 | wasm_alloc.as_ref(), 419 | &mut store, 420 | )? 421 | } else { 422 | None 423 | }; 424 | 425 | // If it's pass by ref, then we aren't expecting a return type in the wasm function... 426 | let mut val = if wasm_return_ty.is_some() && maybe_ret_by_ref.is_none() { 427 | vec![Val::null()] 428 | } else { 429 | vec![] 430 | }; 431 | 432 | // 433 | // Call the WASM function 434 | func.call(&mut *store, &wasm_args, &mut val) 435 | .with_context(|| format!("failed to execute wasm function: {:?}", *func))?; 436 | 437 | if val.len() > 1 { 438 | return Err(anyhow!( 439 | "multiple return values not supported, expected 0 or 1 found: {}", 440 | val.len() 441 | )); 442 | } 443 | 444 | let ret = if let Some(wasm_return_ty) = wasm_return_ty { 445 | unsafe { 446 | wasm_value::return_or_load_or_from_arg( 447 | env, 448 | wasm_return_ty, 449 | val.get(0), 450 | maybe_ret_by_ref, 451 | wasm_alloc.as_ref(), 452 | &mut store, 453 | )? 454 | } 455 | } else { 456 | JObject::null() 457 | }; 458 | 459 | // Giving the return result to java 460 | Ok(ret.into_inner()) 461 | }, 462 | ) 463 | } 464 | 465 | fn get_method_name<'j, O>(env: &JNIEnv<'j>, object: O) -> Result 466 | where 467 | O: Into>, 468 | { 469 | let name = env.call_method(object, "getName", "()Ljava/lang/String;", &[])?; 470 | let name = name.l()?; 471 | let name = JString::from(name); 472 | let name = env.get_string(name)?; 473 | Ok(Cow::from(&name).to_string()) 474 | } 475 | -------------------------------------------------------------------------------- /wasmtime-jni/src/wasm_instance.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use jni::objects::{JClass, JString}; 4 | use jni::sys::jlong; 5 | use jni::JNIEnv; 6 | use log::debug; 7 | use wasmtime::{Instance, Store}; 8 | 9 | use crate::opaque_ptr::OpaquePtr; 10 | use crate::wasm_exception; 11 | use crate::wasm_state::JavaState; 12 | 13 | /// /* 14 | /// * Class: net_bluejekyll_wasmtime_WasmInstance 15 | /// * Method: freeInstance 16 | /// * Signature: (J)V 17 | /// */ 18 | /// JNIEXPORT void JNICALL Java_net_bluejekyll_wasmtime_WasmInstance_freeInstance 19 | /// (JNIEnv *, jclass, jlong); 20 | #[no_mangle] 21 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmInstance_freeInstance<'j>( 22 | _env: JNIEnv<'j>, 23 | _class: JClass<'j>, 24 | instance: OpaquePtr<'j, Instance>, 25 | ) { 26 | drop(instance.take()); 27 | } 28 | 29 | /// /* 30 | /// * Class: net_bluejekyll_wasmtime_WasmInstance 31 | /// * Method: getFunctionNtv 32 | /// * Signature: (JJLjava/lang/String;)J 33 | /// */ 34 | /// JNIEXPORT jlong JNICALL Java_net_bluejekyll_wasmtime_WasmInstance_getFunctionNtv 35 | /// (JNIEnv *, jclass, jlong, jlong, jstring); 36 | #[no_mangle] 37 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmInstance_getFunctionNtv<'j>( 38 | env: JNIEnv<'j>, 39 | _class: JClass<'j>, 40 | instance: OpaquePtr<'j, Instance>, 41 | mut store: OpaquePtr<'j, Store>, 42 | name: JString<'j>, 43 | ) -> jlong { 44 | wasm_exception::attempt(&env, |env| { 45 | let name = env.get_string(name)?; 46 | let name: Cow = Cow::from(&name); 47 | 48 | let func = instance.get_func(&mut *store, &name); 49 | 50 | let func_ptr = if let Some(func) = func { 51 | debug!( 52 | "found function in WASM: {}:{:?}", 53 | name, 54 | func.ty(&mut *store) 55 | ); 56 | let func = OpaquePtr::from(func); 57 | func.make_opaque() 58 | } else { 59 | 0 60 | }; 61 | 62 | Ok(func_ptr) 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /wasmtime-jni/src/wasm_linker.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use jni::objects::{JClass, JString}; 4 | use jni::sys::jlong; 5 | use jni::JNIEnv; 6 | use wasmtime::{Func, Linker, Module, Store}; 7 | 8 | use crate::opaque_ptr::OpaquePtr; 9 | use crate::wasm_exception; 10 | use crate::wasm_state::JavaState; 11 | 12 | /// /* 13 | /// * Class: net_bluejekyll_wasmtime_WasmLinker 14 | /// * Method: freeLinker 15 | /// * Signature: (J)V 16 | /// */ 17 | /// JNIEXPORT void JNICALL Java_net_bluejekyll_wasmtime_WasmLinker_freeLinker 18 | /// (JNIEnv *, jclass, jlong); 19 | #[no_mangle] 20 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmLinker_freeLinker<'j>( 21 | _env: JNIEnv<'j>, 22 | _class: JClass<'j>, 23 | ptr: OpaquePtr<'j, Linker>, 24 | ) { 25 | drop(ptr.take()); 26 | } 27 | 28 | /// /* 29 | /// * Class: net_bluejekyll_wasmtime_WasmLinker 30 | /// * Method: defineFunc 31 | /// * Signature: (JLjava/lang/String;Ljava/lang/String;J)V 32 | /// */ 33 | /// JNIEXPORT void JNICALL Java_net_bluejekyll_wasmtime_WasmLinker_defineFunc 34 | /// (JNIaEnv *, jclass, jlong, jstring, jstring, jlong); 35 | #[no_mangle] 36 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmLinker_defineFunc<'j>( 37 | env: JNIEnv<'j>, 38 | _class: JClass<'j>, 39 | mut linker: OpaquePtr<'j, Linker>, 40 | module: JString<'j>, 41 | name: JString<'j>, 42 | func: OpaquePtr<'j, Func>, 43 | ) { 44 | wasm_exception::attempt(&env, |env| { 45 | let module = env.get_string(module)?; 46 | let name = env.get_string(name)?; 47 | 48 | let module: Cow = Cow::from(&module); 49 | let name: Cow = Cow::from(&name); 50 | 51 | let func = *func; 52 | linker.define(&module, &name, func)?; 53 | Ok(()) 54 | }) 55 | } 56 | 57 | /// /* 58 | /// * Class: net_bluejekyll_wasmtime_WasmLinker 59 | /// * Method: instantiateNtv 60 | /// * Signature: (JJJ)J 61 | /// */ 62 | /// JNIEXPORT jlong JNICALL Java_net_bluejekyll_wasmtime_WasmLinker_instantiateNtv 63 | /// (JNIEnv *, jclass, jlong, jlong, jlong); 64 | #[no_mangle] 65 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmLinker_instantiateNtv<'j>( 66 | env: JNIEnv<'j>, 67 | _class: JClass<'j>, 68 | linker: OpaquePtr<'j, Linker>, 69 | mut store: OpaquePtr<'j, Store>, 70 | module: OpaquePtr<'j, Module>, 71 | ) -> jlong { 72 | wasm_exception::attempt(&env, |_env| { 73 | // // TODO: Security considerations here, we don't want to capture the parent processes env 74 | // // we probably also want custom filehandles for the stdio of the module as well... 75 | // let wasi_ctx = WasiCtxBuilder::new().inherit_env()?.inherit_stdio().build(); 76 | 77 | // sync::add_to_linker(&mut linker, wasi_ctx)?; 78 | 79 | let instance = linker.instantiate(&mut *store, &module)?; 80 | Ok(OpaquePtr::from(instance).make_opaque()) 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /wasmtime-jni/src/wasm_module.rs: -------------------------------------------------------------------------------- 1 | use jni::objects::JClass; 2 | use jni::JNIEnv; 3 | use wasmtime::Module; 4 | 5 | use crate::opaque_ptr::OpaquePtr; 6 | 7 | /// /* 8 | /// * Class: net_bluejekyll_wasmtime_WasmModule 9 | /// * Method: freeModule 10 | /// * Signature: (J)V 11 | /// */ 12 | /// JNIEXPORT void JNICALL Java_net_bluejekyll_wasmtime_WasmModule_freeModule 13 | /// (JNIEnv *, jclass, jlong); 14 | #[no_mangle] 15 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmModule_freeModule<'j>( 16 | _env: JNIEnv<'j>, 17 | _class: JClass<'j>, 18 | module: OpaquePtr<'j, Module>, 19 | ) { 20 | drop(module.take()); 21 | } 22 | -------------------------------------------------------------------------------- /wasmtime-jni/src/wasm_state.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Error}; 2 | use jni::JNIEnv; 3 | 4 | use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx}; 5 | 6 | /// Store associated data, currently empty 7 | pub struct JavaState { 8 | wasi: WasiCtx, 9 | } 10 | 11 | impl JavaState { 12 | pub fn new(_env: JNIEnv<'_>) -> Result { 13 | // TODO: Security considerations here, we don't want to capture the parent processes env 14 | // we probably also want custom filehandles for the stdio of the module as well... 15 | // 16 | // see: https://docs.wasmtime.dev/examples-rust-wasi.html 17 | let wasi = WasiCtxBuilder::new() 18 | .inherit_stdio() 19 | .inherit_args() 20 | .context("failed to establish WASI context")? 21 | .build(); 22 | 23 | Ok(JavaState { wasi }) 24 | } 25 | 26 | pub fn wasi_mut(&mut self) -> &mut WasiCtx { 27 | &mut self.wasi 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /wasmtime-jni/src/wasm_store.rs: -------------------------------------------------------------------------------- 1 | use jni::objects::JClass; 2 | use jni::JNIEnv; 3 | use wasmtime::Store; 4 | 5 | use crate::opaque_ptr::OpaquePtr; 6 | use crate::wasm_state::JavaState; 7 | 8 | // TODO: consider requiring a background thread per store? 9 | 10 | /// /* 11 | /// * Class: net_bluejekyll_wasmtime_WasmStore 12 | /// * Method: freeStore 13 | /// * Signature: (J)V 14 | /// */ 15 | /// JNIEXPORT void JNICALL Java_net_bluejekyll_wasmtime_WasmStore_freeStore 16 | /// (JNIEnv *, jclass, jlong); 17 | #[no_mangle] 18 | pub extern "system" fn Java_net_bluejekyll_wasmtime_WasmStore_freeStore<'j>( 19 | _env: JNIEnv<'j>, 20 | _class: JClass<'j>, 21 | store: OpaquePtr<'j, Store>, 22 | ) { 23 | drop(store.take()); 24 | } 25 | -------------------------------------------------------------------------------- /wasmtime-jni/src/wasm_value.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fmt; 3 | use std::marker::PhantomData; 4 | use std::slice; 5 | 6 | use anyhow::{anyhow, Context, Error}; 7 | use jni::objects::{JClass, JObject, JString, JValue, ReleaseMode}; 8 | use jni::sys::jbyteArray; 9 | use jni::JNIEnv; 10 | use log::debug; 11 | use wasmtime::{AsContextMut, Store, Val, ValType}; 12 | use wasmtime_jni_exports::WasmAllocated; 13 | 14 | use crate::ty::{Abi, ReturnAbi, WasmAlloc, WasmSlice, WasmSliceWrapper}; 15 | use crate::wasm_state::JavaState; 16 | 17 | const CLASS: &str = "Ljava/lang/Class;"; 18 | const I64: &str = "net/bluejekyll/wasmtime/ty/I64"; 19 | const I32: &str = "net/bluejekyll/wasmtime/ty/I32"; 20 | const F64: &str = "net/bluejekyll/wasmtime/ty/F64"; 21 | const F32: &str = "net/bluejekyll/wasmtime/ty/F32"; 22 | const WASM_VOID: &str = "net/bluejekyll/wasmtime/ty/WasmVoid"; 23 | const VOID: &str = "java/lang/Void"; 24 | const STRING: &str = "java/lang/String"; 25 | const BYTE_ARRAY: &str = "[B"; 26 | const PRIMITIVE: &str = "TYPE"; 27 | 28 | const BYTE_BUFFER: &str = "java/nio/ByteBuffer"; 29 | 30 | pub(crate) fn get_class_name_obj<'j>(env: &JNIEnv<'j>, obj: JObject<'j>) -> Result { 31 | get_class_name(env, env.get_object_class(obj)?) 32 | } 33 | 34 | pub(crate) fn get_class_name<'j>(env: &JNIEnv<'j>, clazz: JClass<'j>) -> Result { 35 | let name = env.call_method(clazz, "getCanonicalName", "()Ljava/lang/String;", &[])?; 36 | let name = name.l()?; 37 | let name = JString::from(name); 38 | let name = env.get_string(name)?; 39 | Ok(Cow::from(&name).to_string()) 40 | } 41 | 42 | #[derive(Clone, Debug)] 43 | pub(crate) enum WasmTy { 44 | ByteBuffer, 45 | ByteArray, 46 | String, 47 | ValType(ValType), 48 | } 49 | 50 | impl From for WasmTy { 51 | fn from(val: ValType) -> Self { 52 | WasmTy::ValType(val) 53 | } 54 | } 55 | 56 | impl WasmTy { 57 | pub fn push_arg_tys(&self, args: &mut Vec) { 58 | match self { 59 | WasmTy::ByteBuffer | WasmTy::ByteArray | WasmTy::String => { 60 | WasmSlice::push_arg_tys(args) 61 | } 62 | WasmTy::ValType(ValType::I32) => i32::push_arg_tys(args), 63 | WasmTy::ValType(ValType::I64) => i64::push_arg_tys(args), 64 | WasmTy::ValType(ValType::F32) => f32::push_arg_tys(args), 65 | WasmTy::ValType(ValType::F64) => f64::push_arg_tys(args), 66 | WasmTy::ValType(_) => unimplemented!(), 67 | } 68 | } 69 | 70 | pub fn matches_arg_tys(&self, tys: impl Iterator) -> anyhow::Result<()> { 71 | match self { 72 | WasmTy::ByteBuffer | WasmTy::ByteArray | WasmTy::String => { 73 | WasmSlice::matches_arg_tys(tys) 74 | } 75 | WasmTy::ValType(ValType::I32) => i32::matches_arg_tys(tys), 76 | WasmTy::ValType(ValType::I64) => i64::matches_arg_tys(tys), 77 | WasmTy::ValType(ValType::F32) => f32::matches_arg_tys(tys), 78 | WasmTy::ValType(ValType::F64) => f64::matches_arg_tys(tys), 79 | WasmTy::ValType(_) => unimplemented!(), 80 | } 81 | } 82 | 83 | pub fn get_return_by_ref_arg(&self, args: impl Iterator) -> Option { 84 | match self { 85 | WasmTy::ByteBuffer | WasmTy::ByteArray | WasmTy::String => { 86 | WasmSlice::get_return_by_ref_arg(args) 87 | } 88 | WasmTy::ValType(ValType::I32) => i32::get_return_by_ref_arg(args), 89 | WasmTy::ValType(ValType::I64) => i64::get_return_by_ref_arg(args), 90 | WasmTy::ValType(ValType::F32) => f32::get_return_by_ref_arg(args), 91 | WasmTy::ValType(ValType::F64) => f64::get_return_by_ref_arg(args), 92 | WasmTy::ValType(_) => unimplemented!(), 93 | } 94 | } 95 | 96 | pub(crate) unsafe fn load_from_args<'j>( 97 | &self, 98 | env: &JNIEnv<'j>, 99 | args: impl Iterator, 100 | wasm_alloc: Option<&WasmAlloc>, 101 | store: impl AsContextMut, 102 | ) -> Result, anyhow::Error> { 103 | match self { 104 | WasmTy::ByteBuffer => { 105 | // we do not want to deallocate here... 106 | let wasm_slice = WasmSlice::load_from_args(args)?; 107 | IntoByteBuffer(wasm_slice).into_java(env, wasm_alloc, store) 108 | } 109 | WasmTy::ByteArray => { 110 | let wasm_slice = WasmSlice::load_from_args(args)?; 111 | IntoByteArray(wasm_slice).into_java(env, wasm_alloc, store) 112 | } 113 | WasmTy::String => { 114 | let wasm_slice = WasmSlice::load_from_args(args)?; 115 | IntoString(wasm_slice).into_java(env, wasm_alloc, store) 116 | } 117 | WasmTy::ValType(ValType::I32) => { 118 | i32::load_from_args(args)?.into_java(env, wasm_alloc, store) 119 | } 120 | WasmTy::ValType(ValType::I64) => { 121 | i64::load_from_args(args)?.into_java(env, wasm_alloc, store) 122 | } 123 | WasmTy::ValType(ValType::F32) => { 124 | f32::load_from_args(args)?.into_java(env, wasm_alloc, store) 125 | } 126 | WasmTy::ValType(ValType::F64) => { 127 | f64::load_from_args(args)?.into_java(env, wasm_alloc, store) 128 | } 129 | WasmTy::ValType(_) => unimplemented!(), 130 | } 131 | } 132 | 133 | pub(crate) fn return_or_push_arg_tys(&self, args: &mut Vec) -> Option { 134 | match self { 135 | WasmTy::ByteBuffer | WasmTy::ByteArray | WasmTy::String => { 136 | WasmSlice::return_or_push_arg_tys(args) 137 | } 138 | WasmTy::ValType(ValType::I32) => i32::return_or_push_arg_tys(args), 139 | WasmTy::ValType(ValType::I64) => i64::return_or_push_arg_tys(args), 140 | WasmTy::ValType(ValType::F32) => f32::return_or_push_arg_tys(args), 141 | WasmTy::ValType(ValType::F64) => f64::return_or_push_arg_tys(args), 142 | WasmTy::ValType(_) => unimplemented!(), 143 | } 144 | } 145 | 146 | /// Matches the return type or the arg tys 147 | #[allow(unused)] 148 | pub(crate) fn matches_return_or_arg_tys( 149 | &self, 150 | ret: Option, 151 | arg_tys: impl Iterator, 152 | ) -> Result<(), anyhow::Error> { 153 | match self { 154 | WasmTy::ByteBuffer | WasmTy::ByteArray | WasmTy::String => { 155 | WasmSlice::matches_return_or_arg_tys(ret, arg_tys) 156 | } 157 | WasmTy::ValType(ValType::I32) => i32::matches_return_or_arg_tys(ret, arg_tys), 158 | WasmTy::ValType(ValType::I64) => i64::matches_return_or_arg_tys(ret, arg_tys), 159 | WasmTy::ValType(ValType::F32) => f32::matches_return_or_arg_tys(ret, arg_tys), 160 | WasmTy::ValType(ValType::F64) => f64::matches_return_or_arg_tys(ret, arg_tys), 161 | WasmTy::ValType(_) => unimplemented!(), 162 | } 163 | } 164 | 165 | /// Place the values in the argument list 166 | pub(crate) fn return_or_store_to_arg<'w>( 167 | self, 168 | args: &mut Vec, 169 | wasm_alloc: Option<&'w WasmAlloc>, 170 | store: &mut Store, 171 | ) -> Result>, Error> { 172 | match self { 173 | WasmTy::ByteBuffer | WasmTy::ByteArray | WasmTy::String => { 174 | WasmSlice::return_or_store_to_arg(args, wasm_alloc, store) 175 | } 176 | WasmTy::ValType(ValType::I32) => i32::return_or_store_to_arg(args, wasm_alloc, store), 177 | WasmTy::ValType(ValType::I64) => i64::return_or_store_to_arg(args, wasm_alloc, store), 178 | WasmTy::ValType(ValType::F32) => f32::return_or_store_to_arg(args, wasm_alloc, store), 179 | WasmTy::ValType(ValType::F64) => f64::return_or_store_to_arg(args, wasm_alloc, store), 180 | WasmTy::ValType(_) => unimplemented!(), 181 | } 182 | } 183 | } 184 | 185 | impl fmt::Display for WasmTy { 186 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 187 | match self { 188 | WasmTy::ByteBuffer => write!(f, "ByteBuffer"), 189 | WasmTy::ByteArray => write!(f, "byte[]"), 190 | WasmTy::String => write!(f, "String"), 191 | WasmTy::ValType(val) => val.fmt(f), 192 | } 193 | } 194 | } 195 | 196 | pub(crate) enum WasmVal<'j> { 197 | // TODO: go back and review usage of ByteBuffer... need more thought here. 198 | // ByteBuffer(JByteBuffer<'j>), 199 | ByteArray { 200 | jarray: jbyteArray, 201 | lifetime: PhantomData<&'j ()>, 202 | }, 203 | String(JString<'j>), 204 | Val(Val), 205 | } 206 | 207 | impl<'j> fmt::Debug for WasmVal<'j> { 208 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 209 | match self { 210 | // WasmVal::ByteBuffer(_) => write!(f, "ByteBuffer"), 211 | WasmVal::ByteArray { .. } => write!(f, "byte[]"), 212 | WasmVal::String(_) => write!(f, "String"), 213 | WasmVal::Val(val) => val.fmt(f), 214 | } 215 | } 216 | } 217 | 218 | impl<'j> WasmVal<'j> { 219 | fn from_byte_array(_env: &JNIEnv<'j>, jarray: jbyteArray) -> Self { 220 | WasmVal::ByteArray { 221 | jarray, 222 | lifetime: PhantomData, 223 | } 224 | } 225 | 226 | pub fn ty(&self) -> WasmTy { 227 | match self { 228 | // WasmVal::ByteBuffer(_) => WasmTy::ByteBuffer, 229 | WasmVal::ByteArray { .. } => WasmTy::ByteArray, 230 | WasmVal::String(_) => WasmTy::String, 231 | WasmVal::Val(val) => val.ty().into(), 232 | } 233 | } 234 | 235 | pub fn store_to_args<'w, S: AsContextMut>( 236 | self, 237 | env: &JNIEnv<'j>, 238 | args: &mut Vec, 239 | wasm_alloc: Option<&'w WasmAlloc>, 240 | mut store: S, 241 | ) -> Result>, anyhow::Error> { 242 | match self { 243 | // WasmVal::ByteBuffer(buffer) => { 244 | // let direct_bytes: &[u8] = env.get_direct_buffer_address(buffer)?; 245 | 246 | // // the module might not have the memory exported 247 | // let wasm_alloc = wasm_alloc 248 | // .ok_or_else(|| anyhow!("no memory or allocator supplied from module"))?; 249 | // let wasm_slice = wasm_alloc.alloc_bytes(direct_bytes)?; 250 | // wasm_slice.store_to_args(args); 251 | // return Ok(Some(wasm_slice)); 252 | // } 253 | WasmVal::ByteArray { jarray, .. } => { 254 | // This is should be safe, it's copied into while borrowed the WASM context. 255 | let len = env.get_array_length(jarray)?; 256 | let jbytes = env.get_byte_array_elements(jarray, ReleaseMode::CopyBack)?; 257 | let byte_array: &[u8] = 258 | unsafe { slice::from_raw_parts(jbytes.as_ptr() as *const u8, len as usize) }; 259 | 260 | // the module might not have the memory exported 261 | let wasm_alloc = wasm_alloc 262 | .ok_or_else(|| anyhow!("no memory or allocator supplied from module"))?; 263 | let wasm_slice = wasm_alloc.alloc_bytes(byte_array, &mut store)?; 264 | 265 | wasm_slice.store_to_args(args); 266 | return Ok(Some(wasm_slice)); 267 | } 268 | WasmVal::String(string) => { 269 | // This is should be safe, it's copied into while borrowed the WASM context. 270 | 271 | let jstr = env.get_string(string)?; 272 | let cow = Cow::from(&jstr); 273 | debug!("String from Java going to WASM: {}", cow); 274 | 275 | let cow_bytes = cow.as_bytes(); 276 | 277 | // the module might not have the memory exported 278 | let wasm_alloc = wasm_alloc 279 | .ok_or_else(|| anyhow!("no memory or allocator supplied from module"))?; 280 | let wasm_slice = wasm_alloc.alloc_bytes(cow_bytes, store)?; 281 | 282 | // release the array reference, CopyBack is following the JNI guidelines 283 | wasm_slice.store_to_args(args); 284 | return Ok(Some(wasm_slice)); 285 | } 286 | WasmVal::Val(val @ Val::I32(_)) => val.unwrap_i32().store_to_args(args), 287 | WasmVal::Val(val @ Val::I64(_)) => val.unwrap_i64().store_to_args(args), 288 | WasmVal::Val(val @ Val::F32(_)) => val.unwrap_f32().store_to_args(args), 289 | WasmVal::Val(val @ Val::F64(_)) => val.unwrap_f64().store_to_args(args), 290 | WasmVal::Val(v) => unimplemented!("type not yet supported as an arg: {:?}", v), 291 | } 292 | 293 | Ok(None) 294 | } 295 | } 296 | 297 | // impl<'j> From> for WasmVal<'j> { 298 | // fn from(bytes: JByteBuffer<'j>) -> Self { 299 | // WasmVal::ByteBuffer(bytes) 300 | // } 301 | // } 302 | 303 | impl<'j> From> for WasmVal<'j> { 304 | fn from(string: JString<'j>) -> Self { 305 | WasmVal::String(string) 306 | } 307 | } 308 | 309 | impl From for WasmVal<'_> { 310 | fn from(val: Val) -> Self { 311 | WasmVal::Val(val) 312 | } 313 | } 314 | 315 | trait IntoJavaObject { 316 | unsafe fn into_java<'j, S: AsContextMut>( 317 | self, 318 | env: &JNIEnv<'j>, 319 | wasm_alloc: Option<&WasmAlloc>, 320 | store: S, 321 | ) -> Result, Error>; 322 | } 323 | 324 | struct IntoByteBuffer(WasmSlice); 325 | impl IntoJavaObject for IntoByteBuffer { 326 | unsafe fn into_java<'j, S: AsContextMut>( 327 | self, 328 | env: &JNIEnv<'j>, 329 | wasm_alloc: Option<&WasmAlloc>, 330 | mut store: S, 331 | ) -> Result, Error> { 332 | let wasm_alloc = 333 | wasm_alloc.ok_or_else(|| anyhow!("WasmAlloc is required for this return type"))?; 334 | let bytes = wasm_alloc.as_mut(self.0, &mut store); 335 | 336 | debug!( 337 | "length of bytes for ByteBuffer: {} expected len: {}", 338 | bytes.len(), 339 | self.0.len() 340 | ); 341 | debug!("read bytes from wasm_slice: {:x?}", bytes); 342 | 343 | let buffer = env 344 | .new_direct_byte_buffer(bytes) 345 | .context("Failed to create new ByteBuffer")?; 346 | 347 | Ok(buffer.into()) 348 | } 349 | } 350 | 351 | struct IntoByteArray(WasmSlice); 352 | impl IntoJavaObject for IntoByteArray { 353 | unsafe fn into_java<'j, S: AsContextMut>( 354 | self, 355 | env: &JNIEnv<'j>, 356 | wasm_alloc: Option<&WasmAlloc>, 357 | mut store: S, 358 | ) -> Result, Error> { 359 | let wasm_alloc = 360 | wasm_alloc.ok_or_else(|| anyhow!("WasmAlloc is required for this return type"))?; 361 | let bytes = wasm_alloc.as_mut(self.0, &mut store); 362 | 363 | debug!( 364 | "length of bytes for ByteBuffer: {} expected len: {}", 365 | bytes.len(), 366 | self.0.len() 367 | ); 368 | debug!("read bytes from wasm_slice: {:x?}", bytes); 369 | 370 | let buffer = env 371 | .byte_array_from_slice(bytes) 372 | .context("Failed to create new byte[]")?; 373 | 374 | Ok(buffer.into()) 375 | } 376 | } 377 | 378 | struct IntoString(WasmSlice); 379 | impl IntoJavaObject for IntoString { 380 | unsafe fn into_java<'j, S: AsContextMut>( 381 | self, 382 | env: &JNIEnv<'j>, 383 | wasm_alloc: Option<&WasmAlloc>, 384 | mut store: S, 385 | ) -> Result, Error> { 386 | let wasm_alloc = 387 | wasm_alloc.ok_or_else(|| anyhow!("WasmAlloc is required for this return type"))?; 388 | let bytes = wasm_alloc.as_mut(self.0, &mut store); 389 | 390 | debug!( 391 | "length of bytes for ByteBuffer: {} expected len: {}", 392 | bytes.len(), 393 | self.0.len() 394 | ); 395 | debug!("read bytes from wasm_slice: {:x?}", bytes); 396 | 397 | // TODO: document that we expect utf8 bytes here... 398 | let string = String::from_utf8_lossy(bytes); 399 | let string = env 400 | .new_string(string) 401 | .context("Failed to create new JString")?; 402 | 403 | Ok(string.into()) 404 | } 405 | } 406 | 407 | impl IntoJavaObject for i64 { 408 | unsafe fn into_java<'j, S: AsContextMut>( 409 | self, 410 | env: &JNIEnv<'j>, 411 | _wasm_alloc: Option<&WasmAlloc>, 412 | _store: S, 413 | ) -> Result, Error> { 414 | let jvalue = JValue::Long(self); 415 | env.new_object(I64, "(J)V", &[jvalue]) 416 | .context("Failed to create new Long") 417 | } 418 | } 419 | 420 | impl IntoJavaObject for i32 { 421 | unsafe fn into_java<'j, S: AsContextMut>( 422 | self, 423 | env: &JNIEnv<'j>, 424 | _wasm_alloc: Option<&WasmAlloc>, 425 | _store: S, 426 | ) -> Result, Error> { 427 | let jvalue = JValue::Int(self); 428 | env.new_object(I32, "(I)V", &[jvalue]) 429 | .context("Failed to create new Integer") 430 | } 431 | } 432 | 433 | impl IntoJavaObject for f64 { 434 | unsafe fn into_java<'j, S: AsContextMut>( 435 | self, 436 | env: &JNIEnv<'j>, 437 | _wasm_alloc: Option<&WasmAlloc>, 438 | _store: S, 439 | ) -> Result, Error> { 440 | let jvalue = JValue::Double(self); 441 | env.new_object(F64, "(D)V", &[jvalue]) 442 | .context("Failed to create new Double") 443 | } 444 | } 445 | 446 | impl IntoJavaObject for f32 { 447 | unsafe fn into_java<'j, S: AsContextMut>( 448 | self, 449 | env: &JNIEnv<'j>, 450 | _wasm_alloc: Option<&WasmAlloc>, 451 | _store: S, 452 | ) -> Result, Error> { 453 | let jvalue = JValue::Float(self); 454 | env.new_object(F32, "(F)V", &[jvalue]) 455 | .context("Failed to create new Float") 456 | } 457 | } 458 | 459 | pub(crate) fn from_java_class<'j>( 460 | env: &JNIEnv<'j>, 461 | clazz: JClass<'j>, 462 | for_return: bool, 463 | ) -> Result, Error> { 464 | if clazz.is_null() { 465 | return Ok(None); // FIXME: this should be an exception, right? 466 | } 467 | 468 | let voidp: JClass = env.get_static_field(VOID, PRIMITIVE, CLASS)?.l()?.into(); 469 | 470 | let ty: WasmTy = match clazz { 471 | _ if env.is_assignable_from(clazz, I64)? => ValType::I64.into(), 472 | _ if env.is_assignable_from(clazz, I32)? => ValType::I32.into(), 473 | _ if env.is_assignable_from(clazz, F64)? => ValType::F64.into(), 474 | _ if env.is_assignable_from(clazz, F32)? => ValType::F32.into(), 475 | _ if env.is_assignable_from(clazz, WASM_VOID)? => return Ok(None), 476 | _ if env.is_assignable_from(clazz, VOID)? => return Ok(None), 477 | _ if env.is_assignable_from(clazz, voidp)? => return Ok(None), 478 | _ => { 479 | let name = get_class_name(env, clazz)?; 480 | if !for_return { 481 | return Err(anyhow!("Unsupported Java class as argument: {}", name)); 482 | } else { 483 | return Err(anyhow!("Unsupported Java class as result: {}", name)); 484 | } 485 | } 486 | }; 487 | 488 | Ok(Some(ty)) 489 | } 490 | 491 | pub(crate) fn from_java<'j, 'b: 'j>( 492 | env: &'b JNIEnv<'j>, 493 | obj: JObject<'j>, 494 | ) -> Result, Error> { 495 | //let bytea: JClass = env.find_class("[B")?; 496 | 497 | assert!(!obj.is_null(), "obj should not be null for conversion"); 498 | match obj { 499 | _ if env.is_instance_of(obj, I64)? => { 500 | let jvalue = env.call_method(obj, "longValue", "()J", &[])?; 501 | Ok(Val::I64(jvalue.j()?).into()) 502 | } 503 | _ if env.is_instance_of(obj, I32)? => { 504 | let jvalue = env.call_method(obj, "intValue", "()I", &[])?; 505 | Ok(Val::I32(jvalue.i()?).into()) 506 | } 507 | _ if env.is_instance_of(obj, F64)? => { 508 | let jvalue = env.call_method(obj, "doubleValue", "()D", &[])?; 509 | Ok(Val::F64(jvalue.d()?.to_bits()).into()) 510 | } 511 | _ if env.is_instance_of(obj, F32)? => { 512 | let jvalue = env.call_method(obj, "floatValue", "()F", &[])?; 513 | Ok(Val::F32(jvalue.f()?.to_bits()).into()) 514 | } 515 | // _ if env.is_instance_of(obj, BYTE_BUFFER)? => Ok(WasmVal::from(JByteBuffer::from(obj))), 516 | // _ if env.is_instance_of(obj, BYTE_ARRAY)? => Ok(WasmVal::from_byte_array(env, *obj)), 517 | // _ if env.is_instance_of(obj, STRING)? => Ok(WasmVal::from(JString::from(obj))), 518 | _ => { 519 | let clazz = env.get_object_class(obj)?; 520 | let name = get_class_name(env, clazz)?; 521 | Err(anyhow!("Unsupported Java object: {}", name)) 522 | } 523 | } 524 | } 525 | 526 | pub(crate) fn from_jvalue<'j, 'b: 'j>( 527 | env: &'b JNIEnv<'j>, 528 | val: JValue<'j>, 529 | ) -> Result>, Error> { 530 | let val = match val { 531 | JValue::Object(obj) => { 532 | if obj.is_null() { 533 | return Ok(None); 534 | } else { 535 | return from_java(env, obj).map(Some); 536 | } 537 | } 538 | JValue::Long(v) => Val::I64(v), 539 | JValue::Int(v) => Val::I32(v), 540 | JValue::Double(v) => Val::F64(f64::to_bits(v)), 541 | JValue::Float(v) => Val::F32(f32::to_bits(v)), 542 | _ => return Err(anyhow!("Unsuppored return type: {}", val.type_name())), 543 | }; 544 | 545 | Ok(Some(val.into())) 546 | } 547 | 548 | // pub(crate) fn to_java<'j, 'w: 'j>(env: &JNIEnv<'j>, val: &'w Val) -> Result, Error> { 549 | // let obj = match val { 550 | // Val::I64(val) => { 551 | // let jvalue = JValue::Long(*val); 552 | // env.new_object(LONG, "(J)V", &[jvalue]) 553 | // } 554 | // Val::I32(val) => { 555 | // let jvalue = JValue::Int(*val); 556 | // env.new_object(INTEGER, "(I)V", &[jvalue]) 557 | // } 558 | // Val::F64(val) => { 559 | // let jvalue = JValue::Double(f64::from_bits(*val)); 560 | // env.new_object(DOUBLE, "(D)V", &[jvalue]) 561 | // } 562 | // Val::F32(val) => { 563 | // let jvalue = JValue::Float(f32::from_bits(*val)); 564 | // env.new_object(FLOAT, "(F)V", &[jvalue]) 565 | // } 566 | // _ => return Err(anyhow!("Unsupported WASM type: {}", val.ty())), 567 | // }; 568 | 569 | // obj.with_context(|| format!("failed to convert {:?} to java", val)) 570 | // } 571 | 572 | pub(crate) unsafe fn return_or_load_or_from_arg<'a, 'w>( 573 | env: &JNIEnv<'a>, 574 | ty: WasmTy, 575 | ret: Option<&Val>, 576 | ret_by_ref_ptr: Option>, 577 | wasm_alloc: Option<&'w WasmAlloc>, 578 | store: &mut Store, 579 | ) -> Result, anyhow::Error> { 580 | match ty { 581 | WasmTy::ByteBuffer => Err(anyhow!("ByteBuffer unsupported as return Java return type")), 582 | // WasmTy::ByteBuffer => { 583 | // let wasm_slice = 584 | // WasmSlice::return_or_load_or_from_args(ret, ret_by_ref_ptr, wasm_alloc)?; 585 | // // we need to drop this returned slice... 586 | // // TODO: would be nice to do this in the return_or_load_or_from_args, move that to WasmSliceWrapper? 587 | // let wasm_slice = WasmSliceWrapper::new( 588 | // wasm_alloc.ok_or_else(|| anyhow!("WasmAlloc required"))?, 589 | // wasm_slice, 590 | // ); 591 | // IntoByteBuffer(wasm_slice.wasm_slice()).into_java(env, wasm_alloc) 592 | // } 593 | WasmTy::ByteArray => { 594 | let wasm_slice = 595 | WasmSlice::return_or_load_or_from_args(ret, ret_by_ref_ptr, wasm_alloc, store)?; 596 | let wasm_slice = WasmSliceWrapper::new( 597 | wasm_alloc.ok_or_else(|| anyhow!("WasmAlloc required"))?, 598 | wasm_slice, 599 | ); 600 | IntoByteArray(wasm_slice.wasm_slice()).into_java(env, wasm_alloc, store) 601 | } 602 | WasmTy::String => { 603 | let wasm_slice = 604 | WasmSlice::return_or_load_or_from_args(ret, ret_by_ref_ptr, wasm_alloc, store)?; 605 | let wasm_slice = WasmSliceWrapper::new( 606 | wasm_alloc.ok_or_else(|| anyhow!("WasmAlloc required"))?, 607 | wasm_slice, 608 | ); 609 | IntoString(wasm_slice.wasm_slice()).into_java(env, wasm_alloc, store) 610 | } 611 | WasmTy::ValType(ValType::I32) => { 612 | i32::return_or_load_or_from_args(ret, ret_by_ref_ptr, wasm_alloc, store)? 613 | .into_java(env, wasm_alloc, store) 614 | } 615 | WasmTy::ValType(ValType::I64) => { 616 | i64::return_or_load_or_from_args(ret, ret_by_ref_ptr, wasm_alloc, store)? 617 | .into_java(env, wasm_alloc, store) 618 | } 619 | WasmTy::ValType(ValType::F32) => { 620 | f32::return_or_load_or_from_args(ret, ret_by_ref_ptr, wasm_alloc, store)? 621 | .into_java(env, wasm_alloc, store) 622 | } 623 | WasmTy::ValType(ValType::F64) => { 624 | f64::return_or_load_or_from_args(ret, ret_by_ref_ptr, wasm_alloc, store)? 625 | .into_java(env, wasm_alloc, store) 626 | } 627 | WasmTy::ValType(v) => Err(anyhow!("{} unsupported as return Java return type", v)), 628 | } 629 | } 630 | -------------------------------------------------------------------------------- /wasmtime-jni/src/wasmtime.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | 3 | //use env_logger::{self, Target}; 4 | use flexi_logger::{opt_format, Logger}; 5 | use jni::objects::JClass; 6 | use jni::sys::{jint, jlong, JavaVM, JNI_VERSION_1_8}; 7 | use jni::JNIEnv; 8 | use log::info; 9 | use wasmtime::Engine; 10 | 11 | use crate::opaque_ptr::OpaquePtr; 12 | 13 | /// Optional function defined by dynamically linked libraries. The VM calls JNI_OnLoad when the native library is loaded (for example, through System.loadLibrary). 14 | /// 15 | /// In order to make use of functions defined at a certain version of the JNI API, JNI_OnLoad must return a constant defining at least that version. For example, libraries wishing to use AttachCurrentThreadAsDaemon function introduced in JDK 1.4, need to return at least JNI_VERSION_1_4. If the native library does not export a JNI_OnLoad function, the VM assumes that the library only requires JNI version JNI_VERSION_1_1. If the VM does not recognize the version number returned by JNI_OnLoad, the VM will unload the library and act as if the library was never loaded. 16 | /// LINKAGE: 17 | /// 18 | /// Exported from dynamically linked native libraries that contain native method implementations. 19 | /// PARAMETERS: 20 | /// 21 | /// vm: a pointer to the current VM structure. 22 | /// 23 | /// reserved: unused pointer. 24 | /// RETURNS: 25 | /// 26 | /// Return the required JNI_VERSION constant (see also GetVersion). 27 | /// jint JNI_OnLoad(JavaVM *vm, void *reserved); 28 | #[no_mangle] 29 | pub extern "system" fn JNI_OnLoad(_vm: JavaVM, _reserved: *mut c_void) -> jint { 30 | Logger::with_env_or_str("wasmtime=info,wasmtime-jni=info") 31 | .log_to_file() 32 | .directory("target/wasm-logs") 33 | .format(opt_format) 34 | .start() 35 | .ok(); 36 | 37 | // env_logger::builder().target(Target::Stdout).init(); 38 | 39 | info!("wasmtime JNI loaded"); 40 | JNI_VERSION_1_8 41 | } 42 | 43 | /// /* 44 | /// * Class: net_bluejekyll_wasmtime_Wasmtime 45 | /// * Method: newWasmEngineNtv 46 | /// * Signature: ()J 47 | /// */ 48 | /// JNIEXPORT jlong JNICALL Java_net_bluejekyll_wasmtime_Wasmtime_newWasmEngineNtv 49 | /// (JNIEnv *, jclass); 50 | #[no_mangle] 51 | pub extern "system" fn Java_net_bluejekyll_wasmtime_Wasmtime_newWasmEngineNtv( 52 | _env: JNIEnv, 53 | _input: JClass, 54 | ) -> jlong { 55 | info!("wasmtime-jni: getting engine"); 56 | 57 | // Box it... 58 | let engine = Engine::default(); 59 | OpaquePtr::from(engine).make_opaque() 60 | } 61 | --------------------------------------------------------------------------------