From 529e46d9882108dddd10a1b3b9156e646e69c624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Fri, 1 Jul 2022 15:34:32 +0200 Subject: [PATCH 1/4] Support --invoke option for emscripten files without _start function --- CHANGELOG.md | 1 + lib/cli/src/commands/run.rs | 5 +--- lib/emscripten/src/lib.rs | 58 ++++++++++++++++++++++++++++--------- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e2a4b2c788..e9eae516e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Looking for changes that affect our C API? See the [C API Changelog](lib/c-api/C ### Changed - [#3003](https://github.com/wasmerio/wasmer/pull/3003) Remove RuntimeError::raise from public API +- [#2999](https://github.com/wasmerio/wasmer/pull/2999) Allow `--invoke` CLI option for Emscripten files without a `main` function - [#2946](https://github.com/wasmerio/wasmer/pull/2946) Remove dylib,staticlib engines in favor of a single Universal engine - [#2949](https://github.com/wasmerio/wasmer/pull/2949) Switch back to using custom LLVM builds on CI diff --git a/lib/cli/src/commands/run.rs b/lib/cli/src/commands/run.rs index ec7b81a2cfc..ea5e38f415d 100644 --- a/lib/cli/src/commands/run.rs +++ b/lib/cli/src/commands/run.rs @@ -105,9 +105,6 @@ impl Run { }; // TODO: refactor this if is_emscripten_module(&module) { - if self.invoke.is_some() { - bail!("--invoke is not supported with emscripten modules"); - } let mut emscripten_globals = EmscriptenGlobals::new(module.store(), &module) .map_err(|e| anyhow!("{}", e))?; let mut em_env = EmEnv::new(&emscripten_globals.data, Default::default()); @@ -137,7 +134,7 @@ impl Run { self.path.to_str().unwrap() }, self.args.iter().map(|arg| arg.as_str()).collect(), - None, //run.em_entrypoint.clone(), + self.invoke.clone(), )?; return Ok(()); } diff --git a/lib/emscripten/src/lib.rs b/lib/emscripten/src/lib.rs index 30e619a69c5..6cc873adcc9 100644 --- a/lib/emscripten/src/lib.rs +++ b/lib/emscripten/src/lib.rs @@ -23,9 +23,9 @@ use std::f64; use std::path::PathBuf; use std::sync::{Arc, Mutex, RwLock}; use wasmer::{ - imports, namespace, Exports, Function, FunctionType, Global, Imports, Instance, LazyInit, - Memory, MemoryType, Module, Pages, RuntimeError, Store, Table, TableType, TypedFunction, Val, - ValType, WasmPtr, WasmerEnv, + imports, namespace, ExportError, Exports, Function, FunctionType, Global, Imports, Instance, + LazyInit, Memory, MemoryType, Module, Pages, RuntimeError, Store, Table, TableType, + TypedFunction, Val, ValType, WasmPtr, WasmerEnv, }; #[cfg(unix)] @@ -313,24 +313,51 @@ pub fn set_up_emscripten(instance: &mut Instance) -> Result<(), RuntimeError> { Ok(()) } +/// Looks for variations of the main function (usually +/// `["_main", "main"])`, then returns a reference to +/// the found function. Useful for determining whether +/// a module is executable. +pub fn emscripten_get_main_func_name<'a>( + instance: &Instance, + main_func_names: &[&'a str], +) -> Result<&'a str, ExportError> { + let mut function_name = None; + let mut last_err = None; + + for func_name in main_func_names.iter() { + match instance.exports.get::(func_name) { + Ok(_) => { + function_name = Some(func_name); + break; + } + Err(e) => { + last_err = Some(e); + } + } + } + + match (function_name, last_err) { + (Some(s), _) => Ok(s), + (None, None) => Err(ExportError::Missing(format!("{main_func_names:?}"))), + (None, Some(e)) => Err(e), + } +} + /// Call the main function in emscripten, assumes that the emscripten state is /// set up. /// /// If you don't want to set it up yourself, consider using [`run_emscripten_instance`]. pub fn emscripten_call_main( instance: &mut Instance, + function_name: &str, env: &EmEnv, path: &str, args: &[&str], ) -> Result<(), RuntimeError> { - let (function_name, main_func) = match instance.exports.get::("_main") { - Ok(func) => Ok(("_main", func)), - Err(_e) => instance - .exports - .get::("main") - .map(|func| ("main", func)), - } - .map_err(|e| RuntimeError::new(e.to_string()))?; + let main_func = instance + .exports + .get::(function_name) + .map_err(|e| RuntimeError::new(e.to_string()))?; let num_params = main_func.ty().params().len(); let _result = match num_params { 2 => { @@ -373,8 +400,7 @@ pub fn run_emscripten_instance( env.set_memory(globals.memory.clone()); set_up_emscripten(instance)?; - // println!("running emscripten instance"); - + let main_func_names = ["_main", "main"]; if let Some(ep) = entrypoint { debug!("Running entry point: {}", &ep); let arg = unsafe { allocate_cstr_on_stack(env, args[0]).0 }; @@ -384,8 +410,12 @@ pub fn run_emscripten_instance( .get(&ep) .map_err(|e| RuntimeError::new(e.to_string()))?; func.call(&[Val::I32(arg as i32)])?; + } else if let Ok(name) = emscripten_get_main_func_name(instance, &main_func_names) { + emscripten_call_main(instance, name, env, path, &args)?; } else { - emscripten_call_main(instance, env, path, &args)?; + return Err(RuntimeError::new(format!( + "No main function found (searched: {main_func_names:?}) and no entrypoint specified" + ))); } // TODO atexit for emscripten From d2eb26c209e3d5ec405b5481ca049b782c734c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Mon, 4 Jul 2022 14:18:41 +0200 Subject: [PATCH 2/4] Rework run() to work with python37.emscripten.wasm --- lib/emscripten/src/lib.rs | 30 +++++++++------------- tests/integration/cli/tests/run.rs | 40 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/lib/emscripten/src/lib.rs b/lib/emscripten/src/lib.rs index 6cc873adcc9..294d0c0af22 100644 --- a/lib/emscripten/src/lib.rs +++ b/lib/emscripten/src/lib.rs @@ -315,20 +315,21 @@ pub fn set_up_emscripten(instance: &mut Instance) -> Result<(), RuntimeError> { /// Looks for variations of the main function (usually /// `["_main", "main"])`, then returns a reference to -/// the found function. Useful for determining whether -/// a module is executable. +/// the name of the first found function. Useful for +/// determining whether a module is executable. +/// +/// Returns `ExportError` if none of the `main_func_names` +/// were found. pub fn emscripten_get_main_func_name<'a>( instance: &Instance, main_func_names: &[&'a str], ) -> Result<&'a str, ExportError> { - let mut function_name = None; let mut last_err = None; for func_name in main_func_names.iter() { match instance.exports.get::(func_name) { Ok(_) => { - function_name = Some(func_name); - break; + return Ok(func_name); } Err(e) => { last_err = Some(e); @@ -336,10 +337,9 @@ pub fn emscripten_get_main_func_name<'a>( } } - match (function_name, last_err) { - (Some(s), _) => Ok(s), - (None, None) => Err(ExportError::Missing(format!("{main_func_names:?}"))), - (None, Some(e)) => Err(e), + match last_err { + None => Err(ExportError::Missing(format!("{main_func_names:?}"))), + Some(e) => Err(e), } } @@ -401,15 +401,9 @@ pub fn run_emscripten_instance( set_up_emscripten(instance)?; let main_func_names = ["_main", "main"]; - if let Some(ep) = entrypoint { - debug!("Running entry point: {}", &ep); - let arg = unsafe { allocate_cstr_on_stack(env, args[0]).0 }; - //let (argc, argv) = store_module_arguments(instance.context_mut(), args); - let func: &Function = instance - .exports - .get(&ep) - .map_err(|e| RuntimeError::new(e.to_string()))?; - func.call(&[Val::I32(arg as i32)])?; + if let Some(ep) = entrypoint.as_ref() { + debug!("Running entry point: {}", ep); + emscripten_call_main(instance, ep, env, path, &args)?; } else if let Ok(name) = emscripten_get_main_func_name(instance, &main_func_names) { emscripten_call_main(instance, name, env, path, &args)?; } else { diff --git a/tests/integration/cli/tests/run.rs b/tests/integration/cli/tests/run.rs index 07fddc86564..36d56d6e6e5 100644 --- a/tests/integration/cli/tests/run.rs +++ b/tests/integration/cli/tests/run.rs @@ -16,6 +16,46 @@ fn test_no_start_wat_path() -> String { format!("{}/{}", ASSET_PATH, "no_start.wat") } +// This test verifies that "wasmer run --invoke _start module.emscripten.wat" +// works the same as "wasmer run module.emscripten.wat" (without --invoke). +#[test] +fn run_invoke_works_with_nomain_emscripten() -> anyhow::Result<()> { + // In this example the function "wasi_unstable.arg_sizes_get" + // is a function that is imported from the WASI env. + let emscripten_wat = include_bytes(""); + let random = rand::random::(); + let module_file = std::env::temp_dir().join(&format!("{random}.emscripten.wat")); + std::fs::write(&module_file, wasi_wat.as_bytes()).unwrap(); + let output = Command::new(WASMER_PATH) + .arg("run") + .arg(&module_file) + .output()?; + + let stderr = std::str::from_utf8(&output.stderr).unwrap().to_string(); + let success = output.status.success(); + if !success { + println!("ERROR in 'wasmer run [module.emscripten.wat]':\r\n{stderr}"); + panic!(); + } + + let output = Command::new(WASMER_PATH) + .arg("run") + .arg("--invoke") + .arg("_start") + .arg(&module_file) + .output()?; + + let stderr = std::str::from_utf8(&output.stderr).unwrap().to_string(); + let success = output.status.success(); + if !success { + println!("ERROR in 'wasmer run --invoke _start [module.emscripten.wat]':\r\n{stderr}"); + panic!(); + } + + std::fs::remove_file(&module_file).unwrap(); + Ok(()) +} + #[test] fn run_wasi_works() -> anyhow::Result<()> { let output = Command::new(WASMER_PATH) From f13d8fb748dfa3c747cb2a3d2e19d82c9539cc73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Mon, 4 Jul 2022 16:39:57 +0200 Subject: [PATCH 3/4] Remove non-working emscripten integration test --- tests/integration/cli/tests/run.rs | 40 ------------------------------ 1 file changed, 40 deletions(-) diff --git a/tests/integration/cli/tests/run.rs b/tests/integration/cli/tests/run.rs index 36d56d6e6e5..07fddc86564 100644 --- a/tests/integration/cli/tests/run.rs +++ b/tests/integration/cli/tests/run.rs @@ -16,46 +16,6 @@ fn test_no_start_wat_path() -> String { format!("{}/{}", ASSET_PATH, "no_start.wat") } -// This test verifies that "wasmer run --invoke _start module.emscripten.wat" -// works the same as "wasmer run module.emscripten.wat" (without --invoke). -#[test] -fn run_invoke_works_with_nomain_emscripten() -> anyhow::Result<()> { - // In this example the function "wasi_unstable.arg_sizes_get" - // is a function that is imported from the WASI env. - let emscripten_wat = include_bytes(""); - let random = rand::random::(); - let module_file = std::env::temp_dir().join(&format!("{random}.emscripten.wat")); - std::fs::write(&module_file, wasi_wat.as_bytes()).unwrap(); - let output = Command::new(WASMER_PATH) - .arg("run") - .arg(&module_file) - .output()?; - - let stderr = std::str::from_utf8(&output.stderr).unwrap().to_string(); - let success = output.status.success(); - if !success { - println!("ERROR in 'wasmer run [module.emscripten.wat]':\r\n{stderr}"); - panic!(); - } - - let output = Command::new(WASMER_PATH) - .arg("run") - .arg("--invoke") - .arg("_start") - .arg(&module_file) - .output()?; - - let stderr = std::str::from_utf8(&output.stderr).unwrap().to_string(); - let success = output.status.success(); - if !success { - println!("ERROR in 'wasmer run --invoke _start [module.emscripten.wat]':\r\n{stderr}"); - panic!(); - } - - std::fs::remove_file(&module_file).unwrap(); - Ok(()) -} - #[test] fn run_wasi_works() -> anyhow::Result<()> { let output = Command::new(WASMER_PATH) From 2d4edeec43f1422b90bca94fd5f9eca4b6ec7291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Mon, 4 Jul 2022 16:41:13 +0200 Subject: [PATCH 4/4] Use --no-fail-fast in test-integration to see all failures in CI --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 93fa0ca5749..66d618fd0f3 100644 --- a/Makefile +++ b/Makefile @@ -539,7 +539,7 @@ test-examples: $(CARGO_BINARY) test $(CARGO_TARGET) --release $(compiler_features) --features wasi --examples test-integration: - $(CARGO_BINARY) test $(CARGO_TARGET) -p wasmer-integration-tests-cli + $(CARGO_BINARY) test $(CARGO_TARGET) --no-fail-fast -p wasmer-integration-tests-cli test-integration-ios: $(CARGO_BINARY) test $(CARGO_TARGET) -p wasmer-integration-tests-ios