From 65265dbd73c01c8660ed79b570ebef9de8e07a2c Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 8 Oct 2020 14:13:54 -0700 Subject: [PATCH 01/16] Add first draft of `wasmer create-exe` --- Cargo.lock | 1 + lib/c-api/wasmer_wasm.h | 3 + lib/cli/Cargo.toml | 1 + lib/cli/src/bin/wasmer.rs | 9 + lib/cli/src/commands.rs | 4 + lib/cli/src/commands/compile.rs | 5 +- lib/cli/src/commands/create_exe.rs | 273 +++++++++++++++++ lib/cli/src/commands/wasmer_create_exe_main.c | 89 ++++++ lib/cli/src/store.rs | 286 ++++++++++-------- 9 files changed, 543 insertions(+), 128 deletions(-) create mode 100644 lib/cli/src/commands/create_exe.rs create mode 100644 lib/cli/src/commands/wasmer_create_exe_main.c diff --git a/Cargo.lock b/Cargo.lock index e27fa5c4a78..3ce702103fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2213,6 +2213,7 @@ dependencies = [ "fern", "log", "structopt", + "tempfile", "wasmer", "wasmer-cache", "wasmer-compiler", diff --git a/lib/c-api/wasmer_wasm.h b/lib/c-api/wasmer_wasm.h index 88510282739..b9eea99ae71 100644 --- a/lib/c-api/wasmer_wasm.h +++ b/lib/c-api/wasmer_wasm.h @@ -28,6 +28,9 @@ # define DEPRECATED(message) __declspec(deprecated(message)) #endif +// The `jit` feature has been enabled for this build. +#define WASMER_JIT_ENABLED + // The `compiler` feature has been enabled for this build. #define WASMER_COMPILER_ENABLED diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index b73828bf84f..19f353b9cef 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -45,6 +45,7 @@ cfg-if = "0.1" # For debug feature fern = { version = "0.6", features = ["colored"], optional = true } log = { version = "0.4", optional = true } +tempfile = "3" [features] # Don't add the compiler features in default, please add them on the Makefile diff --git a/lib/cli/src/bin/wasmer.rs b/lib/cli/src/bin/wasmer.rs index 2b1d5792edc..6d72964bf36 100644 --- a/lib/cli/src/bin/wasmer.rs +++ b/lib/cli/src/bin/wasmer.rs @@ -1,4 +1,6 @@ use anyhow::Result; +#[cfg(feature = "object-file")] +use wasmer_cli::commands::CreateExe; #[cfg(feature = "wast")] use wasmer_cli::commands::Wast; use wasmer_cli::commands::{Cache, Compile, Config, Inspect, Run, SelfUpdate, Validate}; @@ -26,6 +28,11 @@ enum WasmerCLIOptions { #[structopt(name = "compile")] Compile(Compile), + /// Compile a WebAssembly binary into a native executable + #[cfg(feature = "object-file")] + #[structopt(name = "create-exe")] + CreateExe(CreateExe), + /// Get various configuration information needed /// to compile programs which use Wasmer #[structopt(name = "config")] @@ -53,6 +60,8 @@ impl WasmerCLIOptions { Self::Cache(cache) => cache.execute(), Self::Validate(validate) => validate.execute(), Self::Compile(compile) => compile.execute(), + #[cfg(feature = "object-file")] + Self::CreateExe(create_exe) => create_exe.execute(), Self::Config(config) => config.execute(), Self::Inspect(inspect) => inspect.execute(), #[cfg(feature = "wast")] diff --git a/lib/cli/src/commands.rs b/lib/cli/src/commands.rs index fe7036e3625..4d07c37e8ed 100644 --- a/lib/cli/src/commands.rs +++ b/lib/cli/src/commands.rs @@ -2,6 +2,8 @@ mod cache; mod compile; mod config; +#[cfg(feature = "object-file")] +mod create_exe; mod inspect; mod run; mod self_update; @@ -9,6 +11,8 @@ mod validate; #[cfg(feature = "wast")] mod wast; +#[cfg(feature = "object-file")] +pub use create_exe::*; #[cfg(feature = "wast")] pub use wast::*; pub use {cache::*, compile::*, config::*, inspect::*, run::*, self_update::*, validate::*}; diff --git a/lib/cli/src/commands/compile.rs b/lib/cli/src/commands/compile.rs index 1e77a08f2d7..bd70d6ffc25 100644 --- a/lib/cli/src/commands/compile.rs +++ b/lib/cli/src/commands/compile.rs @@ -38,8 +38,7 @@ impl Compile { .context(format!("failed to compile `{}`", self.path.display())) } - fn get_recommend_extension( - &self, + pub(crate) fn get_recommend_extension( engine_type: &EngineType, target_triple: &Triple, ) -> &'static str { @@ -82,7 +81,7 @@ impl Compile { .file_stem() .map(|osstr| osstr.to_string_lossy().to_string()) .unwrap_or_default(); - let recommended_extension = self.get_recommend_extension(&engine_type, target.triple()); + let recommended_extension = Self::get_recommend_extension(&engine_type, target.triple()); match self.output.extension() { Some(ext) => { if ext != recommended_extension { diff --git a/lib/cli/src/commands/create_exe.rs b/lib/cli/src/commands/create_exe.rs new file mode 100644 index 00000000000..0238da70859 --- /dev/null +++ b/lib/cli/src/commands/create_exe.rs @@ -0,0 +1,273 @@ +//! Create a standalone native executable for a given Wasm file. + +use crate::store::{CompilerOptions, EngineType}; +use anyhow::{Context, Result}; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use structopt::StructOpt; +use wasmer::*; + +const WASMER_MAIN_C_SOURCE: &[u8] = include_bytes!("wasmer_create_exe_main.c"); + +// TODO: to get this working locally I had to add wasmer_wasm.h and wasm.h to `.wasmer/include` manually. +// this needs to be fixed before we can ship this. + +#[derive(Debug, StructOpt)] +/// The options for the `wasmer create-exe` subcommand +pub struct CreateExe { + /// Input file + #[structopt(name = "FILE", parse(from_os_str))] + path: PathBuf, + + /// Output file + #[structopt(name = "OUTPUT PATH", short = "o", parse(from_os_str))] + output: PathBuf, + + /// Compilation Target triple + #[structopt(long = "target")] + target_triple: Option, + + #[structopt(flatten)] + compiler: CompilerOptions, + + #[structopt(short = "m", multiple = true)] + cpu_features: Vec, + + /// Additional libraries to link against. + /// This is useful for fixing linker errors that may occur on some systems. + #[structopt(short = "l", multiple = true)] + libraries: Vec, +} + +impl CreateExe { + /// Runs logic for the `compile` subcommand + pub fn execute(&self) -> Result<()> { + let target = self + .target_triple + .as_ref() + .map(|target_triple| { + let mut features = self + .cpu_features + .clone() + .into_iter() + .fold(CpuFeature::set(), |a, b| a | b); + // Cranelift requires SSE2, so we have this "hack" for now to facilitate + // usage + features |= CpuFeature::SSE2; + Target::new(target_triple.clone(), features) + }) + .unwrap_or_default(); + let engine_type = EngineType::ObjectFile; + let (store, compiler_type) = self + .compiler + .get_store_for_target_and_engine(target.clone(), engine_type)?; + + println!("Engine: {}", engine_type.to_string()); + println!("Compiler: {}", compiler_type.to_string()); + println!("Target: {}", target.triple()); + + let working_dir = tempfile::tempdir()?; + let starting_cd = env::current_dir()?; + let output_path = starting_cd.join(&self.output); + env::set_current_dir(&working_dir)?; + + // TODO: encapsulate compile code + + #[cfg(not(windows))] + let wasm_object_path = PathBuf::from("wasm.o"); + #[cfg(windows)] + let wasm_object_path = PathBuf::from("wasm.obj"); + + let wasm_module_path = starting_cd.join(&self.path); + + let header_file_path = Path::new("my_wasm.h"); + let module = + Module::from_file(&store, &wasm_module_path).context("failed to compile Wasm")?; + let _ = module.serialize_to_file(&wasm_object_path)?; + + let artifact: &wasmer_engine_object_file::ObjectFileArtifact = + module.artifact().as_ref().downcast_ref().context( + "Engine type is ObjectFile but could not downcast artifact into ObjectFileArtifact", + )?; + let symbol_registry = artifact.symbol_registry(); + let metadata_length = artifact.metadata_length(); + let module_info = module.info(); + let header_file_src = crate::c_gen::object_file_header::generate_header_file( + module_info, + symbol_registry, + metadata_length, + ); + + let header_path = header_file_path.clone(); + // for C code + let mut header = std::fs::OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&header_path)?; + + use std::io::Write; + header.write(header_file_src.as_bytes())?; + + // auto compilation + // + + // write C src to disk + let c_src_path = Path::new("wasmer_main.c"); + #[cfg(not(windows))] + let c_src_obj = PathBuf::from("wasmer_main.o"); + #[cfg(windows)] + let c_src_obj = PathBuf::from("wasmer_main.obj"); + + { + let mut c_src_file = fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(&c_src_path) + .context("Failed to open C source code file")?; + // TODO: + c_src_file.write_all(WASMER_MAIN_C_SOURCE)?; + } + run_c_compile(&c_src_path, &c_src_obj).context("Failed to compile C source code")?; + LinkCode { + object_paths: vec![c_src_obj, wasm_object_path], + output_path, + additional_libraries: self.libraries.clone(), + ..Default::default() + } + .run() + .context("Failed to link objects together")?; + + eprintln!( + "✔ Native executable compiled successfully to `{}`.", + self.output.display(), + ); + + Ok(()) + } +} + +fn get_wasmer_include_directory() -> anyhow::Result { + let mut path = PathBuf::from(env::var("WASMER_DIR")?); + path.push("include"); + Ok(path) +} + +fn get_libwasmer_path() -> anyhow::Result { + let mut path = PathBuf::from(env::var("WASMER_DIR")?); + path.push("lib"); + + #[cfg(not(windows))] + path.push("libwasmer.a"); + #[cfg(windows)] + path.push("libwasmer.lib"); + + Ok(path) +} + +/// Compile the C code. +fn run_c_compile(path_to_c_src: &Path, output_name: &Path) -> anyhow::Result<()> { + #[cfg(not(windows))] + let c_compiler = "cc"; + #[cfg(windows)] + let c_compiler = "clang++"; + + let output = Command::new(c_compiler) + .arg("-O2") + .arg("-c") + .arg(path_to_c_src) + .arg("-I") + .arg(get_wasmer_include_directory()?) + .arg("-o") + .arg(output_name) + .output()?; + + if !output.status.success() { + bail!( + "C code compile failed with: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + Ok(()) +} + +/// Data used to run a linking command for generated artifacts. +#[derive(Debug)] +struct LinkCode { + /// Path to the linker used to run the linking command. + linker_path: PathBuf, + /// String used as an optimization flag. + optimization_flag: String, + /// Paths of objects to link. + object_paths: Vec, + /// Additional libraries to link against. + additional_libraries: Vec, + /// Path to the output target. + output_path: PathBuf, + /// Path to the static libwasmer library. + libwasmer_path: PathBuf, +} + +impl Default for LinkCode { + fn default() -> Self { + #[cfg(not(windows))] + let linker = "cc"; + #[cfg(windows)] + let linker = "clang"; + Self { + linker_path: PathBuf::from(linker), + optimization_flag: String::from("-O2"), + object_paths: vec![], + additional_libraries: vec![], + output_path: PathBuf::from("a.out"), + libwasmer_path: get_libwasmer_path().unwrap(), + } + } +} + +impl LinkCode { + // TODO: `wasmer create-exe` needs a command line flag for extra libraries to link aganist + // or perhaps we just want to add a flag for passing things to the linker + fn run(&self) -> anyhow::Result<()> { + let mut command = Command::new(&self.linker_path); + let command = command + .arg(&self.optimization_flag) + .args( + self.object_paths + .iter() + .map(|path| path.canonicalize().unwrap()), + ) + .arg( + &self + .libwasmer_path + .canonicalize() + .context("Failed to find libwasmer")?, + ); + #[cfg(windows)] + let command = command.arg("-luserenv").arg("-lWs2_32").arg("-ladvapi32"); + #[cfg(not(windows))] + let command = command.arg("-ldl").arg("-lm").arg("-pthread"); + let link_aganist_extra_libs = self + .additional_libraries + .iter() + .map(|lib| format!("-l{}", lib)); + let command = command.args(link_aganist_extra_libs); + let output = command.arg("-o").arg(&self.output_path).output()?; + + if !output.status.success() { + bail!( + "linking failed with: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + Ok(()) + } +} diff --git a/lib/cli/src/commands/wasmer_create_exe_main.c b/lib/cli/src/commands/wasmer_create_exe_main.c new file mode 100644 index 00000000000..ec00f917942 --- /dev/null +++ b/lib/cli/src/commands/wasmer_create_exe_main.c @@ -0,0 +1,89 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#include "wasmer_wasm.h" +#include "wasm.h" +#include "my_wasm.h" + +#include +#include + +#ifdef __cplusplus +} +#endif + +void print_wasmer_error() +{ + int error_len = wasmer_last_error_length(); + printf("Error len: `%d`\n", error_len); + char* error_str = (char*) malloc(error_len); + wasmer_last_error_message(error_str, error_len); + printf("Error str: `%s`\n", error_str); +} + +int main() { + printf("Initializing...\n"); + wasm_config_t* config = wasm_config_new(); + wasm_config_set_engine(config, OBJECT_FILE); + wasm_engine_t* engine = wasm_engine_new_with_config(config); + wasm_store_t* store = wasm_store_new(engine); + + wasm_module_t* module = wasmer_object_file_engine_new(store, "qjs.wasm"); + if (! module) { + printf("Failed to create module\n"); + print_wasmer_error(); + return -1; + } + + // We have now finished the memory buffer book keeping and we have a valid Module. + + // In this example we're passing some JavaScript source code as a command line argument + // to a WASI module that can evaluate JavaScript. + wasi_config_t* wasi_config = wasi_config_new("constant_value_here"); + const char* js_string = "function greet(name) { return JSON.stringify('Hello, ' + name); }; print(greet('World'));"; + wasi_config_arg(wasi_config, "--eval"); + wasi_config_arg(wasi_config, js_string); + wasi_env_t* wasi_env = wasi_env_new(wasi_config); + if (!wasi_env) { + printf("> Error building WASI env!\n"); + print_wasmer_error(); + return 1; + } + + wasm_importtype_vec_t import_types; + wasm_module_imports(module, &import_types); + int num_imports = import_types.size; + wasm_extern_t** imports = (wasm_extern_t**) malloc(num_imports * sizeof(wasm_extern_t*)); + wasm_importtype_vec_delete(&import_types); + + bool get_imports_result = wasi_get_imports(store, module, wasi_env, imports); + if (!get_imports_result) { + printf("> Error getting WASI imports!\n"); + print_wasmer_error(); + return 1; + } + + wasm_instance_t* instance = wasm_instance_new(store, module, (const wasm_extern_t* const*) imports, NULL); + if (! instance) { + printf("Failed to create instance\n"); + print_wasmer_error(); + return -1; + } + wasi_env_set_instance(wasi_env, instance); + + // WASI is now set up. + + void* vmctx = wasm_instance_get_vmctx_ptr(instance); + wasm_val_t* inout[2] = { NULL, NULL }; + + fflush(stdout); + // We're able to call our compiled function directly through a trampoline. + wasmer_trampoline_function_call__1(vmctx, wasmer_function__1, &inout); + + wasm_instance_delete(instance); + wasm_module_delete(module); + wasm_store_delete(store); + wasm_engine_delete(engine); + return 0; +} diff --git a/lib/cli/src/store.rs b/lib/cli/src/store.rs index 795fcf792fe..9404ac2bcb7 100644 --- a/lib/cli/src/store.rs +++ b/lib/cli/src/store.rs @@ -14,8 +14,27 @@ use wasmer::*; use wasmer_compiler::CompilerConfig; #[derive(Debug, Clone, StructOpt)] -/// The compiler options +/// The compiler and engine options pub struct StoreOptions { + #[structopt(flatten)] + compiler: CompilerOptions, + + /// Use JIT Engine. + #[structopt(long, conflicts_with_all = &["native", "object_file"])] + jit: bool, + + /// Use Native Engine. + #[structopt(long, conflicts_with_all = &["jit", "object_file"])] + native: bool, + + /// Use ObjectFile Engine. + #[structopt(long, conflicts_with_all = &["jit", "native"])] + object_file: bool, +} + +#[derive(Debug, Clone, StructOpt)] +/// The compiler options +pub struct CompilerOptions { /// Use Singlepass compiler. #[structopt(long, conflicts_with_all = &["cranelift", "llvm", "backend"])] singlepass: bool, @@ -36,19 +55,7 @@ pub struct StoreOptions { #[structopt(long, parse(from_os_str))] llvm_debug_dir: Option, - /// Use JIT Engine. - #[structopt(long, conflicts_with_all = &["native", "object_file"])] - jit: bool, - - /// Use Native Engine. - #[structopt(long, conflicts_with_all = &["jit", "object_file"])] - native: bool, - - /// Use ObjectFile Engine. - #[structopt(long, conflicts_with_all = &["jit", "native"])] - object_file: bool, - - /// The deprecated backend flag - Please not use + /// The deprecated backend flag - Please do not use #[structopt(long = "backend", hidden = true, conflicts_with_all = &["singlepass", "cranelift", "llvm"])] backend: Option, @@ -56,80 +63,8 @@ pub struct StoreOptions { features: WasmFeatures, } -/// The compiler used for the store -#[derive(Debug, PartialEq, Eq)] -pub enum CompilerType { - /// Singlepass compiler - Singlepass, - /// Cranelift compiler - Cranelift, - /// LLVM compiler - LLVM, - /// Headless compiler - Headless, -} - -impl CompilerType { - /// Return all enabled compilers - pub fn enabled() -> Vec { - vec![ - #[cfg(feature = "singlepass")] - Self::Singlepass, - #[cfg(feature = "cranelift")] - Self::Cranelift, - #[cfg(feature = "llvm")] - Self::LLVM, - ] - } -} - -impl ToString for CompilerType { - fn to_string(&self) -> String { - match self { - Self::Singlepass => "singlepass".to_string(), - Self::Cranelift => "cranelift".to_string(), - Self::LLVM => "llvm".to_string(), - Self::Headless => "headless".to_string(), - } - } -} - -impl FromStr for CompilerType { - type Err = Error; - fn from_str(s: &str) -> Result { - match s { - "singlepass" => Ok(Self::Singlepass), - "cranelift" => Ok(Self::Cranelift), - "llvm" => Ok(Self::LLVM), - "headless" => Ok(Self::Headless), - backend => bail!("The `{}` compiler does not exist.", backend), - } - } -} - -/// The engine used for the store -#[derive(Debug, PartialEq, Eq)] -pub enum EngineType { - /// JIT Engine - JIT, - /// Native Engine - Native, - /// Object File Engine - ObjectFile, -} - -impl ToString for EngineType { - fn to_string(&self) -> String { - match self { - Self::JIT => "jit".to_string(), - Self::Native => "native".to_string(), - Self::ObjectFile => "objectfile".to_string(), - } - } -} - -#[cfg(all(feature = "compiler", feature = "engine"))] -impl StoreOptions { +#[cfg(feature = "compiler")] +impl CompilerOptions { fn get_compiler(&self) -> Result { if self.cranelift { Ok(CompilerType::Cranelift) @@ -161,7 +96,7 @@ impl StoreOptions { } } - /// Get the Target architecture + /// Get the enaled Wasm features. pub fn get_features(&self, mut features: Features) -> Result { if self.features.threads || self.features.all { features.threads(true); @@ -181,6 +116,63 @@ impl StoreOptions { Ok(features) } + /// Gets the Store for a given target and engine. + pub fn get_store_for_target_and_engine( + &self, + target: Target, + engine_type: EngineType, + ) -> Result<(Store, CompilerType)> { + let (compiler_config, compiler_type) = self.get_compiler_config()?; + let engine = self.get_engine_by_type(target, compiler_config, engine_type)?; + let store = Store::new(&*engine); + Ok((store, compiler_type)) + } + + fn get_engine_by_type( + &self, + target: Target, + compiler_config: Box, + engine_type: EngineType, + ) -> Result> { + let features = self.get_features(compiler_config.default_features_for_target(&target))?; + let engine: Box = match engine_type { + #[cfg(feature = "jit")] + EngineType::JIT => Box::new( + wasmer_engine_jit::JIT::new(&*compiler_config) + .features(features) + .target(target) + .engine(), + ), + #[cfg(feature = "native")] + EngineType::Native => { + let mut compiler_config = compiler_config; + Box::new( + wasmer_engine_native::Native::new(&mut *compiler_config) + .target(target) + .features(features) + .engine(), + ) + } + #[cfg(feature = "object-file")] + EngineType::ObjectFile => { + let mut compiler_config = compiler_config; + Box::new( + wasmer_engine_object_file::ObjectFile::new(&mut *compiler_config) + .target(target) + .features(features) + .engine(), + ) + } + #[cfg(not(all(feature = "jit", feature = "native", feature = "object-file")))] + engine => bail!( + "The `{}` engine is not included in this binary.", + engine.to_string() + ), + }; + + Ok(engine) + } + /// Get the Compiler Config for the current options #[allow(unused_variables)] pub(crate) fn get_compiler_config(&self) -> Result<(Box, CompilerType)> { @@ -315,7 +307,82 @@ impl StoreOptions { #[allow(unreachable_code)] Ok((compiler_config, compiler)) } +} + +/// The compiler used for the store +#[derive(Debug, PartialEq, Eq)] +pub enum CompilerType { + /// Singlepass compiler + Singlepass, + /// Cranelift compiler + Cranelift, + /// LLVM compiler + LLVM, + /// Headless compiler + Headless, +} + +impl CompilerType { + /// Return all enabled compilers + pub fn enabled() -> Vec { + vec![ + #[cfg(feature = "singlepass")] + Self::Singlepass, + #[cfg(feature = "cranelift")] + Self::Cranelift, + #[cfg(feature = "llvm")] + Self::LLVM, + ] + } +} +impl ToString for CompilerType { + fn to_string(&self) -> String { + match self { + Self::Singlepass => "singlepass".to_string(), + Self::Cranelift => "cranelift".to_string(), + Self::LLVM => "llvm".to_string(), + Self::Headless => "headless".to_string(), + } + } +} + +impl FromStr for CompilerType { + type Err = Error; + fn from_str(s: &str) -> Result { + match s { + "singlepass" => Ok(Self::Singlepass), + "cranelift" => Ok(Self::Cranelift), + "llvm" => Ok(Self::LLVM), + "headless" => Ok(Self::Headless), + backend => bail!("The `{}` compiler does not exist.", backend), + } + } +} + +/// The engine used for the store +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum EngineType { + /// JIT Engine + JIT, + /// Native Engine + Native, + /// Object File Engine + ObjectFile, +} + +impl ToString for EngineType { + fn to_string(&self) -> String { + match self { + Self::JIT => "jit".to_string(), + Self::Native => "native".to_string(), + Self::ObjectFile => "objectfile".to_string(), + } + } +} + +#[cfg(all(feature = "compiler", feature = "engine"))] +impl StoreOptions { /// Gets the store for the host target, with the engine name and compiler name selected pub fn get_store(&self) -> Result<(Store, EngineType, CompilerType)> { let target = Target::default(); @@ -327,7 +394,7 @@ impl StoreOptions { &self, target: Target, ) -> Result<(Store, EngineType, CompilerType)> { - let (compiler_config, compiler_type) = self.get_compiler_config()?; + let (compiler_config, compiler_type) = self.compiler.get_compiler_config()?; let (engine, engine_type) = self.get_engine_with_compiler(target, compiler_config)?; let store = Store::new(&*engine); Ok((store, engine_type, compiler_type)) @@ -339,41 +406,10 @@ impl StoreOptions { compiler_config: Box, ) -> Result<(Box, EngineType)> { let engine_type = self.get_engine()?; - let features = self.get_features(compiler_config.default_features_for_target(&target))?; - let engine: Box = match engine_type { - #[cfg(feature = "jit")] - EngineType::JIT => Box::new( - wasmer_engine_jit::JIT::new(&*compiler_config) - .features(features) - .target(target) - .engine(), - ), - #[cfg(feature = "native")] - EngineType::Native => { - let mut compiler_config = compiler_config; - Box::new( - wasmer_engine_native::Native::new(&mut *compiler_config) - .target(target) - .features(features) - .engine(), - ) - } - #[cfg(feature = "object-file")] - EngineType::ObjectFile => { - let mut compiler_config = compiler_config; - Box::new( - wasmer_engine_object_file::ObjectFile::new(&mut *compiler_config) - .target(target) - .features(features) - .engine(), - ) - } - #[cfg(not(all(feature = "jit", feature = "native", feature = "object-file")))] - engine => bail!( - "The `{}` engine is not included in this binary.", - engine.to_string() - ), - }; + let engine = self + .compiler + .get_engine_by_type(target, compiler_config, engine_type)?; + Ok((engine, engine_type)) } } From 4b665adc7229a810f4cb83a27e78fe75d8924ac4 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 8 Oct 2020 16:33:41 -0700 Subject: [PATCH 02/16] Add test for `wasmer create-exe` --- tests/integration/cli/src/assets.rs | 32 ++++ tests/integration/cli/src/lib.rs | 8 +- tests/integration/cli/src/link_code.rs | 66 ++++++++ tests/integration/cli/src/util.rs | 55 ++++++ .../integration/cli/{src => tests}/compile.rs | 159 +----------------- tests/integration/cli/tests/create_exe.rs | 91 ++++++++++ .../object_file_engine_test_c_source.c | 0 7 files changed, 257 insertions(+), 154 deletions(-) create mode 100644 tests/integration/cli/src/assets.rs create mode 100644 tests/integration/cli/src/link_code.rs create mode 100644 tests/integration/cli/src/util.rs rename tests/integration/cli/{src => tests}/compile.rs (50%) create mode 100644 tests/integration/cli/tests/create_exe.rs rename tests/integration/cli/{src => tests}/object_file_engine_test_c_source.c (100%) diff --git a/tests/integration/cli/src/assets.rs b/tests/integration/cli/src/assets.rs new file mode 100644 index 00000000000..4113e0b5cb8 --- /dev/null +++ b/tests/integration/cli/src/assets.rs @@ -0,0 +1,32 @@ +use std::env; +use std::path::PathBuf; + +pub const ASSET_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets"); + +pub const WASMER_PATH: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../target/release/wasmer" +); + +#[cfg(not(windows))] +pub const LIBWASMER_PATH: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../target/release/libwasmer_c_api.a" +); +#[cfg(windows)] +pub const LIBWASMER_PATH: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../target/release/wasmer_c_api.lib" +); + +/// Get the path to the `libwasmer.a` static library. +pub fn get_libwasmer_path() -> PathBuf { + PathBuf::from( + env::var("WASMER_TEST_LIBWASMER_PATH").unwrap_or_else(|_| LIBWASMER_PATH.to_string()), + ) +} + +/// Get the path to the `wasmer` executable to be used in this test. +pub fn get_wasmer_path() -> PathBuf { + PathBuf::from(env::var("WASMER_TEST_WASMER_PATH").unwrap_or_else(|_| WASMER_PATH.to_string())) +} diff --git a/tests/integration/cli/src/lib.rs b/tests/integration/cli/src/lib.rs index 69d222b5eab..43d6ada6a69 100644 --- a/tests/integration/cli/src/lib.rs +++ b/tests/integration/cli/src/lib.rs @@ -1,6 +1,10 @@ #![forbid(unsafe_code)] -#![cfg(test)] //! CLI integration tests -mod compile; +pub mod assets; +pub mod link_code; +pub mod util; + +pub use assets::*; +pub use util::*; diff --git a/tests/integration/cli/src/link_code.rs b/tests/integration/cli/src/link_code.rs new file mode 100644 index 00000000000..72fb18e1b30 --- /dev/null +++ b/tests/integration/cli/src/link_code.rs @@ -0,0 +1,66 @@ +use crate::assets::*; +use anyhow::bail; +use std::path::PathBuf; +use std::process::Command; + +/// Data used to run a linking command for generated artifacts. +#[derive(Debug)] +pub struct LinkCode { + /// Path to the linker used to run the linking command. + pub linker_path: PathBuf, + /// String used as an optimization flag. + pub optimization_flag: String, + /// Paths of objects to link. + pub object_paths: Vec, + /// Path to the output target. + pub output_path: PathBuf, + /// Path to the static libwasmer library. + pub libwasmer_path: PathBuf, +} + +impl Default for LinkCode { + fn default() -> Self { + #[cfg(not(windows))] + let linker = "cc"; + #[cfg(windows)] + let linker = "clang"; + Self { + linker_path: PathBuf::from(linker), + optimization_flag: String::from("-O2"), + object_paths: vec![], + output_path: PathBuf::from("a.out"), + libwasmer_path: get_libwasmer_path(), + } + } +} + +impl LinkCode { + pub fn run(&self) -> anyhow::Result<()> { + let mut command = Command::new(&self.linker_path); + let command = command + .arg(&self.optimization_flag) + .args( + self.object_paths + .iter() + .map(|path| path.canonicalize().unwrap()), + ) + .arg(&self.libwasmer_path.canonicalize()?); + #[cfg(windows)] + let command = command.arg("-luserenv").arg("-lWs2_32").arg("-ladvapi32"); + #[cfg(not(windows))] + // TODO: remove `-lffi` before shipping + let command = command.arg("-ldl").arg("-lm").arg("-pthread").arg("-lffi"); + let output = command.arg("-o").arg(&self.output_path).output()?; + + if !output.status.success() { + bail!( + "linking failed with: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + Ok(()) + } +} diff --git a/tests/integration/cli/src/util.rs b/tests/integration/cli/src/util.rs new file mode 100644 index 00000000000..0e4864a676f --- /dev/null +++ b/tests/integration/cli/src/util.rs @@ -0,0 +1,55 @@ +use anyhow::bail; +use std::path::Path; +use std::process::Command; + +#[derive(Debug, Copy, Clone)] +pub enum Compiler { + Cranelift, + LLVM, + Singlepass, +} + +impl Compiler { + pub const fn to_flag(self) -> &'static str { + match self { + Compiler::Cranelift => "--cranelift", + Compiler::LLVM => "--llvm", + Compiler::Singlepass => "--singlepass", + } + } +} + +#[derive(Debug, Copy, Clone)] +pub enum Engine { + Jit, + Native, + ObjectFile, +} + +impl Engine { + pub const fn to_flag(self) -> &'static str { + match self { + Engine::Jit => "--jit", + Engine::Native => "--native", + Engine::ObjectFile => "--object-file", + } + } +} + +pub fn run_code(executable_path: &Path) -> anyhow::Result { + let output = Command::new(executable_path.canonicalize()?).output()?; + + if !output.status.success() { + bail!( + "running executable failed: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + let output = + std::str::from_utf8(&output.stdout).expect("output from running executable is not utf-8"); + + Ok(output.to_owned()) +} diff --git a/tests/integration/cli/src/compile.rs b/tests/integration/cli/tests/compile.rs similarity index 50% rename from tests/integration/cli/src/compile.rs rename to tests/integration/cli/tests/compile.rs index b14635b37f7..ff9a7ec687e 100644 --- a/tests/integration/cli/src/compile.rs +++ b/tests/integration/cli/tests/compile.rs @@ -1,84 +1,18 @@ //! CLI tests for the compile subcommand. use anyhow::{bail, Context}; -use std::env; use std::fs; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::Command; - -const CLI_INTEGRATION_TESTS_ASSETS: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets"); +use wasmer_integration_tests_cli::link_code::*; +use wasmer_integration_tests_cli::*; const OBJECT_FILE_ENGINE_TEST_C_SOURCE: &[u8] = include_bytes!("object_file_engine_test_c_source.c"); -// TODO: -const OBJECT_FILE_ENGINE_TEST_WASM_PATH: &str = - concat!(env!("CARGO_MANIFEST_DIR"), "/assets/qjs.wasm"); - -const WASMER_PATH: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../../target/release/wasmer" -); - -#[cfg(not(windows))] -const LIBWASMER_PATH: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../../target/release/libwasmer_c_api.a" -); -#[cfg(windows)] -const LIBWASMER_PATH: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../../target/release/wasmer_c_api.lib" -); - -/// Get the path to the `wasmer` executable to be used in this test. -fn get_wasmer_path() -> PathBuf { - PathBuf::from(env::var("WASMER_TEST_WASMER_PATH").unwrap_or_else(|_| WASMER_PATH.to_string())) -} - -/// Get the path to the `libwasmer.a` static library. -fn get_libwasmer_path() -> PathBuf { - PathBuf::from( - env::var("WASMER_TEST_LIBWASMER_PATH").unwrap_or_else(|_| LIBWASMER_PATH.to_string()), - ) -} - -#[allow(dead_code)] -#[derive(Debug, Copy, Clone)] -pub enum Engine { - Jit, - Native, - ObjectFile, -} - -impl Engine { - // TODO: make this `const fn` when Wasmer moves to Rust 1.46.0+ - pub fn to_flag(self) -> &'static str { - match self { - Engine::Jit => "--jit", - Engine::Native => "--native", - Engine::ObjectFile => "--object-file", - } - } -} - -#[allow(dead_code)] -#[derive(Debug, Copy, Clone)] -pub enum Compiler { - Cranelift, - LLVM, - Singlepass, -} -impl Compiler { - // TODO: make this `const fn` when Wasmer moves to Rust 1.46.0+ - pub fn to_flag(self) -> &'static str { - match self { - Compiler::Cranelift => "--cranelift", - Compiler::LLVM => "--llvm", - Compiler::Singlepass => "--singlepass", - } - } +fn object_file_engine_test_wasm_path() -> String { + format!("{}/{}", ASSET_PATH, "qjs.wasm") } /// Data used to run the `wasmer compile` command. @@ -106,7 +40,7 @@ impl Default for WasmerCompile { let wasm_obj_path = "wasm.obj"; Self { wasmer_path: get_wasmer_path(), - wasm_path: PathBuf::from(OBJECT_FILE_ENGINE_TEST_WASM_PATH), + wasm_path: PathBuf::from(object_file_engine_test_wasm_path()), wasm_object_path: PathBuf::from(wasm_obj_path), header_output_path: PathBuf::from("my_wasm.h"), compiler: Compiler::Cranelift, @@ -153,7 +87,7 @@ fn run_c_compile(path_to_c_src: &Path, output_name: &Path) -> anyhow::Result<()> .arg("-c") .arg(path_to_c_src) .arg("-I") - .arg(CLI_INTEGRATION_TESTS_ASSETS) + .arg(ASSET_PATH) .arg("-o") .arg(output_name) .output()?; @@ -170,92 +104,13 @@ fn run_c_compile(path_to_c_src: &Path, output_name: &Path) -> anyhow::Result<()> Ok(()) } -/// Data used to run a linking command for generated artifacts. -#[derive(Debug)] -struct LinkCode { - /// Path to the linker used to run the linking command. - linker_path: PathBuf, - /// String used as an optimization flag. - optimization_flag: String, - /// Paths of objects to link. - object_paths: Vec, - /// Path to the output target. - output_path: PathBuf, - /// Path to the static libwasmer library. - libwasmer_path: PathBuf, -} - -impl Default for LinkCode { - fn default() -> Self { - #[cfg(not(windows))] - let linker = "cc"; - #[cfg(windows)] - let linker = "clang"; - Self { - linker_path: PathBuf::from(linker), - optimization_flag: String::from("-O2"), - object_paths: vec![], - output_path: PathBuf::from("a.out"), - libwasmer_path: get_libwasmer_path(), - } - } -} - -impl LinkCode { - fn run(&self) -> anyhow::Result<()> { - let mut command = Command::new(&self.linker_path); - let command = command - .arg(&self.optimization_flag) - .args( - self.object_paths - .iter() - .map(|path| path.canonicalize().unwrap()), - ) - .arg(&self.libwasmer_path.canonicalize()?); - #[cfg(windows)] - let command = command.arg("-luserenv").arg("-lWs2_32").arg("-ladvapi32"); - #[cfg(not(windows))] - let command = command.arg("-ldl").arg("-lm").arg("-pthread"); - let output = command.arg("-o").arg(&self.output_path).output()?; - - if !output.status.success() { - bail!( - "linking failed with: stdout: {}\n\nstderr: {}", - std::str::from_utf8(&output.stdout) - .expect("stdout is not utf8! need to handle arbitrary bytes"), - std::str::from_utf8(&output.stderr) - .expect("stderr is not utf8! need to handle arbitrary bytes") - ); - } - Ok(()) - } -} - -fn run_code(executable_path: &Path) -> anyhow::Result { - let output = Command::new(executable_path.canonicalize()?).output()?; - - if !output.status.success() { - bail!( - "running executable failed: stdout: {}\n\nstderr: {}", - std::str::from_utf8(&output.stdout) - .expect("stdout is not utf8! need to handle arbitrary bytes"), - std::str::from_utf8(&output.stderr) - .expect("stderr is not utf8! need to handle arbitrary bytes") - ); - } - let output = - std::str::from_utf8(&output.stdout).expect("output from running executable is not utf-8"); - - Ok(output.to_owned()) -} - #[test] fn object_file_engine_works() -> anyhow::Result<()> { let operating_dir = tempfile::tempdir()?; std::env::set_current_dir(&operating_dir)?; - let wasm_path = PathBuf::from(OBJECT_FILE_ENGINE_TEST_WASM_PATH); + let wasm_path = PathBuf::from(object_file_engine_test_wasm_path()); #[cfg(not(windows))] let wasm_object_path = PathBuf::from("wasm.o"); #[cfg(windows)] diff --git a/tests/integration/cli/tests/create_exe.rs b/tests/integration/cli/tests/create_exe.rs new file mode 100644 index 00000000000..d2f50a6a9b2 --- /dev/null +++ b/tests/integration/cli/tests/create_exe.rs @@ -0,0 +1,91 @@ +//! Tests of the `wasmer create-exe` command. + +use anyhow::{bail, Context}; +use std::path::PathBuf; +use std::process::Command; +use wasmer_integration_tests_cli::*; + +fn create_exe_test_wasm_path() -> String { + format!("{}/{}", ASSET_PATH, "qjs.wasm") +} + +/// Data used to run the `wasmer compile` command. +#[derive(Debug)] +struct WasmerCreateExe { + /// Path to wasmer executable used to run the command. + wasmer_path: PathBuf, + /// Path to the Wasm file to compile. + wasm_path: PathBuf, + /// Path to the native executable produced by compiling the Wasm. + native_executable_path: PathBuf, + /// Compiler with which to compile the Wasm. + compiler: Compiler, +} + +impl Default for WasmerCreateExe { + fn default() -> Self { + #[cfg(not(windows))] + let native_executable_path = PathBuf::from("wasm.out"); + #[cfg(windows)] + let native_executable_path = PathBuf::from("wasm.exe"); + Self { + wasmer_path: get_wasmer_path(), + wasm_path: PathBuf::from(create_exe_test_wasm_path()), + native_executable_path, + compiler: Compiler::Cranelift, + } + } +} + +impl WasmerCreateExe { + fn run(&self) -> anyhow::Result<()> { + let output = Command::new(&self.wasmer_path) + .arg("create-exe") + .arg(&self.wasm_path.canonicalize()?) + .arg(&self.compiler.to_flag()) + // TODO: remove before shipping + .arg("-lffi") + .arg("-o") + .arg(&self.native_executable_path) + .output()?; + + if !output.status.success() { + bail!( + "wasmer create-exe failed with: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + Ok(()) + } +} + +#[test] +fn create_exe_works() -> anyhow::Result<()> { + let operating_dir = tempfile::tempdir()?; + + std::env::set_current_dir(&operating_dir)?; + + let wasm_path = PathBuf::from(create_exe_test_wasm_path()); + #[cfg(not(windows))] + let executable_path = PathBuf::from("wasm.out"); + #[cfg(windows)] + let executable_path = PathBuf::from("wasm.exe"); + + WasmerCreateExe { + wasm_path: wasm_path.clone(), + native_executable_path: executable_path.clone(), + compiler: Compiler::Cranelift, + ..Default::default() + } + .run() + .context("Failed to create-exe wasm with Wasmer")?; + + let result = run_code(&executable_path).context("Failed to run generated executable")?; + let result_lines = result.lines().collect::>(); + assert_eq!(result_lines, vec!["Initializing...", "\"Hello, World\""],); + + Ok(()) +} diff --git a/tests/integration/cli/src/object_file_engine_test_c_source.c b/tests/integration/cli/tests/object_file_engine_test_c_source.c similarity index 100% rename from tests/integration/cli/src/object_file_engine_test_c_source.c rename to tests/integration/cli/tests/object_file_engine_test_c_source.c From ba7b24f696228555603c1ac78da85b2d5bedc945 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Fri, 9 Oct 2020 17:38:14 -0700 Subject: [PATCH 03/16] Add --dir and --mapdir support to native executable --- lib/c-api/build.rs | 2 + lib/c-api/src/wasm_c_api/wasi/mod.rs | 57 +++++ lib/c-api/wasmer_wasm.h | 8 + lib/cli/src/commands/wasmer_create_exe_main.c | 211 ++++++++++++------ tests/integration/cli/src/util.rs | 6 +- tests/integration/cli/tests/compile.rs | 2 +- tests/integration/cli/tests/create_exe.rs | 6 +- 7 files changed, 225 insertions(+), 67 deletions(-) diff --git a/lib/c-api/build.rs b/lib/c-api/build.rs index e788743829a..f9f78ec10b1 100644 --- a/lib/c-api/build.rs +++ b/lib/c-api/build.rs @@ -357,6 +357,8 @@ fn exclude_items_from_wasm_c_api(builder: Builder) -> Builder { builder .exclude_item("wasi_config_arg") .exclude_item("wasi_config_env") + .exclude_item("wasi_config_mapdir") + .exclude_item("wasi_config_preopen_dir") .exclude_item("wasi_config_inherit_stderr") .exclude_item("wasi_config_inherit_stdin") .exclude_item("wasi_config_inherit_stdout") diff --git a/lib/c-api/src/wasm_c_api/wasi/mod.rs b/lib/c-api/src/wasm_c_api/wasi/mod.rs index cf0a08b73d4..fb0047c7379 100644 --- a/lib/c-api/src/wasm_c_api/wasi/mod.rs +++ b/lib/c-api/src/wasm_c_api/wasi/mod.rs @@ -75,6 +75,63 @@ pub unsafe extern "C" fn wasi_config_arg(config: &mut wasi_config_t, arg: *const config.state_builder.arg(arg_bytes); } +#[no_mangle] +pub unsafe extern "C" fn wasi_config_preopen_dir( + config: &mut wasi_config_t, + dir: *const c_char, +) -> bool { + let dir_cstr = CStr::from_ptr(dir); + let dir_bytes = dir_cstr.to_bytes(); + let dir_str = match std::str::from_utf8(dir_bytes) { + Ok(dir_str) => dir_str, + Err(e) => { + update_last_error(e); + return false; + } + }; + + if let Err(e) = config.state_builder.preopen_dir(dir_str) { + update_last_error(e); + return false; + } + + true +} + +#[no_mangle] +pub unsafe extern "C" fn wasi_config_mapdir( + config: &mut wasi_config_t, + alias: *const c_char, + dir: *const c_char, +) -> bool { + let alias_cstr = CStr::from_ptr(alias); + let alias_bytes = alias_cstr.to_bytes(); + let alias_str = match std::str::from_utf8(alias_bytes) { + Ok(alias_str) => alias_str, + Err(e) => { + update_last_error(e); + return false; + } + }; + + let dir_cstr = CStr::from_ptr(dir); + let dir_bytes = dir_cstr.to_bytes(); + let dir_str = match std::str::from_utf8(dir_bytes) { + Ok(dir_str) => dir_str, + Err(e) => { + update_last_error(e); + return false; + } + }; + + if let Err(e) = config.state_builder.map_dir(alias_str, dir_str) { + update_last_error(e); + return false; + } + + true +} + #[no_mangle] pub extern "C" fn wasi_config_inherit_stdout(config: &mut wasi_config_t) { config.inherit_stdout = true; diff --git a/lib/c-api/wasmer_wasm.h b/lib/c-api/wasmer_wasm.h index b9eea99ae71..e8f139ee8d2 100644 --- a/lib/c-api/wasmer_wasm.h +++ b/lib/c-api/wasmer_wasm.h @@ -101,10 +101,18 @@ void wasi_config_inherit_stdin(wasi_config_t *config); void wasi_config_inherit_stdout(wasi_config_t *config); #endif +#if defined(WASMER_WASI_ENABLED) +bool wasi_config_mapdir(wasi_config_t *config, const char *alias, const char *dir); +#endif + #if defined(WASMER_WASI_ENABLED) wasi_config_t *wasi_config_new(const char *program_name); #endif +#if defined(WASMER_WASI_ENABLED) +bool wasi_config_preopen_dir(wasi_config_t *config, const char *dir); +#endif + #if defined(WASMER_WASI_ENABLED) void wasi_env_delete(wasi_env_t *_state); #endif diff --git a/lib/cli/src/commands/wasmer_create_exe_main.c b/lib/cli/src/commands/wasmer_create_exe_main.c index ec00f917942..6b56c02fc30 100644 --- a/lib/cli/src/commands/wasmer_create_exe_main.c +++ b/lib/cli/src/commands/wasmer_create_exe_main.c @@ -9,81 +9,166 @@ extern "C" { #include #include +// TODO: make this define templated so that the Rust code can toggle it on/off +#define WASI + #ifdef __cplusplus } #endif void print_wasmer_error() { - int error_len = wasmer_last_error_length(); - printf("Error len: `%d`\n", error_len); - char* error_str = (char*) malloc(error_len); - wasmer_last_error_message(error_str, error_len); - printf("Error str: `%s`\n", error_str); + int error_len = wasmer_last_error_length(); + printf("Error len: `%d`\n", error_len); + char* error_str = (char*) malloc(error_len); + wasmer_last_error_message(error_str, error_len); + printf("%s\n", error_str); } -int main() { - printf("Initializing...\n"); - wasm_config_t* config = wasm_config_new(); - wasm_config_set_engine(config, OBJECT_FILE); - wasm_engine_t* engine = wasm_engine_new_with_config(config); - wasm_store_t* store = wasm_store_new(engine); - - wasm_module_t* module = wasmer_object_file_engine_new(store, "qjs.wasm"); - if (! module) { - printf("Failed to create module\n"); - print_wasmer_error(); - return -1; - } - - // We have now finished the memory buffer book keeping and we have a valid Module. +#ifdef WASI +int find_colon(char* string) { + int colon_location = 0; + for (int j = 0; j < strlen(string); ++j) { + if (string[j] == ':') { + colon_location = j; + break; + } + } + return colon_location; +} - // In this example we're passing some JavaScript source code as a command line argument - // to a WASI module that can evaluate JavaScript. - wasi_config_t* wasi_config = wasi_config_new("constant_value_here"); - const char* js_string = "function greet(name) { return JSON.stringify('Hello, ' + name); }; print(greet('World'));"; - wasi_config_arg(wasi_config, "--eval"); - wasi_config_arg(wasi_config, js_string); - wasi_env_t* wasi_env = wasi_env_new(wasi_config); - if (!wasi_env) { - printf("> Error building WASI env!\n"); - print_wasmer_error(); - return 1; - } +void pass_mapdir_arg(wasi_config_t* wasi_config, char* mapdir) { + int colon_location = find_colon(mapdir); + if (colon_location == 0) { + // error malformed argument + fprintf(stderr, "Expected mapdir argument of the form alias:directory\n"); + exit(-1); + } + // TODO: triple check for off by one errors + int dir_len = strlen(mapdir) - colon_location; + char* alias = (char*)malloc(colon_location); + char* dir = (char*)malloc(dir_len); + int j = 0; + for (j = 0; j < colon_location; ++j) { + alias[j] = mapdir[j]; + } + alias[j] = 0; + for (j = 0; j < dir_len; ++j) { + dir[j] = mapdir[j + colon_location]; + } + dir[j] = 0; + + wasi_config_mapdir(wasi_config, alias, dir); + free(alias); + free(dir); +} - wasm_importtype_vec_t import_types; - wasm_module_imports(module, &import_types); - int num_imports = import_types.size; - wasm_extern_t** imports = (wasm_extern_t**) malloc(num_imports * sizeof(wasm_extern_t*)); - wasm_importtype_vec_delete(&import_types); - - bool get_imports_result = wasi_get_imports(store, module, wasi_env, imports); - if (!get_imports_result) { - printf("> Error getting WASI imports!\n"); - print_wasmer_error(); - return 1; - } +// We try to parse out `--dir` and `--mapdir` ahead of time and process those +// specially. All other arguments are passed to the guest program. +void handle_arguments(wasi_config_t* wasi_config, int argc, char* argv[]) { + for (int i = 1; i < argc; ++i) { + // We probably want special args like `--dir` and `--mapdir` to not be passed directly + if (strcmp(argv[i], "--dir") == 0) { + // next arg is a preopen directory + if ((i + 1) < argc ) { + i++; + wasi_config_preopen_dir(wasi_config, argv[i]); + } else { + fprintf(stderr, "--dir expects a following argument specifying which directory to preopen\n"); + exit(-1); + } + } + else if (strcmp(argv[i], "--mapdir") == 0) { + // next arg is a mapdir + if ((i + 1) < argc ) { + i++; + pass_mapdir_arg(wasi_config, argv[i]); + } else { + fprintf(stderr, "--mapdir expects a following argument specifying which directory to preopen in the form alias:directory\n"); + exit(-1); + } + } + else if (strncmp(argv[i], "--dir=", strlen("--dir=")) == 0 ) { + // this arg is a preopen dir + char* dir = argv[i] + strlen("--dir="); + wasi_config_preopen_dir(wasi_config, dir); + } + else if (strncmp(argv[i], "--mapdir=", strlen("--mapdir=")) == 0 ) { + // this arg is a mapdir + char* mapdir = argv[i] + strlen("--mapdir="); + pass_mapdir_arg(wasi_config, mapdir); + } + else { + // guest argument + wasi_config_arg(wasi_config, argv[i]); + } + } +} +#endif - wasm_instance_t* instance = wasm_instance_new(store, module, (const wasm_extern_t* const*) imports, NULL); - if (! instance) { - printf("Failed to create instance\n"); - print_wasmer_error(); - return -1; - } - wasi_env_set_instance(wasi_env, instance); - - // WASI is now set up. +int main(int argc, char* argv[]) { + printf("Initializing...\n"); + wasm_config_t* config = wasm_config_new(); + wasm_config_set_engine(config, OBJECT_FILE); + wasm_engine_t* engine = wasm_engine_new_with_config(config); + wasm_store_t* store = wasm_store_new(engine); + + wasm_module_t* module = wasmer_object_file_engine_new(store, "qjs.wasm"); + if (! module) { + fprintf(stderr, "Failed to create module\n"); + print_wasmer_error(); + return -1; + } - void* vmctx = wasm_instance_get_vmctx_ptr(instance); - wasm_val_t* inout[2] = { NULL, NULL }; + // We have now finished the memory buffer book keeping and we have a valid Module. + + #ifdef WASI + wasi_config_t* wasi_config = wasi_config_new(argv[0]); + handle_arguments(wasi_config, argc, argv); - fflush(stdout); - // We're able to call our compiled function directly through a trampoline. - wasmer_trampoline_function_call__1(vmctx, wasmer_function__1, &inout); + wasi_env_t* wasi_env = wasi_env_new(wasi_config); + if (!wasi_env) { + fprintf(stderr, "Error building WASI env!\n"); + print_wasmer_error(); + return 1; + } + #endif + + wasm_importtype_vec_t import_types; + wasm_module_imports(module, &import_types); + int num_imports = import_types.size; + wasm_extern_t** imports = (wasm_extern_t**) malloc(num_imports * sizeof(wasm_extern_t*)); + wasm_importtype_vec_delete(&import_types); + + #ifdef WASI + bool get_imports_result = wasi_get_imports(store, module, wasi_env, imports); + if (!get_imports_result) { + fprintf(stderr, "Error getting WASI imports!\n"); + print_wasmer_error(); + return 1; + } + #endif + + wasm_instance_t* instance = wasm_instance_new(store, module, (const wasm_extern_t* const*) imports, NULL); + if (! instance) { + fprintf(stderr, "Failed to create instance\n"); + print_wasmer_error(); + return -1; + } - wasm_instance_delete(instance); - wasm_module_delete(module); - wasm_store_delete(store); - wasm_engine_delete(engine); - return 0; + #ifdef WASI + wasi_env_set_instance(wasi_env, instance); + #endif + + void* vmctx = wasm_instance_get_vmctx_ptr(instance); + wasm_val_t* inout[2] = { NULL, NULL }; + + // We're able to call our compiled function directly through a trampoline. + wasmer_trampoline_function_call__1(vmctx, wasmer_function__1, &inout); + + wasm_instance_delete(instance); + wasm_module_delete(module); + wasm_store_delete(store); + wasm_engine_delete(engine); + return 0; } diff --git a/tests/integration/cli/src/util.rs b/tests/integration/cli/src/util.rs index 0e4864a676f..8cfcaae20d7 100644 --- a/tests/integration/cli/src/util.rs +++ b/tests/integration/cli/src/util.rs @@ -36,8 +36,10 @@ impl Engine { } } -pub fn run_code(executable_path: &Path) -> anyhow::Result { - let output = Command::new(executable_path.canonicalize()?).output()?; +pub fn run_code(executable_path: &Path, args: &[String]) -> anyhow::Result { + let output = Command::new(executable_path.canonicalize()?) + .args(args) + .output()?; if !output.status.success() { bail!( diff --git a/tests/integration/cli/tests/compile.rs b/tests/integration/cli/tests/compile.rs index ff9a7ec687e..fcbedc938d5 100644 --- a/tests/integration/cli/tests/compile.rs +++ b/tests/integration/cli/tests/compile.rs @@ -153,7 +153,7 @@ fn object_file_engine_works() -> anyhow::Result<()> { .run() .context("Failed to link objects together")?; - let result = run_code(&executable_path).context("Failed to run generated executable")?; + let result = run_code(&executable_path, &[]).context("Failed to run generated executable")?; let result_lines = result.lines().collect::>(); assert_eq!(result_lines, vec!["Initializing...", "\"Hello, World\""],); diff --git a/tests/integration/cli/tests/create_exe.rs b/tests/integration/cli/tests/create_exe.rs index d2f50a6a9b2..2af9e390fb3 100644 --- a/tests/integration/cli/tests/create_exe.rs +++ b/tests/integration/cli/tests/create_exe.rs @@ -83,7 +83,11 @@ fn create_exe_works() -> anyhow::Result<()> { .run() .context("Failed to create-exe wasm with Wasmer")?; - let result = run_code(&executable_path).context("Failed to run generated executable")?; + let result = run_code( + &executable_path, + &["--eval".to_string(), "function greet(name) { return JSON.stringify('Hello, ' + name); }; print(greet('World'));".to_string()], + ) + .context("Failed to run generated executable")?; let result_lines = result.lines().collect::>(); assert_eq!(result_lines, vec!["Initializing...", "\"Hello, World\""],); From c6460d1d77ad4977edbeda3f48a0dc8c94e3c59e Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 12 Oct 2020 13:48:26 -0700 Subject: [PATCH 04/16] Add `target` support to `wasmer create-exe` --- lib/cli/src/commands/create_exe.rs | 37 +++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/cli/src/commands/create_exe.rs b/lib/cli/src/commands/create_exe.rs index 0238da70859..b8cbf85e267 100644 --- a/lib/cli/src/commands/create_exe.rs +++ b/lib/cli/src/commands/create_exe.rs @@ -130,11 +130,13 @@ impl CreateExe { // TODO: c_src_file.write_all(WASMER_MAIN_C_SOURCE)?; } - run_c_compile(&c_src_path, &c_src_obj).context("Failed to compile C source code")?; + run_c_compile(&c_src_path, &c_src_obj, self.target_triple.clone()) + .context("Failed to compile C source code")?; LinkCode { object_paths: vec![c_src_obj, wasm_object_path], output_path, additional_libraries: self.libraries.clone(), + target: self.target_triple.clone(), ..Default::default() } .run() @@ -155,6 +157,7 @@ fn get_wasmer_include_directory() -> anyhow::Result { Ok(path) } +/// path to the static libwasmer fn get_libwasmer_path() -> anyhow::Result { let mut path = PathBuf::from(env::var("WASMER_DIR")?); path.push("lib"); @@ -168,21 +171,31 @@ fn get_libwasmer_path() -> anyhow::Result { } /// Compile the C code. -fn run_c_compile(path_to_c_src: &Path, output_name: &Path) -> anyhow::Result<()> { +fn run_c_compile( + path_to_c_src: &Path, + output_name: &Path, + target: Option, +) -> anyhow::Result<()> { #[cfg(not(windows))] let c_compiler = "cc"; #[cfg(windows)] let c_compiler = "clang++"; - let output = Command::new(c_compiler) + let mut command = Command::new(c_compiler); + let command = command .arg("-O2") .arg("-c") .arg(path_to_c_src) .arg("-I") - .arg(get_wasmer_include_directory()?) - .arg("-o") - .arg(output_name) - .output()?; + .arg(get_wasmer_include_directory()?); + + let command = if let Some(target) = target { + command.arg("-target").arg(format!("{}", target)) + } else { + command + }; + + let output = command.arg("-o").arg(output_name).output()?; if !output.status.success() { bail!( @@ -209,8 +222,10 @@ struct LinkCode { additional_libraries: Vec, /// Path to the output target. output_path: PathBuf, - /// Path to the static libwasmer library. + /// Path to the dir containing the static libwasmer library. libwasmer_path: PathBuf, + /// The target to link the executable for. + target: Option, } impl Default for LinkCode { @@ -226,6 +241,7 @@ impl Default for LinkCode { additional_libraries: vec![], output_path: PathBuf::from("a.out"), libwasmer_path: get_libwasmer_path().unwrap(), + target: None, } } } @@ -248,6 +264,11 @@ impl LinkCode { .canonicalize() .context("Failed to find libwasmer")?, ); + let command = if let Some(target) = &self.target { + command.arg("-target").arg(format!("{}", target)) + } else { + command + }; #[cfg(windows)] let command = command.arg("-luserenv").arg("-lWs2_32").arg("-ladvapi32"); #[cfg(not(windows))] From 21c17704263d6210af1e22583263c285441ea04f Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 12 Oct 2020 13:51:03 -0700 Subject: [PATCH 05/16] Improve error message when `WASMER_DIR` is not set --- lib/cli/src/commands/create_exe.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/cli/src/commands/create_exe.rs b/lib/cli/src/commands/create_exe.rs index b8cbf85e267..3caa5794475 100644 --- a/lib/cli/src/commands/create_exe.rs +++ b/lib/cli/src/commands/create_exe.rs @@ -151,15 +151,21 @@ impl CreateExe { } } +fn get_wasmer_dir() -> anyhow::Result { + Ok(PathBuf::from( + env::var("WASMER_DIR").context("Trying to read env var `WASMER_DIR`")?, + )) +} + fn get_wasmer_include_directory() -> anyhow::Result { - let mut path = PathBuf::from(env::var("WASMER_DIR")?); + let mut path = get_wasmer_dir()?; path.push("include"); Ok(path) } /// path to the static libwasmer fn get_libwasmer_path() -> anyhow::Result { - let mut path = PathBuf::from(env::var("WASMER_DIR")?); + let mut path = get_wasmer_dir()?; path.push("lib"); #[cfg(not(windows))] From a0b237c1f6ddb44853772021f043aef4495cdeba Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 12 Oct 2020 15:04:25 -0700 Subject: [PATCH 06/16] Add test using mapdir and dir, fix misc bugs --- lib/cli/src/commands/wasmer_create_exe_main.c | 10 +-- tests/integration/cli/src/link_code.rs | 4 + tests/integration/cli/src/util.rs | 7 +- tests/integration/cli/tests/compile.rs | 40 +++++---- tests/integration/cli/tests/create_exe.rs | 83 +++++++++++++++++-- 5 files changed, 116 insertions(+), 28 deletions(-) diff --git a/lib/cli/src/commands/wasmer_create_exe_main.c b/lib/cli/src/commands/wasmer_create_exe_main.c index 6b56c02fc30..be7936731e5 100644 --- a/lib/cli/src/commands/wasmer_create_exe_main.c +++ b/lib/cli/src/commands/wasmer_create_exe_main.c @@ -44,20 +44,19 @@ void pass_mapdir_arg(wasi_config_t* wasi_config, char* mapdir) { fprintf(stderr, "Expected mapdir argument of the form alias:directory\n"); exit(-1); } - // TODO: triple check for off by one errors int dir_len = strlen(mapdir) - colon_location; - char* alias = (char*)malloc(colon_location); - char* dir = (char*)malloc(dir_len); + char* alias = (char*)malloc(colon_location + 1); + char* dir = (char*)malloc(dir_len + 1); int j = 0; for (j = 0; j < colon_location; ++j) { alias[j] = mapdir[j]; } alias[j] = 0; for (j = 0; j < dir_len; ++j) { - dir[j] = mapdir[j + colon_location]; + dir[j] = mapdir[j + colon_location + 1]; } dir[j] = 0; - + wasi_config_mapdir(wasi_config, alias, dir); free(alias); free(dir); @@ -107,7 +106,6 @@ void handle_arguments(wasi_config_t* wasi_config, int argc, char* argv[]) { #endif int main(int argc, char* argv[]) { - printf("Initializing...\n"); wasm_config_t* config = wasm_config_new(); wasm_config_set_engine(config, OBJECT_FILE); wasm_engine_t* engine = wasm_engine_new_with_config(config); diff --git a/tests/integration/cli/src/link_code.rs b/tests/integration/cli/src/link_code.rs index 72fb18e1b30..d900f3e66f7 100644 --- a/tests/integration/cli/src/link_code.rs +++ b/tests/integration/cli/src/link_code.rs @@ -6,6 +6,8 @@ use std::process::Command; /// Data used to run a linking command for generated artifacts. #[derive(Debug)] pub struct LinkCode { + /// The directory to operate in. + pub current_dir: PathBuf, /// Path to the linker used to run the linking command. pub linker_path: PathBuf, /// String used as an optimization flag. @@ -25,6 +27,7 @@ impl Default for LinkCode { #[cfg(windows)] let linker = "clang"; Self { + current_dir: std::env::current_dir().unwrap(), linker_path: PathBuf::from(linker), optimization_flag: String::from("-O2"), object_paths: vec![], @@ -38,6 +41,7 @@ impl LinkCode { pub fn run(&self) -> anyhow::Result<()> { let mut command = Command::new(&self.linker_path); let command = command + .current_dir(&self.current_dir) .arg(&self.optimization_flag) .args( self.object_paths diff --git a/tests/integration/cli/src/util.rs b/tests/integration/cli/src/util.rs index 8cfcaae20d7..30006732798 100644 --- a/tests/integration/cli/src/util.rs +++ b/tests/integration/cli/src/util.rs @@ -36,8 +36,13 @@ impl Engine { } } -pub fn run_code(executable_path: &Path, args: &[String]) -> anyhow::Result { +pub fn run_code( + operating_dir: &Path, + executable_path: &Path, + args: &[String], +) -> anyhow::Result { let output = Command::new(executable_path.canonicalize()?) + .current_dir(operating_dir) .args(args) .output()?; diff --git a/tests/integration/cli/tests/compile.rs b/tests/integration/cli/tests/compile.rs index fcbedc938d5..71fa6bd58c9 100644 --- a/tests/integration/cli/tests/compile.rs +++ b/tests/integration/cli/tests/compile.rs @@ -18,6 +18,8 @@ fn object_file_engine_test_wasm_path() -> String { /// Data used to run the `wasmer compile` command. #[derive(Debug)] struct WasmerCompile { + /// The directory to operate in. + current_dir: PathBuf, /// Path to wasmer executable used to run the command. wasmer_path: PathBuf, /// Path to the Wasm file to compile. @@ -39,6 +41,7 @@ impl Default for WasmerCompile { #[cfg(windows)] let wasm_obj_path = "wasm.obj"; Self { + current_dir: std::env::current_dir().unwrap(), wasmer_path: get_wasmer_path(), wasm_path: PathBuf::from(object_file_engine_test_wasm_path()), wasm_object_path: PathBuf::from(wasm_obj_path), @@ -52,6 +55,7 @@ impl Default for WasmerCompile { impl WasmerCompile { fn run(&self) -> anyhow::Result<()> { let output = Command::new(&self.wasmer_path) + .current_dir(&self.current_dir) .arg("compile") .arg(&self.wasm_path.canonicalize()?) .arg(&self.compiler.to_flag()) @@ -76,13 +80,18 @@ impl WasmerCompile { } /// Compile the C code. -fn run_c_compile(path_to_c_src: &Path, output_name: &Path) -> anyhow::Result<()> { +fn run_c_compile( + current_dir: &Path, + path_to_c_src: &Path, + output_name: &Path, +) -> anyhow::Result<()> { #[cfg(not(windows))] let c_compiler = "cc"; #[cfg(windows)] let c_compiler = "clang++"; let output = Command::new(c_compiler) + .current_dir(current_dir) .arg("-O2") .arg("-c") .arg(path_to_c_src) @@ -106,18 +115,18 @@ fn run_c_compile(path_to_c_src: &Path, output_name: &Path) -> anyhow::Result<()> #[test] fn object_file_engine_works() -> anyhow::Result<()> { - let operating_dir = tempfile::tempdir()?; + let temp_dir = tempfile::tempdir()?; + let operating_dir: PathBuf = temp_dir.path().to_owned(); - std::env::set_current_dir(&operating_dir)?; - - let wasm_path = PathBuf::from(object_file_engine_test_wasm_path()); + let wasm_path = operating_dir.join(object_file_engine_test_wasm_path()); #[cfg(not(windows))] - let wasm_object_path = PathBuf::from("wasm.o"); + let wasm_object_path = operating_dir.join("wasm.o"); #[cfg(windows)] - let wasm_object_path = PathBuf::from("wasm.obj"); - let header_output_path = PathBuf::from("my_wasm.h"); + let wasm_object_path = operating_dir.join("wasm.obj"); + let header_output_path = operating_dir.join("my_wasm.h"); WasmerCompile { + current_dir: operating_dir.clone(), wasm_path: wasm_path.clone(), wasm_object_path: wasm_object_path.clone(), header_output_path, @@ -128,12 +137,12 @@ fn object_file_engine_works() -> anyhow::Result<()> { .run() .context("Failed to compile wasm with Wasmer")?; - let c_src_file_name = Path::new("c_src.c"); + let c_src_file_name = operating_dir.join("c_src.c"); #[cfg(not(windows))] - let c_object_path = PathBuf::from("c_src.o"); + let c_object_path = operating_dir.join("c_src.o"); #[cfg(windows)] - let c_object_path = PathBuf::from("c_src.obj"); - let executable_path = PathBuf::from("a.out"); + let c_object_path = operating_dir.join("c_src.obj"); + let executable_path = operating_dir.join("a.out"); // TODO: adjust C source code based on locations of things { @@ -144,8 +153,10 @@ fn object_file_engine_works() -> anyhow::Result<()> { .context("Failed to open C source code file")?; c_src_file.write_all(OBJECT_FILE_ENGINE_TEST_C_SOURCE)?; } - run_c_compile(&c_src_file_name, &c_object_path).context("Failed to compile C source code")?; + run_c_compile(&operating_dir, &c_src_file_name, &c_object_path) + .context("Failed to compile C source code")?; LinkCode { + current_dir: operating_dir.clone(), object_paths: vec![c_object_path, wasm_object_path], output_path: executable_path.clone(), ..Default::default() @@ -153,7 +164,8 @@ fn object_file_engine_works() -> anyhow::Result<()> { .run() .context("Failed to link objects together")?; - let result = run_code(&executable_path, &[]).context("Failed to run generated executable")?; + let result = run_code(&operating_dir, &executable_path, &[]) + .context("Failed to run generated executable")?; let result_lines = result.lines().collect::>(); assert_eq!(result_lines, vec!["Initializing...", "\"Hello, World\""],); diff --git a/tests/integration/cli/tests/create_exe.rs b/tests/integration/cli/tests/create_exe.rs index 2af9e390fb3..ec2eb54ff6b 100644 --- a/tests/integration/cli/tests/create_exe.rs +++ b/tests/integration/cli/tests/create_exe.rs @@ -1,6 +1,8 @@ //! Tests of the `wasmer create-exe` command. use anyhow::{bail, Context}; +use std::fs; +use std::io::prelude::*; use std::path::PathBuf; use std::process::Command; use wasmer_integration_tests_cli::*; @@ -8,10 +10,14 @@ use wasmer_integration_tests_cli::*; fn create_exe_test_wasm_path() -> String { format!("{}/{}", ASSET_PATH, "qjs.wasm") } +const JS_TEST_SRC_CODE: &[u8] = + b"function greet(name) { return JSON.stringify('Hello, ' + name); }; print(greet('World'));\n"; /// Data used to run the `wasmer compile` command. #[derive(Debug)] struct WasmerCreateExe { + /// The directory to operate in. + current_dir: PathBuf, /// Path to wasmer executable used to run the command. wasmer_path: PathBuf, /// Path to the Wasm file to compile. @@ -29,6 +35,7 @@ impl Default for WasmerCreateExe { #[cfg(windows)] let native_executable_path = PathBuf::from("wasm.exe"); Self { + current_dir: std::env::current_dir().unwrap(), wasmer_path: get_wasmer_path(), wasm_path: PathBuf::from(create_exe_test_wasm_path()), native_executable_path, @@ -40,6 +47,7 @@ impl Default for WasmerCreateExe { impl WasmerCreateExe { fn run(&self) -> anyhow::Result<()> { let output = Command::new(&self.wasmer_path) + .current_dir(&self.current_dir) .arg("create-exe") .arg(&self.wasm_path.canonicalize()?) .arg(&self.compiler.to_flag()) @@ -64,17 +72,17 @@ impl WasmerCreateExe { #[test] fn create_exe_works() -> anyhow::Result<()> { - let operating_dir = tempfile::tempdir()?; + let temp_dir = tempfile::tempdir()?; + let operating_dir: PathBuf = temp_dir.path().to_owned(); - std::env::set_current_dir(&operating_dir)?; - - let wasm_path = PathBuf::from(create_exe_test_wasm_path()); + let wasm_path = operating_dir.join(create_exe_test_wasm_path()); #[cfg(not(windows))] - let executable_path = PathBuf::from("wasm.out"); + let executable_path = operating_dir.join("wasm.out"); #[cfg(windows)] - let executable_path = PathBuf::from("wasm.exe"); + let executable_path = operating_dir.join("wasm.exe"); WasmerCreateExe { + current_dir: operating_dir.clone(), wasm_path: wasm_path.clone(), native_executable_path: executable_path.clone(), compiler: Compiler::Cranelift, @@ -84,12 +92,73 @@ fn create_exe_works() -> anyhow::Result<()> { .context("Failed to create-exe wasm with Wasmer")?; let result = run_code( + &operating_dir, &executable_path, &["--eval".to_string(), "function greet(name) { return JSON.stringify('Hello, ' + name); }; print(greet('World'));".to_string()], ) .context("Failed to run generated executable")?; let result_lines = result.lines().collect::>(); - assert_eq!(result_lines, vec!["Initializing...", "\"Hello, World\""],); + assert_eq!(result_lines, vec!["\"Hello, World\""],); + + Ok(()) +} + +#[test] +fn create_exe_works_with_file() -> anyhow::Result<()> { + let temp_dir = tempfile::tempdir()?; + let operating_dir: PathBuf = temp_dir.path().to_owned(); + + let wasm_path = operating_dir.join(create_exe_test_wasm_path()); + #[cfg(not(windows))] + let executable_path = operating_dir.join("wasm.out"); + #[cfg(windows)] + let executable_path = operating_dir.join("wasm.exe"); + + WasmerCreateExe { + current_dir: operating_dir.clone(), + wasm_path: wasm_path.clone(), + native_executable_path: executable_path.clone(), + compiler: Compiler::Cranelift, + ..Default::default() + } + .run() + .context("Failed to create-exe wasm with Wasmer")?; + + { + let mut f = fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(operating_dir.join("test.js"))?; + f.write_all(JS_TEST_SRC_CODE)?; + } + + // test with `--dir` + let result = run_code( + &operating_dir, + &executable_path, + &[ + "--dir=.".to_string(), + "--script".to_string(), + "test.js".to_string(), + ], + ) + .context("Failed to run generated executable")?; + let result_lines = result.lines().collect::>(); + assert_eq!(result_lines, vec!["\"Hello, World\""],); + + // test with `--mapdir` + let result = run_code( + &operating_dir, + &executable_path, + &[ + "--mapdir=abc:.".to_string(), + "--script".to_string(), + "abc/test.js".to_string(), + ], + ) + .context("Failed to run generated executable")?; + let result_lines = result.lines().collect::>(); + assert_eq!(result_lines, vec!["\"Hello, World\""],); Ok(()) } From 81fd4fc0426bebaec6a5a70d55e2b82f3fb6327a Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 12 Oct 2020 15:53:46 -0700 Subject: [PATCH 07/16] Clean create-exe code a bit --- Makefile | 1 + lib/cli/src/commands/create_exe.rs | 54 ++++++++++--------- lib/cli/src/commands/wasmer_create_exe_main.c | 2 +- tests/integration/cli/src/link_code.rs | 3 +- tests/integration/cli/tests/create_exe.rs | 2 - 5 files changed, 32 insertions(+), 30 deletions(-) diff --git a/Makefile b/Makefile index 8d07bf70c8c..dd39c5afc5a 100644 --- a/Makefile +++ b/Makefile @@ -166,6 +166,7 @@ package-capi: mkdir -p "package/include" mkdir -p "package/lib" cp lib/c-api/wasmer.h* package/include + cp lib/c-api/wasm.h* package/include cp lib/c-api/doc/index.md package/include/README.md ifeq ($(OS), Windows_NT) cp target/release/wasmer_c_api.dll package/lib diff --git a/lib/cli/src/commands/create_exe.rs b/lib/cli/src/commands/create_exe.rs index 3caa5794475..d4893e9476e 100644 --- a/lib/cli/src/commands/create_exe.rs +++ b/lib/cli/src/commands/create_exe.rs @@ -11,9 +11,6 @@ use wasmer::*; const WASMER_MAIN_C_SOURCE: &[u8] = include_bytes!("wasmer_create_exe_main.c"); -// TODO: to get this working locally I had to add wasmer_wasm.h and wasm.h to `.wasmer/include` manually. -// this needs to be fixed before we can ship this. - #[derive(Debug, StructOpt)] /// The options for the `wasmer create-exe` subcommand pub struct CreateExe { @@ -73,8 +70,6 @@ impl CreateExe { let output_path = starting_cd.join(&self.output); env::set_current_dir(&working_dir)?; - // TODO: encapsulate compile code - #[cfg(not(windows))] let wasm_object_path = PathBuf::from("wasm.o"); #[cfg(windows)] @@ -82,7 +77,6 @@ impl CreateExe { let wasm_module_path = starting_cd.join(&self.path); - let header_file_path = Path::new("my_wasm.h"); let module = Module::from_file(&store, &wasm_module_path).context("failed to compile Wasm")?; let _ = module.serialize_to_file(&wasm_object_path)?; @@ -100,19 +94,19 @@ impl CreateExe { metadata_length, ); - let header_path = header_file_path.clone(); - // for C code - let mut header = std::fs::OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(&header_path)?; + generate_header(header_file_src.as_bytes())?; + self.compile_c(wasm_object_path, output_path)?; - use std::io::Write; - header.write(header_file_src.as_bytes())?; + eprintln!( + "✔ Native executable compiled successfully to `{}`.", + self.output.display(), + ); - // auto compilation - // + Ok(()) + } + + fn compile_c(&self, wasm_object_path: PathBuf, output_path: PathBuf) -> anyhow::Result<()> { + use std::io::Write; // write C src to disk let c_src_path = Path::new("wasmer_main.c"); @@ -127,7 +121,6 @@ impl CreateExe { .write(true) .open(&c_src_path) .context("Failed to open C source code file")?; - // TODO: c_src_file.write_all(WASMER_MAIN_C_SOURCE)?; } run_c_compile(&c_src_path, &c_src_obj, self.target_triple.clone()) @@ -142,15 +135,24 @@ impl CreateExe { .run() .context("Failed to link objects together")?; - eprintln!( - "✔ Native executable compiled successfully to `{}`.", - self.output.display(), - ); - Ok(()) } } +fn generate_header(header_file_src: &[u8]) -> anyhow::Result<()> { + let header_file_path = Path::new("my_wasm.h"); + let mut header = std::fs::OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&header_file_path)?; + + use std::io::Write; + header.write(header_file_src)?; + + Ok(()) +} + fn get_wasmer_dir() -> anyhow::Result { Ok(PathBuf::from( env::var("WASMER_DIR").context("Trying to read env var `WASMER_DIR`")?, @@ -168,6 +170,7 @@ fn get_libwasmer_path() -> anyhow::Result { let mut path = get_wasmer_dir()?; path.push("lib"); + // TODO: prefer headless Wasmer if/when it's a separate library. #[cfg(not(windows))] path.push("libwasmer.a"); #[cfg(windows)] @@ -253,8 +256,6 @@ impl Default for LinkCode { } impl LinkCode { - // TODO: `wasmer create-exe` needs a command line flag for extra libraries to link aganist - // or perhaps we just want to add a flag for passing things to the linker fn run(&self) -> anyhow::Result<()> { let mut command = Command::new(&self.linker_path); let command = command @@ -275,8 +276,11 @@ impl LinkCode { } else { command }; + // Add libraries required per platform. + // We need userenv, sockets (Ws2_32), and advapi32 to call a system call (for random numbers I think). #[cfg(windows)] let command = command.arg("-luserenv").arg("-lWs2_32").arg("-ladvapi32"); + // On unix we need dlopen-related symbols, libmath for a few things, and pthreads. #[cfg(not(windows))] let command = command.arg("-ldl").arg("-lm").arg("-pthread"); let link_aganist_extra_libs = self diff --git a/lib/cli/src/commands/wasmer_create_exe_main.c b/lib/cli/src/commands/wasmer_create_exe_main.c index be7936731e5..11adb1656a8 100644 --- a/lib/cli/src/commands/wasmer_create_exe_main.c +++ b/lib/cli/src/commands/wasmer_create_exe_main.c @@ -111,7 +111,7 @@ int main(int argc, char* argv[]) { wasm_engine_t* engine = wasm_engine_new_with_config(config); wasm_store_t* store = wasm_store_new(engine); - wasm_module_t* module = wasmer_object_file_engine_new(store, "qjs.wasm"); + wasm_module_t* module = wasmer_object_file_engine_new(store, argv[0]); if (! module) { fprintf(stderr, "Failed to create module\n"); print_wasmer_error(); diff --git a/tests/integration/cli/src/link_code.rs b/tests/integration/cli/src/link_code.rs index d900f3e66f7..3dce2363526 100644 --- a/tests/integration/cli/src/link_code.rs +++ b/tests/integration/cli/src/link_code.rs @@ -52,8 +52,7 @@ impl LinkCode { #[cfg(windows)] let command = command.arg("-luserenv").arg("-lWs2_32").arg("-ladvapi32"); #[cfg(not(windows))] - // TODO: remove `-lffi` before shipping - let command = command.arg("-ldl").arg("-lm").arg("-pthread").arg("-lffi"); + let command = command.arg("-ldl").arg("-lm").arg("-pthread"); let output = command.arg("-o").arg(&self.output_path).output()?; if !output.status.success() { diff --git a/tests/integration/cli/tests/create_exe.rs b/tests/integration/cli/tests/create_exe.rs index ec2eb54ff6b..b8302bccd5a 100644 --- a/tests/integration/cli/tests/create_exe.rs +++ b/tests/integration/cli/tests/create_exe.rs @@ -51,8 +51,6 @@ impl WasmerCreateExe { .arg("create-exe") .arg(&self.wasm_path.canonicalize()?) .arg(&self.compiler.to_flag()) - // TODO: remove before shipping - .arg("-lffi") .arg("-o") .arg(&self.native_executable_path) .output()?; From d51cff62bdb426317ac7b035f721d63e369d2af4 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 12 Oct 2020 15:59:27 -0700 Subject: [PATCH 08/16] Fix merge issue caused by GitHub UI --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f213bb2f6ec..106e2781cc8 100644 --- a/Makefile +++ b/Makefile @@ -174,7 +174,7 @@ package-capi: mkdir -p "package/include" mkdir -p "package/lib" cp lib/c-api/wasmer.h* package/include - cp lib/c-api/wasmer_wasm.h* package/include + cp lib/c-api/wasmer_wasm.h* package/include cp lib/c-api/wasm.h* package/include cp lib/c-api/doc/deprecated/index.md package/include/README.md ifeq ($(OS), Windows_NT) From e430bbaf4f1ad8ee54b14faa17b9775fc6e3907d Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 12 Oct 2020 16:17:34 -0700 Subject: [PATCH 09/16] More explicitly feature gate `wasmer create-exe` --- lib/cli/src/bin/wasmer.rs | 4 ++-- lib/cli/src/commands.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/cli/src/bin/wasmer.rs b/lib/cli/src/bin/wasmer.rs index 6d72964bf36..7c749641360 100644 --- a/lib/cli/src/bin/wasmer.rs +++ b/lib/cli/src/bin/wasmer.rs @@ -1,5 +1,5 @@ use anyhow::Result; -#[cfg(feature = "object-file")] +#[cfg(all(feature = "object-file", feature = "compiler"))] use wasmer_cli::commands::CreateExe; #[cfg(feature = "wast")] use wasmer_cli::commands::Wast; @@ -60,7 +60,7 @@ impl WasmerCLIOptions { Self::Cache(cache) => cache.execute(), Self::Validate(validate) => validate.execute(), Self::Compile(compile) => compile.execute(), - #[cfg(feature = "object-file")] + #[cfg(all(feature = "object-file", feature = "compiler"))] Self::CreateExe(create_exe) => create_exe.execute(), Self::Config(config) => config.execute(), Self::Inspect(inspect) => inspect.execute(), diff --git a/lib/cli/src/commands.rs b/lib/cli/src/commands.rs index 4d07c37e8ed..7107150ccf4 100644 --- a/lib/cli/src/commands.rs +++ b/lib/cli/src/commands.rs @@ -2,7 +2,7 @@ mod cache; mod compile; mod config; -#[cfg(feature = "object-file")] +#[cfg(all(feature = "object-file", feature = "compiler"))] mod create_exe; mod inspect; mod run; @@ -11,7 +11,7 @@ mod validate; #[cfg(feature = "wast")] mod wast; -#[cfg(feature = "object-file")] +#[cfg(all(feature = "object-file", feature = "compiler"))] pub use create_exe::*; #[cfg(feature = "wast")] pub use wast::*; From fc11c1006339a63d65764a3a40af27cfcc84929a Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 12 Oct 2020 16:25:40 -0700 Subject: [PATCH 10/16] Add comment about why Windows is using clang++ --- lib/cli/src/commands/create_exe.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/cli/src/commands/create_exe.rs b/lib/cli/src/commands/create_exe.rs index d4893e9476e..62b9a42bd1f 100644 --- a/lib/cli/src/commands/create_exe.rs +++ b/lib/cli/src/commands/create_exe.rs @@ -187,6 +187,8 @@ fn run_c_compile( ) -> anyhow::Result<()> { #[cfg(not(windows))] let c_compiler = "cc"; + // We must use a C++ compiler on Windows because wasm.h uses `static_assert` + // which isn't available in `clang` on Windows. #[cfg(windows)] let c_compiler = "clang++"; From aeb17cda6a1dbfb5b571429a55bd22b0f34340a9 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 12 Oct 2020 16:30:13 -0700 Subject: [PATCH 11/16] Fix cfg logic in wasmer.rs again --- lib/cli/src/bin/wasmer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cli/src/bin/wasmer.rs b/lib/cli/src/bin/wasmer.rs index 7c749641360..509bbdeded5 100644 --- a/lib/cli/src/bin/wasmer.rs +++ b/lib/cli/src/bin/wasmer.rs @@ -29,7 +29,7 @@ enum WasmerCLIOptions { Compile(Compile), /// Compile a WebAssembly binary into a native executable - #[cfg(feature = "object-file")] + #[cfg(all(feature = "object-file", feature = "compiler"))] #[structopt(name = "create-exe")] CreateExe(CreateExe), From 7cf1e124ceaa00c7832585ee8722db9f09dd25a1 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 12 Oct 2020 17:21:34 -0700 Subject: [PATCH 12/16] Attempt to fake install Wasmer for integration tests --- .github/workflows/main.yaml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 9318e1094c6..333df8c692e 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -150,19 +150,23 @@ jobs: run: | make build-wapm if: needs.setup.outputs.DOING_RELEASE == '1' + - name: Package Wasmer + run: | + make package - name: Run integration tests (Windows) shell: cmd run: | call refreshenv + cd dist + wasmer-windows.exe + cd .. make test-integration if: matrix.run_integration_tests && matrix.os == 'windows-latest' - name: Run integration tests (Unix) - run: make test-integration - if: matrix.run_integration_tests && matrix.os != 'windows-latest' - - name: Package Wasmer run: | - make package - if: needs.setup.outputs.DOING_RELEASE == '1' + export WASMER_DIR=`pwd`/package + make test-integration + if: matrix.run_integration_tests && matrix.os != 'windows-latest' - name: Upload Artifacts uses: actions/upload-artifact@v2 if: needs.setup.outputs.DOING_RELEASE == '1' From f336ac13caf61bb8608d97035316687bd30b0f7a Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 12 Oct 2020 17:30:36 -0700 Subject: [PATCH 13/16] Lie to Windows about installation of Wasmer for tests too --- .github/workflows/main.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 333df8c692e..8b2ce31655a 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -157,9 +157,7 @@ jobs: shell: cmd run: | call refreshenv - cd dist - wasmer-windows.exe - cd .. + setx WASMER_DIR "%CD%/package" make test-integration if: matrix.run_integration_tests && matrix.os == 'windows-latest' - name: Run integration tests (Unix) From 30d34f1b5f2b52d8654a00858700a21c07024e69 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 12 Oct 2020 18:02:10 -0700 Subject: [PATCH 14/16] Package wasmer without wapm for integration tests --- .github/workflows/main.yaml | 4 ++++ Makefile | 3 +++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 8b2ce31655a..246d9368237 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -150,9 +150,13 @@ jobs: run: | make build-wapm if: needs.setup.outputs.DOING_RELEASE == '1' + - name: Package Wasmer for integration tests + run: make package-without-wapm-for-integration-tests + if: needs.setup.outputs.DOING_RELEASE != '1' - name: Package Wasmer run: | make package + if: needs.setup.outputs.DOING_RELEASE == '1' - name: Run integration tests (Windows) shell: cmd run: | diff --git a/Makefile b/Makefile index 106e2781cc8..d79280cc388 100644 --- a/Makefile +++ b/Makefile @@ -214,6 +214,9 @@ else cp ./wasmer.tar.gz ./dist/$(shell ./scripts/binary-name.sh) endif +# command for simulating installing Wasmer without wapm. +package-without-wapm-for-integration-tests: package-wasmer package-capi + ################# # Miscellaneous # ################# From 58b05a9881790ce30890d00c8e96cce1389cfd29 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 13 Oct 2020 11:11:45 -0700 Subject: [PATCH 15/16] Fix env var setting on Windows --- .github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 246d9368237..d74303e3f64 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -161,7 +161,7 @@ jobs: shell: cmd run: | call refreshenv - setx WASMER_DIR "%CD%/package" + set WASMER_DIR=%CD%\package make test-integration if: matrix.run_integration_tests && matrix.os == 'windows-latest' - name: Run integration tests (Unix) From 3a59e8a59d4c537856cfcbe128dd9b3be4a31f01 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 13 Oct 2020 14:45:19 -0700 Subject: [PATCH 16/16] Fix mistake in name of C API lib on Windows --- lib/cli/src/commands/create_exe.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cli/src/commands/create_exe.rs b/lib/cli/src/commands/create_exe.rs index 62b9a42bd1f..41b46d61580 100644 --- a/lib/cli/src/commands/create_exe.rs +++ b/lib/cli/src/commands/create_exe.rs @@ -174,7 +174,7 @@ fn get_libwasmer_path() -> anyhow::Result { #[cfg(not(windows))] path.push("libwasmer.a"); #[cfg(windows)] - path.push("libwasmer.lib"); + path.push("wasmer_c_api.lib"); Ok(path) }