From c64c2d09a2677f9596c6ff04518aeaad83436140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heinrichs?= Date: Sun, 8 May 2022 15:04:05 +0200 Subject: [PATCH 1/4] Implement Accessing Export Memory --- .../io/github/kawamuray/wasmtime/Engine.java | 9 + .../github/kawamuray/wasmtime/ExportType.java | 55 ++++++ .../io/github/kawamuray/wasmtime/Func.java | 47 +++++- .../io/github/kawamuray/wasmtime/Module.java | 2 + .../io/github/kawamuray/wasmtime/Store.java | 41 +++++ .../github/kawamuray/wasmtime/ModuleTest.java | 129 ++++++++++++++ .../imp.rs | 158 +++++++++++------- .../mod.rs | 13 ++ 8 files changed, 387 insertions(+), 67 deletions(-) create mode 100644 src/main/java/io/github/kawamuray/wasmtime/ExportType.java diff --git a/src/main/java/io/github/kawamuray/wasmtime/Engine.java b/src/main/java/io/github/kawamuray/wasmtime/Engine.java index 2a61dd3..a802808 100644 --- a/src/main/java/io/github/kawamuray/wasmtime/Engine.java +++ b/src/main/java/io/github/kawamuray/wasmtime/Engine.java @@ -6,6 +6,15 @@ import lombok.Getter; import lombok.experimental.Accessors; +/** + * An Engine which is a global context for compilation and management of wasm modules. + *

+ * An engine can be safely shared across threads and is a cheap cloneable handle to the actual engine. + * The engine itself will be deallocated once all references to it have gone away. + *

+ * Engines store global configuration preferences such as compilation settings, enabled features, etc. + * You'll likely only need at most one of these for a program. + */ @Accessors(fluent = true) @EqualsAndHashCode @AllArgsConstructor(access = AccessLevel.PACKAGE) diff --git a/src/main/java/io/github/kawamuray/wasmtime/ExportType.java b/src/main/java/io/github/kawamuray/wasmtime/ExportType.java new file mode 100644 index 0000000..f487ed0 --- /dev/null +++ b/src/main/java/io/github/kawamuray/wasmtime/ExportType.java @@ -0,0 +1,55 @@ +package io.github.kawamuray.wasmtime; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.experimental.Accessors; + +@Accessors(fluent = true) +@AllArgsConstructor(access = AccessLevel.PACKAGE) +public class ExportType { + public enum Type { + FUNC, + GLOBAL, + TABLE, + MEMORY, + // TODO: Currently Unsupported + INSTANCE, + MODULE; + } + @Getter + private final Type type; + + @Getter(AccessLevel.PACKAGE) + private final Object typeObj; + + @Getter + private final String name; + + private void ensureType(Type expected) { + if (type != expected) { + throw new RuntimeException( + String.format("ImportType expected to have type %s but is actually %s", expected, type)); + } + } + + public FuncType func() { + ensureType(ExportType.Type.FUNC); + return (FuncType) typeObj; + } + + public GlobalType global() { + ensureType(ExportType.Type.GLOBAL); + return (GlobalType) typeObj; + } + + public MemoryType memory() { + ensureType(ExportType.Type.MEMORY); + return (MemoryType) typeObj; + } + + public TableType table() { + ensureType(ExportType.Type.TABLE); + return (TableType) typeObj; + } +} diff --git a/src/main/java/io/github/kawamuray/wasmtime/Func.java b/src/main/java/io/github/kawamuray/wasmtime/Func.java index c1aa1ff..8526091 100644 --- a/src/main/java/io/github/kawamuray/wasmtime/Func.java +++ b/src/main/java/io/github/kawamuray/wasmtime/Func.java @@ -1,16 +1,53 @@ package io.github.kawamuray.wasmtime; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Optional; - import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * A WebAssembly function which can be called. + * + * This type can represent either an exported function from a WebAssembly module or a host-defined function which can + * be used to satisfy an import of a module. Func and can be used to both instantiate an Instance as well as be + * extracted from an Instance. + * + * A Func "belongs" to the store that it was originally created within. Operations on a Func only work with the store + * it belongs to, and if another store is passed in by accident then methods will panic. + * + *

Func and async

+ * Functions from the perspective of WebAssembly are always synchronous. You might have an async function in Rust, + * however, which you’d like to make available from WebAssembly. Wasmtime supports asynchronously calling WebAssembly + * through native stack switching. You can get some more information about asynchronous configs, but from the + * perspective of Func it’s important to know that whether or not your Store is asynchronous will dictate whether you + * call functions through Func::call or Func::call_async (or the typed wrappers such as TypedFunc::call vs + * TypedFunc::call_async). + * + *

To Func::call or to Func::typed().call()

+ * There’s a 2x2 matrix of methods to call Func. Invocations can either be asynchronous or synchronous. + * They can also be statically typed or not. Whether or not an invocation is asynchronous is indicated via the method + * being async and call_async being the entry point. Otherwise for statically typed or not your options are: + * + * Dynamically typed - if you don’t statically know the signature of the function that you’re calling you’ll be + * using Func::call or Func::call_async. These functions take a variable-length slice of "boxed" arguments + * in their Val representation. Additionally the results are returned as an owned slice of Val. These methods + * are not optimized due to the dynamic type checks that must occur, in addition to some dynamic allocations + * for where to put all the arguments. While this allows you to call all possible wasm function signatures, + * if you’re looking for a speedier alternative you can also use… + * + * Statically typed - if you statically know the type signature of the wasm function you’re calling, then you’ll + * want to use the Func::typed method to acquire an instance of TypedFunc. This structure is static proof + * that the underlying wasm function has the ascripted type, and type validation is only done once up-front. + * The TypedFunc::call and TypedFunc::call_async methods are much more efficient than Func::call and + * Func::call_async because the type signature is statically known. This eschews runtime checks as much as + * possible to get into wasm as fast as possible. + */ @Slf4j @Accessors(fluent = true) @AllArgsConstructor(access = AccessLevel.PACKAGE) diff --git a/src/main/java/io/github/kawamuray/wasmtime/Module.java b/src/main/java/io/github/kawamuray/wasmtime/Module.java index 4e4cbdc..72634c8 100644 --- a/src/main/java/io/github/kawamuray/wasmtime/Module.java +++ b/src/main/java/io/github/kawamuray/wasmtime/Module.java @@ -25,6 +25,8 @@ public static Module fromBinary(Engine engine, byte[] bytes) { public native ImportType[] imports(); + public native ExportType[] exports(); + @Override public native void dispose(); diff --git a/src/main/java/io/github/kawamuray/wasmtime/Store.java b/src/main/java/io/github/kawamuray/wasmtime/Store.java index f4a34fc..f091eec 100644 --- a/src/main/java/io/github/kawamuray/wasmtime/Store.java +++ b/src/main/java/io/github/kawamuray/wasmtime/Store.java @@ -6,6 +6,47 @@ import lombok.Getter; import lombok.experimental.Accessors; +/** + * A Store is a collection of WebAssembly instances and host-defined state. + *

+ * All WebAssembly instances and items will be attached to and refer to a Store. For example instances, functions, + * globals, and tables are all attached to a Store. Instances are created by instantiating a Module within a Store. + *

+ * A Store is intended to be a short-lived object in a program. No form of GC is implemented at this time so once an + * instance is created within a Store it will not be deallocated until the Store itself is dropped. This makes Store + * unsuitable for creating an unbounded number of instances in it because Store will never release this memory. It’s + * recommended to have a Store correspond roughly to the lifetime of a "main instance" that an embedding is interested + * in executing. + * + *

Type parameter T

+ *

+ * Each Store has a type parameter T associated with it. This T represents state defined by the host. + * This state will be accessible through the Caller type that host-defined functions get access to. + * This T is suitable for storing Store-specific information which imported functions may want access to. + *

+ * The data T can be accessed through methods like Store::data and Store::data_mut. + * Stores, contexts, oh my + *

+ * Most methods in Wasmtime take something of the form AsContext or AsContextMut as the first argument. + * These two traits allow ergonomically passing in the context you currently have to any method. + * The primary two sources of contexts are: + *

+ * Store<T> + * Caller<'_, T> + *

+ * corresponding to what you create and what you have access to in a host function. You can also explicitly acquire + * a StoreContext or StoreContextMut and pass that around as well. + *

+ * Note that all methods on Store are mirrored onto StoreContext, StoreContextMut, and Caller. This way no matter what + * form of context you have you can call various methods, create objects, etc. + * + *

Stores and Default

+ *

+ * You can create a store with default configuration settings using Store::default(). This will create a + * brand new Engine with default configuration (see Config for more information). + * + * @param State defined by the host + */ @Accessors(fluent = true) @AllArgsConstructor(access = AccessLevel.PACKAGE) public class Store implements Disposable { diff --git a/src/test/java/io/github/kawamuray/wasmtime/ModuleTest.java b/src/test/java/io/github/kawamuray/wasmtime/ModuleTest.java index 25dee18..5f6095c 100644 --- a/src/test/java/io/github/kawamuray/wasmtime/ModuleTest.java +++ b/src/test/java/io/github/kawamuray/wasmtime/ModuleTest.java @@ -33,6 +33,15 @@ public class ModuleTest { " (func (export \"run\") (call $hello))\n" + ")").getBytes(); + private static final byte[] EXPORT_WAT_BINARY = ("(module" + + " (memory (export \"memmy\") 20 22)\n" + + " (table (export \"tabby\") 0 1 funcref)\n" + + " (export \"globby\" (global 0)) (global i32 (i32.const 0))\n" + + " (func (export \"life\") (param $p1 i32) (param $p2 i32) (result i32)" + + " (i32.const 42)\n" + + " )" + + ")").getBytes(); + @Test public void testCreateDispose() { try (Engine engine = new Engine()) { @@ -75,6 +84,43 @@ public void testAccessImports() { } } + @Test + public void testAccessExport() { + try ( + Engine engine = new Engine(); + Module module = new Module(engine, EXPORT_WAT_BINARY) + ) { + runExportTest(module, new TestExportData[]{ + TestExportData.memory("memmy", 20, 22), + TestExportData.table("tabby", Val.Type.FUNC_REF, 0, 1), + TestExportData.global("globby", Val.Type.I32, Mutability.CONST), + TestExportData.func("life", new Val.Type[]{Val.Type.I32, Val.Type.I32}, new Val.Type[]{Val.Type.I32}), + }); + } + } + + private void runExportTest(Module module, TestExportData[] testData) { + int i = 0; + for (ExportType export : module.exports()) { + Assert.assertTrue("Test Data not big enough", testData.length > i); + TestExportData data = testData[i]; + Assert.assertEquals(data.getName(), export.name()); + Assert.assertEquals(data.getType(), export.type()); + checkExportType(data, export); + i += 1; + } + Assert.assertEquals("Not Every Test Case was returned", testData.length, i); + } + + private void checkExportType(TestExportData data, ExportType type) { + Class clazz = data.getClazz(); + Object typeObj = type.typeObj(); + Assert.assertNotNull("Type Object is null", typeObj); + Class typeObjClass = typeObj.getClass(); + Assert.assertTrue(String.format("Expected Type is different. Expected %s but was %s", clazz, typeObjClass), clazz.isAssignableFrom(typeObjClass)); + data.verify(type, typeObj); + } + private void runImportTest(Module module, TestImportData[] testData) { int i = 0; for (ImportType imp : module.imports()) { @@ -98,6 +144,89 @@ private void checkImportType(TestImportData data, ImportType type) { data.verify(type, typeObj); } + @Data + private static class TestExportData { + private final String name; + private final ExportType.Type type; + private final Class clazz; + private final Consumer verifyExport; + private final Consumer consumer; + + public static TestExportData func(String name, Val.Type[] params, Val.Type[] results) { + return new TestExportData<>( + name, ExportType.Type.FUNC, FuncType.class, + mod -> { + Assert.assertEquals(mod.typeObj(), mod.func()); + Assert.assertThrows(RuntimeException.class, mod::global); + Assert.assertThrows(RuntimeException.class, mod::memory); + Assert.assertThrows(RuntimeException.class, mod::table); + }, + func -> { + Assert.assertArrayEquals(params, func.getParams()); + Assert.assertArrayEquals(results, func.getResults()); + } + ); + } + + public static TestExportData memory(String name, int min, int max) { + return new TestExportData<>( + name, ExportType.Type.MEMORY, MemoryType.class, + mod -> { + Assert.assertThrows(RuntimeException.class, mod::func); + Assert.assertThrows(RuntimeException.class, mod::global); + Assert.assertEquals(mod.typeObj(), mod.memory()); + Assert.assertThrows(RuntimeException.class, mod::table); + }, + mem -> { + MemoryType.Limit limit = mem.limit(); + Assert.assertEquals(min, limit.min()); + Assert.assertEquals(max, limit.max()); + } + ); + } + + public static TestExportData table(String name, Val.Type content, int min, int max) { + return new TestExportData<>( + name, ExportType.Type.TABLE, TableType.class, + mod -> { + Assert.assertThrows(RuntimeException.class, mod::func); + Assert.assertThrows(RuntimeException.class, mod::global); + Assert.assertThrows(RuntimeException.class, mod::memory); + Assert.assertEquals(mod.typeObj(), mod.table()); + }, + table -> { + Assert.assertEquals(content, table.element()); + + MemoryType.Limit limit = table.limit(); + Assert.assertEquals(min, limit.min()); + Assert.assertEquals(max, limit.max()); + } + ); + } + + public static TestExportData global(String name, Val.Type content, Mutability mutability) { + return new TestExportData<>( + name, ExportType.Type.GLOBAL, GlobalType.class, + mod -> { + Assert.assertThrows(RuntimeException.class, mod::func); + Assert.assertEquals(mod.typeObj(), mod.global()); + Assert.assertThrows(RuntimeException.class, mod::memory); + Assert.assertThrows(RuntimeException.class, mod::table); + }, + global -> { + Assert.assertEquals(content, global.getContent()); + Assert.assertEquals(mutability, global.getMutability()); + } + ); + } + + @SuppressWarnings("unchecked") + public void verify(final ExportType imp, final Object typeObj) { + this.verifyExport.accept(imp); + this.consumer.accept((T) typeObj); + } + } + @Data private static class TestImportData { private final String module; diff --git a/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/imp.rs b/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/imp.rs index 74b47e5..30797c9 100644 --- a/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/imp.rs +++ b/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/imp.rs @@ -12,6 +12,70 @@ pub(super) struct JniModuleImpl; const OBJECT_CLASS: &'static str = "java/lang/Object"; const LIMIT_TYPE: &str = "io/github/kawamuray/wasmtime/MemoryType$Limit"; pub const IMPORT_TYPE_CLASS: &'static str = "io/github/kawamuray/wasmtime/ImportType$Type"; +pub const EXPORT_TYPE_CLASS: &'static str = "io/github/kawamuray/wasmtime/ExportType$Type"; +pub const STRING_CLASS: &str = "java/lang/String"; + +macro_rules! meow { + ($into_type:ident, $ty:expr, $env:ident) => {{ + match $ty { + ExternType::Func(func) => { + let results = types_into_java_array($env, func.results()); + let params = types_into_java_array($env, func.params()); + + ( + $into_type($env, "FUNC"), + $env.new_object( + "io/github/kawamuray/wasmtime/FuncType", + format!("([L{};[L{};)V", wval::VAL_TYPE, wval::VAL_TYPE), + &[params?.into(), results?.into()], + ), + ) + } + ExternType::Global(global) => ( + $into_type($env, "GLOBAL"), + $env.new_object( + "io/github/kawamuray/wasmtime/GlobalType", + format!("(L{};L{};)V", wval::VAL_TYPE, wmut::MUT_TYPE), + &[ + wval::type_into_java($env, global.content().to_owned())? + .into_inner() + .into(), + wmut::mutability_into_java($env, global.mutability())? + .into_inner() + .into(), + ], + ), + ), + ExternType::Table(tab) => { + const TABLE_TYPE: &str = "io/github/kawamuray/wasmtime/TableType"; + let limit = limit_into_java($env, tab.limits()); + let val = wval::type_into_java($env, tab.element().to_owned()); + let table = $env.new_object( + TABLE_TYPE, + format!("(L{};L{};)V", wval::VAL_TYPE, LIMIT_TYPE), + &[val?.into_inner().into(), limit?.into_inner().into()], + ); + + ($into_type($env, "TABLE"), table) + } + ExternType::Memory(mem) => { + const MEMORY_TYPE: &str = "io/github/kawamuray/wasmtime/MemoryType"; + let limit = limit_into_java($env, mem.limits()); + let mem = $env.new_object( + MEMORY_TYPE, + format!("(L{};)V", LIMIT_TYPE), + &[limit?.into_inner().into()], + ); + + ($into_type($env, "MEMORY"), mem) + } + // WebAssembly module-linking proposal + ExternType::Instance(_) => ($into_type($env, "INSTANCE"), Ok(JObject::null())), + // WebAssembly module-linking proposal + ExternType::Module(_) => ($into_type($env, "MODULE"), Ok(JObject::null())), + } + }}; +} impl<'a> JniModule<'a> for JniModuleImpl { type Error = errors::Error; @@ -21,8 +85,33 @@ impl<'a> JniModule<'a> for JniModuleImpl { Ok(()) } + fn exports(env: &JNIEnv, this: JObject) -> std::result::Result { + const EXPORT_TYPE: &str = "io/github/kawamuray/wasmtime/ExportType"; + let module = interop::get_inner::(env, this)?; + let it = module.exports(); + let mut exports = Vec::with_capacity(it.len()); + for obj in it { + // let obj: ExportType = obj; + let name = env.new_string(obj.name()); + let (ty, ty_obj) = meow!(into_java_export_type, obj.ty(), env); + let export = env.new_object( + EXPORT_TYPE, + format!( + "(L{};L{};L{};)V", + EXPORT_TYPE_CLASS, OBJECT_CLASS, STRING_CLASS + ), + &[ + ty?.into_inner().into(), + ty_obj?.into_inner().into(), + name?.into(), + ], + )?; + exports.push(export); + } + Ok(utils::into_java_array(env, EXPORT_TYPE, exports)?) + } + fn imports(env: &JNIEnv, this: JObject) -> std::result::Result { - const STRING_CLASS: &str = "java/lang/String"; const IMPORT_TYPE: &str = "io/github/kawamuray/wasmtime/ImportType"; let module = interop::get_inner::(env, this)?; @@ -30,67 +119,7 @@ impl<'a> JniModule<'a> for JniModuleImpl { let mut imports = Vec::with_capacity(it.len()); for obj in it { let module = obj.module(); - let (ty, ty_obj) = match obj.ty() { - ExternType::Func(func) => { - let results = types_into_java_array(env, func.results()); - let params = types_into_java_array(env, func.params()); - - ( - into_java_import_type(env, "FUNC"), - env.new_object( - "io/github/kawamuray/wasmtime/FuncType", - format!("([L{};[L{};)V", wval::VAL_TYPE, wval::VAL_TYPE), - &[params?.into(), results?.into()], - ), - ) - } - ExternType::Global(global) => ( - into_java_import_type(env, "GLOBAL"), - env.new_object( - "io/github/kawamuray/wasmtime/GlobalType", - format!("(L{};L{};)V", wval::VAL_TYPE, wmut::MUT_TYPE), - &[ - wval::type_into_java(env, global.content().to_owned())? - .into_inner() - .into(), - wmut::mutability_into_java(env, global.mutability())? - .into_inner() - .into(), - ], - ), - ), - ExternType::Table(tab) => { - const TABLE_TYPE: &str = "io/github/kawamuray/wasmtime/TableType"; - let limit = limit_into_java(env, tab.limits()); - let val = wval::type_into_java(env, tab.element().to_owned()); - let table = env.new_object( - TABLE_TYPE, - format!("(L{};L{};)V", wval::VAL_TYPE, LIMIT_TYPE), - &[val?.into_inner().into(), limit?.into_inner().into()], - ); - - (into_java_import_type(env, "TABLE"), table) - } - ExternType::Memory(mem) => { - const MEMORY_TYPE: &str = "io/github/kawamuray/wasmtime/MemoryType"; - let limit = limit_into_java(env, mem.limits()); - let mem = env.new_object( - MEMORY_TYPE, - format!("(L{};)V", LIMIT_TYPE), - &[limit?.into_inner().into()], - ); - - (into_java_import_type(env, "MEMORY"), mem) - } - // WebAssembly module-linking proposal - ExternType::Instance(_) => { - (into_java_import_type(env, "INSTANCE"), Ok(JObject::null())) - } - // WebAssembly module-linking proposal - ExternType::Module(_) => { - (into_java_import_type(env, "MODULE"), Ok(JObject::null())) - } - }; + let (ty, ty_obj) = meow!(into_java_import_type, obj.ty(), env); let name = obj.name().unwrap_or_else(|| ""); let import = env.new_object( @@ -156,6 +185,11 @@ fn limit_into_java<'a>(env: &'a JNIEnv, limits: &Limits) -> jni::errors::Result< env.new_object(LIMIT_TYPE, "(II)V", &[min.into(), max.into()]) } +pub fn into_java_export_type<'a>(env: &'a JNIEnv, ty: &'a str) -> jni::errors::Result> { + env.get_static_field(EXPORT_TYPE_CLASS, ty, format!("L{};", EXPORT_TYPE_CLASS))? + .l() +} + pub fn into_java_import_type<'a>(env: &'a JNIEnv, ty: &'a str) -> jni::errors::Result> { env.get_static_field(IMPORT_TYPE_CLASS, ty, format!("L{};", IMPORT_TYPE_CLASS))? .l() diff --git a/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/mod.rs b/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/mod.rs index c893163..27f9551 100644 --- a/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/mod.rs +++ b/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/mod.rs @@ -22,6 +22,7 @@ macro_rules! wrap_error { trait JniModule<'a> { type Error: Desc<'a, JThrowable<'a>>; fn dispose(env: &JNIEnv, this: JObject) -> Result<(), Self::Error>; + fn exports(env: &JNIEnv, this: JObject) -> Result; fn imports(env: &JNIEnv, this: JObject) -> Result; fn new_from_binary( env: &JNIEnv, @@ -48,6 +49,18 @@ extern "system" fn Java_io_github_kawamuray_wasmtime_Module_dispose(env: JNIEnv, wrap_error!(env, JniModuleImpl::dispose(&env, this), Default::default()) } +#[no_mangle] +extern "system" fn Java_io_github_kawamuray_wasmtime_Module_exports( + env: JNIEnv, + this: JObject, +) -> jobjectArray { + wrap_error!( + env, + JniModuleImpl::exports(&env, this), + JObject::null().into_inner() + ) +} + #[no_mangle] extern "system" fn Java_io_github_kawamuray_wasmtime_Module_imports( env: JNIEnv, From 56643f50a6d204c1891204e03d2915e8d4f9af79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heinrichs?= Date: Sun, 8 May 2022 15:22:00 +0200 Subject: [PATCH 2/4] Code cleanup Still unsure about the name match_extern_type --- .../src/io_github_kawamuray_wasmtime_Module/imp.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/imp.rs b/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/imp.rs index 30797c9..b0b0f87 100644 --- a/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/imp.rs +++ b/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/imp.rs @@ -15,7 +15,7 @@ pub const IMPORT_TYPE_CLASS: &'static str = "io/github/kawamuray/wasmtime/Import pub const EXPORT_TYPE_CLASS: &'static str = "io/github/kawamuray/wasmtime/ExportType$Type"; pub const STRING_CLASS: &str = "java/lang/String"; -macro_rules! meow { +macro_rules! match_extern_type { ($into_type:ident, $ty:expr, $env:ident) => {{ match $ty { ExternType::Func(func) => { @@ -91,9 +91,8 @@ impl<'a> JniModule<'a> for JniModuleImpl { let it = module.exports(); let mut exports = Vec::with_capacity(it.len()); for obj in it { - // let obj: ExportType = obj; let name = env.new_string(obj.name()); - let (ty, ty_obj) = meow!(into_java_export_type, obj.ty(), env); + let (ty, ty_obj) = match_extern_type!(into_java_export_type, obj.ty(), env); let export = env.new_object( EXPORT_TYPE, format!( @@ -119,7 +118,7 @@ impl<'a> JniModule<'a> for JniModuleImpl { let mut imports = Vec::with_capacity(it.len()); for obj in it { let module = obj.module(); - let (ty, ty_obj) = meow!(into_java_import_type, obj.ty(), env); + let (ty, ty_obj) = match_extern_type!(into_java_import_type, obj.ty(), env); let name = obj.name().unwrap_or_else(|| ""); let import = env.new_object( From a531fbd049d51182fdb62a5a9764dd4969d23234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heinrichs?= Date: Thu, 12 May 2022 09:17:09 +0200 Subject: [PATCH 3/4] Breaking Change: Unify (Import/Export)Type$Type to ExternType Follows the rust bindings choice for just one type. Clean up the duplicated code as a result. Dependent code needs to change accordingly. --- .../github/kawamuray/wasmtime/ExportType.java | 21 +--- .../github/kawamuray/wasmtime/ExternType.java | 11 ++ .../github/kawamuray/wasmtime/ImportType.java | 22 +--- .../github/kawamuray/wasmtime/ModuleTest.java | 20 ++-- .../imp.rs | 108 ++---------------- wasmtime-jni/src/wextern.rs | 86 +++++++++++++- 6 files changed, 129 insertions(+), 139 deletions(-) create mode 100644 src/main/java/io/github/kawamuray/wasmtime/ExternType.java diff --git a/src/main/java/io/github/kawamuray/wasmtime/ExportType.java b/src/main/java/io/github/kawamuray/wasmtime/ExportType.java index f487ed0..4cf4866 100644 --- a/src/main/java/io/github/kawamuray/wasmtime/ExportType.java +++ b/src/main/java/io/github/kawamuray/wasmtime/ExportType.java @@ -8,17 +8,8 @@ @Accessors(fluent = true) @AllArgsConstructor(access = AccessLevel.PACKAGE) public class ExportType { - public enum Type { - FUNC, - GLOBAL, - TABLE, - MEMORY, - // TODO: Currently Unsupported - INSTANCE, - MODULE; - } @Getter - private final Type type; + private final ExternType type; @Getter(AccessLevel.PACKAGE) private final Object typeObj; @@ -26,7 +17,7 @@ public enum Type { @Getter private final String name; - private void ensureType(Type expected) { + private void ensureType(ExternType expected) { if (type != expected) { throw new RuntimeException( String.format("ImportType expected to have type %s but is actually %s", expected, type)); @@ -34,22 +25,22 @@ private void ensureType(Type expected) { } public FuncType func() { - ensureType(ExportType.Type.FUNC); + ensureType(ExternType.FUNC); return (FuncType) typeObj; } public GlobalType global() { - ensureType(ExportType.Type.GLOBAL); + ensureType(ExternType.GLOBAL); return (GlobalType) typeObj; } public MemoryType memory() { - ensureType(ExportType.Type.MEMORY); + ensureType(ExternType.MEMORY); return (MemoryType) typeObj; } public TableType table() { - ensureType(ExportType.Type.TABLE); + ensureType(ExternType.TABLE); return (TableType) typeObj; } } diff --git a/src/main/java/io/github/kawamuray/wasmtime/ExternType.java b/src/main/java/io/github/kawamuray/wasmtime/ExternType.java new file mode 100644 index 0000000..bb4d097 --- /dev/null +++ b/src/main/java/io/github/kawamuray/wasmtime/ExternType.java @@ -0,0 +1,11 @@ +package io.github.kawamuray.wasmtime; + +public enum ExternType { + FUNC, + GLOBAL, + TABLE, + MEMORY, + // TODO: Currently Unsupported + INSTANCE, + MODULE +} diff --git a/src/main/java/io/github/kawamuray/wasmtime/ImportType.java b/src/main/java/io/github/kawamuray/wasmtime/ImportType.java index baea6d7..d3b6c3a 100644 --- a/src/main/java/io/github/kawamuray/wasmtime/ImportType.java +++ b/src/main/java/io/github/kawamuray/wasmtime/ImportType.java @@ -8,18 +8,8 @@ @Accessors(fluent = true) @AllArgsConstructor(access = AccessLevel.PACKAGE) public class ImportType { - public enum Type { - FUNC, - GLOBAL, - TABLE, - MEMORY, - // TODO: Currently Unsupported - INSTANCE, - MODULE - } - @Getter - private final Type type; + private final ExternType type; @Getter(AccessLevel.PACKAGE) private final Object typeObj; @@ -30,7 +20,7 @@ public enum Type { @Getter private final String name; - private void ensureType(ImportType.Type expected) { + private void ensureType(ExternType expected) { if (type != expected) { throw new RuntimeException( String.format("ImportType expected to have type %s but is actually %s", expected, type)); @@ -38,22 +28,22 @@ private void ensureType(ImportType.Type expected) { } public FuncType func() { - ensureType(ImportType.Type.FUNC); + ensureType(ExternType.FUNC); return (FuncType) typeObj; } public GlobalType global() { - ensureType(ImportType.Type.GLOBAL); + ensureType(ExternType.GLOBAL); return (GlobalType) typeObj; } public MemoryType memory() { - ensureType(ImportType.Type.MEMORY); + ensureType(ExternType.MEMORY); return (MemoryType) typeObj; } public TableType table() { - ensureType(ImportType.Type.TABLE); + ensureType(ExternType.TABLE); return (TableType) typeObj; } } diff --git a/src/test/java/io/github/kawamuray/wasmtime/ModuleTest.java b/src/test/java/io/github/kawamuray/wasmtime/ModuleTest.java index 5f6095c..4dbc716 100644 --- a/src/test/java/io/github/kawamuray/wasmtime/ModuleTest.java +++ b/src/test/java/io/github/kawamuray/wasmtime/ModuleTest.java @@ -147,14 +147,14 @@ private void checkImportType(TestImportData data, ImportType type) { @Data private static class TestExportData { private final String name; - private final ExportType.Type type; + private final ExternType type; private final Class clazz; private final Consumer verifyExport; private final Consumer consumer; public static TestExportData func(String name, Val.Type[] params, Val.Type[] results) { return new TestExportData<>( - name, ExportType.Type.FUNC, FuncType.class, + name, ExternType.FUNC, FuncType.class, mod -> { Assert.assertEquals(mod.typeObj(), mod.func()); Assert.assertThrows(RuntimeException.class, mod::global); @@ -170,7 +170,7 @@ public static TestExportData func(String name, Val.Type[] params, Val. public static TestExportData memory(String name, int min, int max) { return new TestExportData<>( - name, ExportType.Type.MEMORY, MemoryType.class, + name, ExternType.MEMORY, MemoryType.class, mod -> { Assert.assertThrows(RuntimeException.class, mod::func); Assert.assertThrows(RuntimeException.class, mod::global); @@ -187,7 +187,7 @@ public static TestExportData memory(String name, int min, int max) { public static TestExportData table(String name, Val.Type content, int min, int max) { return new TestExportData<>( - name, ExportType.Type.TABLE, TableType.class, + name, ExternType.TABLE, TableType.class, mod -> { Assert.assertThrows(RuntimeException.class, mod::func); Assert.assertThrows(RuntimeException.class, mod::global); @@ -206,7 +206,7 @@ public static TestExportData table(String name, Val.Type content, int public static TestExportData global(String name, Val.Type content, Mutability mutability) { return new TestExportData<>( - name, ExportType.Type.GLOBAL, GlobalType.class, + name, ExternType.GLOBAL, GlobalType.class, mod -> { Assert.assertThrows(RuntimeException.class, mod::func); Assert.assertEquals(mod.typeObj(), mod.global()); @@ -231,14 +231,14 @@ public void verify(final ExportType imp, final Object typeObj) { private static class TestImportData { private final String module; private final String name; - private final ImportType.Type type; + private final ExternType type; private final Class clazz; private final Consumer verifyImport; private final Consumer consumer; static TestImportData memory(String module, String name, int min, int max) { return new TestImportData<>( - module, name, ImportType.Type.MEMORY, MemoryType.class, + module, name, ExternType.MEMORY, MemoryType.class, mod -> { Assert.assertThrows(RuntimeException.class, mod::func); Assert.assertThrows(RuntimeException.class, mod::global); @@ -255,7 +255,7 @@ static TestImportData memory(String module, String name, int min, in static TestImportData global(String module, String name, Val.Type content, Mutability mutability) { return new TestImportData<>( - module, name, ImportType.Type.GLOBAL, GlobalType.class, + module, name, ExternType.GLOBAL, GlobalType.class, mod -> { Assert.assertThrows(RuntimeException.class, mod::func); Assert.assertEquals(mod.typeObj(), mod.global()); @@ -271,7 +271,7 @@ static TestImportData global(String module, String name, Val.Type co static TestImportData func(String module, String name, Val.Type[] params, Val.Type[] results) { return new TestImportData<>( - module, name, ImportType.Type.FUNC, FuncType.class, + module, name, ExternType.FUNC, FuncType.class, mod -> { Assert.assertEquals(mod.typeObj(), mod.func()); Assert.assertThrows(RuntimeException.class, mod::global); @@ -287,7 +287,7 @@ static TestImportData func(String module, String name, Val.Type[] para public static TestImportData table(String module, String name, Val.Type content, int min, int max) { return new TestImportData<>( - module, name, ImportType.Type.TABLE, TableType.class, + module, name, ExternType.TABLE, TableType.class, mod -> { Assert.assertThrows(RuntimeException.class, mod::func); Assert.assertThrows(RuntimeException.class, mod::global); diff --git a/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/imp.rs b/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/imp.rs index b0b0f87..59fdc36 100644 --- a/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/imp.rs +++ b/wasmtime-jni/src/io_github_kawamuray_wasmtime_Module/imp.rs @@ -1,82 +1,17 @@ use super::JniModule; use crate::errors::{self, Result}; -use crate::wval::types_into_java_array; -use crate::{interop, utils, wmut, wval}; +use crate::{interop, utils}; use jni::objects::{JClass, JObject, JString}; -use jni::sys::{jbyteArray, jint, jlong, jobjectArray}; +use jni::sys::{jbyteArray, jlong, jobjectArray}; use jni::JNIEnv; -use wasmtime::{Engine, ExternType, Limits, Module}; +use wasmtime::{Engine, Module}; +use crate::wextern::{EXTERN_TYPE_CLASS, type_into_java}; pub(super) struct JniModuleImpl; const OBJECT_CLASS: &'static str = "java/lang/Object"; -const LIMIT_TYPE: &str = "io/github/kawamuray/wasmtime/MemoryType$Limit"; -pub const IMPORT_TYPE_CLASS: &'static str = "io/github/kawamuray/wasmtime/ImportType$Type"; -pub const EXPORT_TYPE_CLASS: &'static str = "io/github/kawamuray/wasmtime/ExportType$Type"; pub const STRING_CLASS: &str = "java/lang/String"; -macro_rules! match_extern_type { - ($into_type:ident, $ty:expr, $env:ident) => {{ - match $ty { - ExternType::Func(func) => { - let results = types_into_java_array($env, func.results()); - let params = types_into_java_array($env, func.params()); - - ( - $into_type($env, "FUNC"), - $env.new_object( - "io/github/kawamuray/wasmtime/FuncType", - format!("([L{};[L{};)V", wval::VAL_TYPE, wval::VAL_TYPE), - &[params?.into(), results?.into()], - ), - ) - } - ExternType::Global(global) => ( - $into_type($env, "GLOBAL"), - $env.new_object( - "io/github/kawamuray/wasmtime/GlobalType", - format!("(L{};L{};)V", wval::VAL_TYPE, wmut::MUT_TYPE), - &[ - wval::type_into_java($env, global.content().to_owned())? - .into_inner() - .into(), - wmut::mutability_into_java($env, global.mutability())? - .into_inner() - .into(), - ], - ), - ), - ExternType::Table(tab) => { - const TABLE_TYPE: &str = "io/github/kawamuray/wasmtime/TableType"; - let limit = limit_into_java($env, tab.limits()); - let val = wval::type_into_java($env, tab.element().to_owned()); - let table = $env.new_object( - TABLE_TYPE, - format!("(L{};L{};)V", wval::VAL_TYPE, LIMIT_TYPE), - &[val?.into_inner().into(), limit?.into_inner().into()], - ); - - ($into_type($env, "TABLE"), table) - } - ExternType::Memory(mem) => { - const MEMORY_TYPE: &str = "io/github/kawamuray/wasmtime/MemoryType"; - let limit = limit_into_java($env, mem.limits()); - let mem = $env.new_object( - MEMORY_TYPE, - format!("(L{};)V", LIMIT_TYPE), - &[limit?.into_inner().into()], - ); - - ($into_type($env, "MEMORY"), mem) - } - // WebAssembly module-linking proposal - ExternType::Instance(_) => ($into_type($env, "INSTANCE"), Ok(JObject::null())), - // WebAssembly module-linking proposal - ExternType::Module(_) => ($into_type($env, "MODULE"), Ok(JObject::null())), - } - }}; -} - impl<'a> JniModule<'a> for JniModuleImpl { type Error = errors::Error; @@ -92,16 +27,16 @@ impl<'a> JniModule<'a> for JniModuleImpl { let mut exports = Vec::with_capacity(it.len()); for obj in it { let name = env.new_string(obj.name()); - let (ty, ty_obj) = match_extern_type!(into_java_export_type, obj.ty(), env); + let (ty, ty_obj) = type_into_java(env, obj.ty())?; let export = env.new_object( EXPORT_TYPE, format!( "(L{};L{};L{};)V", - EXPORT_TYPE_CLASS, OBJECT_CLASS, STRING_CLASS + EXTERN_TYPE_CLASS, OBJECT_CLASS, STRING_CLASS ), &[ - ty?.into_inner().into(), - ty_obj?.into_inner().into(), + ty.into_inner().into(), + ty_obj.into_inner().into(), name?.into(), ], )?; @@ -118,18 +53,18 @@ impl<'a> JniModule<'a> for JniModuleImpl { let mut imports = Vec::with_capacity(it.len()); for obj in it { let module = obj.module(); - let (ty, ty_obj) = match_extern_type!(into_java_import_type, obj.ty(), env); + let (ty, ty_obj) = type_into_java(env, obj.ty())?; let name = obj.name().unwrap_or_else(|| ""); let import = env.new_object( IMPORT_TYPE, format!( "(L{};L{};L{};L{};)V", - IMPORT_TYPE_CLASS, OBJECT_CLASS, STRING_CLASS, STRING_CLASS + EXTERN_TYPE_CLASS, OBJECT_CLASS, STRING_CLASS, STRING_CLASS ), &[ - ty?.into_inner().into(), - ty_obj?.into_inner().into(), + ty.into_inner().into(), + ty_obj.into_inner().into(), env.new_string(module)?.into(), env.new_string(name)?.into(), ], @@ -174,22 +109,3 @@ impl<'a> JniModule<'a> for JniModuleImpl { Ok(interop::into_raw::(module)) } } - -fn limit_into_java<'a>(env: &'a JNIEnv, limits: &Limits) -> jni::errors::Result> { - let min = limits.min() as jint; - let max = match limits.max() { - None => -1, - Some(max) => max as jint, - }; - env.new_object(LIMIT_TYPE, "(II)V", &[min.into(), max.into()]) -} - -pub fn into_java_export_type<'a>(env: &'a JNIEnv, ty: &'a str) -> jni::errors::Result> { - env.get_static_field(EXPORT_TYPE_CLASS, ty, format!("L{};", EXPORT_TYPE_CLASS))? - .l() -} - -pub fn into_java_import_type<'a>(env: &'a JNIEnv, ty: &'a str) -> jni::errors::Result> { - env.get_static_field(IMPORT_TYPE_CLASS, ty, format!("L{};", IMPORT_TYPE_CLASS))? - .l() -} diff --git a/wasmtime-jni/src/wextern.rs b/wasmtime-jni/src/wextern.rs index c250dad..27913b0 100644 --- a/wasmtime-jni/src/wextern.rs +++ b/wasmtime-jni/src/wextern.rs @@ -1,8 +1,14 @@ use crate::errors::{Error, Result}; -use crate::{interop, utils}; +use crate::{interop, utils, wval}; use jni::objects::JObject; use jni::JNIEnv; -use wasmtime::{Extern, Func, Global, Memory, Table}; +use jni::sys::jint; +use wasmtime::{Extern, ExternType, Func, Global, Limits, Memory, Table}; +use crate::wmut::{MUT_TYPE, mutability_into_java}; +use crate::wval::VAL_TYPE; + +pub const EXTERN_TYPE_CLASS: &'static str = "io/github/kawamuray/wasmtime/ExternType"; +const LIMIT_TYPE: &str = "io/github/kawamuray/wasmtime/MemoryType$Limit"; pub fn from_java(env: &JNIEnv, obj: JObject) -> Result { let ty = env @@ -96,6 +102,68 @@ pub fn into_java<'a>(env: &'a JNIEnv, ext: Extern) -> Result> { }) } +pub fn type_into_java<'a>(env: &'a JNIEnv, ty: ExternType) -> Result<(JObject<'a>, JObject<'a>)> { + let (ty, ty_obj) = match ty { + ExternType::Func(func) => { + let results = wval::types_into_java_array(env, func.results()); + let params = wval::types_into_java_array(env, func.params()); + + ( + into_java_extern_type(env, "FUNC"), + env.new_object( + "io/github/kawamuray/wasmtime/FuncType", + format!("([L{};[L{};)V", VAL_TYPE, VAL_TYPE), + &[params?.into(), results?.into()], + ), + ) + } + ExternType::Global(global) => ( + into_java_extern_type(env, "GLOBAL"), + env.new_object( + "io/github/kawamuray/wasmtime/GlobalType", + format!("(L{};L{};)V", VAL_TYPE, MUT_TYPE), + &[ + wval::type_into_java(env, global.content().to_owned())? + .into_inner() + .into(), + mutability_into_java(env, global.mutability())? + .into_inner() + .into(), + ], + ), + ), + ExternType::Table(tab) => { + const TABLE_TYPE: &str = "io/github/kawamuray/wasmtime/TableType"; + let limit = limit_into_java(env, tab.limits()); + let val = wval::type_into_java(env, tab.element().to_owned()); + let table = env.new_object( + TABLE_TYPE, + format!("(L{};L{};)V", VAL_TYPE, LIMIT_TYPE), + &[val?.into_inner().into(), limit?.into_inner().into()], + ); + + (into_java_extern_type(env, "TABLE"), table) + } + ExternType::Memory(mem) => { + const MEMORY_TYPE: &str = "io/github/kawamuray/wasmtime/MemoryType"; + let limit = limit_into_java(env, mem.limits()); + let mem = env.new_object( + MEMORY_TYPE, + format!("(L{};)V", LIMIT_TYPE), + &[limit?.into_inner().into()], + ); + + (into_java_extern_type(env, "MEMORY"), mem) + } + // WebAssembly module-linking proposal + ExternType::Instance(_) => (into_java_extern_type(env, "INSTANCE"), Ok(JObject::null())), + // WebAssembly module-linking proposal + ExternType::Module(_) => (into_java_extern_type(env, "MODULE"), Ok(JObject::null())), + }; + + Ok((ty?, ty_obj?)) +} + pub fn unknown<'a>(env: &'a JNIEnv) -> Result> { Ok(env .get_static_field( @@ -105,3 +173,17 @@ pub fn unknown<'a>(env: &'a JNIEnv) -> Result> { )? .l()?) } + +fn limit_into_java<'a>(env: &'a JNIEnv, limits: &Limits) -> jni::errors::Result> { + let min = limits.min() as jint; + let max = match limits.max() { + None => -1, + Some(max) => max as jint, + }; + env.new_object(LIMIT_TYPE, "(II)V", &[min.into(), max.into()]) +} + +fn into_java_extern_type<'a>(env: &'a JNIEnv, ty: &'a str) -> jni::errors::Result> { + env.get_static_field(EXTERN_TYPE_CLASS, ty, format!("L{};", EXTERN_TYPE_CLASS))? + .l() +} From 6fdd2341d5ba36729c4cd2349ddd8c334ce8f1a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heinrichs?= Date: Wed, 25 May 2022 20:53:23 +0200 Subject: [PATCH 4/4] Fix documentation --- .../io/github/kawamuray/wasmtime/Engine.java | 5 +-- .../io/github/kawamuray/wasmtime/Func.java | 37 +++---------------- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/src/main/java/io/github/kawamuray/wasmtime/Engine.java b/src/main/java/io/github/kawamuray/wasmtime/Engine.java index a802808..063d368 100644 --- a/src/main/java/io/github/kawamuray/wasmtime/Engine.java +++ b/src/main/java/io/github/kawamuray/wasmtime/Engine.java @@ -9,11 +9,10 @@ /** * An Engine which is a global context for compilation and management of wasm modules. *

- * An engine can be safely shared across threads and is a cheap cloneable handle to the actual engine. - * The engine itself will be deallocated once all references to it have gone away. - *

* Engines store global configuration preferences such as compilation settings, enabled features, etc. * You'll likely only need at most one of these for a program. + * + * @see Rust Documentation */ @Accessors(fluent = true) @EqualsAndHashCode diff --git a/src/main/java/io/github/kawamuray/wasmtime/Func.java b/src/main/java/io/github/kawamuray/wasmtime/Func.java index 8526091..feedcad 100644 --- a/src/main/java/io/github/kawamuray/wasmtime/Func.java +++ b/src/main/java/io/github/kawamuray/wasmtime/Func.java @@ -14,40 +14,13 @@ /** * A WebAssembly function which can be called. * - * This type can represent either an exported function from a WebAssembly module or a host-defined function which can - * be used to satisfy an import of a module. Func and can be used to both instantiate an Instance as well as be - * extracted from an Instance. + * This type is either provided by a WebAssembly Module or implemented in Java as a Host-function. * * A Func "belongs" to the store that it was originally created within. Operations on a Func only work with the store - * it belongs to, and if another store is passed in by accident then methods will panic. - * - *

Func and async

- * Functions from the perspective of WebAssembly are always synchronous. You might have an async function in Rust, - * however, which you’d like to make available from WebAssembly. Wasmtime supports asynchronously calling WebAssembly - * through native stack switching. You can get some more information about asynchronous configs, but from the - * perspective of Func it’s important to know that whether or not your Store is asynchronous will dictate whether you - * call functions through Func::call or Func::call_async (or the typed wrappers such as TypedFunc::call vs - * TypedFunc::call_async). - * - *

To Func::call or to Func::typed().call()

- * There’s a 2x2 matrix of methods to call Func. Invocations can either be asynchronous or synchronous. - * They can also be statically typed or not. Whether or not an invocation is asynchronous is indicated via the method - * being async and call_async being the entry point. Otherwise for statically typed or not your options are: - * - * Dynamically typed - if you don’t statically know the signature of the function that you’re calling you’ll be - * using Func::call or Func::call_async. These functions take a variable-length slice of "boxed" arguments - * in their Val representation. Additionally the results are returned as an owned slice of Val. These methods - * are not optimized due to the dynamic type checks that must occur, in addition to some dynamic allocations - * for where to put all the arguments. While this allows you to call all possible wasm function signatures, - * if you’re looking for a speedier alternative you can also use… - * - * Statically typed - if you statically know the type signature of the wasm function you’re calling, then you’ll - * want to use the Func::typed method to acquire an instance of TypedFunc. This structure is static proof - * that the underlying wasm function has the ascripted type, and type validation is only done once up-front. - * The TypedFunc::call and TypedFunc::call_async methods are much more efficient than Func::call and - * Func::call_async because the type signature is statically known. This eschews runtime checks as much as - * possible to get into wasm as fast as possible. - */ + * it belongs to. Otherwise an exception will be thrown. + * + * @see Rust Documentation + */ @Slf4j @Accessors(fluent = true) @AllArgsConstructor(access = AccessLevel.PACKAGE)