From 5632d684ae42e957f2f856811cb15af798db3575 Mon Sep 17 00:00:00 2001 From: vms Date: Tue, 25 Aug 2020 19:26:21 +0300 Subject: [PATCH] Introduce call parameters (#21) --- .circleci/config.yml | 4 +- .gitignore | 6 +- Cargo.lock | 55 ++++++------ crates/wit-generator/Cargo.toml | 2 +- crates/wit-interfaces/Cargo.toml | 2 +- crates/wit-parser/Cargo.toml | 2 +- engine/Cargo.toml | 2 +- examples/greeting/Cargo.toml | 4 + examples/greeting/Config_cp.toml | 6 ++ examples/greeting/artifacts/greeting.wasm | Bin 70611 -> 70838 bytes examples/greeting/artifacts/greeting_cp.wasm | Bin 0 -> 71011 bytes examples/greeting/src/main_cp.rs | 24 ++++++ fluence-app-service/src/lib.rs | 1 + fluence-app-service/src/service.rs | 3 +- fluence-faas/Cargo.toml | 4 +- fluence-faas/src/faas.rs | 24 +++++- fluence-faas/src/faas_interface.rs | 4 +- fluence-faas/src/lib.rs | 3 + fluence-faas/src/misc/imports.rs | 86 ++++++++++++++++--- fluence-faas/src/misc/utils.rs | 8 +- fluence-faas/tests/greeting.rs | 69 ++++++++++++++- fluence-faas/tests/records.rs | 2 +- tools/repl/src/repl.rs | 19 ++-- 23 files changed, 267 insertions(+), 63 deletions(-) create mode 100644 examples/greeting/Config_cp.toml create mode 100755 examples/greeting/artifacts/greeting_cp.wasm create mode 100644 examples/greeting/src/main_cp.rs diff --git a/.circleci/config.yml b/.circleci/config.yml index 303d6363..9cf419b7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ jobs: - checkout - restore_cache: keys: - - fce01-{{ checksum "Cargo.lock" }}-{{ checksum "examples/greeting/artifacts/greeting.wasm" }}-{{ checksum "examples/records/artifacts/pure.wasm" }}-{{ checksum "examples/records/artifacts/effector.wasm" }} + - fce01-{{ checksum "Cargo.lock" }} - run: | rustup toolchain install nightly rustup component add rustfmt @@ -25,7 +25,7 @@ jobs: paths: - ~/.cargo - ~/.rustup - key: fce01-{{ checksum "Cargo.lock" }}-{{ checksum "examples/greeting/artifacts/greeting.wasm" }}-{{ checksum "examples/records/artifacts/pure.wasm" }}-{{ checksum "examples/records/artifacts/effector.wasm" }} + key: fce01-{{ checksum "Cargo.lock" }} examples: docker: diff --git a/.gitignore b/.gitignore index 4e74a486..7233e486 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,6 @@ target/ /examples/ipfs_node/wasm/artifacts/ipfs_rpc_file # Allowed Wasm files for examples -!/examples/greeting/artifacts/greeting.wasm -!/examples/ipfs_node/artifacts/wasm_modules/* -!/examples/records/artifacts/wasm_modules/* +!/examples/greeting/artifacts/*.wasm +!/examples/ipfs_node/artifacts/wasm_modules/*.wasm +!/examples/records/artifacts/wasm_modules/*.wasm diff --git a/Cargo.lock b/Cargo.lock index 5d9c0036..93bbb6f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,16 @@ dependencies = [ "target-lexicon", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ee0cc8804d5393478d743b035099520087a5186f3b93fa58cec08fa62407b6" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -321,17 +331,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "crossbeam-queue" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "maybe-uninit", -] - [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -487,9 +486,9 @@ checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" [[package]] name = "encoding_rs" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" +checksum = "a51b8cf747471cb9499b6d59e59b0444f4c90eba8968c4e44874e92b5b64ace2" dependencies = [ "cfg-if", ] @@ -638,7 +637,7 @@ dependencies = [ [[package]] name = "fluence" version = "0.2.0" -source = "git+https://github.com/fluencelabs/rust-sdk#55f965f9b88b1a8bbceaee3fc9fb6b1e5f3aa63f" +source = "git+https://github.com/fluencelabs/rust-sdk#d0db9a365f68046e3fe8d2ae19badb988c68ea3f" dependencies = [ "fluence-sdk-macro", "fluence-sdk-main", @@ -661,7 +660,9 @@ version = "0.1.0" dependencies = [ "cmd_lib", "fce", + "fluence-sdk-main", "log", + "safe-transmute", "serde", "serde_derive", "serde_json", @@ -675,7 +676,7 @@ dependencies = [ [[package]] name = "fluence-sdk-macro" version = "0.2.0" -source = "git+https://github.com/fluencelabs/rust-sdk#55f965f9b88b1a8bbceaee3fc9fb6b1e5f3aa63f" +source = "git+https://github.com/fluencelabs/rust-sdk#d0db9a365f68046e3fe8d2ae19badb988c68ea3f" dependencies = [ "fluence-sdk-wit 0.2.0 (git+https://github.com/fluencelabs/rust-sdk)", ] @@ -683,15 +684,17 @@ dependencies = [ [[package]] name = "fluence-sdk-main" version = "0.2.0" -source = "git+https://github.com/fluencelabs/rust-sdk#55f965f9b88b1a8bbceaee3fc9fb6b1e5f3aa63f" +source = "git+https://github.com/fluencelabs/rust-sdk#d0db9a365f68046e3fe8d2ae19badb988c68ea3f" dependencies = [ + "fluence-sdk-macro", "log", + "serde", ] [[package]] name = "fluence-sdk-wit" version = "0.2.0" -source = "git+https://github.com/fluencelabs/rust-sdk#55f965f9b88b1a8bbceaee3fc9fb6b1e5f3aa63f" +source = "git+https://github.com/fluencelabs/rust-sdk#d0db9a365f68046e3fe8d2ae19badb988c68ea3f" dependencies = [ "proc-macro2", "quote", @@ -1608,9 +1611,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080" +checksum = "cfd016f0c045ad38b5251be2c9c0ab806917f82da4d36b2a327e5166adad9270" dependencies = [ "autocfg", "crossbeam-deque", @@ -1620,12 +1623,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280" +checksum = "91739a34c4355b5434ce54c9086c5895604a9c278586d1f1aa95e04f66b525a0" dependencies = [ + "crossbeam-channel", "crossbeam-deque", - "crossbeam-queue", "crossbeam-utils", "lazy_static", "num_cpus", @@ -2210,9 +2213,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db63662723c316b43ca36d833707cc93dff82a02ba3d7e354f342682cc8b3545" +checksum = "4f0e00789804e99b20f12bc7003ca416309d28a6f495d6af58d1e2c2842461b5" dependencies = [ "lazy_static", ] @@ -2548,9 +2551,9 @@ dependencies = [ [[package]] name = "wasmer-interface-types-fl" -version = "0.17.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ea6479fe1487fc529af1d59f33f2ba89f1f50c5c89614ce8385f7915c8fbcb" +checksum = "7b12519a2a53ea1b2166ff47e56c6b57a3f7d714a2d34d26a6b04bdf22c37a41" dependencies = [ "nom", "safe-transmute", diff --git a/crates/wit-generator/Cargo.toml b/crates/wit-generator/Cargo.toml index e0d70ed0..4adf6c93 100644 --- a/crates/wit-generator/Cargo.toml +++ b/crates/wit-generator/Cargo.toml @@ -15,6 +15,6 @@ fce-wit-parser = { path = "../wit-parser", version = "0.1.1"} walrus = "0.17.0" fluence-sdk-wit = "0.2.0" once_cell = "1.4.0" -wasmer-wit = { package = "wasmer-interface-types-fl", version = "=0.17.1", features = ["serde"] } +wasmer-wit = { package = "wasmer-interface-types-fl", version = "=0.17.0", features = ["serde"] } serde = { version = "1.0.110", features = ["derive"] } serde_json = "1.0.56" diff --git a/crates/wit-interfaces/Cargo.toml b/crates/wit-interfaces/Cargo.toml index 39e0a530..9e6c5720 100644 --- a/crates/wit-interfaces/Cargo.toml +++ b/crates/wit-interfaces/Cargo.toml @@ -11,5 +11,5 @@ name = "fce_wit_interfaces" path = "src/lib.rs" [dependencies] -wasmer-wit = { package = "wasmer-interface-types-fl", version = "=0.17.1"} +wasmer-wit = { package = "wasmer-interface-types-fl", version = "=0.17.0"} multimap = "0.8.1" diff --git a/crates/wit-parser/Cargo.toml b/crates/wit-parser/Cargo.toml index 1a597673..3258d0e3 100644 --- a/crates/wit-parser/Cargo.toml +++ b/crates/wit-parser/Cargo.toml @@ -13,7 +13,7 @@ path = "src/lib.rs" [dependencies] walrus = "0.17.0" wasmer-core = { package = "wasmer-runtime-core-fl", version = "0.17.0"} -wasmer-wit = { package = "wasmer-interface-types-fl", version = "=0.17.1"} +wasmer-wit = { package = "wasmer-interface-types-fl", version = "=0.17.0"} fce-wit-interfaces = { path = "../wit-interfaces", version = "0.1.0" } anyhow = "1.0.31" diff --git a/engine/Cargo.toml b/engine/Cargo.toml index df323ae6..81928d95 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -17,7 +17,7 @@ fce-wit-parser = { path = "../crates/wit-parser", version = "0.1.2" } wasmer-runtime = { package = "wasmer-runtime-fl", version = "0.17.0" } # dynamicfunc-fat-closures allows using state inside DynamicFunc wasmer-core = { package = "wasmer-runtime-core-fl", version = "0.17.0", features = ["dynamicfunc-fat-closures"] } -wasmer-wit = { package = "wasmer-interface-types-fl", version = "=0.17.1", features = ["serde"] } +wasmer-wit = { package = "wasmer-interface-types-fl", version = "=0.17.0", features = ["serde"] } wasmer-wasi = { package = "wasmer-wasi-fl", version = "0.17.0" } serde = { version = "1.0.114", default-features = false, features = [ "derive" ] } diff --git a/examples/greeting/Cargo.toml b/examples/greeting/Cargo.toml index 8f7b7b3c..a7ef7303 100644 --- a/examples/greeting/Cargo.toml +++ b/examples/greeting/Cargo.toml @@ -8,5 +8,9 @@ edition = "2018" name = "greeting" path = "src/main.rs" +[[bin]] +name = "greeting_cp" +path = "src/main_cp.rs" + [dependencies] fluence = { git = "https://github.com/fluencelabs/rust-sdk" } diff --git a/examples/greeting/Config_cp.toml b/examples/greeting/Config_cp.toml new file mode 100644 index 00000000..d7e63a9d --- /dev/null +++ b/examples/greeting/Config_cp.toml @@ -0,0 +1,6 @@ +modules_dir = "artifacts/" + +[[module]] + name = "greeting_cp" + mem_pages_count = 1 + logger_enabled = false diff --git a/examples/greeting/artifacts/greeting.wasm b/examples/greeting/artifacts/greeting.wasm index 42e0515f0a4deba4d381af922c9fb25c3a82f88e..889237c54680db6f6c8daf671539d1caa0728b61 100755 GIT binary patch delta 329 zcmcb-oMqcemJRDZFc}(b-tgfq6Tg|6iJ6&^iKTg>L29D8$>uemv*qh=G&VFasK>{r zC8x%xr{<*=C6=V7#22R~mt^MW#m74*=Hvt<7A5AUmZTOHS1TnJm&BJ;7Nja!DFvk_ z=NF|Y=_us^Wr2KDr9knt%+#EeVkN8SYM7E_plR`$AU!3OKpmkaY4sMtB}JKe=}NUa za8;$nsYUT%LlNq*C`&9T$jMAj1iKT(Ot{+ET82F!@9}5m0bQS#n4GE$a(giYCj%o7 pBM$=;56^USVMakAMrJN9E(S&}MhL;g$UZ$(n6Z+TgN2cU9RL9cX=VTb delta 92 zcmdnClI8MpmJRDZFd0~E-tgfq6TeZ4g{5gynn_ZMp`odH^5!+4v*pFNH#Rgd@Mq?g tq!y(mCa3C_R2HNbGjL4j5n+_#WnkoDMbs*;yFb*#JSf8vXzP diff --git a/examples/greeting/artifacts/greeting_cp.wasm b/examples/greeting/artifacts/greeting_cp.wasm new file mode 100755 index 0000000000000000000000000000000000000000..8bcfed7a8d21ad6a49a57dcc65c24a55cf463bf2 GIT binary patch literal 71011 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 String { + let name = fluence::get_call_parameters().user_name; + format!("Hi, {}", name) +} diff --git a/fluence-app-service/src/lib.rs b/fluence-app-service/src/lib.rs index 0209d1d9..b5b405b1 100644 --- a/fluence-app-service/src/lib.rs +++ b/fluence-app-service/src/lib.rs @@ -32,6 +32,7 @@ pub(crate) type Result = std::result::Result; pub use errors::AppServiceError; pub use service::AppService; +pub use fluence_faas::CallParameters; pub use fluence_faas::IValue; pub use fluence_faas::IType; pub use fluence_faas::FaaSInterface; diff --git a/fluence-app-service/src/service.rs b/fluence-app-service/src/service.rs index 20247d0c..8a4a6b09 100644 --- a/fluence-app-service/src/service.rs +++ b/fluence-app-service/src/service.rs @@ -60,11 +60,12 @@ impl AppService { module_name: MN, func_name: FN, arguments: serde_json::Value, + call_parameters: crate::CallParameters, ) -> Result> { let arguments = Self::json_to_ivalue(arguments)?; self.faas - .call(module_name, func_name, &arguments) + .call(module_name, func_name, &arguments, call_parameters) .map_err(Into::into) } diff --git a/fluence-faas/Cargo.toml b/fluence-faas/Cargo.toml index 3508c686..fc021485 100644 --- a/fluence-faas/Cargo.toml +++ b/fluence-faas/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] fce = { path = "../engine", version = "0.1.1" } +fluence-sdk-main = { git = "https://github.com/fluencelabs/rust-sdk" } wasmer-runtime = { package = "wasmer-runtime-fl", version = "0.17.0" } # dynamicfunc-fat-closures allows using state inside DynamicFunc @@ -20,9 +21,10 @@ serde_json = "1.0.53" serde_derive = "1.0.111" cmd_lib = "0.7.8" log = "0.4.8" +safe-transmute = "0.11.0" [dev-dependencies] -wasmer-wit = { package = "wasmer-interface-types-fl", version = "=0.17.1"} +wasmer-wit = { package = "wasmer-interface-types-fl", version = "=0.17.0"} [features] raw-module-api = [] diff --git a/fluence-faas/src/faas.rs b/fluence-faas/src/faas.rs index 614eef3c..1894923b 100644 --- a/fluence-faas/src/faas.rs +++ b/fluence-faas/src/faas.rs @@ -22,12 +22,16 @@ use crate::Result; use crate::IValue; use fce::FCE; +use fluence_sdk_main::CallParameters; +use std::cell::RefCell; use std::convert::TryInto; use std::collections::HashSet; use std::collections::HashMap; +use std::rc::Rc; use std::fs; -use std::path::{PathBuf, Path}; +use std::path::PathBuf; +use std::path::Path; // TODO: remove and use mutex instead unsafe impl Send for FluenceFaaS {} @@ -92,7 +96,11 @@ impl<'a> ModulesLoadStrategy<'a> { } pub struct FluenceFaaS { + /// The Fluence Compute Engine instance. fce: FCE, + + /// Parameters of call accessible by Wasm modules. + call_parameters: Rc>, } impl FluenceFaaS { @@ -127,6 +135,7 @@ impl FluenceFaaS { { let mut fce = FCE::new(); let config = config.try_into()?; + let call_parameters = Rc::new(RefCell::new(<_>::default())); for (module_name, module_config) in config.modules_config { let module_bytes = modules.remove(&module_name).ok_or_else(|| { @@ -135,11 +144,15 @@ impl FluenceFaaS { module_name )) })?; - let fce_module_config = crate::misc::make_fce_config(Some(module_config))?; + let fce_module_config = + crate::misc::make_fce_config(Some(module_config), call_parameters.clone())?; fce.load_module(module_name, &module_bytes, fce_module_config)?; } - Ok(Self { fce }) + Ok(Self { + fce, + call_parameters, + }) } /// Searches for modules in `config.modules_dir`, loads only those in the `names` set @@ -215,7 +228,10 @@ impl FluenceFaaS { module_name: MN, func_name: FN, args: &[IValue], + call_parameters: fluence_sdk_main::CallParameters, ) -> Result> { + self.call_parameters.replace(call_parameters); + self.fce .call(module_name, func_name, args) .map_err(Into::into) @@ -258,7 +274,7 @@ impl FluenceFaaS { { let config = config.map(|c| c.try_into()).transpose()?; - let fce_module_config = crate::misc::make_fce_config(config)?; + let fce_module_config = crate::misc::make_fce_config(config, self.call_parameters.clone())?; self.fce .load_module(name, &wasm_bytes, fce_module_config) .map_err(Into::into) diff --git a/fluence-faas/src/faas_interface.rs b/fluence-faas/src/faas_interface.rs index 70f919f2..8f6fcfca 100644 --- a/fluence-faas/src/faas_interface.rs +++ b/fluence-faas/src/faas_interface.rs @@ -22,12 +22,12 @@ use serde::Serializer; use std::fmt; use std::collections::HashMap; -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub struct FaaSInterface<'a> { pub modules: HashMap<&'a str, HashMap<&'a str, FaaSFunctionSignature<'a>>>, } -#[derive(Debug, Serialize)] +#[derive(Debug, PartialEq, Clone)] pub struct FaaSFunctionSignature<'a> { pub input_types: &'a Vec, pub output_types: &'a Vec, diff --git a/fluence-faas/src/lib.rs b/fluence-faas/src/lib.rs index 42c2995a..e869664f 100644 --- a/fluence-faas/src/lib.rs +++ b/fluence-faas/src/lib.rs @@ -38,8 +38,11 @@ pub use fce::IType; pub use fce::to_interface_value; pub use fce::from_interface_values; +pub use fluence_sdk_main::CallParameters; + pub use faas::FluenceFaaS; pub use faas_interface::FaaSInterface; +pub use faas_interface::FaaSFunctionSignature; pub use misc::RawModulesConfig; pub use misc::RawModuleConfig; diff --git a/fluence-faas/src/misc/imports.rs b/fluence-faas/src/misc/imports.rs index c5813cca..317e125a 100644 --- a/fluence-faas/src/misc/imports.rs +++ b/fluence-faas/src/misc/imports.rs @@ -23,6 +23,9 @@ use wasmer_core::types::Value; use wasmer_core::types::Type; use wasmer_core::types::FuncSig; +use std::cell::RefCell; +use std::rc::Rc; + const ALLOCATE_FUNC_NAME: &str = "allocate"; const SET_PTR_FUNC_NAME: &str = "set_result_ptr"; const SET_SIZE_FUNC_NAME: &str = "set_result_size"; @@ -33,19 +36,17 @@ pub(super) fn log_utf8_string(ctx: &mut Ctx, offset: i32, size: i32) { let wasm_ptr = WasmPtr::::new(offset as _); match wasm_ptr.get_utf8_string(ctx.memory(0), size as _) { Some(msg) => log::info!("{}", msg), - None => log::warn!("ipfs node logger: incorrect UTF8 string's been supplied to logger"), + None => log::warn!("logger: incorrect UTF8 string's been supplied to logger"), } } fn write_to_mem(context: &mut Ctx, address: usize, value: &[u8]) { let memory = context.memory(0); - for (byte_id, cell) in memory.view::()[address as usize..(address + value.len())] + memory.view::()[address..(address + value.len())] .iter() - .enumerate() - { - cell.set(value[byte_id]); - } + .zip(value.iter()) + .for_each(|(cell, byte)| cell.set(*byte)); } pub(super) fn create_host_import_func(host_cmd: S) -> DynamicFunc<'static> @@ -53,9 +54,7 @@ where S: Into, { use wasmer_core::Func; - use std::cell::RefCell; - //#[rustfmt:skip] let allocate_func: Box>>> = Box::new(RefCell::new(None)); let set_result_ptr_func: Box>>> = Box::new(RefCell::new(None)); @@ -75,7 +74,7 @@ where let wasm_ptr = WasmPtr::::new(array_ptr as _); let result = match wasm_ptr.get_utf8_string(ctx.memory(0), array_size as _) { Some(arg_value) => cmd_lib::run_fun!("{} {}", host_cmd, arg_value).unwrap(), - None => return vec![Value::I32(1)], + None => return vec![], }; unsafe { @@ -88,7 +87,7 @@ where call_wasm_func!(set_result_ptr_func, mem_address); call_wasm_func!(set_result_size_func, result.len() as i32); - vec![Value::I32(0)] + vec![] } }; @@ -97,3 +96,70 @@ where func, ) } + +pub(super) fn create_get_call_parameters_func( + call_parameters: Rc>, +) -> DynamicFunc<'static> { + use wasmer_core::Func; + + let allocate_func: Box>>> = Box::new(RefCell::new(None)); + + // TODO: refactor this approach after switching to the new Wasmer + let func = move |ctx: &mut Ctx, _inputs: &[Value]| -> Vec { + unsafe { + init_wasm_func_once!(allocate_func, ctx, i32, i32, ALLOCATE_FUNC_NAME, 2); + + let call_id_ptr = + call_wasm_func!(allocate_func, call_parameters.borrow().call_id.len() as i32); + let user_name_ptr = call_wasm_func!( + allocate_func, + call_parameters.borrow().user_name.len() as i32 + ); + let application_id_ptr = call_wasm_func!( + allocate_func, + call_parameters.borrow().application_id.len() as i32 + ); + + write_to_mem( + ctx, + call_id_ptr as usize, + call_parameters.borrow().call_id.as_bytes(), + ); + write_to_mem( + ctx, + user_name_ptr as usize, + call_parameters.borrow().user_name.as_bytes(), + ); + write_to_mem( + ctx, + application_id_ptr as usize, + call_parameters.borrow().application_id.as_bytes(), + ); + + let mut serialized_call_parameters = Vec::new(); + serialized_call_parameters.push(call_id_ptr as u64); + serialized_call_parameters.push(call_parameters.borrow().call_id.len() as u64); + serialized_call_parameters.push(user_name_ptr as u64); + serialized_call_parameters.push(call_parameters.borrow().user_name.len() as u64); + serialized_call_parameters.push(application_id_ptr as u64); + serialized_call_parameters.push(call_parameters.borrow().application_id.len() as u64); + + let serialized_call_parameters_ptr = + call_wasm_func!(allocate_func, serialized_call_parameters.len() as i32); + let serialized_call_parameters_bytes = + safe_transmute::transmute_to_bytes::(&serialized_call_parameters); + write_to_mem( + ctx, + serialized_call_parameters_ptr as usize, + serialized_call_parameters_bytes, + ); + + vec![Value::I32(serialized_call_parameters_ptr as _)] + } + }; + + DynamicFunc::new( + std::sync::Arc::new(FuncSig::new(vec![], vec![Type::I32])), + func, + ) +} diff --git a/fluence-faas/src/misc/utils.rs b/fluence-faas/src/misc/utils.rs index 8e39a7fb..edd49b1b 100644 --- a/fluence-faas/src/misc/utils.rs +++ b/fluence-faas/src/misc/utils.rs @@ -102,8 +102,10 @@ where /// Make FCE config based on parsed config. pub(crate) fn make_fce_config( module_config: Option, + call_parameters: std::rc::Rc>, ) -> crate::Result { use super::imports::create_host_import_func; + use super::imports::create_get_call_parameters_func; use super::imports::log_utf8_string; use wasmer_core::import::Namespace; use std::path::PathBuf; @@ -159,6 +161,10 @@ pub(crate) fn make_fce_config( namespace.insert(import_name, host_import); } } + namespace.insert( + "get_call_parameters", + create_get_call_parameters_func(call_parameters), + ); let mut import_object = ImportObject::new(); import_object.register("host", namespace); @@ -181,7 +187,7 @@ macro_rules! init_wasm_func_once { }; // assumed that this function will be used only in the context of closure - // linked to a corresponding Wasm import - os it is safe to make is static + // linked to a corresponding Wasm import, so it is safe to make is static let raw_func = std::mem::transmute::, Func<'static, _, _>>(raw_func); *$func.borrow_mut() = Some(raw_func); diff --git a/fluence-faas/tests/greeting.rs b/fluence-faas/tests/greeting.rs index b8c40c7b..9bcbeff8 100644 --- a/fluence-faas/tests/greeting.rs +++ b/fluence-faas/tests/greeting.rs @@ -36,13 +36,80 @@ pub fn greeting() { "greeting", "greeting", &[IValue::String(String::from("Fluence"))], + <_>::default(), ) .unwrap_or_else(|e| panic!("can't invoke greeting: {:?}", e)); let result2 = faas - .call("greeting", "greeting", &[IValue::String(String::from(""))]) + .call( + "greeting", + "greeting", + &[IValue::String(String::from(""))], + <_>::default(), + ) .unwrap_or_else(|e| panic!("can't invoke greeting: {:?}", e)); assert_eq!(result1, vec![IValue::String(String::from("Hi, Fluence"))]); assert_eq!(result2, vec![IValue::String(String::from("Hi, "))]); } + +#[test] +pub fn get_interfaces() { + let greeting_config_path = "../examples/greeting/Config.toml"; + + let greeting_config_raw = std::fs::read(greeting_config_path) + .expect("../examples/greeting/Config.toml should presence"); + + let mut greeting_config: fluence_faas::RawModulesConfig = + toml::from_slice(&greeting_config_raw).expect("greeting config should be well-formed"); + greeting_config.modules_dir = Some(String::from("../examples/greeting/artifacts")); + + let faas = FluenceFaaS::with_raw_config(greeting_config) + .unwrap_or_else(|e| panic!("can't crate Fluence FaaS instance: {:?}", e)); + + let interface = faas.get_interface(); + + let string_type_params = vec![fluence_faas::IType::String]; + let greeting_sign = fluence_faas::FaaSFunctionSignature { + input_types: &string_type_params, + output_types: &string_type_params, + }; + + let mut functions = std::collections::HashMap::new(); + functions.insert("greeting", greeting_sign); + + let mut modules = std::collections::HashMap::new(); + modules.insert("greeting", functions); + + assert_eq!(interface, fluence_faas::FaaSInterface { modules }); +} + +#[test] +pub fn call_parameters() { + let greeting_config_path = "../examples/greeting/Config_cp.toml"; + + let greeting_config_raw = std::fs::read(greeting_config_path) + .expect("../examples/greeting/Config_cp.toml should presence"); + + let mut greeting_config: fluence_faas::RawModulesConfig = + toml::from_slice(&greeting_config_raw).expect("greeting config should be well-formed"); + greeting_config.modules_dir = Some(String::from("../examples/greeting/artifacts")); + + let mut faas = FluenceFaaS::with_raw_config(greeting_config) + .unwrap_or_else(|e| panic!("can't crate Fluence FaaS instance: {:?}", e)); + + let result = faas + .call( + "greeting_cp", + "greeting", + &[], + fluence_sdk_main::CallParameters { + call_id: "0x1337".to_string(), + user_name: "root".to_string(), + application_id: "0x31337".to_string(), + }, + ) + .unwrap_or_else(|e| panic!("can't invoke greeting_cp: {:?}", e)); + + assert_eq!(result, vec![IValue::String(String::from("Hi, root"))]); +} diff --git a/fluence-faas/tests/records.rs b/fluence-faas/tests/records.rs index 34f2c767..0d81c03b 100644 --- a/fluence-faas/tests/records.rs +++ b/fluence-faas/tests/records.rs @@ -33,7 +33,7 @@ pub fn records() { .unwrap_or_else(|e| panic!("can't crate Fluence FaaS instance: {:?}", e)); let result = faas - .call("pure", "invoke", &[]) + .call("pure", "invoke", &[], <_>::default()) .unwrap_or_else(|e| panic!("can't invoke pure: {:?}", e)); assert_eq!( diff --git a/tools/repl/src/repl.rs b/tools/repl/src/repl.rs index 1e3b9f0d..b0c44d8b 100644 --- a/tools/repl/src/repl.rs +++ b/tools/repl/src/repl.rs @@ -110,13 +110,18 @@ impl REPL { }; let start = Instant::now(); - let result = match self.app_service.call(module_name, func_name, module_arg) { - Ok(result) => { - let elapsed_time = start.elapsed(); - format!("result: {:?}\n elapsed time: {:?}", result, elapsed_time) - } - Err(e) => format!("execution failed with {:?}", e), - }; + // TODO: add support of call parameters + let result = + match self + .app_service + .call(module_name, func_name, module_arg, <_>::default()) + { + Ok(result) => { + let elapsed_time = start.elapsed(); + format!("result: {:?}\n elapsed time: {:?}", result, elapsed_time) + } + Err(e) => format!("execution failed with {:?}", e), + }; println!("{}", result); } Some("envs") => {