cli: add create-obj command

lib/compiler: read static object
This commit is contained in:
Manos Pitsidianakis
2022-07-13 23:25:19 +03:00
committed by Manos Pitsidianakis
parent 8b096a504e
commit 83a97f5533
17 changed files with 1731 additions and 84 deletions

View File

@ -106,6 +106,10 @@ singlepass = ["wasmer-compiler-singlepass", "compiler"]
cranelift = ["wasmer-compiler-cranelift", "compiler"]
llvm = ["wasmer-compiler-llvm", "compiler"]
middlewares = ["wasmer-middlewares"]
wasmer-artifact-load = ["wasmer-compiler/wasmer-artifact-load"]
wasmer-artifact-create = ["wasmer-compiler/wasmer-artifact-create"]
static-artifact-load = ["wasmer-compiler/static-artifact-load"]
static-artifact-create = ["wasmer-compiler/static-artifact-create"]
# Testing features
test-singlepass = ["singlepass"]

View File

@ -254,13 +254,13 @@ space := $() $()
comma := ,
# Define the compiler Cargo features for all crates.
compiler_features := --features $(subst $(space),$(comma),$(compilers))
compiler_features := --features $(subst $(space),$(comma),$(compilers)),wasmer-artifact-create,static-artifact-create,wasmer-artifact-load,static-artifact-load
capi_compilers_engines_exclude :=
# Define the compiler Cargo features for the C API. It always excludes
# LLVM for the moment because it causes the linker to fail since LLVM is not statically linked.
# TODO: Reenable LLVM in C-API
capi_compiler_features := --features $(subst $(space),$(comma),$(filter-out llvm, $(compilers)))
capi_compiler_features := --features $(subst $(space),$(comma),$(filter-out llvm, $(compilers))),wasmer-artifact-create,static-artifact-create,wasmer-artifact-load,static-artifact-load
capi_compilers_engines_exclude += llvm-universal
# We exclude singlepass-universal because it doesn't support multivalue (required in wasm-c-api tests)

View File

@ -25,6 +25,10 @@ cranelift = [ "wasmer-compiler-cranelift" ]
llvm = [ "wasmer-compiler-llvm" ]
singlepass = [ "wasmer-compiler-singlepass" ]
universal = [ "wasmer-compiler" ]
wasmer-artifact-load = ["wasmer-compiler/wasmer-artifact-load"]
wasmer-artifact-create = ["wasmer-compiler/wasmer-artifact-create"]
static-artifact-load = ["wasmer-compiler/static-artifact-load"]
static-artifact-create = ["wasmer-compiler/static-artifact-create"]
[[bin]]
name = "equivalence_universal"

View File

@ -120,5 +120,10 @@ enable-serde = [
"wasmer-types/enable-serde",
]
wasmer-artifact-load = ["wasmer-compiler/wasmer-artifact-load"]
wasmer-artifact-create = ["wasmer-compiler/wasmer-artifact-create"]
static-artifact-load = ["wasmer-compiler/static-artifact-load"]
static-artifact-create = ["wasmer-compiler/static-artifact-create"]
[package.metadata.docs.rs]
features = ["compiler", "core", "cranelift", "engine", "jit", "native", "singlepass", "sys", "sys-default" ]

View File

@ -79,6 +79,10 @@ llvm = [
"wasmer-compiler-llvm",
"compiler",
]
wasmer-artifact-load = ["wasmer-compiler/wasmer-artifact-load"]
wasmer-artifact-create = ["wasmer-compiler/wasmer-artifact-create"]
static-artifact-load = ["wasmer-compiler/static-artifact-load"]
static-artifact-create = ["wasmer-compiler/static-artifact-create"]
# Deprecated features.
jit = ["compiler"]

View File

@ -67,6 +67,7 @@ default = [
"emscripten",
"compiler",
"wasmer-artifact-create",
"static-artifact-create",
]
cache = ["wasmer-cache"]
cache-blake3-pure = ["wasmer-cache/blake3-pure"]
@ -78,8 +79,28 @@ compiler = [
"wasmer-compiler/translator",
"wasmer-compiler/compiler",
]
wasmer-artifact-create = ["compiler", "wasmer-compiler/wasmer-artifact-create", "wasmer-object"]
static-artifact-create = ["compiler", "wasmer-compiler/static-artifact-create", "wasmer-object"]
wasmer-artifact-create = ["compiler",
"wasmer/wasmer-artifact-load",
"wasmer/wasmer-artifact-create",
"wasmer-compiler/wasmer-artifact-load",
"wasmer-compiler/wasmer-artifact-create",
"wasmer-object",
]
static-artifact-create = ["compiler",
"wasmer/static-artifact-load",
"wasmer/static-artifact-create",
"wasmer-compiler/static-artifact-load",
"wasmer-compiler/static-artifact-create",
"wasmer-object",
]
wasmer-artifact-load = ["compiler",
"wasmer/wasmer-artifact-load",
"wasmer-compiler/wasmer-artifact-load",
]
static-artifact-load = ["compiler",
"wasmer/static-artifact-load",
"wasmer-compiler/static-artifact-load",
]
experimental-io-devices = [
"wasmer-wasi-experimental-io-devices",

550
lib/cli/src/c_gen/mod.rs Normal file
View File

@ -0,0 +1,550 @@
//! A convenient little abstraction for building up C expressions and generating
//! simple C code.
pub mod staticlib_header;
/// An identifier in C.
pub type CIdent = String;
/// A Type in the C language.
#[derive(Debug, Clone)]
pub enum CType {
/// C `void` type.
Void,
/// A pointer to some other type.
PointerTo {
/// Whether the pointer is `const`.
is_const: bool,
/// The type that the pointer points to.
inner: Box<CType>,
},
/// C 8 bit unsigned integer type.
U8,
/// C 16 bit unsigned integer type.
U16,
/// C 32 bit unsigned integer type.
U32,
/// C 64 bit unsigned integer type.
U64,
/// C pointer sized unsigned integer type.
USize,
/// C 8 bit signed integer type.
I8,
/// C 16 bit signed integer type.
I16,
/// C 32 bit signed integer type.
I32,
/// C 64 bit signed integer type.
I64,
/// C pointer sized signed integer type.
ISize,
/// A function or function pointer.
Function {
/// The arguments the function takes.
arguments: Vec<CType>,
/// The return value if it has one
///
/// None is equivalent to Some(Box(Ctype::Void)).
return_value: Option<Box<CType>>,
},
/// C constant array.
Array {
/// The type of the array.
inner: Box<CType>,
},
/// A user defined type.
TypeDef(String),
}
impl CType {
/// Convenience function to get a mutable void pointer type.
pub fn void_ptr() -> Self {
CType::PointerTo {
is_const: false,
inner: Box::new(CType::Void),
}
}
/// Convenience function to get a const void pointer type.
#[allow(dead_code)]
pub fn const_void_ptr() -> Self {
CType::PointerTo {
is_const: true,
inner: Box::new(CType::Void),
}
}
/// Generate the C source code for a type into the given `String`.
fn generate_c(&self, w: &mut String) {
match &self {
Self::Void => {
w.push_str("void");
}
Self::PointerTo { is_const, inner } => {
if *is_const {
w.push_str("const ");
}
inner.generate_c(w);
w.push('*');
}
Self::U8 => {
w.push_str("unsigned char");
}
Self::U16 => {
w.push_str("unsigned short");
}
Self::U32 => {
w.push_str("unsigned int");
}
Self::U64 => {
w.push_str("unsigned long long");
}
Self::USize => {
w.push_str("unsigned size_t");
}
Self::I8 => {
w.push_str("char");
}
Self::I16 => {
w.push_str("short");
}
Self::I32 => {
w.push_str("int");
}
Self::I64 => {
w.push_str("long long");
}
Self::ISize => {
w.push_str("size_t");
}
Self::Function {
arguments,
return_value,
} => {
// function with no, name, assume it's a function pointer
#[allow(clippy::borrowed_box)]
let ret: CType = return_value
.as_ref()
.map(|i: &Box<CType>| (&**i).clone())
.unwrap_or_default();
ret.generate_c(w);
w.push(' ');
w.push_str("(*)");
w.push('(');
match arguments.len() {
l if l > 1 => {
for arg in &arguments[..arguments.len() - 1] {
arg.generate_c(w);
w.push_str(", ");
}
arguments.last().unwrap().generate_c(w);
}
1 => {
arguments[0].generate_c(w);
}
_ => {}
}
w.push(')');
}
Self::Array { inner } => {
inner.generate_c(w);
w.push_str("[]");
}
Self::TypeDef(inner) => {
w.push_str(inner);
}
}
}
/// Generate the C source code for a type with a nameinto the given `String`.
fn generate_c_with_name(&self, name: &str, w: &mut String) {
match &self {
Self::PointerTo { .. }
| Self::TypeDef { .. }
| Self::Void
| Self::U8
| Self::U16
| Self::U32
| Self::U64
| Self::USize
| Self::I8
| Self::I16
| Self::I32
| Self::I64
| Self::ISize => {
self.generate_c(w);
w.push(' ');
w.push_str(name);
}
Self::Function {
arguments,
return_value,
} => {
#[allow(clippy::borrowed_box)]
let ret: CType = return_value
.as_ref()
.map(|i: &Box<CType>| (&**i).clone())
.unwrap_or_default();
ret.generate_c(w);
w.push(' ');
w.push_str(name);
w.push('(');
match arguments.len() {
l if l > 1 => {
for arg in &arguments[..arguments.len() - 1] {
arg.generate_c(w);
w.push_str(", ");
}
arguments.last().unwrap().generate_c(w);
}
1 => {
arguments[0].generate_c(w);
}
_ => {}
}
w.push(')');
}
Self::Array { inner } => {
inner.generate_c(w);
w.push(' ');
w.push_str(name);
w.push_str("[]");
}
}
}
}
impl Default for CType {
fn default() -> CType {
CType::Void
}
}
/// A statement in the C programming language. This may not be exact to what an
/// AST would look like or what the C standard says about the C language, it's
/// simply a structed way to organize data for generating C code.
#[derive(Debug, Clone)]
pub enum CStatement {
/// A declaration of some kind.
Declaration {
/// The name of the thing being declared.
name: CIdent,
/// Whether the thing being declared is `extern`.
is_extern: bool,
/// Whether the thing being declared is `const`.
is_const: bool,
/// The type of the thing being declared.
ctype: CType,
/// The definition of the thing being declared.
///
/// This is useful for initializing constant arrays, for example.
definition: Option<Box<CStatement>>,
},
/// A literal array of CStatements.
LiteralArray {
/// The contents of the array.
items: Vec<CStatement>,
},
/// A literal constant value, passed through directly as a string.
LiteralConstant {
/// The raw value acting as a constant.
value: String,
},
/// A C-style cast
Cast {
/// The type to cast to.
target_type: CType,
/// The thing being cast.
expression: Box<CStatement>,
},
/// Typedef one type to another.
TypeDef {
/// The type of the thing being typedef'd.
source_type: CType,
/// The new name by which this type may be called.
new_name: CIdent,
},
}
impl CStatement {
/// Generate C source code for the given CStatement.
fn generate_c(&self, w: &mut String) {
match &self {
Self::Declaration {
name,
is_extern,
is_const,
ctype,
definition,
} => {
if *is_const {
w.push_str("const ");
}
if *is_extern {
w.push_str("extern ");
}
ctype.generate_c_with_name(name, w);
if let Some(def) = definition {
w.push_str(" = ");
def.generate_c(w);
}
w.push(';');
w.push('\n');
}
Self::LiteralArray { items } => {
w.push('{');
if !items.is_empty() {
w.push('\n');
}
for item in items {
w.push('\t');
item.generate_c(w);
w.push(',');
w.push('\n');
}
w.push('}');
}
Self::LiteralConstant { value } => {
w.push_str(value);
}
Self::Cast {
target_type,
expression,
} => {
w.push('(');
target_type.generate_c(w);
w.push(')');
w.push(' ');
expression.generate_c(w);
}
Self::TypeDef {
source_type,
new_name,
} => {
w.push_str("typedef ");
// leaky abstraction / hack, doesn't fully solve the problem
if let CType::Function { .. } = source_type {
source_type.generate_c_with_name(&format!("(*{})", new_name), w);
} else {
source_type.generate_c(w);
w.push(' ');
w.push_str(new_name);
}
w.push(';');
w.push('\n');
}
}
}
}
/// Generate C source code from some `CStatements` into a String.
// TODO: add config section
pub fn generate_c(statements: &[CStatement]) -> String {
let mut out = String::new();
for statement in statements {
statement.generate_c(&mut out);
}
out
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn generate_types() {
macro_rules! assert_c_type {
($ctype:expr, $expected:expr) => {
let mut w = String::new();
let ctype = $ctype;
ctype.generate_c(&mut w);
assert_eq!(w, $expected);
};
}
assert_c_type!(CType::Void, "void");
assert_c_type!(CType::void_ptr(), "void*");
assert_c_type!(CType::const_void_ptr(), "const void*");
assert_c_type!(CType::U8, "unsigned char");
assert_c_type!(CType::U16, "unsigned short");
assert_c_type!(CType::U32, "unsigned int");
assert_c_type!(CType::U64, "unsigned long long");
assert_c_type!(CType::USize, "unsigned size_t");
assert_c_type!(CType::I8, "char");
assert_c_type!(CType::I16, "short");
assert_c_type!(CType::I32, "int");
assert_c_type!(CType::I64, "long long");
assert_c_type!(CType::ISize, "size_t");
assert_c_type!(CType::TypeDef("my_type".to_string()), "my_type");
assert_c_type!(
CType::Function {
arguments: vec![CType::U8, CType::ISize],
return_value: None
},
"void (*)(unsigned char, size_t)"
);
assert_c_type!(
CType::Function {
arguments: vec![],
return_value: Some(Box::new(CType::ISize))
},
"size_t (*)()"
);
assert_c_type!(
CType::PointerTo {
is_const: true,
inner: Box::new(CType::PointerTo {
is_const: false,
inner: Box::new(CType::U32)
})
},
"const unsigned int**"
);
// TODO: test more complicated const correctness rules: there are bugs relating to it.
}
#[test]
fn generate_types_with_names() {
macro_rules! assert_c_type {
($ctype:expr, $name:literal, $expected:expr) => {
let mut w = String::new();
let ctype = $ctype;
ctype.generate_c_with_name($name, &mut w);
assert_eq!(w, $expected);
};
}
assert_c_type!(CType::Void, "main", "void main");
assert_c_type!(CType::void_ptr(), "data", "void* data");
assert_c_type!(CType::const_void_ptr(), "data", "const void* data");
assert_c_type!(CType::U8, "data", "unsigned char data");
assert_c_type!(CType::U16, "data", "unsigned short data");
assert_c_type!(CType::U32, "data", "unsigned int data");
assert_c_type!(CType::U64, "data", "unsigned long long data");
assert_c_type!(CType::USize, "data", "unsigned size_t data");
assert_c_type!(CType::I8, "data", "char data");
assert_c_type!(CType::I16, "data", "short data");
assert_c_type!(CType::I32, "data", "int data");
assert_c_type!(CType::I64, "data", "long long data");
assert_c_type!(CType::ISize, "data", "size_t data");
assert_c_type!(
CType::TypeDef("my_type".to_string()),
"data",
"my_type data"
);
assert_c_type!(
CType::Function {
arguments: vec![CType::U8, CType::ISize],
return_value: None
},
"my_func",
"void my_func(unsigned char, size_t)"
);
assert_c_type!(
CType::Function {
arguments: vec![],
return_value: Some(Box::new(CType::ISize))
},
"my_func",
"size_t my_func()"
);
assert_c_type!(
CType::PointerTo {
is_const: true,
inner: Box::new(CType::PointerTo {
is_const: false,
inner: Box::new(CType::U32)
})
},
"data",
"const unsigned int** data"
);
// TODO: test more complicated const correctness rules: there are bugs relating to it.
}
#[test]
fn generate_expressions_works() {
macro_rules! assert_c_expr {
($cexpr:expr, $expected:expr) => {
let mut w = String::new();
let cexpr = $cexpr;
cexpr.generate_c(&mut w);
assert_eq!(w, $expected);
};
}
assert_c_expr!(
CStatement::LiteralConstant {
value: "\"Hello, world!\"".to_string()
},
"\"Hello, world!\""
);
assert_c_expr!(
CStatement::TypeDef {
source_type: CType::Function {
arguments: vec![CType::I32, CType::I32],
return_value: None,
},
new_name: "my_func_ptr".to_string(),
},
"typedef void (*my_func_ptr)(int, int);\n"
);
assert_c_expr!(
CStatement::LiteralArray {
items: vec![
CStatement::LiteralConstant {
value: "1".to_string()
},
CStatement::LiteralConstant {
value: "2".to_string()
},
CStatement::LiteralConstant {
value: "3".to_string()
},
]
},
"{\n\t1,\n\t2,\n\t3,\n}"
);
assert_c_expr!(CStatement::LiteralArray { items: vec![] }, "{}");
assert_c_expr!(
CStatement::Declaration {
name: "my_array".to_string(),
is_extern: false,
is_const: true,
ctype: CType::Array {
inner: Box::new(CType::I32)
},
definition: Some(Box::new(CStatement::LiteralArray {
items: vec![
CStatement::LiteralConstant {
value: "1".to_string()
},
CStatement::LiteralConstant {
value: "2".to_string()
},
CStatement::LiteralConstant {
value: "3".to_string()
},
]
}))
},
"const int my_array[] = {\n\t1,\n\t2,\n\t3,\n};\n"
);
assert_c_expr!(
CStatement::Declaration {
name: "my_array".to_string(),
is_extern: true,
is_const: true,
ctype: CType::Array {
inner: Box::new(CType::I32)
},
definition: None,
},
"const extern int my_array[];\n"
);
}
}

View File

@ -0,0 +1,298 @@
//! Generate a header file for the static object file produced.
use super::{generate_c, CStatement, CType};
use wasmer_types::ModuleInfo;
use wasmer_types::{Symbol, SymbolRegistry};
/// Helper functions to simplify the usage of the static artifact.
const HELPER_FUNCTIONS: &str = r#"
wasm_byte_vec_t generate_serialized_data() {
// We need to pass all the bytes as one big buffer so we have to do all this logic to memcpy
// the various pieces together from the generated header file.
//
// We should provide a `deseralize_vectored` function to avoid requiring this extra work.
char* byte_ptr = (char*)&WASMER_METADATA[0];
size_t num_function_pointers
= sizeof(function_pointers) / sizeof(void*);
size_t num_function_trampolines
= sizeof(function_trampolines) / sizeof(void*);
size_t num_dynamic_function_trampoline_pointers
= sizeof(dynamic_function_trampoline_pointers) / sizeof(void*);
size_t buffer_size = module_bytes_len
+ sizeof(size_t) + sizeof(function_pointers)
+ sizeof(size_t) + sizeof(function_trampolines)
+ sizeof(size_t) + sizeof(dynamic_function_trampoline_pointers);
char* memory_buffer = (char*) malloc(buffer_size);
size_t current_offset = 0;
memcpy(memory_buffer + current_offset, byte_ptr, module_bytes_len);
current_offset += module_bytes_len;
memcpy(memory_buffer + current_offset, (void*)&num_function_pointers, sizeof(size_t));
current_offset += sizeof(size_t);
memcpy(memory_buffer + current_offset, (void*)&function_pointers[0], sizeof(function_pointers));
current_offset += sizeof(function_pointers);
memcpy(memory_buffer + current_offset, (void*)&num_function_trampolines, sizeof(size_t));
current_offset += sizeof(size_t);
memcpy(memory_buffer + current_offset, (void*)&function_trampolines[0], sizeof(function_trampolines));
current_offset += sizeof(function_trampolines);
memcpy(memory_buffer + current_offset, (void*)&num_dynamic_function_trampoline_pointers, sizeof(size_t));
current_offset += sizeof(size_t);
memcpy(memory_buffer + current_offset, (void*)&dynamic_function_trampoline_pointers[0], sizeof(dynamic_function_trampoline_pointers));
current_offset += sizeof(dynamic_function_trampoline_pointers);
wasm_byte_vec_t module_byte_vec = {
.size = buffer_size,
.data = memory_buffer,
};
return module_byte_vec;
}
wasm_module_t* wasmer_static_module_new(wasm_store_t* store, const char* wasm_name) {
// wasm_name intentionally unused for now: will be used in the future.
wasm_byte_vec_t module_byte_vec = generate_serialized_data();
wasm_module_t* module = wasm_module_deserialize(store, &module_byte_vec);
free(module_byte_vec.data);
return module;
}
"#;
/// Generate the header file that goes with the generated object file.
pub fn generate_header_file(
module_info: &ModuleInfo,
symbol_registry: &dyn SymbolRegistry,
metadata_length: usize,
) -> String {
let mut c_statements = vec![
CStatement::LiteralConstant {
value: "#include \"wasmer.h\"\n#include <stdlib.h>\n#include <string.h>\n\n"
.to_string(),
},
CStatement::LiteralConstant {
value: "#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n".to_string(),
},
CStatement::Declaration {
name: "module_bytes_len".to_string(),
is_extern: false,
is_const: true,
ctype: CType::U32,
definition: Some(Box::new(CStatement::LiteralConstant {
value: metadata_length.to_string(),
})),
},
CStatement::Declaration {
name: "WASMER_METADATA".to_string(),
is_extern: true,
is_const: true,
ctype: CType::Array {
inner: Box::new(CType::U8),
},
definition: None,
},
];
let function_declarations = module_info
.functions
.iter()
.filter_map(|(f_index, sig_index)| {
Some((module_info.local_func_index(f_index)?, sig_index))
})
.map(|(function_local_index, _sig_index)| {
let function_name =
symbol_registry.symbol_to_name(Symbol::LocalFunction(function_local_index));
// TODO: figure out the signature here too
CStatement::Declaration {
name: function_name,
is_extern: true,
is_const: false,
ctype: CType::Function {
arguments: vec![CType::Void],
return_value: None,
},
definition: None,
}
});
c_statements.push(CStatement::LiteralConstant {
value: r#"
// Compiled Wasm function pointers ordered by function index: the order they
// appeared in in the Wasm module.
"#
.to_string(),
});
c_statements.extend(function_declarations);
// function pointer array
{
let function_pointer_array_statements = module_info
.functions
.iter()
.filter_map(|(f_index, sig_index)| {
Some((module_info.local_func_index(f_index)?, sig_index))
})
.map(|(function_local_index, _sig_index)| {
let function_name =
symbol_registry.symbol_to_name(Symbol::LocalFunction(function_local_index));
// TODO: figure out the signature here too
CStatement::Cast {
target_type: CType::void_ptr(),
expression: Box::new(CStatement::LiteralConstant {
value: function_name,
}),
}
})
.collect::<Vec<_>>();
c_statements.push(CStatement::Declaration {
name: "function_pointers".to_string(),
is_extern: false,
is_const: true,
ctype: CType::Array {
inner: Box::new(CType::void_ptr()),
},
definition: Some(Box::new(CStatement::LiteralArray {
items: function_pointer_array_statements,
})),
});
}
let func_trampoline_declarations =
module_info
.signatures
.iter()
.map(|(sig_index, _func_type)| {
let function_name =
symbol_registry.symbol_to_name(Symbol::FunctionCallTrampoline(sig_index));
CStatement::Declaration {
name: function_name,
is_extern: true,
is_const: false,
ctype: CType::Function {
arguments: vec![CType::void_ptr(), CType::void_ptr(), CType::void_ptr()],
return_value: None,
},
definition: None,
}
});
c_statements.push(CStatement::LiteralConstant {
value: r#"
// Trampolines (functions by which we can call into Wasm) ordered by signature.
// There is 1 trampoline per function signature in the order they appear in
// the Wasm module.
"#
.to_string(),
});
c_statements.extend(func_trampoline_declarations);
// function trampolines
{
let function_trampoline_statements = module_info
.signatures
.iter()
.map(|(sig_index, _vm_shared_index)| {
let function_name =
symbol_registry.symbol_to_name(Symbol::FunctionCallTrampoline(sig_index));
CStatement::LiteralConstant {
value: function_name,
}
})
.collect::<Vec<_>>();
c_statements.push(CStatement::Declaration {
name: "function_trampolines".to_string(),
is_extern: false,
is_const: true,
ctype: CType::Array {
inner: Box::new(CType::void_ptr()),
},
definition: Some(Box::new(CStatement::LiteralArray {
items: function_trampoline_statements,
})),
});
}
let dyn_func_declarations = module_info
.functions
.keys()
.take(module_info.num_imported_functions)
.map(|func_index| {
let function_name =
symbol_registry.symbol_to_name(Symbol::DynamicFunctionTrampoline(func_index));
// TODO: figure out the signature here
CStatement::Declaration {
name: function_name,
is_extern: true,
is_const: false,
ctype: CType::Function {
arguments: vec![CType::void_ptr(), CType::void_ptr(), CType::void_ptr()],
return_value: None,
},
definition: None,
}
});
c_statements.push(CStatement::LiteralConstant {
value: r#"
// Dynamic trampolines are per-function and are used for each function where
// the type signature is not known statically. In this case, this corresponds to
// the imported functions.
"#
.to_string(),
});
c_statements.extend(dyn_func_declarations);
c_statements.push(CStatement::TypeDef {
source_type: CType::Function {
arguments: vec![CType::void_ptr(), CType::void_ptr(), CType::void_ptr()],
return_value: None,
},
new_name: "dyn_func_trampoline_t".to_string(),
});
// dynamic function trampoline pointer array
{
let dynamic_function_trampoline_statements = module_info
.functions
.keys()
.take(module_info.num_imported_functions)
.map(|func_index| {
let function_name =
symbol_registry.symbol_to_name(Symbol::DynamicFunctionTrampoline(func_index));
CStatement::LiteralConstant {
value: function_name,
}
})
.collect::<Vec<_>>();
c_statements.push(CStatement::Declaration {
name: "dynamic_function_trampoline_pointers".to_string(),
is_extern: false,
is_const: true,
ctype: CType::Array {
inner: Box::new(CType::TypeDef("dyn_func_trampoline_t".to_string())),
},
definition: Some(Box::new(CStatement::LiteralArray {
items: dynamic_function_trampoline_statements,
})),
});
}
c_statements.push(CStatement::LiteralConstant {
value: HELPER_FUNCTIONS.to_string(),
});
c_statements.push(CStatement::LiteralConstant {
value: "\n#ifdef __cplusplus\n}\n#endif\n\n".to_string(),
});
generate_c(&c_statements)
}

View File

@ -6,6 +6,8 @@ use crate::commands::Binfmt;
use crate::commands::Compile;
#[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
use crate::commands::CreateExe;
#[cfg(feature = "static-artifact-create")]
use crate::commands::CreateObj;
#[cfg(feature = "wast")]
use crate::commands::Wast;
use crate::commands::{Cache, Config, Inspect, Run, SelfUpdate, Validate};
@ -51,6 +53,11 @@ enum WasmerCLIOptions {
#[structopt(name = "create-exe")]
CreateExe(CreateExe),
/// Compile a WebAssembly binary into an object file
#[cfg(feature = "static-artifact-create")]
#[structopt(name = "create-obj")]
CreateObj(CreateObj),
/// Get various configuration information needed
/// to compile programs which use Wasmer
#[structopt(name = "config")]
@ -86,6 +93,8 @@ impl WasmerCLIOptions {
Self::Compile(compile) => compile.execute(),
#[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
Self::CreateExe(create_exe) => create_exe.execute(),
#[cfg(feature = "static-artifact-create")]
Self::CreateObj(create_obj) => create_obj.execute(),
Self::Config(config) => config.execute(),
Self::Inspect(inspect) => inspect.execute(),
#[cfg(feature = "wast")]

View File

@ -7,6 +7,8 @@ mod compile;
mod config;
#[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
mod create_exe;
#[cfg(feature = "static-artifact-create")]
mod create_obj;
mod inspect;
mod run;
mod self_update;
@ -20,6 +22,31 @@ pub use binfmt::*;
pub use compile::*;
#[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
pub use create_exe::*;
#[cfg(feature = "static-artifact-create")]
pub use create_obj::*;
#[cfg(feature = "wast")]
pub use wast::*;
pub use {cache::*, config::*, inspect::*, run::*, self_update::*, validate::*};
/// The kind of object format to emit.
#[derive(Debug, Copy, Clone, structopt::StructOpt)]
#[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
pub enum ObjectFormat {
/// Serialize the entire module into an object file.
Serialized,
/// Serialize only the module metadata into an object file and emit functions as symbols.
Symbols,
}
#[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))]
impl std::str::FromStr for ObjectFormat {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"serialized" => Ok(Self::Serialized),
"symbols" => Ok(Self::Symbols),
_ => Err("must be one of two options: `serialized` or `symbols`."),
}
}
}

View File

@ -1,5 +1,6 @@
//! Create a standalone native executable for a given Wasm file.
use super::ObjectFormat;
use crate::store::CompilerOptions;
use anyhow::{Context, Result};
use std::env;
@ -14,6 +15,8 @@ use wasmer::*;
use wasmer_object::{emit_serialized, get_object_for_target};
const WASMER_MAIN_C_SOURCE: &[u8] = include_bytes!("wasmer_create_exe_main.c");
#[cfg(feature = "static-artifact-create")]
const WASMER_STATIC_MAIN_C_SOURCE: &[u8] = include_bytes!("wasmer_static_create_exe_main.c");
#[derive(Debug, StructOpt)]
/// The options for the `wasmer create-exe` subcommand
@ -30,9 +33,9 @@ pub struct CreateExe {
#[structopt(long = "target")]
target_triple: Option<Triple>,
#[structopt(flatten)]
compiler: CompilerOptions,
/// Object format options
#[structopt(name = "OBJECT_FORMAT", long = "object-format")]
object_format: Option<ObjectFormat>,
#[structopt(short = "m", multiple = true, number_of_values = 1)]
cpu_features: Vec<CpuFeature>,
@ -40,6 +43,9 @@ pub struct CreateExe {
/// This is useful for fixing linker errors that may occur on some systems.
#[structopt(short = "l", multiple = true, number_of_values = 1)]
libraries: Vec<String>,
#[structopt(flatten)]
compiler: CompilerOptions,
}
impl CreateExe {
@ -61,9 +67,11 @@ impl CreateExe {
})
.unwrap_or_default();
let (store, compiler_type) = self.compiler.get_store_for_target(target.clone())?;
let object_format = self.object_format.unwrap_or(ObjectFormat::Symbols);
println!("Compiler: {}", compiler_type.to_string());
println!("Target: {}", target.triple());
println!("Format: {:?}", object_format);
let working_dir = tempfile::tempdir()?;
let starting_cd = env::current_dir()?;
@ -77,8 +85,10 @@ impl CreateExe {
let wasm_module_path = starting_cd.join(&self.path);
let module =
Module::from_file(&store, &wasm_module_path).context("failed to compile Wasm")?;
match object_format {
ObjectFormat::Serialized => {
let module = Module::from_file(&store, &wasm_module_path)
.context("failed to compile Wasm")?;
let bytes = module.serialize()?;
let mut obj = get_object_for_target(target.triple())?;
emit_serialized(&mut obj, &bytes, target.triple())?;
@ -89,7 +99,49 @@ impl CreateExe {
drop(writer);
self.compile_c(wasm_object_path, output_path)?;
}
#[cfg(not(feature = "static-artifact-create"))]
ObjectFormat::Symbols => {
return Err(anyhow!("This version of wasmer-cli hasn't been compiled with static artifact support. You need to enable the `static-artifact-create` feature during compilation."));
}
#[cfg(feature = "static-artifact-create")]
ObjectFormat::Symbols => {
let engine = store.engine();
let engine_inner = engine.inner();
let compiler = engine_inner.compiler()?;
let features = engine_inner.features();
let tunables = store.tunables();
let data: Vec<u8> = fs::read(wasm_module_path)?;
let prefixer: Option<Box<dyn Fn(&[u8]) -> String + Send>> = None;
let (module_info, obj, metadata_length, symbol_registry) =
Artifact::generate_object(
compiler, &data, prefixer, &target, tunables, features,
)?;
let header_file_src = crate::c_gen::staticlib_header::generate_header_file(
&module_info,
&*symbol_registry,
metadata_length,
);
/* Write object file with functions */
let object_file_path: std::path::PathBuf =
std::path::Path::new("functions.o").into();
let mut writer = BufWriter::new(File::create(&object_file_path)?);
obj.write_stream(&mut writer)
.map_err(|err| anyhow::anyhow!(err.to_string()))?;
writer.flush()?;
/* Write down header file that includes pointer arrays and the deserialize function
* */
let mut writer = BufWriter::new(File::create("static_defs.h")?);
writer.write_all(header_file_src.as_bytes())?;
writer.flush()?;
link(
output_path,
object_file_path,
std::path::Path::new("static_defs.h").into(),
)?;
}
}
eprintln!(
"✔ Native executable compiled successfully to `{}`.",
self.output.display(),
@ -130,6 +182,62 @@ impl CreateExe {
}
}
#[cfg(feature = "static-artifact-create")]
fn link(
output_path: PathBuf,
object_path: PathBuf,
header_code_path: PathBuf,
) -> anyhow::Result<()> {
let c_src_path = Path::new("wasmer_main.c");
let mut libwasmer_path = get_libwasmer_path()?
.canonicalize()
.context("Failed to find libwasmer")?;
println!("Using libwasmer: {}", libwasmer_path.display());
let lib_filename = libwasmer_path
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string();
libwasmer_path.pop();
{
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")?;
c_src_file.write_all(WASMER_STATIC_MAIN_C_SOURCE)?;
}
println!(
"link output {:?}",
Command::new("cc")
.arg(&object_path)
.arg(&header_code_path)
.arg(&c_src_path)
.arg(&format!("-L{}", libwasmer_path.display()))
.arg(&format!("-I{}", get_wasmer_include_directory()?.display()))
.arg(&format!("-l:{}", lib_filename))
// Add libraries required per platform.
// We need userenv, sockets (Ws2_32), advapi32 for some system calls and bcrypt for random numbers.
//#[cfg(windows)]
// .arg("-luserenv")
// .arg("-lWs2_32")
// .arg("-ladvapi32")
// .arg("-lbcrypt")
// On unix we need dlopen-related symbols, libmath for a few things, and pthreads.
//#[cfg(not(windows))]
.arg("-ldl")
.arg("-lm")
.arg("-pthread")
//.arg(&format!("-I{}", header_code_path.display()))
.arg("-o")
.arg(&output_path)
.output()?
);
Ok(())
}
fn get_wasmer_dir() -> anyhow::Result<PathBuf> {
Ok(PathBuf::from(
env::var("WASMER_DIR")

View File

@ -0,0 +1,193 @@
#![allow(dead_code)]
//! Create a standalone native executable for a given Wasm file.
use super::ObjectFormat;
use crate::store::CompilerOptions;
use anyhow::{Context, Result};
use std::env;
use std::fs;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufWriter;
use std::path::PathBuf;
use std::process::Command;
use structopt::StructOpt;
use wasmer::*;
use wasmer_object::{emit_serialized, get_object_for_target};
#[derive(Debug, StructOpt)]
/// The options for the `wasmer create-exe` subcommand
pub struct CreateObj {
/// 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<Triple>,
/// Object format options
#[structopt(name = "OBJECT_FORMAT", long = "object-format")]
object_format: Option<ObjectFormat>,
#[structopt(short = "m", multiple = true, number_of_values = 1)]
cpu_features: Vec<CpuFeature>,
#[structopt(flatten)]
compiler: CompilerOptions,
}
impl CreateObj {
/// Runs logic for the `create-obj` subcommand
pub fn execute(&self) -> Result<()> {
println!("objectformat: {:?}", &self.object_format);
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 (store, compiler_type) = self.compiler.get_store_for_target(target.clone())?;
let object_format = self.object_format.unwrap_or(ObjectFormat::Symbols);
println!("Compiler: {}", compiler_type.to_string());
println!("Target: {}", target.triple());
println!("Format: {:?}", object_format);
let starting_cd = env::current_dir()?;
let output_path = starting_cd.join(&self.output);
let wasm_module_path = starting_cd.join(&self.path);
match object_format {
ObjectFormat::Serialized => {
let module = Module::from_file(&store, &wasm_module_path)
.context("failed to compile Wasm")?;
let bytes = module.serialize()?;
let mut obj = get_object_for_target(target.triple())?;
emit_serialized(&mut obj, &bytes, target.triple())?;
let mut writer = BufWriter::new(File::create(&output_path)?);
obj.write_stream(&mut writer)
.map_err(|err| anyhow::anyhow!(err.to_string()))?;
writer.flush()?;
}
ObjectFormat::Symbols => {
let engine = store.engine();
let engine_inner = engine.inner();
let compiler = engine_inner.compiler()?;
let features = engine_inner.features();
let tunables = store.tunables();
let data: Vec<u8> = fs::read(wasm_module_path)?;
let prefixer: Option<Box<dyn Fn(&[u8]) -> String + Send>> = None;
let (module_info, obj, metadata_length, symbol_registry) =
Artifact::generate_object(
compiler, &data, prefixer, &target, tunables, features,
)?;
let header_file_src = crate::c_gen::staticlib_header::generate_header_file(
&module_info,
&*symbol_registry,
metadata_length,
);
let mut writer = BufWriter::new(File::create(&output_path)?);
obj.write_stream(&mut writer)
.map_err(|err| anyhow::anyhow!(err.to_string()))?;
writer.flush()?;
{
let mut writer = BufWriter::new(File::create("/tmp/main_obj.o")?);
obj.write_stream(&mut writer)
.map_err(|err| anyhow::anyhow!(err.to_string()))?;
writer.flush()?;
}
let mut writer = BufWriter::new(File::create("func.c")?);
writer.write_all(header_file_src.as_bytes())?;
writer.flush()?;
{
let mut writer = BufWriter::new(File::create("/tmp/func.c")?);
writer.write_all(header_file_src.as_bytes())?;
writer.flush()?;
}
//link(output_path.clone(), std::path::Path::new("func.c").into())?;
}
}
eprintln!(
"✔ Object compiled successfully to `{}`.",
self.output.display(),
);
Ok(())
}
}
fn link(
output_path: PathBuf,
object_path: PathBuf,
header_code_path: PathBuf,
) -> anyhow::Result<()> {
let libwasmer_path = get_libwasmer_path()?
.canonicalize()
.context("Failed to find libwasmer")?;
println!(
"link output {:?}",
Command::new("cc")
.arg(&header_code_path)
.arg(&format!("-L{}", libwasmer_path.display()))
//.arg(&format!("-I{}", header_code_path.display()))
.arg("-pie")
.arg("-o")
.arg("header_obj.o")
.output()?
);
//ld -relocatable a.o b.o -o c.o
println!(
"link output {:?}",
Command::new("ld")
.arg("-relocatable")
.arg(&object_path)
.arg("header_obj.o")
.arg("-o")
.arg(&output_path)
.output()?
);
Ok(())
}
/// path to the static libwasmer
fn get_libwasmer_path() -> anyhow::Result<PathBuf> {
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)]
path.push("wasmer.lib");
Ok(path)
}
fn get_wasmer_dir() -> anyhow::Result<PathBuf> {
Ok(PathBuf::from(
env::var("WASMER_DIR")
.or_else(|e| {
option_env!("WASMER_INSTALL_PREFIX")
.map(str::to_string)
.ok_or(e)
})
.context("Trying to read env var `WASMER_DIR`")?,
))
}

View File

@ -0,0 +1,192 @@
#include "wasmer.h"
#include "static_defs.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define own
// TODO: make this define templated so that the Rust code can toggle it on/off
#define WASI
extern wasm_module_t* wasmer_module_new(wasm_store_t* store) asm("wasmer_module_new");
extern wasm_module_t* wasmer_static_module_new(wasm_store_t* store,const char* wasm_name) asm("wasmer_static_module_new");
static 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("%s\n", error_str);
free(error_str);
}
#ifdef WASI
static void pass_mapdir_arg(wasi_config_t *wasi_config, char *mapdir) {
int colon_location = strchr(mapdir, ':') - mapdir;
if (colon_location == 0) {
// error malformed argument
fprintf(stderr, "Expected mapdir argument of the form alias:directory\n");
exit(-1);
}
char *alias = (char *)malloc(colon_location + 1);
memcpy(alias, mapdir, colon_location);
alias[colon_location] = '\0';
int dir_len = strlen(mapdir) - colon_location;
char *dir = (char *)malloc(dir_len + 1);
memcpy(dir, &mapdir[colon_location + 1], dir_len);
dir[dir_len] = '\0';
wasi_config_mapdir(wasi_config, alias, dir);
free(alias);
free(dir);
}
// We try to parse out `--dir` and `--mapdir` ahead of time and process those
// specially. All other arguments are passed to the guest program.
static 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
int main(int argc, char *argv[]) {
wasm_config_t *config = wasm_config_new();
wasm_engine_t *engine = wasm_engine_new_with_config(config);
wasm_store_t *store = wasm_store_new(engine);
wasm_module_t *module = wasmer_static_module_new(store, "module");
if (!module) {
fprintf(stderr, "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
wasi_config_t *wasi_config = wasi_config_new(argv[0]);
handle_arguments(wasi_config, argc, argv);
wasi_env_t *wasi_env = wasi_env_new(store, 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);
wasm_extern_vec_t imports;
wasm_extern_vec_new_uninitialized(&imports, import_types.size);
wasm_importtype_vec_delete(&import_types);
#ifdef WASI
bool get_imports_result = wasi_get_imports(store, wasi_env, module, &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, &imports, NULL);
if (!instance) {
fprintf(stderr, "Failed to create instance\n");
print_wasmer_error();
return -1;
}
#ifdef WASI
// Read the exports.
wasm_extern_vec_t exports;
wasm_instance_exports(instance, &exports);
wasm_memory_t* mem = NULL;
for (size_t i = 0; i < exports.size; i++) {
mem = wasm_extern_as_memory(exports.data[i]);
if (mem) {
break;
}
}
if (!mem) {
fprintf(stderr, "Failed to create instance: Could not find memory in exports\n");
print_wasmer_error();
return -1;
}
wasi_env_set_memory(wasi_env, mem);
own wasm_func_t *start_function = wasi_get_start_function(instance);
if (!start_function) {
fprintf(stderr, "`_start` function not found\n");
print_wasmer_error();
return -1;
}
wasm_val_vec_t args = WASM_EMPTY_VEC;
wasm_val_vec_t results = WASM_EMPTY_VEC;
own wasm_trap_t *trap = wasm_func_call(start_function, &args, &results);
if (trap) {
fprintf(stderr, "Trap is not NULL: TODO:\n");
return -1;
}
#endif
// TODO: handle non-WASI start (maybe with invoke?)
#ifdef WASI
wasi_env_delete(wasi_env);
wasm_extern_vec_delete(&exports);
#endif
wasm_instance_delete(instance);
wasm_module_delete(module);
wasm_store_delete(store);
wasm_engine_delete(engine);
return 0;
}

View File

@ -20,6 +20,7 @@ pub mod commands;
pub mod common;
#[macro_use]
pub mod error;
pub mod c_gen;
pub mod cli;
#[cfg(feature = "debug")]
pub mod logging;

View File

@ -5,15 +5,19 @@ use crate::engine::link::link_module;
use crate::ArtifactBuild;
use crate::ArtifactCreate;
use crate::Features;
#[cfg(feature = "static-artifact-create")]
use crate::{FunctionBodyData, Compiler, ModuleTranslationState};
use crate::ModuleEnvironment;
use crate::{
register_frame_info, resolve_imports, FunctionExtent, GlobalFrameInfoRegistration,
InstantiationError, RuntimeError, Tunables,
};
#[cfg(feature = "static-artifact-create")]
use crate::{Compiler, FunctionBodyData, ModuleTranslationState};
use crate::{Engine, EngineInner};
use crate::ModuleEnvironment;
use enumset::EnumSet;
#[cfg(feature = "static-artifact-create")]
use std::collections::BTreeMap;
#[cfg(feature = "static-artifact-create")]
use std::mem;
use std::sync::Arc;
use std::sync::Mutex;
#[cfg(feature = "static-artifact-create")]
@ -21,8 +25,11 @@ use wasmer_object::{emit_compilation, emit_data, get_object_for_target, Object};
use wasmer_types::entity::{BoxedSlice, PrimaryMap};
use wasmer_types::MetadataHeader;
#[cfg(feature = "static-artifact-create")]
#[cfg(feature = "compiler")]
use wasmer_types::{compilation::symbols::ModuleMetadata, CompileModuleInfo, Target};
use wasmer_types::{
compilation::symbols::{ModuleMetadata, ModuleMetadataSymbolRegistry},
entity::EntityRef,
CompileModuleInfo, Target,
};
use wasmer_types::{
CompileError, CpuFeature, DataInitializer, DeserializeError, FunctionIndex, LocalFunctionIndex,
MemoryIndex, ModuleInfo, OwnedDataInitializer, SerializableModule, SerializeError,
@ -32,7 +39,17 @@ use wasmer_vm::{FunctionBodyPtr, MemoryStyle, TableStyle, VMSharedSignatureIndex
use wasmer_vm::{InstanceAllocator, InstanceHandle, StoreObjects, TrapHandlerFn, VMExtern};
/// A compiled wasm module, ready to be instantiated.
pub struct Artifact {
pub enum Artifact {
/// The default artifact format.
Universal(UniversalArtifact),
/// Stores functions etc as symbols and data meant to be stored in object files and
/// executables.
#[cfg(feature = "static-artifact-create")]
Static(StaticArtifact),
}
/// The default artifact format.
pub struct UniversalArtifact {
artifact: ArtifactBuild,
finished_functions: BoxedSlice<LocalFunctionIndex, FunctionBodyPtr>,
finished_function_call_trampolines: BoxedSlice<SignatureIndex, VMTrampoline>,
@ -42,6 +59,24 @@ pub struct Artifact {
finished_function_lengths: BoxedSlice<LocalFunctionIndex, usize>,
}
/// Stores functions etc as symbols and data meant to be stored in object files and
/// executables.
#[cfg(feature = "static-artifact-create")]
pub struct StaticArtifact {
metadata: ModuleMetadata,
_module_bytes: Vec<u8>,
finished_functions: BoxedSlice<LocalFunctionIndex, FunctionBodyPtr>,
finished_function_call_trampolines: BoxedSlice<SignatureIndex, VMTrampoline>,
finished_dynamic_function_trampolines: BoxedSlice<FunctionIndex, FunctionBodyPtr>,
signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
// Length of the serialized metadata
_metadata_length: usize,
_symbol_registry: ModuleMetadataSymbolRegistry,
}
#[cfg(feature = "static-artifact-create")]
const WASMER_METADATA_SYMBOL: &[u8] = b"WASMER_METADATA";
impl Artifact {
/// Compile a data buffer into a `ArtifactBuild`, which may then be instantiated.
#[cfg(feature = "compiler")]
@ -91,6 +126,15 @@ impl Artifact {
/// the data.
pub unsafe fn deserialize(engine: &Engine, bytes: &[u8]) -> Result<Self, DeserializeError> {
if !ArtifactBuild::is_deserializable(bytes) {
let static_artifact = Self::deserialize_object(engine, bytes);
match static_artifact {
Ok(v) => {
return Ok(v);
}
Err(err) => {
eprintln!("Could not deserialize as static object: {}", err);
}
}
return Err(DeserializeError::Incompatible(
"The provided bytes are not wasmer-universal".to_string(),
));
@ -177,7 +221,7 @@ impl Artifact {
finished_dynamic_function_trampolines.into_boxed_slice();
let signatures = signatures.into_boxed_slice();
Ok(Self {
Ok(Self::Universal(UniversalArtifact {
artifact,
finished_functions,
finished_function_call_trampolines,
@ -185,7 +229,7 @@ impl Artifact {
signatures,
frame_info_registration: Mutex::new(None),
finished_function_lengths,
})
}))
}
/// Check if the provided bytes look like a serialized `ArtifactBuild`.
@ -196,31 +240,59 @@ impl Artifact {
impl ArtifactCreate for Artifact {
fn create_module_info(&self) -> ModuleInfo {
self.artifact.create_module_info()
match self {
Self::Universal(UniversalArtifact { artifact, .. }) => artifact.create_module_info(),
#[cfg(feature = "static-artifact-create")]
Self::Static(StaticArtifact { metadata, .. }) => metadata.compile_info.module.clone(),
}
}
fn features(&self) -> &Features {
self.artifact.features()
match self {
Self::Universal(UniversalArtifact { artifact, .. }) => artifact.features(),
#[cfg(feature = "static-artifact-create")]
Self::Static(StaticArtifact { metadata, .. }) => &metadata.compile_info.features,
}
}
fn cpu_features(&self) -> EnumSet<CpuFeature> {
self.artifact.cpu_features()
match self {
Self::Universal(_self) => _self.artifact.cpu_features(),
#[cfg(feature = "static-artifact-create")]
Self::Static(_self) => EnumSet::from_u64(_self.metadata.cpu_features),
}
}
fn data_initializers(&self) -> &[OwnedDataInitializer] {
self.artifact.data_initializers()
match self {
Self::Universal(_self) => _self.artifact.data_initializers(),
#[cfg(feature = "static-artifact-create")]
Self::Static(_self) => &_self.metadata.data_initializers,
}
}
fn memory_styles(&self) -> &PrimaryMap<MemoryIndex, MemoryStyle> {
self.artifact.memory_styles()
match self {
Self::Universal(_self) => _self.artifact.memory_styles(),
#[cfg(feature = "static-artifact-create")]
Self::Static(_self) => &_self.metadata.compile_info.memory_styles,
}
}
fn table_styles(&self) -> &PrimaryMap<TableIndex, TableStyle> {
self.artifact.table_styles()
match self {
Self::Universal(UniversalArtifact { artifact, .. }) => artifact.table_styles(),
#[cfg(feature = "static-artifact-create")]
Self::Static(StaticArtifact { metadata, .. }) => &metadata.compile_info.table_styles,
}
}
fn serialize(&self) -> Result<Vec<u8>, SerializeError> {
self.artifact.serialize()
match self {
Self::Universal(UniversalArtifact { artifact, .. }) => artifact.serialize(),
#[cfg(feature = "static-artifact-create")]
Self::Static(StaticArtifact { .. }) => todo!(),
}
}
}
@ -229,39 +301,55 @@ impl Artifact {
///
/// This is required to ensure that any traps can be properly symbolicated.
pub fn register_frame_info(&self) {
let mut info = self.frame_info_registration.lock().unwrap();
match self {
Self::Universal(_self) => {
let mut info = _self.frame_info_registration.lock().unwrap();
if info.is_some() {
return;
}
let finished_function_extents = self
let finished_function_extents = _self
.finished_functions
.values()
.copied()
.zip(self.finished_function_lengths.values().copied())
.zip(_self.finished_function_lengths.values().copied())
.map(|(ptr, length)| FunctionExtent { ptr, length })
.collect::<PrimaryMap<LocalFunctionIndex, _>>()
.into_boxed_slice();
let frame_infos = self.artifact.get_frame_info_ref();
let frame_infos = _self.artifact.get_frame_info_ref();
*info = register_frame_info(
self.artifact.create_module_info(),
_self.artifact.create_module_info(),
&finished_function_extents,
frame_infos.clone(),
);
}
#[cfg(feature = "static-artifact-create")]
Self::Static(_self) => {
// Do nothing for static artifact
}
}
}
/// Returns the functions allocated in memory or this `Artifact`
/// ready to be run.
pub fn finished_functions(&self) -> &BoxedSlice<LocalFunctionIndex, FunctionBodyPtr> {
&self.finished_functions
match self {
Self::Universal(_self) => &_self.finished_functions,
#[cfg(feature = "static-artifact-create")]
Self::Static(_self) => &_self.finished_functions,
}
}
/// Returns the function call trampolines allocated in memory of this
/// `Artifact`, ready to be run.
pub fn finished_function_call_trampolines(&self) -> &BoxedSlice<SignatureIndex, VMTrampoline> {
&self.finished_function_call_trampolines
match self {
Self::Universal(_self) => &_self.finished_function_call_trampolines,
#[cfg(feature = "static-artifact-create")]
Self::Static(_self) => &_self.finished_function_call_trampolines,
}
}
/// Returns the dynamic function trampolines allocated in memory
@ -269,12 +357,20 @@ impl Artifact {
pub fn finished_dynamic_function_trampolines(
&self,
) -> &BoxedSlice<FunctionIndex, FunctionBodyPtr> {
&self.finished_dynamic_function_trampolines
match self {
Self::Universal(_self) => &_self.finished_dynamic_function_trampolines,
#[cfg(feature = "static-artifact-create")]
Self::Static(_self) => &_self.finished_dynamic_function_trampolines,
}
}
/// Returns the associated VM signatures for this `Artifact`.
pub fn signatures(&self) -> &BoxedSlice<SignatureIndex, VMSharedSignatureIndex> {
&self.signatures
match self {
Self::Universal(_self) => &_self.signatures,
#[cfg(feature = "static-artifact-create")]
Self::Static(_self) => &_self.signatures,
}
}
/// Do preinstantiation logic that is executed before instantiating
@ -385,13 +481,14 @@ impl Artifact {
.map_err(|trap| InstantiationError::Start(RuntimeError::from_trap(trap)))
}
#[allow(clippy::type_complexity)]
#[cfg(feature = "static-artifact-create")]
/// Generate a compilation
fn generate_metadata<'data>(
&self,
data: &'data [u8],
compiler: &dyn Compiler,
tunables: &dyn Tunables,
features: &Features,
) -> Result<
(
CompileModuleInfo,
@ -403,7 +500,6 @@ impl Artifact {
> {
let environ = ModuleEnvironment::new();
let translation = environ.translate(data).map_err(CompileError::Wasm)?;
let features = self.features().clone();
// We try to apply the middleware first
use crate::translator::ModuleMiddlewareChain;
@ -424,7 +520,7 @@ impl Artifact {
let compile_info = CompileModuleInfo {
module,
features,
features: features.clone(),
memory_styles,
table_styles,
};
@ -443,22 +539,28 @@ impl Artifact {
/// so we can assure no collisions.
#[cfg(feature = "static-artifact-create")]
pub fn generate_object<'data>(
&self,
compiler: &dyn Compiler,
data: &[u8],
prefixer: Option<Box<dyn Fn(&[u8]) -> String + Send>>,
target: &'data Target,
tunables: &dyn Tunables,
) -> Result<Object<'data>, CompileError> {
features: &Features,
) -> Result<
(
ModuleInfo,
Object<'data>,
usize,
Box<dyn wasmer_types::SymbolRegistry>,
),
CompileError,
> {
fn to_compile_error(err: impl std::error::Error) -> CompileError {
CompileError::Codegen(format!("{}", err))
}
#[allow(dead_code)]
const WASMER_METADATA_SYMBOL: &[u8] = b"WASMER_METADATA";
let (compile_info, function_body_inputs, data_initializers, module_translation) =
self.generate_metadata(data, compiler, tunables)?;
Self::generate_metadata(data, compiler, tunables, features)?;
let data_initializers = data_initializers
.iter()
@ -508,21 +610,150 @@ impl Artifact {
let (_compile_info, symbol_registry) = metadata.split();
let compilation = compiler.compile_module(
&target,
let compilation: wasmer_types::compilation::function::Compilation = compiler
.compile_module(
target,
&metadata.compile_info,
module_translation.as_ref().unwrap(),
function_body_inputs,
)?;
let mut obj = get_object_for_target(target_triple).map_err(to_compile_error)?;
let mut obj = get_object_for_target(&target_triple).map_err(to_compile_error)?;
emit_data(&mut obj, b"WASMER_MODULE_METADATA", &metadata_binary, 1)
emit_data(&mut obj, WASMER_METADATA_SYMBOL, &metadata_binary, 1)
.map_err(to_compile_error)?;
emit_compilation(&mut obj, compilation, &symbol_registry, &target_triple)
emit_compilation(&mut obj, compilation, &symbol_registry, target_triple)
.map_err(to_compile_error)?;
Ok((
metadata.compile_info.module,
obj,
metadata_binary.len(),
Box::new(symbol_registry),
))
}
Ok(obj)
/// Deserialize a ArtifactBuild from an object file
///
/// # Safety
/// The object must be a valid static object generated by wasmer.
#[cfg(not(feature = "static-artifact-load"))]
pub unsafe fn deserialize_object(
_engine: &Engine,
_bytes: &[u8],
) -> Result<Self, DeserializeError> {
Err(DeserializeError::Compiler(
CompileError::UnsupportedFeature("static load is not compiled in".to_string()),
))
}
/// Deserialize a ArtifactBuild from an object file
///
/// # Safety
/// The object must be a valid static object generated by wasmer.
#[cfg(feature = "static-artifact-load")]
pub unsafe fn deserialize_object(
engine: &Engine,
bytes: &[u8],
) -> Result<Self, DeserializeError> {
let metadata_len = MetadataHeader::parse(bytes)?;
let metadata_slice = &bytes[MetadataHeader::LEN..][..metadata_len];
let mut metadata: ModuleMetadata = ModuleMetadata::deserialize(metadata_slice)?;
const WORD_SIZE: usize = mem::size_of::<usize>();
let mut byte_buffer = [0u8; WORD_SIZE];
let mut cur_offset = MetadataHeader::LEN + metadata_len;
byte_buffer[0..WORD_SIZE].clone_from_slice(&bytes[cur_offset..(cur_offset + WORD_SIZE)]);
cur_offset += WORD_SIZE;
let num_finished_functions = usize::from_ne_bytes(byte_buffer);
let mut finished_functions: PrimaryMap<LocalFunctionIndex, FunctionBodyPtr> =
PrimaryMap::new();
let engine_inner = engine.inner();
let signature_registry = engine_inner.signatures();
let mut sig_map: BTreeMap<SignatureIndex, VMSharedSignatureIndex> = BTreeMap::new();
let num_imported_functions = metadata.compile_info.module.num_imported_functions;
// set up the imported functions first...
for i in 0..num_imported_functions {
let sig_idx = metadata.compile_info.module.functions[FunctionIndex::new(i)];
let func_type = &metadata.compile_info.module.signatures[sig_idx];
let vm_shared_idx = signature_registry.register(func_type);
sig_map.insert(sig_idx, vm_shared_idx);
}
// read finished functions in order now...
for i in 0..num_finished_functions {
let local_func_idx = LocalFunctionIndex::new(i);
let func_idx = metadata.compile_info.module.func_index(local_func_idx);
let sig_idx = metadata.compile_info.module.functions[func_idx];
let func_type = &metadata.compile_info.module.signatures[sig_idx];
let vm_shared_idx = signature_registry.register(func_type);
sig_map.insert(sig_idx, vm_shared_idx);
byte_buffer[0..WORD_SIZE]
.clone_from_slice(&bytes[cur_offset..(cur_offset + WORD_SIZE)]);
let fp = FunctionBodyPtr(usize::from_ne_bytes(byte_buffer) as _);
cur_offset += WORD_SIZE;
// TODO: we can read back the length here if we serialize it. This will improve debug output.
finished_functions.push(fp);
}
let mut signatures: PrimaryMap<_, VMSharedSignatureIndex> = PrimaryMap::new();
for i in 0..(sig_map.len()) {
if let Some(shared_idx) = sig_map.get(&SignatureIndex::new(i)) {
signatures.push(*shared_idx);
} else {
panic!("Invalid data, missing sig idx; TODO: handle this error");
}
}
// read trampolines in order
let mut finished_function_call_trampolines = PrimaryMap::new();
byte_buffer[0..WORD_SIZE].clone_from_slice(&bytes[cur_offset..(cur_offset + WORD_SIZE)]);
cur_offset += WORD_SIZE;
let num_function_trampolines = usize::from_ne_bytes(byte_buffer);
for _ in 0..num_function_trampolines {
byte_buffer[0..WORD_SIZE]
.clone_from_slice(&bytes[cur_offset..(cur_offset + WORD_SIZE)]);
cur_offset += WORD_SIZE;
let trampoline_ptr_bytes = usize::from_ne_bytes(byte_buffer);
let trampoline = mem::transmute::<usize, VMTrampoline>(trampoline_ptr_bytes);
finished_function_call_trampolines.push(trampoline);
// TODO: we can read back the length here if we serialize it. This will improve debug output.
}
// read dynamic function trampolines in order now...
let mut finished_dynamic_function_trampolines = PrimaryMap::new();
byte_buffer[0..WORD_SIZE].clone_from_slice(&bytes[cur_offset..(cur_offset + WORD_SIZE)]);
cur_offset += WORD_SIZE;
let num_dynamic_trampoline_functions = usize::from_ne_bytes(byte_buffer);
for _i in 0..num_dynamic_trampoline_functions {
byte_buffer[0..WORD_SIZE]
.clone_from_slice(&bytes[cur_offset..(cur_offset + WORD_SIZE)]);
let fp = FunctionBodyPtr(usize::from_ne_bytes(byte_buffer) as _);
cur_offset += WORD_SIZE;
// TODO: we can read back the length here if we serialize it. This will improve debug output.
finished_dynamic_function_trampolines.push(fp);
}
let (_compile_info, _symbol_registry) = metadata.split();
Ok(Self::Static(StaticArtifact {
metadata,
_module_bytes: bytes.to_vec(),
finished_functions: finished_functions.into_boxed_slice(),
finished_function_call_trampolines: finished_function_call_trampolines
.into_boxed_slice(),
finished_dynamic_function_trampolines: finished_dynamic_function_trampolines
.into_boxed_slice(),
signatures: signatures.into_boxed_slice(),
_metadata_length: metadata_len,
_symbol_registry,
}))
}
}

View File

@ -57,26 +57,26 @@ pub trait SymbolRegistry: Send + Sync {
#[derive(Debug, RkyvSerialize, RkyvDeserialize, Archive)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct ModuleMetadata {
/// FIXME
/// Compile info
pub compile_info: CompileModuleInfo,
/// FIXME
/// Prefix for function etc symbols
pub prefix: String,
/// FIXME
/// Data initializers
pub data_initializers: Box<[OwnedDataInitializer]>,
/// The function body lengths (used to find function by address)
pub function_body_lengths: PrimaryMap<LocalFunctionIndex, u64>,
/// FIXME
/// CPU features used (See [`CpuFeature`])
pub cpu_features: u64,
}
/// FIXME
/// A simple metadata registry
pub struct ModuleMetadataSymbolRegistry {
/// FIXME
/// Symbol prefix stirng
pub prefix: String,
}
impl ModuleMetadata {
/// FIXME
/// Get mutable ref to compile info and a copy of the registry
pub fn split(&mut self) -> (&mut CompileModuleInfo, ModuleMetadataSymbolRegistry) {
let compile_info = &mut self.compile_info;
let symbol_registry = ModuleMetadataSymbolRegistry {
@ -85,7 +85,7 @@ impl ModuleMetadata {
(compile_info, symbol_registry)
}
/// FIXME
/// Returns symbol registry.
pub fn get_symbol_registry(&self) -> ModuleMetadataSymbolRegistry {
ModuleMetadataSymbolRegistry {
prefix: self.prefix.clone(),

View File

@ -34,7 +34,7 @@ pub enum DeserializeError {
/// The binary was valid, but we got an error when
/// trying to allocate the required resources.
#[error(transparent)]
Compiler(CompileError),
Compiler(#[from] CompileError),
}
/// An ImportError.