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

Implement a WASI instantiation C API. #977

Merged
merged 10 commits into from
Feb 26, 2020
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ rusty-tags.*
*~
\#*\#
docs/book
.vscode/
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion crates/api/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ fn from_wasmtime_abiparam(param: &ir::AbiParam) -> Option<ValType> {
/// A descriptor for a function in a WebAssembly module.
///
/// WebAssembly functions can have 0 or more parameters and results.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct FuncType {
params: Box<[ValType]>,
results: Box<[ValType]>,
Expand Down
2 changes: 2 additions & 0 deletions crates/c-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ doctest = false

[dependencies]
wasmtime = { path = "../api" }
wasi-common = { path = "../wasi-common" }
wasmtime-wasi = { path = "../wasi" }
yurydelendik marked this conversation as resolved.
Show resolved Hide resolved
70 changes: 70 additions & 0 deletions crates/c-api/include/wasi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// WASI C API

#ifndef WASI_H
#define WASI_H

#include "wasm.h"

#ifndef WASI_API_EXTERN
#ifdef _WIN32
#define WASI_API_EXTERN __declspec(dllimport)
#else
#define WASI_API_EXTERN
#endif
#endif

#ifdef __cplusplus
extern "C" {
#endif

#define own

#define WASI_DECLARE_OWN(name) \
typedef struct wasi_##name##_t wasi_##name##_t; \
WASI_API_EXTERN void wasi_##name##_delete(own wasi_##name##_t*);

// WASI config

WASI_DECLARE_OWN(config)

WASI_API_EXTERN own wasi_config_t* wasi_config_new();

WASI_API_EXTERN void wasi_config_set_argv(wasi_config_t* config, int argc, const char* argv[]);
WASI_API_EXTERN void wasi_config_inherit_argv(wasi_config_t* config);

WASI_API_EXTERN void wasi_config_set_env(wasi_config_t* config, int envc, const char* names[], const char* values[]);
WASI_API_EXTERN void wasi_config_inherit_env(wasi_config_t* config);

WASI_API_EXTERN bool wasi_config_set_stdin_file(wasi_config_t* config, const char* path);
WASI_API_EXTERN void wasi_config_inherit_stdin(wasi_config_t* config);

WASI_API_EXTERN bool wasi_config_set_stdout_file(wasi_config_t* config, const char* path);
WASI_API_EXTERN void wasi_config_inherit_stdout(wasi_config_t* config);

WASI_API_EXTERN bool wasi_config_set_stderr_file(wasi_config_t* config, const char* path);
WASI_API_EXTERN void wasi_config_inherit_stderr(wasi_config_t* config);

WASI_API_EXTERN bool wasi_config_preopen_dir(wasi_config_t* config, const char* path, const char* guest_path);

// WASI instance

WASI_DECLARE_OWN(instance)

WASI_API_EXTERN own wasi_instance_t* wasi_instance_new(
wasm_store_t* store,
own wasi_config_t* config,
own wasm_trap_t** trap
);

WASI_API_EXTERN const wasm_extern_t* wasi_instance_bind_import(
const wasi_instance_t* instance,
const wasm_importtype_t* import
);

#undef own

#ifdef __cplusplus
} // extern "C"
#endif

#endif // #ifdef WASI_H
12 changes: 7 additions & 5 deletions crates/c-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ use wasmtime::{
Table, TableType, Trap, Val, ValType,
};

mod ext;
mod wasi;

pub use crate::ext::*;
pub use crate::wasi::*;

macro_rules! declare_vec {
($name:ident, $elem_ty:path) => {
#[repr(C)]
Expand Down Expand Up @@ -1025,7 +1031,7 @@ pub unsafe extern "C" fn wasm_trap_new(
if message[message.len() - 1] != 0 {
panic!("wasm_trap_new message stringz expected");
}
let message = String::from_utf8_lossy(message);
let message = String::from_utf8_lossy(&message[..message.len() - 1]);
let trap = Box::new(wasm_trap_t {
trap: HostRef::new(Trap::new(message)),
});
Expand Down Expand Up @@ -1777,7 +1783,3 @@ pub unsafe extern "C" fn wasm_valtype_vec_copy(
let slice = slice::from_raw_parts((*src).data, (*src).size);
(*out).set_from_slice(slice);
}

mod ext;

pub use crate::ext::*;
239 changes: 239 additions & 0 deletions crates/c-api/src/wasi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
//! The WASI embedding API definitions for Wasmtime.
use crate::{wasm_extern_t, wasm_importtype_t, wasm_store_t, wasm_trap_t, ExternHost, ExternType};
use std::collections::HashMap;
use std::ffi::CStr;
use std::fs::File;
use std::os::raw::{c_char, c_int};
use std::path::Path;
use std::slice;
use wasi_common::{preopen_dir, WasiCtxBuilder};
use wasmtime::{HostRef, Trap};
use wasmtime_wasi::Wasi;

unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> {
CStr::from_ptr(path).to_str().map(Path::new).ok()
}

unsafe fn open_file(path: *const c_char) -> Option<File> {
File::open(cstr_to_path(path)?).ok()
}

unsafe fn create_file(path: *const c_char) -> Option<File> {
File::create(cstr_to_path(path)?).ok()
}

#[repr(C)]
pub struct wasi_config_t {
builder: WasiCtxBuilder,
}

impl wasi_config_t {}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_new() -> *mut wasi_config_t {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW for new APIs we may want to explore updating how we write bindings. For example we could make all functions safe ideally, and this function could, for example, return Box<wasi_config_t> instead of *mut which would avoid extra boilerplate.

Another example would be that wasi_config_delete would be safe and take a Box<wasi_config_t> as well perhaps. I've been meaning to see how far we can take this because it'd be great to avoid so much unsafety in the bindings, but it definitely breaks down at some point, for example when handling *const c_char

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be interesting to do. I don't like the unsafety of this layer either and while it needs to exist at some level to interact with C, perhaps we could make it much more difficult for us to introduce leaks and use-after-frees by generating the unsafe boilerplate.

Box::into_raw(Box::new(wasi_config_t {
builder: WasiCtxBuilder::new(),
}))
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_delete(config: *mut wasi_config_t) {
drop(Box::from_raw(config));
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_set_argv(
config: *mut wasi_config_t,
argc: c_int,
argv: *const *const c_char,
) {
(*config).builder.args(
slice::from_raw_parts(argv, argc as usize)
.iter()
.map(|a| slice::from_raw_parts(*a as *const u8, CStr::from_ptr(*a).to_bytes().len())),
);
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_argv(config: *mut wasi_config_t) {
(*config).builder.inherit_args();
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_set_env(
config: *mut wasi_config_t,
envc: c_int,
names: *const *const c_char,
values: *const *const c_char,
) {
let names = slice::from_raw_parts(names, envc as usize);
let values = slice::from_raw_parts(values, envc as usize);

(*config).builder.envs(
names
.iter()
.map(|p| CStr::from_ptr(*p).to_bytes())
.zip(values.iter().map(|p| CStr::from_ptr(*p).to_bytes())),
);
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_env(config: *mut wasi_config_t) {
(*config).builder.inherit_env();
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_set_stdin_file(
config: *mut wasi_config_t,
path: *const c_char,
) -> bool {
let file = match open_file(path) {
Some(f) => f,
None => return false,
};

(*config).builder.stdin(file);

true
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_stdin(config: *mut wasi_config_t) {
(*config).builder.inherit_stdin();
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_set_stdout_file(
config: *mut wasi_config_t,
path: *const c_char,
) -> bool {
let file = match create_file(path) {
Some(f) => f,
None => return false,
};

(*config).builder.stdout(file);

true
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_stdout(config: *mut wasi_config_t) {
(*config).builder.inherit_stdout();
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_set_stderr_file(
config: *mut wasi_config_t,
path: *const c_char,
) -> bool {
let file = match create_file(path) {
Some(f) => f,
None => return false,
};

(*config).builder.stderr(file);

true
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_stderr(config: *mut wasi_config_t) {
(*config).builder.inherit_stderr();
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_preopen_dir(
config: *mut wasi_config_t,
path: *const c_char,
guest_path: *const c_char,
) -> bool {
let guest_path = match cstr_to_path(guest_path) {
Some(p) => p,
None => return false,
};

let dir = match cstr_to_path(path) {
Some(p) => match preopen_dir(p) {
Ok(d) => d,
Err(_) => return false,
},
None => return false,
};

(*config).builder.preopened_dir(dir, guest_path);

true
}

#[repr(C)]
pub struct wasi_instance_t {
wasi: Wasi,
export_cache: HashMap<String, Box<wasm_extern_t>>,
}

#[no_mangle]
pub unsafe extern "C" fn wasi_instance_new(
store: *mut wasm_store_t,
config: *mut wasi_config_t,
trap: *mut *mut wasm_trap_t,
) -> *mut wasi_instance_t {
let store = &(*store).store.borrow();
let mut config = Box::from_raw(config);

match config.builder.build() {
Ok(ctx) => Box::into_raw(Box::new(wasi_instance_t {
wasi: Wasi::new(store, ctx),
export_cache: HashMap::new(),
})),
Err(e) => {
(*trap) = Box::into_raw(Box::new(wasm_trap_t {
trap: HostRef::new(Trap::new(e.to_string())),
}));

std::ptr::null_mut()
}
}
}

#[no_mangle]
pub unsafe extern "C" fn wasi_instance_delete(instance: *mut wasi_instance_t) {
drop(Box::from_raw(instance));
}

#[no_mangle]
pub unsafe extern "C" fn wasi_instance_bind_import(
instance: *mut wasi_instance_t,
import: *const wasm_importtype_t,
) -> *const wasm_extern_t {
// TODO: support previous versions?
if (*import).ty.module() != "wasi_snapshot_preview1" {
return std::ptr::null_mut();
}

// The import should be a function (WASI only exports functions)
let func_type = match (*import).ty.ty() {
ExternType::Func(f) => f,
_ => return std::ptr::null_mut(),
};

let name = (*import).ty.name();

match (*instance).wasi.get_export(name) {
Some(export) => {
if export.ty() != func_type {
return std::ptr::null_mut();
}

&**(*instance)
peterhuene marked this conversation as resolved.
Show resolved Hide resolved
.export_cache
.entry(name.to_string())
.or_insert_with(|| {
Box::new(wasm_extern_t {
which: ExternHost::Func(HostRef::new(export.clone())),
})
}) as *const wasm_extern_t
}
None => std::ptr::null_mut(),
}
}
Loading