Skip to content

Commit

Permalink
Better Wasmtime API for embedder (#287)
Browse files Browse the repository at this point in the history
* Migrate wasm-rust-api code to wasmtime.
  • Loading branch information
yurydelendik authored and sunfishcode committed Aug 21, 2019
1 parent 8ea883a commit ca79149
Show file tree
Hide file tree
Showing 23 changed files with 2,791 additions and 29 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
path = lightbeam
url = https://github.com/CraneStation/lightbeam.git
branch = master
[submodule "wasmtime-api/c-examples/wasm-c-api"]
path = wasmtime-api/c-examples/wasm-c-api
url = https://github.com/WebAssembly/wasm-c-api
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ cranelift-codegen = { version = "0.40.0", features = ["enable-serde"] }
cranelift-native = "0.40.0"
cranelift-entity = { version = "0.40.0", features = ["enable-serde"] }
cranelift-wasm = { version = "0.40.0", features = ["enable-serde"] }
wasmtime-api = { path = "wasmtime-api" }
wasmtime-debug = { path = "wasmtime-debug" }
wasmtime-environ = { path = "wasmtime-environ" }
wasmtime-interface-types = { path = "wasmtime-interface-types" }
Expand Down
107 changes: 78 additions & 29 deletions src/bin/wasmtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,24 @@

use cranelift_codegen::settings;
use cranelift_codegen::settings::Configurable;
use cranelift_native;
use docopt::Docopt;
use failure::{bail, format_err, Error, ResultExt};
use failure::{bail, Error, ResultExt};
use pretty_env_logger;
use serde::Deserialize;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs::File;
use std::path::Component;
use std::path::{Path, PathBuf};
use std::process::exit;
use std::rc::Rc;
use wabt;
use wasi_common::preopen_dir;
use wasmtime_api::{Config, Engine, Instance, Module, Store};
use wasmtime_environ::cache_conf;
use wasmtime_interface_types::ModuleData;
use wasmtime_jit::{Context, Features, InstanceHandle};
use wasmtime_jit::Features;
use wasmtime_wasi::instantiate_wasi;
use wasmtime_wast::instantiate_spectest;

Expand Down Expand Up @@ -219,11 +222,12 @@ fn rmain() -> Result<(), Error> {
args.flag_cache_dir.as_ref(),
);

let isa_builder = cranelift_native::builder()
.map_err(|s| format_err!("host machine is not a supported target: {}", s))?;
let mut flag_builder = settings::builder();
let mut features: Features = Default::default();

// Enable/disable producing of debug info.
let debug_info = args.flag_g;

// Enable verifier passes in debug mode.
if cfg!(debug_assertions) {
flag_builder.enable("enable_verifier")?;
Expand All @@ -240,89 +244,133 @@ fn rmain() -> Result<(), Error> {
flag_builder.set("opt_level", "best")?;
}

let isa = isa_builder.finish(settings::Flags::new(flag_builder));
let mut context = Context::with_isa(isa).with_features(features);
let config = Config::new(settings::Flags::new(flag_builder), features, debug_info);
let engine = Rc::new(RefCell::new(Engine::new(config)));
let store = Rc::new(RefCell::new(Store::new(engine)));

let mut module_registry = HashMap::new();

// Make spectest available by default.
context.name_instance("spectest".to_owned(), instantiate_spectest()?);
module_registry.insert(
"spectest".to_owned(),
Instance::from_handle(store.clone(), instantiate_spectest()?)?,
);

// Make wasi available by default.
let global_exports = context.get_global_exports();
let global_exports = store.borrow().global_exports().clone();
let preopen_dirs = compute_preopen_dirs(&args.flag_dir, &args.flag_mapdir);
let argv = compute_argv(&args.arg_file, &args.arg_arg);
let environ = compute_environ(&args.flag_env);

let wasi = if args.flag_wasi_c {
#[cfg(feature = "wasi-c")]
{
instantiate_wasi_c("", global_exports, &preopen_dirs, &argv, &environ)?
instantiate_wasi_c("", global_exports.clone(), &preopen_dirs, &argv, &environ)?
}
#[cfg(not(feature = "wasi-c"))]
{
bail!("wasi-c feature not enabled at build time")
}
} else {
instantiate_wasi("", global_exports, &preopen_dirs, &argv, &environ)?
instantiate_wasi("", global_exports.clone(), &preopen_dirs, &argv, &environ)?
};

context.name_instance("wasi_unstable".to_owned(), wasi);

// Enable/disable producing of debug info.
context.set_debug_info(args.flag_g);
module_registry.insert(
"wasi_unstable".to_owned(),
Instance::from_handle(store.clone(), wasi)?,
);

// Load the preload wasm modules.
for filename in &args.flag_preload {
let path = Path::new(&filename);
instantiate_module(&mut context, path)
instantiate_module(store.clone(), &module_registry, path)
.with_context(|_| format!("failed to process preload at `{}`", path.display()))?;
}

// Load the main wasm module.
let path = Path::new(&args.arg_file);
handle_module(&mut context, &args, path)
handle_module(store, &module_registry, &args, path)
.with_context(|_| format!("failed to process main module `{}`", path.display()))?;
Ok(())
}

fn instantiate_module(
context: &mut Context,
store: Rc<RefCell<Store>>,
module_registry: &HashMap<String, (Instance, HashMap<String, usize>)>,
path: &Path,
) -> Result<(InstanceHandle, Vec<u8>), Error> {
) -> Result<(Rc<RefCell<Instance>>, Rc<RefCell<Module>>, Vec<u8>), Error> {
// Read the wasm module binary.
let data = read_wasm(path.to_path_buf())?;

// Compile and instantiating a wasm module.
let handle = context.instantiate_module(None, &data)?;
Ok((handle, data))
let module = Rc::new(RefCell::new(Module::new(store.clone(), &data)?));

// Resolve import using module_registry.
let imports = module
.borrow()
.imports()
.iter()
.map(|i| {
let module_name = i.module().to_string();
if let Some((instance, map)) = module_registry.get(&module_name) {
let field_name = i.name().to_string();
if let Some(export_index) = map.get(&field_name) {
Ok(instance.exports()[*export_index].clone())
} else {
bail!(
"Import {} was not found in module {}",
field_name,
module_name
)
}
} else {
bail!("Import module {} was not found", module_name)
}
})
.collect::<Result<Vec<_>, _>>()?;

let instance = Rc::new(RefCell::new(Instance::new(
store.clone(),
module.clone(),
&imports,
)?));

Ok((instance, module, data))
}

fn handle_module(context: &mut Context, args: &Args, path: &Path) -> Result<(), Error> {
let (mut instance, data) = instantiate_module(context, path)?;
fn handle_module(
store: Rc<RefCell<Store>>,
module_registry: &HashMap<String, (Instance, HashMap<String, usize>)>,
args: &Args,
path: &Path,
) -> Result<(), Error> {
let (instance, _module, data) = instantiate_module(store.clone(), module_registry, path)?;

// If a function to invoke was given, invoke it.
if let Some(f) = &args.flag_invoke {
let data = ModuleData::new(&data)?;
invoke_export(context, &mut instance, &data, f, args)?;
invoke_export(store, instance, &data, f, args)?;
}

Ok(())
}

fn invoke_export(
context: &mut Context,
instance: &mut InstanceHandle,
store: Rc<RefCell<Store>>,
instance: Rc<RefCell<Instance>>,
data: &ModuleData,
name: &str,
args: &Args,
) -> Result<(), Error> {
use wasm_webidl_bindings::ast;
use wasmtime_interface_types::Value;

let mut handle = instance.borrow().handle().clone();

// Use the binding information in `ModuleData` to figure out what arguments
// need to be passed to the function that we're invoking. Currently we take
// the CLI parameters and attempt to parse them into function arguments for
// the function we'll invoke.
let binding = data.binding_for_export(instance, name)?;
let binding = data.binding_for_export(&mut handle, name)?;
if binding.param_types()?.len() > 0 {
eprintln!(
"warning: using `--render` with a function that takes arguments \
Expand Down Expand Up @@ -358,8 +406,9 @@ fn invoke_export(

// Invoke the function and then afterwards print all the results that came
// out, if there are any.
let mut context = store.borrow().engine().borrow().create_wasmtime_context();
let results = data
.invoke(context, instance, name, &values)
.invoke(&mut context, &mut handle, name, &values)
.with_context(|_| format!("failed to invoke `{}`", name))?;
if results.len() > 0 {
eprintln!(
Expand Down
43 changes: 43 additions & 0 deletions wasmtime-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[package]
name = "wasmtime-api"
authors = ["The Wasmtime Project Developers"]
version = "0.1.0"
description = "High-level API to expose the Wasmtime runtime"
license = "Apache-2.0 WITH LLVM-exception"
repository = "https://github.com/CraneStation/wasmtime"
edition = "2018"

[lib]
name = "wasmtime_api"
crate-type = ["lib", "staticlib", "cdylib"]

[dependencies]
cranelift-codegen = "0.40.0"
cranelift-native = "0.40.0"
cranelift-entity = "0.40.0"
cranelift-wasm = "0.40.0"
cranelift-frontend = "0.40.0"
wasmtime-runtime = { path="../wasmtime-runtime" }
wasmtime-environ = { path="../wasmtime-environ" }
wasmtime-jit = { path="../wasmtime-jit" }
wasmparser = "0.36"
failure = { version = "0.1.3", default-features = false }
failure_derive = { version = "0.1.3", default-features = false }
target-lexicon = { version = "0.4.0", default-features = false }
region = "2.0.0"

[features]
default = []
wasm-c-api = []

[dev-dependencies]
# for wasmtime.rs
wasi-common = { git = "https://github.com/CraneStation/wasi-common", rev = "8ea7a98"}
docopt = "1.0.1"
serde = { "version" = "1.0.94", features = ["derive"] }
pretty_env_logger = "0.3.0"
wabt = "0.9.0"
wasmtime-wast = { path="../wasmtime-wast" }
wasmtime-wasi = { path="../wasmtime-wasi" }
rayon = "1.1"
file-per-thread-logger = "0.1.1"
3 changes: 3 additions & 0 deletions wasmtime-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Implementation of wasm-c-api in Rust

https://github.com/WebAssembly/wasm-c-api
Loading

0 comments on commit ca79149

Please sign in to comment.