mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-06 20:58:28 +00:00
Move header file generation logic
WIP, still need to get the length of the metadat object, but otherwise everything should be working.
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2229,6 +2229,7 @@ dependencies = [
|
|||||||
"wasmer-engine-native",
|
"wasmer-engine-native",
|
||||||
"wasmer-engine-object-file",
|
"wasmer-engine-object-file",
|
||||||
"wasmer-types",
|
"wasmer-types",
|
||||||
|
"wasmer-vm",
|
||||||
"wasmer-wasi",
|
"wasmer-wasi",
|
||||||
"wasmer-wasi-experimental-io-devices",
|
"wasmer-wasi-experimental-io-devices",
|
||||||
"wasmer-wast",
|
"wasmer-wast",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ wasmer-engine-object-file = { version = "1.0.0-alpha01.0", path = "../engine-obj
|
|||||||
wasmer-wasi = { version = "1.0.0-alpha01.0", path = "../wasi", optional = true }
|
wasmer-wasi = { version = "1.0.0-alpha01.0", path = "../wasi", optional = true }
|
||||||
wasmer-wasi-experimental-io-devices = { version = "1.0.0-alpha01.0", path = "../wasi-experimental-io-devices", optional = true }
|
wasmer-wasi-experimental-io-devices = { version = "1.0.0-alpha01.0", path = "../wasi-experimental-io-devices", optional = true }
|
||||||
wasmer-wast = { version = "1.0.0-alpha01.0", path = "../../tests/lib/wast", optional = true }
|
wasmer-wast = { version = "1.0.0-alpha01.0", path = "../../tests/lib/wast", optional = true }
|
||||||
|
wasmer-vm = { version = "1.0.0-alpha01.0", path = "../vm" }
|
||||||
wasmer-cache = { version = "1.0.0-alpha01.0", path = "../cache", optional = true }
|
wasmer-cache = { version = "1.0.0-alpha01.0", path = "../cache", optional = true }
|
||||||
wasmer-types = { version = "1.0.0-alpha01.0", path = "../wasmer-types" }
|
wasmer-types = { version = "1.0.0-alpha01.0", path = "../wasmer-types" }
|
||||||
atty = "0.2"
|
atty = "0.2"
|
||||||
|
|||||||
@@ -106,37 +106,34 @@ impl Compile {
|
|||||||
|
|
||||||
#[cfg(feature = "object-file")]
|
#[cfg(feature = "object-file")]
|
||||||
if engine_type == EngineType::ObjectFile {
|
if engine_type == EngineType::ObjectFile {
|
||||||
use wasmer_engine_object_file::ObjectFileArtifact;
|
let symbol_registry = module.artifact().symbol_registry();
|
||||||
|
let module_info = module.info();
|
||||||
|
let header_file_src =
|
||||||
|
crate::header_file_generation::generate_header_file(module_info, symbol_registry);
|
||||||
|
|
||||||
if let Some(obj_file) = module.artifact().downcast_ref::<ObjectFileArtifact>() {
|
let header_path = self.header_path.as_ref().cloned().unwrap_or_else(|| {
|
||||||
let header_file_src = obj_file.generate_header_file();
|
let mut hp = PathBuf::from(
|
||||||
let header_path = self.header_path.as_ref().cloned().unwrap_or_else(|| {
|
self.path
|
||||||
let mut hp = PathBuf::from(
|
.file_stem()
|
||||||
self.path
|
.map(|fs| fs.to_string_lossy().to_string())
|
||||||
.file_stem()
|
.unwrap_or_else(|| "wasm_out".to_string()),
|
||||||
.map(|fs| fs.to_string_lossy().to_string())
|
|
||||||
.unwrap_or_else(|| "wasm_out".to_string()),
|
|
||||||
);
|
|
||||||
hp.set_extension("h");
|
|
||||||
hp
|
|
||||||
});
|
|
||||||
// for C code
|
|
||||||
let mut header = std::fs::OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.truncate(true)
|
|
||||||
.write(true)
|
|
||||||
.open(&header_path)?;
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
header.write(header_file_src.as_bytes())?;
|
|
||||||
eprintln!(
|
|
||||||
"✔ Header file generated successfully at `{}`.",
|
|
||||||
header_path.display(),
|
|
||||||
);
|
);
|
||||||
} else {
|
hp.set_extension("h");
|
||||||
// TODO: handle error
|
hp
|
||||||
panic!("Downcast failed!")
|
});
|
||||||
}
|
// for C code
|
||||||
|
let mut header = std::fs::OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.write(true)
|
||||||
|
.open(&header_path)?;
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
header.write(header_file_src.as_bytes())?;
|
||||||
|
eprintln!(
|
||||||
|
"✔ Header file generated successfully at `{}`.",
|
||||||
|
header_path.display(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
456
lib/cli/src/header_file_generation/mod.rs
Normal file
456
lib/cli/src/header_file_generation/mod.rs
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
//! A convenient little abstraction for building up C expressions and generating
|
||||||
|
//! simple C code.
|
||||||
|
|
||||||
|
/// 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>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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: false,
|
||||||
|
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_str("*");
|
||||||
|
}
|
||||||
|
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
|
||||||
|
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('(');
|
||||||
|
if arguments.len() > 1 {
|
||||||
|
for arg in &arguments[..arguments.len() - 1] {
|
||||||
|
arg.generate_c(w);
|
||||||
|
w.push_str(", ");
|
||||||
|
}
|
||||||
|
arguments.last().unwrap().generate_c(w);
|
||||||
|
} else if arguments.len() == 1 {
|
||||||
|
arguments[0].generate_c(w);
|
||||||
|
}
|
||||||
|
w.push(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate the C source code for a type with a nameinto the given `String`.
|
||||||
|
fn generate_c_with_name(&self, name: &String, w: &mut String) {
|
||||||
|
match &self {
|
||||||
|
Self::PointerTo { .. }
|
||||||
|
| 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,
|
||||||
|
} => {
|
||||||
|
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('(');
|
||||||
|
if arguments.len() > 1 {
|
||||||
|
for arg in &arguments[..arguments.len() - 1] {
|
||||||
|
arg.generate_c(w);
|
||||||
|
w.push_str(", ");
|
||||||
|
}
|
||||||
|
arguments.last().unwrap().generate_c(w);
|
||||||
|
} else if arguments.len() == 1 {
|
||||||
|
arguments[0].generate_c(w);
|
||||||
|
}
|
||||||
|
w.push(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: String,
|
||||||
|
/// Whether the thing being declared is an array.
|
||||||
|
// TODO: probably make this part of CType
|
||||||
|
array: bool,
|
||||||
|
/// 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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CStatement {
|
||||||
|
/// Generate C source code for the given CStatement.
|
||||||
|
fn generate_c(&self, w: &mut String) {
|
||||||
|
match &self {
|
||||||
|
Self::Declaration {
|
||||||
|
name,
|
||||||
|
array,
|
||||||
|
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);
|
||||||
|
// TODO: array should be part of the type
|
||||||
|
if *array {
|
||||||
|
w.push_str("[]");
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: split the bottom part into its own file
|
||||||
|
|
||||||
|
use wasmer_compiler::{Symbol, SymbolRegistry};
|
||||||
|
use wasmer_vm::ModuleInfo;
|
||||||
|
|
||||||
|
/// Generate the header file that goes with the generated object file.
|
||||||
|
pub fn generate_header_file(
|
||||||
|
module_info: &ModuleInfo,
|
||||||
|
symbol_registry: &dyn SymbolRegistry,
|
||||||
|
) -> String {
|
||||||
|
let mut c_statements = vec![];
|
||||||
|
c_statements.push(CStatement::Declaration {
|
||||||
|
name: "module_bytes_len".to_string(),
|
||||||
|
array: false,
|
||||||
|
is_extern: false,
|
||||||
|
is_const: true,
|
||||||
|
ctype: CType::U32,
|
||||||
|
definition: Some(Box::new(CStatement::LiteralConstant {
|
||||||
|
value: "0".to_string(), //todo!("get the metadata length from somewhere"), //format!("{}", self.metadata_length),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
c_statements.push(CStatement::Declaration {
|
||||||
|
name: "WASMER_METADATA".to_string(),
|
||||||
|
array: true,
|
||||||
|
is_extern: true,
|
||||||
|
is_const: true,
|
||||||
|
ctype: CType::U8,
|
||||||
|
definition: None,
|
||||||
|
});
|
||||||
|
for (function_local_index, _sig_index) in
|
||||||
|
module_info
|
||||||
|
.functions
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(f_index, sig_index)| {
|
||||||
|
Some((module_info.local_func_index(f_index)?, sig_index))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
let function_name =
|
||||||
|
symbol_registry.symbol_to_name(Symbol::LocalFunction(function_local_index));
|
||||||
|
// TODO: figure out the signature here too
|
||||||
|
c_statements.push(CStatement::Declaration {
|
||||||
|
name: function_name.clone(),
|
||||||
|
array: false,
|
||||||
|
is_extern: false,
|
||||||
|
is_const: false,
|
||||||
|
ctype: CType::Function {
|
||||||
|
arguments: vec![CType::Void],
|
||||||
|
return_value: None,
|
||||||
|
},
|
||||||
|
definition: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// function pointer array
|
||||||
|
{
|
||||||
|
let mut function_pointer_array_statements = vec![];
|
||||||
|
for (function_local_index, _sig_index) in
|
||||||
|
module_info
|
||||||
|
.functions
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(f_index, sig_index)| {
|
||||||
|
Some((module_info.local_func_index(f_index)?, sig_index))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
let function_name =
|
||||||
|
symbol_registry.symbol_to_name(Symbol::LocalFunction(function_local_index));
|
||||||
|
// TODO: figure out the signature here too
|
||||||
|
|
||||||
|
function_pointer_array_statements.push(CStatement::LiteralConstant {
|
||||||
|
value: function_name.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
c_statements.push(CStatement::Declaration {
|
||||||
|
name: "function_pointers".to_string(),
|
||||||
|
array: true,
|
||||||
|
is_extern: false,
|
||||||
|
is_const: true,
|
||||||
|
ctype: CType::void_ptr(),
|
||||||
|
definition: Some(Box::new(CStatement::LiteralArray {
|
||||||
|
items: function_pointer_array_statements,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (sig_index, _func_type) in module_info.signatures.iter() {
|
||||||
|
let function_name =
|
||||||
|
symbol_registry.symbol_to_name(Symbol::FunctionCallTrampoline(sig_index));
|
||||||
|
|
||||||
|
c_statements.push(CStatement::Declaration {
|
||||||
|
name: function_name.clone(),
|
||||||
|
array: false,
|
||||||
|
is_extern: false,
|
||||||
|
is_const: false,
|
||||||
|
ctype: CType::Function {
|
||||||
|
arguments: vec![CType::void_ptr(), CType::void_ptr(), CType::void_ptr()],
|
||||||
|
return_value: None,
|
||||||
|
},
|
||||||
|
definition: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// function trampolines
|
||||||
|
{
|
||||||
|
let mut function_trampoline_statements = vec![];
|
||||||
|
for (sig_index, _vm_shared_index) in module_info.signatures.iter() {
|
||||||
|
let function_name =
|
||||||
|
symbol_registry.symbol_to_name(Symbol::FunctionCallTrampoline(sig_index));
|
||||||
|
function_trampoline_statements.push(CStatement::LiteralConstant {
|
||||||
|
value: function_name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
c_statements.push(CStatement::Declaration {
|
||||||
|
name: "function_trampolines".to_string(),
|
||||||
|
array: true,
|
||||||
|
is_extern: false,
|
||||||
|
is_const: true,
|
||||||
|
ctype: CType::void_ptr(),
|
||||||
|
definition: Some(Box::new(CStatement::LiteralArray {
|
||||||
|
items: function_trampoline_statements,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for func_index in module_info
|
||||||
|
.functions
|
||||||
|
.keys()
|
||||||
|
.take(module_info.num_imported_functions)
|
||||||
|
{
|
||||||
|
let function_name =
|
||||||
|
symbol_registry.symbol_to_name(Symbol::DynamicFunctionTrampoline(func_index));
|
||||||
|
// TODO: figure out the signature here
|
||||||
|
c_statements.push(CStatement::Declaration {
|
||||||
|
name: function_name,
|
||||||
|
array: false,
|
||||||
|
is_extern: false,
|
||||||
|
is_const: false,
|
||||||
|
ctype: CType::Function {
|
||||||
|
arguments: vec![CType::void_ptr(), CType::void_ptr(), CType::void_ptr()],
|
||||||
|
return_value: None,
|
||||||
|
},
|
||||||
|
definition: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// dynamic function trampoline pointer array
|
||||||
|
{
|
||||||
|
let mut dynamic_function_trampoline_statements = vec![];
|
||||||
|
for func_index in module_info
|
||||||
|
.functions
|
||||||
|
.keys()
|
||||||
|
.take(module_info.num_imported_functions)
|
||||||
|
{
|
||||||
|
let function_name =
|
||||||
|
symbol_registry.symbol_to_name(Symbol::DynamicFunctionTrampoline(func_index));
|
||||||
|
dynamic_function_trampoline_statements.push(CStatement::LiteralConstant {
|
||||||
|
value: function_name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
c_statements.push(CStatement::Declaration {
|
||||||
|
name: "dynamic_function_trampoline_pointers".to_string(),
|
||||||
|
array: true,
|
||||||
|
is_extern: false,
|
||||||
|
is_const: true,
|
||||||
|
ctype: CType::void_ptr(),
|
||||||
|
definition: Some(Box::new(CStatement::LiteralArray {
|
||||||
|
items: dynamic_function_trampoline_statements,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_c(&c_statements)
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ pub mod commands;
|
|||||||
pub mod common;
|
pub mod common;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod header_file_generation;
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::serialize::SerializableCompilation;
|
|||||||
use crate::serialize::SerializableModule;
|
use crate::serialize::SerializableModule;
|
||||||
use crate::unwind::UnwindRegistry;
|
use crate::unwind::UnwindRegistry;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use wasmer_compiler::{CompileError, Features, Triple};
|
use wasmer_compiler::{CompileError, Features, SymbolRegistry, Triple};
|
||||||
#[cfg(feature = "compiler")]
|
#[cfg(feature = "compiler")]
|
||||||
use wasmer_compiler::{CompileModuleInfo, ModuleEnvironment};
|
use wasmer_compiler::{CompileModuleInfo, ModuleEnvironment};
|
||||||
use wasmer_engine::{
|
use wasmer_engine::{
|
||||||
@@ -303,4 +303,8 @@ impl Artifact for JITArtifact {
|
|||||||
serialized.extend(bytes);
|
serialized.extend(bytes);
|
||||||
Ok(serialized)
|
Ok(serialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn symbol_registry(&self) -> &dyn SymbolRegistry {
|
||||||
|
unimplemented!("TODO: figure out why engine JIT doesn't have a SymbolRegistry")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -598,4 +598,8 @@ impl Artifact for NativeArtifact {
|
|||||||
fn serialize(&self) -> Result<Vec<u8>, SerializeError> {
|
fn serialize(&self) -> Result<Vec<u8>, SerializeError> {
|
||||||
Ok(std::fs::read(&self.sharedobject_path)?)
|
Ok(std::fs::read(&self.sharedobject_path)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn symbol_registry(&self) -> &dyn SymbolRegistry {
|
||||||
|
&self.metadata
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,174 +237,6 @@ impl ObjectFileArtifact {
|
|||||||
Self::from_parts_crosscompiled(&mut *engine_inner, metadata, obj_bytes, metadata_length)
|
Self::from_parts_crosscompiled(&mut *engine_inner, metadata, obj_bytes, metadata_length)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate the header file that goes with the generated object file.
|
|
||||||
pub fn generate_header_file(&self) -> String {
|
|
||||||
use crate::header::*;
|
|
||||||
let mut c_statements = vec![];
|
|
||||||
c_statements.push(CStatement::Declaration {
|
|
||||||
name: "module_bytes_len".to_string(),
|
|
||||||
array: false,
|
|
||||||
is_extern: false,
|
|
||||||
is_const: true,
|
|
||||||
ctype: CType::U32,
|
|
||||||
definition: Some(Box::new(CStatement::LiteralConstant {
|
|
||||||
value: format!("{}", self.metadata_length),
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
c_statements.push(CStatement::Declaration {
|
|
||||||
name: "WASMER_METADATA".to_string(),
|
|
||||||
array: true,
|
|
||||||
is_extern: true,
|
|
||||||
is_const: true,
|
|
||||||
ctype: CType::U8,
|
|
||||||
definition: None,
|
|
||||||
});
|
|
||||||
for (function_local_index, _function_len) in self.metadata.function_body_lengths.iter() {
|
|
||||||
let function_name = self
|
|
||||||
.metadata
|
|
||||||
.symbol_to_name(Symbol::LocalFunction(function_local_index));
|
|
||||||
// TODO: figure out the signature here too
|
|
||||||
c_statements.push(CStatement::Declaration {
|
|
||||||
name: function_name.clone(),
|
|
||||||
array: false,
|
|
||||||
is_extern: false,
|
|
||||||
is_const: false,
|
|
||||||
ctype: CType::Function {
|
|
||||||
arguments: vec![CType::Void],
|
|
||||||
return_value: None,
|
|
||||||
},
|
|
||||||
definition: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// function pointer array
|
|
||||||
{
|
|
||||||
let mut function_pointer_array_statements = vec![];
|
|
||||||
for (function_local_index, _function_len) in self.metadata.function_body_lengths.iter()
|
|
||||||
{
|
|
||||||
let function_name = self
|
|
||||||
.metadata
|
|
||||||
.symbol_to_name(Symbol::LocalFunction(function_local_index));
|
|
||||||
// TODO: figure out the signature here too
|
|
||||||
|
|
||||||
function_pointer_array_statements.push(CStatement::LiteralConstant {
|
|
||||||
value: function_name.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
c_statements.push(CStatement::Declaration {
|
|
||||||
name: "function_pointers".to_string(),
|
|
||||||
array: true,
|
|
||||||
is_extern: false,
|
|
||||||
is_const: true,
|
|
||||||
ctype: CType::void_ptr(),
|
|
||||||
definition: Some(Box::new(CStatement::LiteralArray {
|
|
||||||
items: function_pointer_array_statements,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (sig_index, _func_type) in self.metadata.compile_info.module.signatures.iter() {
|
|
||||||
let function_name = self
|
|
||||||
.metadata
|
|
||||||
.symbol_to_name(Symbol::FunctionCallTrampoline(sig_index));
|
|
||||||
|
|
||||||
c_statements.push(CStatement::Declaration {
|
|
||||||
name: function_name.clone(),
|
|
||||||
array: false,
|
|
||||||
is_extern: false,
|
|
||||||
is_const: false,
|
|
||||||
ctype: CType::Function {
|
|
||||||
arguments: vec![CType::void_ptr(), CType::void_ptr(), CType::void_ptr()],
|
|
||||||
return_value: None,
|
|
||||||
},
|
|
||||||
definition: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// function trampolines
|
|
||||||
{
|
|
||||||
let mut function_trampoline_statements = vec![];
|
|
||||||
for (sig_index, _vm_shared_index) in self.metadata.compile_info.module.signatures.iter()
|
|
||||||
{
|
|
||||||
let function_name = self
|
|
||||||
.metadata
|
|
||||||
.symbol_to_name(Symbol::FunctionCallTrampoline(sig_index));
|
|
||||||
function_trampoline_statements.push(CStatement::LiteralConstant {
|
|
||||||
value: function_name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
c_statements.push(CStatement::Declaration {
|
|
||||||
name: "function_trampolines".to_string(),
|
|
||||||
array: true,
|
|
||||||
is_extern: false,
|
|
||||||
is_const: true,
|
|
||||||
ctype: CType::void_ptr(),
|
|
||||||
definition: Some(Box::new(CStatement::LiteralArray {
|
|
||||||
items: function_trampoline_statements,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for func_index in self
|
|
||||||
.metadata
|
|
||||||
.compile_info
|
|
||||||
.module
|
|
||||||
.functions
|
|
||||||
.keys()
|
|
||||||
.take(self.metadata.compile_info.module.num_imported_functions)
|
|
||||||
{
|
|
||||||
let function_name = self
|
|
||||||
.metadata
|
|
||||||
.symbol_to_name(Symbol::DynamicFunctionTrampoline(func_index));
|
|
||||||
// TODO: figure out the signature here
|
|
||||||
c_statements.push(CStatement::Declaration {
|
|
||||||
name: function_name,
|
|
||||||
array: false,
|
|
||||||
is_extern: false,
|
|
||||||
is_const: false,
|
|
||||||
ctype: CType::Function {
|
|
||||||
arguments: vec![CType::void_ptr(), CType::void_ptr(), CType::void_ptr()],
|
|
||||||
return_value: None,
|
|
||||||
},
|
|
||||||
definition: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// dynamic function trampoline pointer array
|
|
||||||
{
|
|
||||||
let mut dynamic_function_trampoline_statements = vec![];
|
|
||||||
for func_index in self
|
|
||||||
.metadata
|
|
||||||
.compile_info
|
|
||||||
.module
|
|
||||||
.functions
|
|
||||||
.keys()
|
|
||||||
.take(self.metadata.compile_info.module.num_imported_functions)
|
|
||||||
{
|
|
||||||
let function_name = self
|
|
||||||
.metadata
|
|
||||||
.symbol_to_name(Symbol::DynamicFunctionTrampoline(func_index));
|
|
||||||
dynamic_function_trampoline_statements.push(CStatement::LiteralConstant {
|
|
||||||
value: function_name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
c_statements.push(CStatement::Declaration {
|
|
||||||
name: "dynamic_function_trampoline_pointers".to_string(),
|
|
||||||
array: true,
|
|
||||||
is_extern: false,
|
|
||||||
is_const: true,
|
|
||||||
ctype: CType::void_ptr(),
|
|
||||||
definition: Some(Box::new(CStatement::LiteralArray {
|
|
||||||
items: dynamic_function_trampoline_statements,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_c(&c_statements)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the default extension when serializing this artifact
|
/// Get the default extension when serializing this artifact
|
||||||
pub fn get_default_extension(triple: &Triple) -> &'static str {
|
pub fn get_default_extension(triple: &Triple) -> &'static str {
|
||||||
match triple.operating_system {
|
match triple.operating_system {
|
||||||
@@ -640,4 +472,8 @@ impl Artifact for ObjectFileArtifact {
|
|||||||
fn serialize(&self) -> Result<Vec<u8>, SerializeError> {
|
fn serialize(&self) -> Result<Vec<u8>, SerializeError> {
|
||||||
Ok(self.module_bytes.clone())
|
Ok(self.module_bytes.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn symbol_registry(&self) -> &dyn SymbolRegistry {
|
||||||
|
&self.metadata
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,234 +0,0 @@
|
|||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum CType {
|
|
||||||
Void,
|
|
||||||
PointerTo {
|
|
||||||
is_const: bool,
|
|
||||||
inner: Box<CType>,
|
|
||||||
},
|
|
||||||
U8,
|
|
||||||
U16,
|
|
||||||
U32,
|
|
||||||
U64,
|
|
||||||
USize,
|
|
||||||
I8,
|
|
||||||
I16,
|
|
||||||
I32,
|
|
||||||
I64,
|
|
||||||
ISize,
|
|
||||||
Function {
|
|
||||||
arguments: Vec<CType>,
|
|
||||||
return_value: Option<Box<CType>>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CType {
|
|
||||||
pub fn void_ptr() -> Self {
|
|
||||||
CType::PointerTo {
|
|
||||||
is_const: false,
|
|
||||||
inner: Box::new(CType::Void),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn const_void_ptr() -> Self {
|
|
||||||
CType::PointerTo {
|
|
||||||
is_const: false,
|
|
||||||
inner: Box::new(CType::Void),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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_str("*");
|
|
||||||
}
|
|
||||||
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
|
|
||||||
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('(');
|
|
||||||
if arguments.len() > 1 {
|
|
||||||
for arg in &arguments[..arguments.len() - 1] {
|
|
||||||
arg.generate_c(w);
|
|
||||||
w.push_str(", ");
|
|
||||||
}
|
|
||||||
arguments.last().unwrap().generate_c(w);
|
|
||||||
} else if arguments.len() == 1 {
|
|
||||||
arguments[0].generate_c(w);
|
|
||||||
}
|
|
||||||
w.push(')');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_c_with_name(&self, name: &String, w: &mut String) {
|
|
||||||
match &self {
|
|
||||||
Self::PointerTo { .. }
|
|
||||||
| 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,
|
|
||||||
} => {
|
|
||||||
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('(');
|
|
||||||
if arguments.len() > 1 {
|
|
||||||
for arg in &arguments[..arguments.len() - 1] {
|
|
||||||
arg.generate_c(w);
|
|
||||||
w.push_str(", ");
|
|
||||||
}
|
|
||||||
arguments.last().unwrap().generate_c(w);
|
|
||||||
} else if arguments.len() == 1 {
|
|
||||||
arguments[0].generate_c(w);
|
|
||||||
}
|
|
||||||
w.push(')');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CType {
|
|
||||||
fn default() -> CType {
|
|
||||||
CType::Void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum CStatement {
|
|
||||||
Declaration {
|
|
||||||
name: String,
|
|
||||||
array: bool,
|
|
||||||
is_extern: bool,
|
|
||||||
is_const: bool,
|
|
||||||
ctype: CType,
|
|
||||||
definition: Option<Box<CStatement>>,
|
|
||||||
},
|
|
||||||
LiteralArray {
|
|
||||||
items: Vec<CStatement>,
|
|
||||||
},
|
|
||||||
LiteralConstant {
|
|
||||||
value: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CStatement {
|
|
||||||
fn generate_c(&self, w: &mut String) {
|
|
||||||
match &self {
|
|
||||||
Self::Declaration {
|
|
||||||
name,
|
|
||||||
array,
|
|
||||||
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);
|
|
||||||
// TODO: array should be part of the type
|
|
||||||
if *array {
|
|
||||||
w.push_str("[]");
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
mod artifact;
|
mod artifact;
|
||||||
mod builder;
|
mod builder;
|
||||||
mod engine;
|
mod engine;
|
||||||
pub(crate) mod header;
|
|
||||||
mod serialize;
|
mod serialize;
|
||||||
|
|
||||||
pub use crate::artifact::ObjectFileArtifact;
|
pub use crate::artifact::ObjectFileArtifact;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::any::Any;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasmer_compiler::Features;
|
use wasmer_compiler::{Features, SymbolRegistry};
|
||||||
use wasmer_types::entity::{BoxedSlice, PrimaryMap};
|
use wasmer_types::entity::{BoxedSlice, PrimaryMap};
|
||||||
use wasmer_types::{
|
use wasmer_types::{
|
||||||
DataInitializer, FunctionIndex, LocalFunctionIndex, MemoryIndex, OwnedDataInitializer,
|
DataInitializer, FunctionIndex, LocalFunctionIndex, MemoryIndex, OwnedDataInitializer,
|
||||||
@@ -21,7 +21,7 @@ use wasmer_vm::{
|
|||||||
/// The `Artifact` contains the compiled data for a given
|
/// The `Artifact` contains the compiled data for a given
|
||||||
/// module as well as extra information needed to run the
|
/// module as well as extra information needed to run the
|
||||||
/// module at runtime, such as [`ModuleInfo`] and [`Features`].
|
/// module at runtime, such as [`ModuleInfo`] and [`Features`].
|
||||||
pub trait Artifact: Send + Sync + 'static + Upcastable {
|
pub trait Artifact: Send + Sync {
|
||||||
/// Return a reference-counted pointer to the module
|
/// Return a reference-counted pointer to the module
|
||||||
fn module(&self) -> Arc<ModuleInfo>;
|
fn module(&self) -> Arc<ModuleInfo>;
|
||||||
|
|
||||||
@@ -148,41 +148,7 @@ pub trait Artifact: Send + Sync + 'static + Upcastable {
|
|||||||
.finish_instantiation(is_bulk_memory, &data_initializers)
|
.finish_instantiation(is_bulk_memory, &data_initializers)
|
||||||
.map_err(|trap| InstantiationError::Start(RuntimeError::from_trap(trap)))
|
.map_err(|trap| InstantiationError::Start(RuntimeError::from_trap(trap)))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of `Upcastable` taken from https://users.rust-lang.org/t/why-does-downcasting-not-work-for-subtraits/33286/7 .
|
/// Get the `SymbolRegistry` used to generate the names used in the Artifact.
|
||||||
/// Trait needed to get downcasting from `Artifact` to work.
|
fn symbol_registry(&self) -> &dyn SymbolRegistry;
|
||||||
pub trait Upcastable {
|
|
||||||
fn upcast_any_ref(self: &'_ Self) -> &'_ dyn Any;
|
|
||||||
fn upcast_any_mut(self: &'_ mut Self) -> &'_ mut dyn Any;
|
|
||||||
fn upcast_any_box(self: Box<Self>) -> Box<dyn Any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Any + 'static> Upcastable for T {
|
|
||||||
#[inline]
|
|
||||||
fn upcast_any_ref(self: &'_ Self) -> &'_ dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn upcast_any_mut(self: &'_ mut Self) -> &'_ mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn upcast_any_box(self: Box<Self>) -> Box<dyn Any> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl dyn Artifact + 'static {
|
|
||||||
/// Downcast a reference to an `Artifact` into a specific implementor.
|
|
||||||
#[inline]
|
|
||||||
pub fn downcast_ref<T: 'static>(self: &'_ Self) -> Option<&'_ T> {
|
|
||||||
self.upcast_any_ref().downcast_ref::<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Downcast a mutable reference to an `Artifact` into a specific implementor.
|
|
||||||
#[inline]
|
|
||||||
pub fn downcast_mut<T: 'static>(self: &'_ mut Self) -> Option<&'_ mut T> {
|
|
||||||
self.upcast_any_mut().downcast_mut::<T>()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user