Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add emscripten functions and types to the C API #1062

Merged
merged 7 commits into from
Dec 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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