Skip to content

Commit

Permalink
Merge #1062
Browse files Browse the repository at this point in the history
1062: Add emscripten functions and types to the C API r=syrusakbary a=MarkMcCaskey

resolves #574 

Left to do:
- [x] Add tests
- [x] Add memory set up, etc (need to break up monolithic ("run" function in wasmer_emscripten into pieces)
- [x] Add passing args, etc

# Review

- [x] Add a short description of the the change to the CHANGELOG.md file


Co-authored-by: Mark McCaskey <[email protected]>
Co-authored-by: Mark McCaskey <[email protected]>
Co-authored-by: Syrus Akbary <[email protected]>
  • Loading branch information
4 people authored Dec 17, 2019
2 parents 971f4e3 + 83fded4 commit 229b99a
Show file tree
Hide file tree
Showing 17 changed files with 557 additions and 67 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## **[Unreleased]**

- [#1062](https://github.com/wasmerio/wasmer/pull/1062) Expose some opt-in Emscripten functions to the C API
- [#1032](https://github.com/wasmerio/wasmer/pull/1032) Change the signature of the Emscripten `abort` function to work with Emscripten 1.38.30
- [#1060](https://github.com/wasmerio/wasmer/pull/1060) Test the capi with all the backends
- [#1069](https://github.com/wasmerio/wasmer/pull/1069) Add function `get_memory_and_data` to `Ctx` to help prevent undefined behavior and mutable aliasing. It allows accessing memory while borrowing data mutably for the `Ctx` lifetime. This new function is now being used in `wasmer-wasi`.
- [#1058](https://github.com/wasmerio/wasmer/pull/1058) Fix minor panic issue when `wasmer::compile_with` called with llvm backend.
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ capi-llvm:
cargo build --manifest-path lib/runtime-c-api/Cargo.toml --release \
--no-default-features --features llvm-backend,wasi

capi-emscripten:
cargo build --manifest-path lib/runtime-c-api/Cargo.toml --release \
--no-default-features --features singlepass-backend,emscripten

# We use cranelift as the default backend for the capi for now
capi: capi-cranelift

Expand All @@ -129,7 +133,11 @@ test-capi-llvm: capi-llvm
cargo test --manifest-path lib/runtime-c-api/Cargo.toml --release \
--no-default-features --features llvm-backend,wasi

test-capi: test-capi-singlepass test-capi-cranelift test-capi-llvm
test-capi-emscripten: capi-emscripten
cargo test --manifest-path lib/runtime-c-api/Cargo.toml --release \
--no-default-features --features singlepass-backend,emscripten

test-capi: test-capi-singlepass test-capi-cranelift test-capi-llvm test-capi-emscripten

capi-test: test-capi

Expand Down
93 changes: 57 additions & 36 deletions lib/emscripten/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,61 @@ impl<'a> EmscriptenData<'a> {
}
}

/// Call the global constructors for C++ and set up the emscripten environment.
///
/// Note that this function does not completely set up Emscripten to be called.
/// before calling this function, please initialize `Ctx::data` with a pointer
/// to [`EmscriptenData`].
pub fn set_up_emscripten(instance: &mut Instance) -> CallResult<()> {
// ATINIT
// (used by C++)
if let Ok(_func) = instance.dyn_func("globalCtors") {
instance.call("globalCtors", &[])?;
}

if let Ok(_func) = instance.dyn_func("___emscripten_environ_constructor") {
instance.call("___emscripten_environ_constructor", &[])?;
}
Ok(())
}

/// 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, path: &str, args: &[&str]) -> CallResult<()> {
let (func_name, main_func) = match instance.dyn_func("_main") {
Ok(func) => Ok(("_main", func)),
Err(_e) => match instance.dyn_func("main") {
Ok(func) => Ok(("main", func)),
Err(e) => Err(e),
},
}?;
let num_params = main_func.signature().params().len();
let _result = match num_params {
2 => {
let mut new_args = vec![path];
new_args.extend(args);
let (argc, argv) = store_module_arguments(instance.context_mut(), new_args);
instance.call(
func_name,
&[Value::I32(argc as i32), Value::I32(argv as i32)],
)?;
}
0 => {
instance.call(func_name, &[])?;
}
_ => {
return Err(CallError::Resolve(ResolveError::ExportWrongType {
name: "main".to_string(),
}))
}
};

Ok(())
}

/// Top level function to execute emscripten
pub fn run_emscripten_instance(
_module: &Module,
instance: &mut Instance,
Expand All @@ -338,15 +393,7 @@ pub fn run_emscripten_instance(
let data_ptr = &mut data as *mut _ as *mut c_void;
instance.context_mut().data = data_ptr;

// ATINIT
// (used by C++)
if let Ok(_func) = instance.dyn_func("globalCtors") {
instance.call("globalCtors", &[])?;
}

if let Ok(_func) = instance.dyn_func("___emscripten_environ_constructor") {
instance.call("___emscripten_environ_constructor", &[])?;
}
set_up_emscripten(instance)?;

// println!("running emscripten instance");

Expand All @@ -356,33 +403,7 @@ pub fn run_emscripten_instance(
//let (argc, argv) = store_module_arguments(instance.context_mut(), args);
instance.call(&ep, &[Value::I32(arg as i32)])?;
} else {
let (func_name, main_func) = match instance.dyn_func("_main") {
Ok(func) => Ok(("_main", func)),
Err(_e) => match instance.dyn_func("main") {
Ok(func) => Ok(("main", func)),
Err(e) => Err(e),
},
}?;
let num_params = main_func.signature().params().len();
let _result = match num_params {
2 => {
let mut new_args = vec![path];
new_args.extend(args);
let (argc, argv) = store_module_arguments(instance.context_mut(), new_args);
instance.call(
func_name,
&[Value::I32(argc as i32), Value::I32(argv as i32)],
)?;
}
0 => {
instance.call(func_name, &[])?;
}
_ => {
return Err(CallError::Resolve(ResolveError::ExportWrongType {
name: "main".to_string(),
}))
}
};
emscripten_call_main(instance, path, &args)?;
}

// TODO atexit for emscripten
Expand Down
6 changes: 6 additions & 0 deletions lib/runtime-c-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,19 @@ path = "../wasi"
version = "0.11.0"
optional = true

[dependencies.wasmer-emscripten]
path = "../emscripten"
version = "0.11.0"
optional = true

[features]
default = ["cranelift-backend", "wasi"]
debug = ["wasmer-runtime/debug"]
singlepass-backend = ["wasmer-runtime/singlepass", "wasmer-runtime/default-backend-singlepass"]
cranelift-backend = ["wasmer-runtime/cranelift", "wasmer-runtime/default-backend-cranelift"]
llvm-backend = ["wasmer-runtime/llvm", "wasmer-runtime/default-backend-llvm"]
wasi = ["wasmer-wasi"]
emscripten = ["wasmer-emscripten"]

[build-dependencies]
cbindgen = "0.9"
21 changes: 16 additions & 5 deletions lib/runtime-c-api/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn main() {
let mut out_wasmer_header_file = PathBuf::from(&out_dir);
out_wasmer_header_file.push("wasmer");

const WASMER_PRE_HEADER: &str = r#"
let mut pre_header = r#"
#if !defined(WASMER_H_MACROS)
#define WASMER_H_MACROS
Expand All @@ -28,17 +28,27 @@ fn main() {
#endif
#endif
#endif // WASMER_H_MACROS
"#;
"#
.to_string();

#[cfg(feature = "emscripten")]
{
pre_header += "#define WASMER_EMSCRIPTEN_ENABLED\n";
}

// close pre header
pre_header += "#endif // WASMER_H_MACROS\n";

// Generate the C bindings in the `OUT_DIR`.
out_wasmer_header_file.set_extension("h");
Builder::new()
.with_crate(crate_dir.clone())
.with_language(Language::C)
.with_include_guard("WASMER_H")
.with_header(WASMER_PRE_HEADER)
.with_header(&pre_header)
.with_define("target_family", "windows", "_WIN32")
.with_define("target_arch", "x86_64", "ARCH_X86_64")
.with_define("feature", "emscripten", "WASMER_EMSCRIPTEN_ENABLED")
.generate()
.expect("Unable to generate C bindings")
.write_to_file(out_wasmer_header_file.as_path());
Expand All @@ -49,9 +59,10 @@ fn main() {
.with_crate(crate_dir)
.with_language(Language::Cxx)
.with_include_guard("WASMER_H")
.with_header(WASMER_PRE_HEADER)
.with_header(&pre_header)
.with_define("target_family", "windows", "_WIN32")
.with_define("target_arch", "x86_64", "ARCH_X86_64")
.with_define("feature", "emscripten", "WASMER_EMSCRIPTEN_ENABLED")
.generate()
.expect("Unable to generate C++ bindings")
.write_to_file(out_wasmer_header_file.as_path());
Expand Down
145 changes: 145 additions & 0 deletions lib/runtime-c-api/src/import/emscripten.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//! Functions and types for dealing with Emscripten imports
use super::*;
use crate::{get_slice_checked, instance::wasmer_instance_t, module::wasmer_module_t};

use std::ptr;
use wasmer_emscripten::{EmscriptenData, EmscriptenGlobals};
use wasmer_runtime::{Instance, Module};

/// Type used to construct an import_object_t with Emscripten imports.
#[repr(C)]
pub struct wasmer_emscripten_globals_t;

/// Create a `wasmer_emscripten_globals_t` from a Wasm module.
#[no_mangle]
pub unsafe extern "C" fn wasmer_emscripten_get_globals(
module: *const wasmer_module_t,
) -> *mut wasmer_emscripten_globals_t {
if module.is_null() {
return ptr::null_mut();
}
let module = &*(module as *const Module);
match EmscriptenGlobals::new(module) {
Ok(globals) => Box::into_raw(Box::new(globals)) as *mut wasmer_emscripten_globals_t,
Err(msg) => {
update_last_error(CApiError { msg });
return ptr::null_mut();
}
}
}

/// Destroy `wasmer_emscrpten_globals_t` created by
/// `wasmer_emscripten_get_emscripten_globals`.
#[no_mangle]
pub unsafe extern "C" fn wasmer_emscripten_destroy_globals(
globals: *mut wasmer_emscripten_globals_t,
) {
if globals.is_null() {
return;
}
let _ = Box::from_raw(globals);
}

/// Execute global constructors (required if the module is compiled from C++)
/// and sets up the internal environment.
///
/// This function sets the data pointer in the same way that
/// [`wasmer_instance_context_data_set`] does.
#[no_mangle]
pub unsafe extern "C" fn wasmer_emscripten_set_up(
instance: *mut wasmer_instance_t,
globals: *mut wasmer_emscripten_globals_t,
) -> wasmer_result_t {
if globals.is_null() || instance.is_null() {
return wasmer_result_t::WASMER_ERROR;
}
let instance = &mut *(instance as *mut Instance);
let globals = &*(globals as *mut EmscriptenGlobals);
let em_data = Box::into_raw(Box::new(EmscriptenData::new(
instance,
&globals.data,
Default::default(),
))) as *mut c_void;
instance.context_mut().data = em_data;

match wasmer_emscripten::set_up_emscripten(instance) {
Ok(_) => wasmer_result_t::WASMER_OK,
Err(e) => {
update_last_error(e);
wasmer_result_t::WASMER_ERROR
}
}
}

/// Convenience function for setting up arguments and calling the Emscripten
/// main function.
///
/// WARNING:
///
/// Do not call this function on untrusted code when operating without
/// additional sandboxing in place.
/// Emscripten has access to many host system calls and therefore may do very
/// bad things.
#[no_mangle]
pub unsafe extern "C" fn wasmer_emscripten_call_main(
instance: *mut wasmer_instance_t,
args: *const wasmer_byte_array,
args_len: c_uint,
) -> wasmer_result_t {
if instance.is_null() || args.is_null() {
return wasmer_result_t::WASMER_ERROR;
}
let instance = &mut *(instance as *mut Instance);

let arg_list = get_slice_checked(args, args_len as usize);
let arg_process_result: Result<Vec<&str>, _> =
arg_list.iter().map(|arg| arg.as_str()).collect();
let arg_vec = match arg_process_result.as_ref() {
Ok(arg_vec) => arg_vec,
Err(err) => {
update_last_error(*err);
return wasmer_result_t::WASMER_ERROR;
}
};

let prog_name = if let Some(prog_name) = arg_vec.first() {
prog_name
} else {
update_last_error(CApiError {
msg: "First argument (program name) is required to execute Emscripten's main function"
.to_string(),
});
return wasmer_result_t::WASMER_ERROR;
};

match wasmer_emscripten::emscripten_call_main(instance, prog_name, &arg_vec[1..]) {
Ok(_) => wasmer_result_t::WASMER_OK,
Err(e) => {
update_last_error(e);
wasmer_result_t::WASMER_ERROR
}
}
}

/// Create a `wasmer_import_object_t` with Emscripten imports, use
/// `wasmer_emscripten_get_emscripten_globals` to get a
/// `wasmer_emscripten_globals_t` from a `wasmer_module_t`.
///
/// WARNING:
///1
/// This `import_object_t` contains thin-wrappers around host system calls.
/// Do not use this to execute untrusted code without additional sandboxing.
#[no_mangle]
pub unsafe extern "C" fn wasmer_emscripten_generate_import_object(
globals: *mut wasmer_emscripten_globals_t,
) -> *mut wasmer_import_object_t {
if globals.is_null() {
return ptr::null_mut();
}
// TODO: figure out if we should be using UnsafeCell here or something
let g = &mut *(globals as *mut EmscriptenGlobals);
let import_object = Box::new(wasmer_emscripten::generate_emscripten_env(g));

Box::into_raw(import_object) as *mut wasmer_import_object_t
}
Loading

0 comments on commit 229b99a

Please sign in to comment.