Initial reorg.
This is largely the same as #305, but updated for the current tree.
This commit is contained in:
5550
crates/lightbeam/src/backend.rs
Normal file
5550
crates/lightbeam/src/backend.rs
Normal file
File diff suppressed because it is too large
Load Diff
51
crates/lightbeam/src/disassemble.rs
Normal file
51
crates/lightbeam/src/disassemble.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use capstone::prelude::*;
|
||||
use dynasmrt::AssemblyOffset;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Write};
|
||||
|
||||
pub fn disassemble(
|
||||
mem: &[u8],
|
||||
mut ops: &[(AssemblyOffset, impl Display)],
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let cs = Capstone::new()
|
||||
.x86()
|
||||
.mode(arch::x86::ArchMode::Mode64)
|
||||
.build()?;
|
||||
|
||||
println!("{} bytes:", mem.len());
|
||||
let insns = cs.disasm_all(&mem, 0x0)?;
|
||||
for i in insns.iter() {
|
||||
let mut line = String::new();
|
||||
|
||||
let address = i.address();
|
||||
|
||||
while let Some((offset, op)) = ops.first() {
|
||||
if offset.0 as u64 <= address {
|
||||
ops = &ops[1..];
|
||||
println!("{}", op);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
write!(&mut line, "{:4x}:\t", i.address())?;
|
||||
|
||||
let mut bytes_str = String::new();
|
||||
for b in i.bytes() {
|
||||
write!(&mut bytes_str, "{:02x} ", b)?;
|
||||
}
|
||||
write!(&mut line, "{:24}\t", bytes_str)?;
|
||||
|
||||
if let Some(s) = i.mnemonic() {
|
||||
write!(&mut line, "{}\t", s)?;
|
||||
}
|
||||
|
||||
if let Some(s) = i.op_str() {
|
||||
write!(&mut line, "{}", s)?;
|
||||
}
|
||||
|
||||
println!("{}", line);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
28
crates/lightbeam/src/error.rs
Normal file
28
crates/lightbeam/src/error.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use capstone;
|
||||
use thiserror::Error;
|
||||
use wasmparser::BinaryReaderError;
|
||||
|
||||
#[derive(Error, PartialEq, Eq, Clone, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Disassembler error: {0}")]
|
||||
Disassembler(String),
|
||||
|
||||
#[error("Assembler error: {0}")]
|
||||
Assembler(String),
|
||||
|
||||
#[error("Input error: {0}")]
|
||||
Input(String),
|
||||
}
|
||||
|
||||
impl From<BinaryReaderError> for Error {
|
||||
fn from(e: BinaryReaderError) -> Self {
|
||||
let BinaryReaderError { message, offset } = e;
|
||||
Error::Input(format!("At wasm offset {}: {}", offset, message))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<capstone::Error> for Error {
|
||||
fn from(e: capstone::Error) -> Self {
|
||||
Error::Disassembler(e.to_string())
|
||||
}
|
||||
}
|
||||
859
crates/lightbeam/src/function_body.rs
Normal file
859
crates/lightbeam/src/function_body.rs
Normal file
@@ -0,0 +1,859 @@
|
||||
use crate::backend::{
|
||||
ret_locs, BlockCallingConvention, CodeGenSession, Context, Label, Registers, ValueLocation,
|
||||
VirtualCallingConvention,
|
||||
};
|
||||
use crate::error::Error;
|
||||
use crate::microwasm::*;
|
||||
use crate::module::{ModuleContext, SigType, Signature};
|
||||
use core::{fmt, mem};
|
||||
use cranelift_codegen::binemit;
|
||||
use dynasmrt::DynasmApi;
|
||||
use either::{Either, Left, Right};
|
||||
use multi_mut::HashMapMultiMut;
|
||||
use std::{collections::HashMap, hash::Hash};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Block {
|
||||
label: BrTarget<Label>,
|
||||
calling_convention: Option<Either<BlockCallingConvention, VirtualCallingConvention>>,
|
||||
params: u32,
|
||||
// TODO: Is there a cleaner way to do this? `has_backwards_callers` should always be set if `is_next`
|
||||
// is false, so we should probably use an `enum` here.
|
||||
is_next: bool,
|
||||
num_callers: Option<u32>,
|
||||
actual_num_callers: u32,
|
||||
has_backwards_callers: bool,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
fn should_serialize_args(&self) -> bool {
|
||||
self.calling_convention.is_none()
|
||||
&& (self.num_callers != Some(1) || self.has_backwards_callers)
|
||||
}
|
||||
}
|
||||
|
||||
const DISASSEMBLE: bool = false;
|
||||
|
||||
pub fn translate_wasm<M>(
|
||||
session: &mut CodeGenSession<M>,
|
||||
reloc_sink: &mut dyn binemit::RelocSink,
|
||||
func_idx: u32,
|
||||
body: &wasmparser::FunctionBody,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
M: ModuleContext,
|
||||
for<'any> &'any M::Signature: Into<OpSig>,
|
||||
{
|
||||
let ty = session.module_context.defined_func_type(func_idx);
|
||||
|
||||
if DISASSEMBLE {
|
||||
let microwasm_conv = MicrowasmConv::new(
|
||||
session.module_context,
|
||||
ty.params().iter().map(SigType::to_microwasm_type),
|
||||
ty.returns().iter().map(SigType::to_microwasm_type),
|
||||
body,
|
||||
);
|
||||
|
||||
let _ = crate::microwasm::dis(
|
||||
std::io::stdout(),
|
||||
func_idx,
|
||||
microwasm_conv.flat_map(|ops| ops.unwrap()),
|
||||
);
|
||||
}
|
||||
|
||||
let microwasm_conv = MicrowasmConv::new(
|
||||
session.module_context,
|
||||
ty.params().iter().map(SigType::to_microwasm_type),
|
||||
ty.returns().iter().map(SigType::to_microwasm_type),
|
||||
body,
|
||||
);
|
||||
|
||||
translate(
|
||||
session,
|
||||
reloc_sink,
|
||||
func_idx,
|
||||
microwasm_conv.flat_map(|i| i.expect("TODO: Make this not panic")),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn translate<M, I, L: Send + Sync + 'static>(
|
||||
session: &mut CodeGenSession<M>,
|
||||
reloc_sink: &mut dyn binemit::RelocSink,
|
||||
func_idx: u32,
|
||||
body: I,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
M: ModuleContext,
|
||||
I: IntoIterator<Item = Operator<L>>,
|
||||
L: Hash + Clone + Eq,
|
||||
BrTarget<L>: std::fmt::Display,
|
||||
{
|
||||
fn drop_elements<T>(stack: &mut Vec<T>, depths: std::ops::RangeInclusive<u32>) {
|
||||
let _ = (|| {
|
||||
let start = stack
|
||||
.len()
|
||||
.checked_sub(1)?
|
||||
.checked_sub(*depths.end() as usize)?;
|
||||
let end = stack
|
||||
.len()
|
||||
.checked_sub(1)?
|
||||
.checked_sub(*depths.start() as usize)?;
|
||||
let real_range = start..=end;
|
||||
|
||||
stack.drain(real_range);
|
||||
|
||||
Some(())
|
||||
})();
|
||||
}
|
||||
|
||||
let func_type = session.module_context.defined_func_type(func_idx);
|
||||
let mut body = body.into_iter().peekable();
|
||||
|
||||
let module_context = &*session.module_context;
|
||||
let mut op_offset_map = mem::replace(&mut session.op_offset_map, vec![]);
|
||||
let ctx = &mut session.new_context(func_idx, reloc_sink);
|
||||
op_offset_map.push((
|
||||
ctx.asm.offset(),
|
||||
Box::new(format!("Function {}:", func_idx)),
|
||||
));
|
||||
|
||||
let params = func_type
|
||||
.params()
|
||||
.iter()
|
||||
.map(|t| t.to_microwasm_type())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
ctx.start_function(params.iter().cloned());
|
||||
|
||||
let mut blocks = HashMap::<BrTarget<L>, Block>::new();
|
||||
|
||||
let num_returns = func_type.returns().len();
|
||||
|
||||
blocks.insert(
|
||||
BrTarget::Return,
|
||||
Block {
|
||||
label: BrTarget::Return,
|
||||
params: num_returns as u32,
|
||||
calling_convention: Some(Left(BlockCallingConvention::function_start(ret_locs(
|
||||
func_type.returns().iter().map(|t| t.to_microwasm_type()),
|
||||
)))),
|
||||
is_next: false,
|
||||
has_backwards_callers: false,
|
||||
actual_num_callers: 0,
|
||||
num_callers: None,
|
||||
},
|
||||
);
|
||||
|
||||
while let Some(op) = body.next() {
|
||||
if let Some(Operator::Label(label)) = body.peek() {
|
||||
let block = blocks
|
||||
.get_mut(&BrTarget::Label(label.clone()))
|
||||
.expect("Label defined before being declared");
|
||||
block.is_next = true;
|
||||
}
|
||||
|
||||
macro_rules! assert_ge {
|
||||
($left:expr, $right:expr) => ({
|
||||
match (&$left, &$right) {
|
||||
(left_val, right_val) => {
|
||||
if !(*left_val >= *right_val) {
|
||||
// The reborrows below are intentional. Without them, the stack slot for the
|
||||
// borrow is initialized even before the values are compared, leading to a
|
||||
// noticeable slow down.
|
||||
panic!(r#"assertion failed: `(left >= right)`
|
||||
left: `{:?}`,
|
||||
right: `{:?}`"#, &*left_val, &*right_val)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
($left:expr, $right:expr,) => ({
|
||||
assert_ge!($left, $right)
|
||||
});
|
||||
}
|
||||
|
||||
// `cfg` on blocks doesn't work in the compiler right now, so we have to write a dummy macro
|
||||
#[cfg(debug_assertions)]
|
||||
macro_rules! assertions {
|
||||
() => {
|
||||
if let Operator::Label(label) = &op {
|
||||
let block = &blocks[&BrTarget::Label(label.clone())];
|
||||
let num_cc_params = block.calling_convention.as_ref().map(|cc| match cc {
|
||||
Left(cc) => cc.arguments.len(),
|
||||
Right(cc) => cc.stack.len(),
|
||||
});
|
||||
if let Some(num_cc_params) = num_cc_params {
|
||||
assert_ge!(num_cc_params, block.params as usize);
|
||||
}
|
||||
} else {
|
||||
let mut actual_regs = Registers::new();
|
||||
for val in &ctx.block_state.stack {
|
||||
if let ValueLocation::Reg(gpr) = val {
|
||||
actual_regs.mark_used(*gpr);
|
||||
}
|
||||
}
|
||||
assert_eq!(actual_regs, ctx.block_state.regs,);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
macro_rules! assertions {
|
||||
() => {};
|
||||
}
|
||||
|
||||
assertions!();
|
||||
|
||||
struct DisassemblyOpFormatter<Label>(Operator<Label>);
|
||||
|
||||
impl<Label> fmt::Display for DisassemblyOpFormatter<Label>
|
||||
where
|
||||
Operator<Label>: fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.0 {
|
||||
Operator::Label(_) => write!(f, "{}", self.0),
|
||||
Operator::Block { .. } => write!(f, "{:5}\t{}", "", self.0),
|
||||
_ => write!(f, "{:5}\t {}", "", self.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
op_offset_map.push((
|
||||
ctx.asm.offset(),
|
||||
Box::new(DisassemblyOpFormatter(op.clone())),
|
||||
));
|
||||
|
||||
match op {
|
||||
Operator::Unreachable => {
|
||||
ctx.trap();
|
||||
}
|
||||
Operator::Label(label) => {
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
if let Entry::Occupied(mut entry) = blocks.entry(BrTarget::Label(label.clone())) {
|
||||
let has_backwards_callers = {
|
||||
let block = entry.get_mut();
|
||||
|
||||
// TODO: Maybe we want to restrict Microwasm so that at least one of its callers
|
||||
// must be before the label. In an ideal world the restriction would be that
|
||||
// blocks without callers are illegal, but that's not reasonably possible for
|
||||
// Microwasm generated from Wasm.
|
||||
if block.actual_num_callers == 0 {
|
||||
loop {
|
||||
let done = match body.peek() {
|
||||
Some(Operator::Label(_)) | None => true,
|
||||
Some(_) => false,
|
||||
};
|
||||
|
||||
if done {
|
||||
break;
|
||||
}
|
||||
|
||||
let skipped = body.next();
|
||||
|
||||
// We still want to honour block definitions even in unreachable code
|
||||
if let Some(Operator::Block {
|
||||
label,
|
||||
has_backwards_callers,
|
||||
params,
|
||||
num_callers,
|
||||
}) = skipped
|
||||
{
|
||||
let asm_label = ctx.create_label();
|
||||
blocks.insert(
|
||||
BrTarget::Label(label),
|
||||
Block {
|
||||
label: BrTarget::Label(asm_label),
|
||||
params: params.len() as _,
|
||||
calling_convention: None,
|
||||
is_next: false,
|
||||
has_backwards_callers,
|
||||
actual_num_callers: 0,
|
||||
num_callers,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
block.is_next = false;
|
||||
|
||||
// TODO: We can `take` this if it's a `Right`
|
||||
match block.calling_convention.as_ref() {
|
||||
Some(Left(cc)) => {
|
||||
ctx.apply_cc(cc);
|
||||
}
|
||||
Some(Right(virt)) => {
|
||||
ctx.set_state(virt.clone());
|
||||
}
|
||||
_ => assert_eq!(block.params as usize, ctx.block_state.stack.len()),
|
||||
}
|
||||
|
||||
ctx.define_label(block.label.label().unwrap().clone());
|
||||
|
||||
block.has_backwards_callers
|
||||
};
|
||||
|
||||
// To reduce memory overhead
|
||||
if !has_backwards_callers {
|
||||
entry.remove_entry();
|
||||
}
|
||||
} else {
|
||||
panic!("Label defined before being declared");
|
||||
}
|
||||
}
|
||||
Operator::Block {
|
||||
label,
|
||||
has_backwards_callers,
|
||||
params,
|
||||
num_callers,
|
||||
} => {
|
||||
let asm_label = ctx.create_label();
|
||||
blocks.insert(
|
||||
BrTarget::Label(label),
|
||||
Block {
|
||||
label: BrTarget::Label(asm_label),
|
||||
params: params.len() as _,
|
||||
calling_convention: None,
|
||||
is_next: false,
|
||||
has_backwards_callers,
|
||||
actual_num_callers: 0,
|
||||
num_callers,
|
||||
},
|
||||
);
|
||||
}
|
||||
Operator::Br { target } => {
|
||||
// TODO: We should add the block to the hashmap if we don't have it already
|
||||
let block = blocks.get_mut(&target).unwrap();
|
||||
block.actual_num_callers += 1;
|
||||
|
||||
let should_serialize_args = block.should_serialize_args();
|
||||
|
||||
match block {
|
||||
Block {
|
||||
is_next,
|
||||
label: BrTarget::Label(l),
|
||||
calling_convention,
|
||||
..
|
||||
} => {
|
||||
let cc = if should_serialize_args {
|
||||
*calling_convention = Some(Left(ctx.serialize_args(block.params)));
|
||||
None
|
||||
} else {
|
||||
calling_convention
|
||||
.as_ref()
|
||||
.map(Either::as_ref)
|
||||
.and_then(Either::left)
|
||||
};
|
||||
|
||||
if let Some(cc) = cc {
|
||||
ctx.pass_block_args(cc);
|
||||
}
|
||||
|
||||
if !*is_next {
|
||||
ctx.br(*l);
|
||||
}
|
||||
}
|
||||
Block {
|
||||
label: BrTarget::Return,
|
||||
calling_convention: Some(Left(cc)),
|
||||
..
|
||||
} => {
|
||||
ctx.pass_block_args(cc);
|
||||
ctx.ret();
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
Operator::BrIf { then, else_ } => {
|
||||
let (then_block, else_block) = blocks.pair_mut(&then.target, &else_.target);
|
||||
// TODO: If actual_num_callers == num_callers then we can remove this block from the hashmap.
|
||||
// This frees memory and acts as a kind of verification that `num_callers` is set
|
||||
// correctly. It doesn't help for loops and block ends generated from Wasm.
|
||||
then_block.actual_num_callers += 1;
|
||||
else_block.actual_num_callers += 1;
|
||||
|
||||
let then_block_parts = (then_block.is_next, then_block.label);
|
||||
let else_block_parts = (else_block.is_next, else_block.label);
|
||||
|
||||
// TODO: The blocks should have compatible (one must be subset of other?) calling
|
||||
// conventions or else at least one must have no calling convention. This
|
||||
// should always be true for converting from WebAssembly AIUI.
|
||||
let f = |ctx: &mut Context<_>| {
|
||||
let then_block_should_serialize_args = then_block.should_serialize_args();
|
||||
let else_block_should_serialize_args = else_block.should_serialize_args();
|
||||
let max_params = then_block.params.max(else_block.params);
|
||||
|
||||
match (
|
||||
(&mut then_block.calling_convention, &then.to_drop),
|
||||
(&mut else_block.calling_convention, &else_.to_drop),
|
||||
) {
|
||||
((Some(Left(ref cc)), _), ref mut other @ (None, _))
|
||||
| (ref mut other @ (None, _), (Some(Left(ref cc)), _)) => {
|
||||
let mut cc = ctx.serialize_block_args(cc, max_params);
|
||||
if let Some(to_drop) = other.1 {
|
||||
drop_elements(&mut cc.arguments, to_drop.clone());
|
||||
}
|
||||
*other.0 = Some(Left(cc));
|
||||
}
|
||||
(
|
||||
(ref mut then_cc @ None, then_to_drop),
|
||||
(ref mut else_cc @ None, else_to_drop),
|
||||
) => {
|
||||
let virt_cc = if !then_block_should_serialize_args
|
||||
|| !else_block_should_serialize_args
|
||||
{
|
||||
Some(ctx.virtual_calling_convention())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let cc = if then_block_should_serialize_args
|
||||
|| else_block_should_serialize_args
|
||||
{
|
||||
Some(ctx.serialize_args(max_params))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
**then_cc = if then_block_should_serialize_args {
|
||||
let mut cc = cc.clone().unwrap();
|
||||
if let Some(to_drop) = then_to_drop.clone() {
|
||||
drop_elements(&mut cc.arguments, to_drop);
|
||||
}
|
||||
Some(Left(cc))
|
||||
} else {
|
||||
let mut cc = virt_cc.clone().unwrap();
|
||||
if let Some(to_drop) = then_to_drop.clone() {
|
||||
drop_elements(&mut cc.stack, to_drop);
|
||||
}
|
||||
Some(Right(cc))
|
||||
};
|
||||
**else_cc = if else_block_should_serialize_args {
|
||||
let mut cc = cc.unwrap();
|
||||
if let Some(to_drop) = else_to_drop.clone() {
|
||||
drop_elements(&mut cc.arguments, to_drop);
|
||||
}
|
||||
Some(Left(cc))
|
||||
} else {
|
||||
let mut cc = virt_cc.unwrap();
|
||||
if let Some(to_drop) = else_to_drop.clone() {
|
||||
drop_elements(&mut cc.stack, to_drop);
|
||||
}
|
||||
Some(Right(cc))
|
||||
};
|
||||
}
|
||||
_ => unimplemented!(
|
||||
"Can't pass different params to different sides of `br_if` yet"
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
match (then_block_parts, else_block_parts) {
|
||||
((true, _), (false, else_)) => {
|
||||
ctx.br_if_false(else_, f);
|
||||
}
|
||||
((false, then), (true, _)) => {
|
||||
ctx.br_if_true(then, f);
|
||||
}
|
||||
((false, then), (false, else_)) => {
|
||||
ctx.br_if_true(then, f);
|
||||
ctx.br(else_);
|
||||
}
|
||||
other => unimplemented!("{:#?}", other),
|
||||
}
|
||||
}
|
||||
Operator::BrTable(BrTable { targets, default }) => {
|
||||
use itertools::Itertools;
|
||||
|
||||
let (label, num_callers, params) = {
|
||||
let def = &blocks[&default.target];
|
||||
(
|
||||
if def.is_next { None } else { Some(def.label) },
|
||||
def.num_callers,
|
||||
def.params
|
||||
+ default
|
||||
.to_drop
|
||||
.as_ref()
|
||||
.map(|t| t.clone().count())
|
||||
.unwrap_or_default() as u32,
|
||||
)
|
||||
};
|
||||
|
||||
let target_labels = targets
|
||||
.iter()
|
||||
.map(|target| {
|
||||
let block = &blocks[&target.target];
|
||||
if block.is_next {
|
||||
None
|
||||
} else {
|
||||
Some(block.label)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
ctx.br_table(target_labels, label, |ctx| {
|
||||
let mut cc = None;
|
||||
let mut max_params = params;
|
||||
let mut max_num_callers = num_callers;
|
||||
|
||||
for target in targets.iter().chain(std::iter::once(&default)).unique() {
|
||||
let block = blocks.get_mut(&target.target).unwrap();
|
||||
block.actual_num_callers += 1;
|
||||
|
||||
if block.calling_convention.is_some() {
|
||||
let new_cc = block.calling_convention.clone();
|
||||
assert!(cc.is_none() || cc == new_cc, "Can't pass different params to different elements of `br_table` yet");
|
||||
cc = new_cc;
|
||||
}
|
||||
|
||||
if let Some(max) = max_num_callers {
|
||||
max_num_callers = block.num_callers.map(|n| max.max(n));
|
||||
}
|
||||
|
||||
max_params = max_params.max(
|
||||
block.params
|
||||
+ target
|
||||
.to_drop
|
||||
.as_ref()
|
||||
.map(|t| t.clone().count())
|
||||
.unwrap_or_default() as u32
|
||||
);
|
||||
}
|
||||
|
||||
let cc = cc.map(|cc| {
|
||||
match cc {
|
||||
Left(cc) => Left(ctx.serialize_block_args(&cc, max_params)),
|
||||
Right(cc) => Right(cc),
|
||||
}
|
||||
}).unwrap_or_else(||
|
||||
if max_num_callers.map(|callers| callers <= 1).unwrap_or(false) {
|
||||
Right(ctx.virtual_calling_convention())
|
||||
} else {
|
||||
Left(ctx.serialize_args(max_params))
|
||||
}
|
||||
);
|
||||
|
||||
for target in targets.iter().chain(std::iter::once(&default)).unique() {
|
||||
let block = blocks.get_mut(&target.target).unwrap();
|
||||
let mut cc = cc.clone();
|
||||
if let Some(to_drop) = target.to_drop.clone() {
|
||||
match &mut cc {
|
||||
Left(cc) => drop_elements(&mut cc.arguments, to_drop),
|
||||
Right(cc) => drop_elements(&mut cc.stack, to_drop),
|
||||
}
|
||||
}
|
||||
block.calling_convention = Some(cc);
|
||||
}
|
||||
});
|
||||
}
|
||||
Operator::Swap(depth) => ctx.swap(depth),
|
||||
Operator::Pick(depth) => ctx.pick(depth),
|
||||
Operator::Eq(I32) => ctx.i32_eq(),
|
||||
Operator::Eqz(Size::_32) => ctx.i32_eqz(),
|
||||
Operator::Ne(I32) => ctx.i32_neq(),
|
||||
Operator::Lt(SI32) => ctx.i32_lt_s(),
|
||||
Operator::Le(SI32) => ctx.i32_le_s(),
|
||||
Operator::Gt(SI32) => ctx.i32_gt_s(),
|
||||
Operator::Ge(SI32) => ctx.i32_ge_s(),
|
||||
Operator::Lt(SU32) => ctx.i32_lt_u(),
|
||||
Operator::Le(SU32) => ctx.i32_le_u(),
|
||||
Operator::Gt(SU32) => ctx.i32_gt_u(),
|
||||
Operator::Ge(SU32) => ctx.i32_ge_u(),
|
||||
Operator::Add(I32) => ctx.i32_add(),
|
||||
Operator::Sub(I32) => ctx.i32_sub(),
|
||||
Operator::And(Size::_32) => ctx.i32_and(),
|
||||
Operator::Or(Size::_32) => ctx.i32_or(),
|
||||
Operator::Xor(Size::_32) => ctx.i32_xor(),
|
||||
Operator::Mul(I32) => ctx.i32_mul(),
|
||||
Operator::Div(SU32) => ctx.i32_div_u(),
|
||||
Operator::Div(SI32) => ctx.i32_div_s(),
|
||||
Operator::Rem(sint::I32) => ctx.i32_rem_s(),
|
||||
Operator::Rem(sint::U32) => ctx.i32_rem_u(),
|
||||
Operator::Shl(Size::_32) => ctx.i32_shl(),
|
||||
Operator::Shr(sint::I32) => ctx.i32_shr_s(),
|
||||
Operator::Shr(sint::U32) => ctx.i32_shr_u(),
|
||||
Operator::Rotl(Size::_32) => ctx.i32_rotl(),
|
||||
Operator::Rotr(Size::_32) => ctx.i32_rotr(),
|
||||
Operator::Clz(Size::_32) => ctx.i32_clz(),
|
||||
Operator::Ctz(Size::_32) => ctx.i32_ctz(),
|
||||
Operator::Popcnt(Size::_32) => ctx.i32_popcnt(),
|
||||
Operator::Eq(I64) => ctx.i64_eq(),
|
||||
Operator::Eqz(Size::_64) => ctx.i64_eqz(),
|
||||
Operator::Ne(I64) => ctx.i64_neq(),
|
||||
Operator::Lt(SI64) => ctx.i64_lt_s(),
|
||||
Operator::Le(SI64) => ctx.i64_le_s(),
|
||||
Operator::Gt(SI64) => ctx.i64_gt_s(),
|
||||
Operator::Ge(SI64) => ctx.i64_ge_s(),
|
||||
Operator::Lt(SU64) => ctx.i64_lt_u(),
|
||||
Operator::Le(SU64) => ctx.i64_le_u(),
|
||||
Operator::Gt(SU64) => ctx.i64_gt_u(),
|
||||
Operator::Ge(SU64) => ctx.i64_ge_u(),
|
||||
Operator::Add(I64) => ctx.i64_add(),
|
||||
Operator::Sub(I64) => ctx.i64_sub(),
|
||||
Operator::And(Size::_64) => ctx.i64_and(),
|
||||
Operator::Or(Size::_64) => ctx.i64_or(),
|
||||
Operator::Xor(Size::_64) => ctx.i64_xor(),
|
||||
Operator::Mul(I64) => ctx.i64_mul(),
|
||||
Operator::Div(SU64) => ctx.i64_div_u(),
|
||||
Operator::Div(SI64) => ctx.i64_div_s(),
|
||||
Operator::Rem(sint::I64) => ctx.i64_rem_s(),
|
||||
Operator::Rem(sint::U64) => ctx.i64_rem_u(),
|
||||
Operator::Shl(Size::_64) => ctx.i64_shl(),
|
||||
Operator::Shr(sint::I64) => ctx.i64_shr_s(),
|
||||
Operator::Shr(sint::U64) => ctx.i64_shr_u(),
|
||||
Operator::Rotl(Size::_64) => ctx.i64_rotl(),
|
||||
Operator::Rotr(Size::_64) => ctx.i64_rotr(),
|
||||
Operator::Clz(Size::_64) => ctx.i64_clz(),
|
||||
Operator::Ctz(Size::_64) => ctx.i64_ctz(),
|
||||
Operator::Popcnt(Size::_64) => ctx.i64_popcnt(),
|
||||
Operator::Add(F32) => ctx.f32_add(),
|
||||
Operator::Mul(F32) => ctx.f32_mul(),
|
||||
Operator::Sub(F32) => ctx.f32_sub(),
|
||||
Operator::Div(SF32) => ctx.f32_div(),
|
||||
Operator::Min(Size::_32) => ctx.f32_min(),
|
||||
Operator::Max(Size::_32) => ctx.f32_max(),
|
||||
Operator::Copysign(Size::_32) => ctx.f32_copysign(),
|
||||
Operator::Sqrt(Size::_32) => ctx.f32_sqrt(),
|
||||
Operator::Neg(Size::_32) => ctx.f32_neg(),
|
||||
Operator::Abs(Size::_32) => ctx.f32_abs(),
|
||||
Operator::Floor(Size::_32) => ctx.f32_floor(),
|
||||
Operator::Ceil(Size::_32) => ctx.f32_ceil(),
|
||||
Operator::Nearest(Size::_32) => ctx.f32_nearest(),
|
||||
Operator::Trunc(Size::_32) => ctx.f32_trunc(),
|
||||
Operator::Eq(F32) => ctx.f32_eq(),
|
||||
Operator::Ne(F32) => ctx.f32_ne(),
|
||||
Operator::Gt(SF32) => ctx.f32_gt(),
|
||||
Operator::Ge(SF32) => ctx.f32_ge(),
|
||||
Operator::Lt(SF32) => ctx.f32_lt(),
|
||||
Operator::Le(SF32) => ctx.f32_le(),
|
||||
Operator::Add(F64) => ctx.f64_add(),
|
||||
Operator::Mul(F64) => ctx.f64_mul(),
|
||||
Operator::Sub(F64) => ctx.f64_sub(),
|
||||
Operator::Div(SF64) => ctx.f64_div(),
|
||||
Operator::Min(Size::_64) => ctx.f64_min(),
|
||||
Operator::Max(Size::_64) => ctx.f64_max(),
|
||||
Operator::Copysign(Size::_64) => ctx.f64_copysign(),
|
||||
Operator::Sqrt(Size::_64) => ctx.f64_sqrt(),
|
||||
Operator::Neg(Size::_64) => ctx.f64_neg(),
|
||||
Operator::Abs(Size::_64) => ctx.f64_abs(),
|
||||
Operator::Floor(Size::_64) => ctx.f64_floor(),
|
||||
Operator::Ceil(Size::_64) => ctx.f64_ceil(),
|
||||
Operator::Nearest(Size::_64) => ctx.f64_nearest(),
|
||||
Operator::Trunc(Size::_64) => ctx.f64_trunc(),
|
||||
Operator::Eq(F64) => ctx.f64_eq(),
|
||||
Operator::Ne(F64) => ctx.f64_ne(),
|
||||
Operator::Gt(SF64) => ctx.f64_gt(),
|
||||
Operator::Ge(SF64) => ctx.f64_ge(),
|
||||
Operator::Lt(SF64) => ctx.f64_lt(),
|
||||
Operator::Le(SF64) => ctx.f64_le(),
|
||||
Operator::Drop(range) => ctx.drop(range),
|
||||
Operator::Const(val) => ctx.const_(val),
|
||||
Operator::I32WrapFromI64 => ctx.i32_wrap_from_i64(),
|
||||
Operator::I32ReinterpretFromF32 => ctx.i32_reinterpret_from_f32(),
|
||||
Operator::I64ReinterpretFromF64 => ctx.i64_reinterpret_from_f64(),
|
||||
Operator::F32ReinterpretFromI32 => ctx.f32_reinterpret_from_i32(),
|
||||
Operator::F64ReinterpretFromI64 => ctx.f64_reinterpret_from_i64(),
|
||||
Operator::ITruncFromF {
|
||||
input_ty: Size::_32,
|
||||
output_ty: sint::I32,
|
||||
} => {
|
||||
ctx.i32_truncate_f32_s();
|
||||
}
|
||||
Operator::ITruncFromF {
|
||||
input_ty: Size::_32,
|
||||
output_ty: sint::U32,
|
||||
} => {
|
||||
ctx.i32_truncate_f32_u();
|
||||
}
|
||||
Operator::ITruncFromF {
|
||||
input_ty: Size::_64,
|
||||
output_ty: sint::I32,
|
||||
} => {
|
||||
ctx.i32_truncate_f64_s();
|
||||
}
|
||||
Operator::ITruncFromF {
|
||||
input_ty: Size::_64,
|
||||
output_ty: sint::U32,
|
||||
} => {
|
||||
ctx.i32_truncate_f64_u();
|
||||
}
|
||||
Operator::ITruncFromF {
|
||||
input_ty: Size::_32,
|
||||
output_ty: sint::I64,
|
||||
} => {
|
||||
ctx.i64_truncate_f32_s();
|
||||
}
|
||||
Operator::ITruncFromF {
|
||||
input_ty: Size::_32,
|
||||
output_ty: sint::U64,
|
||||
} => {
|
||||
ctx.i64_truncate_f32_u();
|
||||
}
|
||||
Operator::ITruncFromF {
|
||||
input_ty: Size::_64,
|
||||
output_ty: sint::I64,
|
||||
} => {
|
||||
ctx.i64_truncate_f64_s();
|
||||
}
|
||||
Operator::ITruncFromF {
|
||||
input_ty: Size::_64,
|
||||
output_ty: sint::U64,
|
||||
} => {
|
||||
ctx.i64_truncate_f64_u();
|
||||
}
|
||||
Operator::Extend {
|
||||
sign: Signedness::Unsigned,
|
||||
} => ctx.i32_extend_u(),
|
||||
Operator::Extend {
|
||||
sign: Signedness::Signed,
|
||||
} => ctx.i32_extend_s(),
|
||||
Operator::FConvertFromI {
|
||||
input_ty: sint::I32,
|
||||
output_ty: Size::_32,
|
||||
} => ctx.f32_convert_from_i32_s(),
|
||||
Operator::FConvertFromI {
|
||||
input_ty: sint::I32,
|
||||
output_ty: Size::_64,
|
||||
} => ctx.f64_convert_from_i32_s(),
|
||||
Operator::FConvertFromI {
|
||||
input_ty: sint::I64,
|
||||
output_ty: Size::_32,
|
||||
} => ctx.f32_convert_from_i64_s(),
|
||||
Operator::FConvertFromI {
|
||||
input_ty: sint::I64,
|
||||
output_ty: Size::_64,
|
||||
} => ctx.f64_convert_from_i64_s(),
|
||||
Operator::FConvertFromI {
|
||||
input_ty: sint::U32,
|
||||
output_ty: Size::_32,
|
||||
} => ctx.f32_convert_from_i32_u(),
|
||||
Operator::FConvertFromI {
|
||||
input_ty: sint::U32,
|
||||
output_ty: Size::_64,
|
||||
} => ctx.f64_convert_from_i32_u(),
|
||||
Operator::FConvertFromI {
|
||||
input_ty: sint::U64,
|
||||
output_ty: Size::_32,
|
||||
} => ctx.f32_convert_from_i64_u(),
|
||||
Operator::FConvertFromI {
|
||||
input_ty: sint::U64,
|
||||
output_ty: Size::_64,
|
||||
} => ctx.f64_convert_from_i64_u(),
|
||||
Operator::F64PromoteFromF32 => ctx.f64_from_f32(),
|
||||
Operator::F32DemoteFromF64 => ctx.f32_from_f64(),
|
||||
Operator::Load8 {
|
||||
ty: sint::U32,
|
||||
memarg,
|
||||
} => ctx.i32_load8_u(memarg.offset),
|
||||
Operator::Load16 {
|
||||
ty: sint::U32,
|
||||
memarg,
|
||||
} => ctx.i32_load16_u(memarg.offset),
|
||||
Operator::Load8 {
|
||||
ty: sint::I32,
|
||||
memarg,
|
||||
} => ctx.i32_load8_s(memarg.offset),
|
||||
Operator::Load16 {
|
||||
ty: sint::I32,
|
||||
memarg,
|
||||
} => ctx.i32_load16_s(memarg.offset),
|
||||
Operator::Load8 {
|
||||
ty: sint::U64,
|
||||
memarg,
|
||||
} => ctx.i64_load8_u(memarg.offset),
|
||||
Operator::Load16 {
|
||||
ty: sint::U64,
|
||||
memarg,
|
||||
} => ctx.i64_load16_u(memarg.offset),
|
||||
Operator::Load8 {
|
||||
ty: sint::I64,
|
||||
memarg,
|
||||
} => ctx.i64_load8_s(memarg.offset),
|
||||
Operator::Load16 {
|
||||
ty: sint::I64,
|
||||
memarg,
|
||||
} => ctx.i64_load16_s(memarg.offset),
|
||||
Operator::Load32 {
|
||||
sign: Signedness::Unsigned,
|
||||
memarg,
|
||||
} => ctx.i64_load32_u(memarg.offset),
|
||||
Operator::Load32 {
|
||||
sign: Signedness::Signed,
|
||||
memarg,
|
||||
} => ctx.i64_load32_s(memarg.offset),
|
||||
Operator::Load { ty: I32, memarg } => ctx.i32_load(memarg.offset),
|
||||
Operator::Load { ty: F32, memarg } => ctx.f32_load(memarg.offset),
|
||||
Operator::Load { ty: I64, memarg } => ctx.i64_load(memarg.offset),
|
||||
Operator::Load { ty: F64, memarg } => ctx.f64_load(memarg.offset),
|
||||
Operator::Store8 { ty: _, memarg } => ctx.store8(memarg.offset),
|
||||
Operator::Store16 { ty: _, memarg } => ctx.store16(memarg.offset),
|
||||
Operator::Store32 { memarg }
|
||||
| Operator::Store { ty: I32, memarg }
|
||||
| Operator::Store { ty: F32, memarg } => ctx.store32(memarg.offset),
|
||||
Operator::Store { ty: I64, memarg } | Operator::Store { ty: F64, memarg } => {
|
||||
ctx.store64(memarg.offset)
|
||||
}
|
||||
Operator::GetGlobal(idx) => ctx.get_global(idx),
|
||||
Operator::SetGlobal(idx) => ctx.set_global(idx),
|
||||
Operator::Select => {
|
||||
ctx.select();
|
||||
}
|
||||
Operator::MemorySize { reserved: _ } => {
|
||||
ctx.memory_size();
|
||||
}
|
||||
Operator::MemoryGrow { reserved: _ } => {
|
||||
ctx.memory_grow();
|
||||
}
|
||||
Operator::Call { function_index } => {
|
||||
let callee_ty = module_context.func_type(function_index);
|
||||
|
||||
if let Some(defined_index) = module_context.defined_func_index(function_index) {
|
||||
if function_index == func_idx {
|
||||
ctx.call_direct_self(
|
||||
defined_index,
|
||||
callee_ty.params().iter().map(|t| t.to_microwasm_type()),
|
||||
callee_ty.returns().iter().map(|t| t.to_microwasm_type()),
|
||||
);
|
||||
} else {
|
||||
ctx.call_direct(
|
||||
function_index,
|
||||
callee_ty.params().iter().map(|t| t.to_microwasm_type()),
|
||||
callee_ty.returns().iter().map(|t| t.to_microwasm_type()),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
ctx.call_direct_imported(
|
||||
function_index,
|
||||
callee_ty.params().iter().map(|t| t.to_microwasm_type()),
|
||||
callee_ty.returns().iter().map(|t| t.to_microwasm_type()),
|
||||
);
|
||||
}
|
||||
}
|
||||
Operator::CallIndirect {
|
||||
type_index,
|
||||
table_index,
|
||||
} => {
|
||||
assert_eq!(table_index, 0);
|
||||
|
||||
let callee_ty = module_context.signature(type_index);
|
||||
|
||||
// TODO: this implementation assumes that this function is locally defined.
|
||||
|
||||
ctx.call_indirect(
|
||||
type_index,
|
||||
callee_ty.params().iter().map(|t| t.to_microwasm_type()),
|
||||
callee_ty.returns().iter().map(|t| t.to_microwasm_type()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.epilogue();
|
||||
|
||||
mem::replace(&mut session.op_offset_map, op_offset_map);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
37
crates/lightbeam/src/lib.rs
Normal file
37
crates/lightbeam/src/lib.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
#![cfg_attr(feature = "bench", feature(test))]
|
||||
#![feature(proc_macro_hygiene)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate smallvec;
|
||||
extern crate capstone;
|
||||
extern crate either;
|
||||
pub extern crate wasmparser;
|
||||
#[macro_use]
|
||||
extern crate memoffset;
|
||||
extern crate dynasm;
|
||||
extern crate dynasmrt;
|
||||
extern crate itertools;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate quickcheck;
|
||||
// Just so we can implement `Signature` for `cranelift_codegen::ir::Signature`
|
||||
extern crate cranelift_codegen;
|
||||
extern crate multi_mut;
|
||||
|
||||
mod backend;
|
||||
mod disassemble;
|
||||
mod error;
|
||||
mod function_body;
|
||||
mod microwasm;
|
||||
mod module;
|
||||
mod translate_sections;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use crate::backend::CodeGenSession;
|
||||
pub use crate::function_body::translate_wasm as translate_function;
|
||||
pub use crate::module::{translate, ExecutableModule, ModuleContext, Signature, TranslatedModule};
|
||||
2109
crates/lightbeam/src/microwasm.rs
Normal file
2109
crates/lightbeam/src/microwasm.rs
Normal file
File diff suppressed because it is too large
Load Diff
638
crates/lightbeam/src/module.rs
Normal file
638
crates/lightbeam/src/module.rs
Normal file
@@ -0,0 +1,638 @@
|
||||
use crate::backend::TranslatedCodeSection;
|
||||
use crate::error::Error;
|
||||
use crate::microwasm;
|
||||
use crate::translate_sections;
|
||||
use core::{convert::TryInto, mem};
|
||||
use cranelift_codegen::{
|
||||
ir::{self, AbiParam, Signature as CraneliftSignature},
|
||||
isa,
|
||||
};
|
||||
use wasmparser::{FuncType, MemoryType, ModuleReader, SectionCode, Type};
|
||||
|
||||
pub trait AsValueType {
|
||||
const TYPE: Type;
|
||||
}
|
||||
|
||||
pub trait TypeList {
|
||||
const TYPE_LIST: &'static [Type];
|
||||
}
|
||||
|
||||
impl<T> TypeList for T
|
||||
where
|
||||
T: AsValueType,
|
||||
{
|
||||
const TYPE_LIST: &'static [Type] = &[T::TYPE];
|
||||
}
|
||||
|
||||
impl AsValueType for i32 {
|
||||
const TYPE: Type = Type::I32;
|
||||
}
|
||||
impl AsValueType for i64 {
|
||||
const TYPE: Type = Type::I64;
|
||||
}
|
||||
impl AsValueType for u32 {
|
||||
const TYPE: Type = Type::I32;
|
||||
}
|
||||
impl AsValueType for u64 {
|
||||
const TYPE: Type = Type::I64;
|
||||
}
|
||||
impl AsValueType for f32 {
|
||||
const TYPE: Type = Type::F32;
|
||||
}
|
||||
impl AsValueType for f64 {
|
||||
const TYPE: Type = Type::F64;
|
||||
}
|
||||
|
||||
pub trait FunctionArgs<O> {
|
||||
type FuncType;
|
||||
|
||||
unsafe fn call(self, func: Self::FuncType, vm_ctx: *const u8) -> O;
|
||||
fn into_func(start: *const u8) -> Self::FuncType;
|
||||
}
|
||||
|
||||
type VmCtxPtr = u64;
|
||||
|
||||
macro_rules! impl_function_args {
|
||||
($first:ident $(, $rest:ident)*) => {
|
||||
impl<Output, $first, $($rest),*> FunctionArgs<Output> for ($first, $($rest),*) {
|
||||
type FuncType = unsafe extern "sysv64" fn(VmCtxPtr, $first $(, $rest)*) -> Output;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn call(self, func: Self::FuncType, vm_ctx: *const u8) -> Output {
|
||||
let ($first, $($rest),*) = self;
|
||||
func(vm_ctx as VmCtxPtr, $first $(, $rest)*)
|
||||
}
|
||||
|
||||
fn into_func(start: *const u8) -> Self::FuncType {
|
||||
unsafe { mem::transmute(start) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<$first: AsValueType, $($rest: AsValueType),*> TypeList for ($first, $($rest),*) {
|
||||
const TYPE_LIST: &'static [Type] = &[$first::TYPE, $($rest::TYPE),*];
|
||||
}
|
||||
|
||||
impl_function_args!($($rest),*);
|
||||
};
|
||||
() => {
|
||||
impl<Output> FunctionArgs<Output> for () {
|
||||
type FuncType = unsafe extern "sysv64" fn(VmCtxPtr) -> Output;
|
||||
|
||||
unsafe fn call(self, func: Self::FuncType, vm_ctx: *const u8) -> Output {
|
||||
func(vm_ctx as VmCtxPtr)
|
||||
}
|
||||
|
||||
fn into_func(start: *const u8) -> Self::FuncType {
|
||||
unsafe { mem::transmute(start) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeList for () {
|
||||
const TYPE_LIST: &'static [Type] = &[];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_function_args!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TranslatedModule {
|
||||
translated_code_section: Option<TranslatedCodeSection>,
|
||||
ctx: SimpleContext,
|
||||
// TODO: Should we wrap this in a `Mutex` so that calling functions from multiple
|
||||
// threads doesn't cause data races?
|
||||
memory: Option<MemoryType>,
|
||||
}
|
||||
|
||||
impl TranslatedModule {
|
||||
pub fn instantiate(self) -> ExecutableModule {
|
||||
let mem_size = self.memory.map(|m| m.limits.initial).unwrap_or(0) as usize;
|
||||
let mem: BoxSlice<_> = vec![0u8; mem_size * WASM_PAGE_SIZE]
|
||||
.into_boxed_slice()
|
||||
.into();
|
||||
|
||||
let ctx = if mem.len > 0 {
|
||||
Some(Box::new(VmCtx { mem }) as Box<VmCtx>)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
ExecutableModule {
|
||||
module: self,
|
||||
context: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disassemble(&self) {
|
||||
self.translated_code_section
|
||||
.as_ref()
|
||||
.expect("no code section")
|
||||
.disassemble();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ExecutionError {
|
||||
FuncIndexOutOfBounds,
|
||||
TypeMismatch,
|
||||
}
|
||||
|
||||
pub struct ExecutableModule {
|
||||
module: TranslatedModule,
|
||||
context: Option<Box<VmCtx>>,
|
||||
}
|
||||
|
||||
impl ExecutableModule {
|
||||
/// Executes the function _without checking types_. This can cause undefined
|
||||
/// memory to be accessed.
|
||||
pub unsafe fn execute_func_unchecked<Args: FunctionArgs<T>, T>(
|
||||
&self,
|
||||
func_idx: u32,
|
||||
args: Args,
|
||||
) -> T {
|
||||
let code_section = self
|
||||
.module
|
||||
.translated_code_section
|
||||
.as_ref()
|
||||
.expect("no code section");
|
||||
let start_buf = code_section.func_start(func_idx as usize);
|
||||
|
||||
args.call(
|
||||
Args::into_func(start_buf),
|
||||
self.context
|
||||
.as_ref()
|
||||
.map(|ctx| (&**ctx) as *const VmCtx as *const u8)
|
||||
.unwrap_or(core::ptr::null()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn execute_func<Args: FunctionArgs<T> + TypeList, T: TypeList>(
|
||||
&self,
|
||||
func_idx: u32,
|
||||
args: Args,
|
||||
) -> Result<T, ExecutionError> {
|
||||
let module = &self.module;
|
||||
|
||||
if func_idx as usize >= module.ctx.func_ty_indicies.len() {
|
||||
return Err(ExecutionError::FuncIndexOutOfBounds);
|
||||
}
|
||||
|
||||
let type_ = module.ctx.func_type(func_idx);
|
||||
|
||||
// TODO: Handle "compatible" types (i.e. f32 and i32)
|
||||
if (&type_.params[..], &type_.returns[..]) != (Args::TYPE_LIST, T::TYPE_LIST) {
|
||||
return Err(ExecutionError::TypeMismatch);
|
||||
}
|
||||
|
||||
Ok(unsafe { self.execute_func_unchecked(func_idx, args) })
|
||||
}
|
||||
|
||||
pub fn disassemble(&self) {
|
||||
self.module.disassemble();
|
||||
}
|
||||
}
|
||||
|
||||
struct BoxSlice<T> {
|
||||
len: usize,
|
||||
ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<T> From<Box<[T]>> for BoxSlice<T> {
|
||||
fn from(mut other: Box<[T]>) -> Self {
|
||||
let out = BoxSlice {
|
||||
len: other.len(),
|
||||
ptr: other.as_mut_ptr(),
|
||||
};
|
||||
mem::forget(other);
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send for BoxSlice<T> {}
|
||||
unsafe impl<T: Sync> Sync for BoxSlice<T> {}
|
||||
|
||||
impl<T> Drop for BoxSlice<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { Vec::from_raw_parts(self.ptr, self.len, self.len) };
|
||||
}
|
||||
}
|
||||
|
||||
type BoxByteSlice = BoxSlice<u8>;
|
||||
|
||||
pub struct VmCtx {
|
||||
mem: BoxByteSlice,
|
||||
}
|
||||
|
||||
impl VmCtx {
|
||||
pub fn offset_of_memory_ptr() -> u32 {
|
||||
(offset_of!(VmCtx, mem) + offset_of!(BoxByteSlice, ptr))
|
||||
.try_into()
|
||||
.expect("Offset exceeded size of u32")
|
||||
}
|
||||
|
||||
pub fn offset_of_memory_len() -> u32 {
|
||||
(offset_of!(VmCtx, mem) + offset_of!(BoxByteSlice, len))
|
||||
.try_into()
|
||||
.expect("Offset exceeded size of u32")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SimpleContext {
|
||||
types: Vec<FuncType>,
|
||||
func_ty_indicies: Vec<u32>,
|
||||
}
|
||||
|
||||
pub const WASM_PAGE_SIZE: usize = 65_536;
|
||||
|
||||
pub trait Signature {
|
||||
type Type: SigType;
|
||||
|
||||
fn params(&self) -> &[Self::Type];
|
||||
fn returns(&self) -> &[Self::Type];
|
||||
}
|
||||
|
||||
pub trait SigType {
|
||||
fn to_microwasm_type(&self) -> microwasm::SignlessType;
|
||||
}
|
||||
|
||||
impl SigType for ir::Type {
|
||||
fn to_microwasm_type(&self) -> microwasm::SignlessType {
|
||||
use crate::microwasm::{Size::*, Type::*};
|
||||
|
||||
if self.is_int() {
|
||||
match self.bits() {
|
||||
32 => Int(_32),
|
||||
64 => Int(_64),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
} else if self.is_float() {
|
||||
match self.bits() {
|
||||
32 => Float(_32),
|
||||
64 => Float(_64),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
} else {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SigType for AbiParam {
|
||||
fn to_microwasm_type(&self) -> microwasm::SignlessType {
|
||||
self.value_type.to_microwasm_type()
|
||||
}
|
||||
}
|
||||
|
||||
impl Signature for CraneliftSignature {
|
||||
type Type = AbiParam;
|
||||
|
||||
fn params(&self) -> &[Self::Type] {
|
||||
// TODO: We want to instead add the `VMContext` to the signature used by
|
||||
// cranelift, removing the special-casing from the internals.
|
||||
assert_eq!(self.params[0].purpose, ir::ArgumentPurpose::VMContext);
|
||||
assert_eq!(self.call_conv, isa::CallConv::SystemV);
|
||||
&self.params[1..]
|
||||
}
|
||||
|
||||
fn returns(&self) -> &[Self::Type] {
|
||||
assert_eq!(self.call_conv, isa::CallConv::SystemV);
|
||||
&self.returns
|
||||
}
|
||||
}
|
||||
|
||||
impl SigType for wasmparser::Type {
|
||||
fn to_microwasm_type(&self) -> microwasm::SignlessType {
|
||||
microwasm::Type::from_wasm(*self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Signature for FuncType {
|
||||
type Type = wasmparser::Type;
|
||||
|
||||
fn params(&self) -> &[Self::Type] {
|
||||
&*self.params
|
||||
}
|
||||
|
||||
fn returns(&self) -> &[Self::Type] {
|
||||
&*self.returns
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ModuleContext {
|
||||
type Signature: Signature;
|
||||
type GlobalType: SigType;
|
||||
|
||||
fn vmctx_vmglobal_definition(&self, index: u32) -> u32;
|
||||
fn vmctx_vmglobal_import_from(&self, index: u32) -> u32;
|
||||
fn vmctx_vmmemory_import_from(&self, memory_index: u32) -> u32;
|
||||
fn vmctx_vmmemory_definition(&self, defined_memory_index: u32) -> u32;
|
||||
fn vmctx_vmmemory_definition_base(&self, defined_memory_index: u32) -> u32;
|
||||
fn vmctx_vmmemory_definition_current_length(&self, defined_memory_index: u32) -> u32;
|
||||
fn vmmemory_definition_base(&self) -> u8;
|
||||
fn vmmemory_definition_current_length(&self) -> u8;
|
||||
fn vmctx_vmtable_import_from(&self, table_index: u32) -> u32;
|
||||
fn vmctx_vmtable_definition(&self, defined_table_index: u32) -> u32;
|
||||
fn vmctx_vmtable_definition_base(&self, defined_table_index: u32) -> u32;
|
||||
fn vmctx_vmtable_definition_current_elements(&self, defined_table_index: u32) -> u32;
|
||||
fn vmctx_vmfunction_import_body(&self, func_index: u32) -> u32;
|
||||
fn vmctx_vmfunction_import_vmctx(&self, func_index: u32) -> u32;
|
||||
fn vmtable_definition_base(&self) -> u8;
|
||||
fn vmtable_definition_current_elements(&self) -> u8;
|
||||
fn vmctx_vmshared_signature_id(&self, signature_idx: u32) -> u32;
|
||||
fn vmcaller_checked_anyfunc_type_index(&self) -> u8;
|
||||
fn vmcaller_checked_anyfunc_func_ptr(&self) -> u8;
|
||||
fn vmcaller_checked_anyfunc_vmctx(&self) -> u8;
|
||||
fn size_of_vmcaller_checked_anyfunc(&self) -> u8;
|
||||
|
||||
fn defined_table_index(&self, table_index: u32) -> Option<u32>;
|
||||
fn defined_memory_index(&self, index: u32) -> Option<u32>;
|
||||
|
||||
fn defined_global_index(&self, global_index: u32) -> Option<u32>;
|
||||
fn global_type(&self, global_index: u32) -> &Self::GlobalType;
|
||||
|
||||
fn func_type_index(&self, func_idx: u32) -> u32;
|
||||
fn signature(&self, index: u32) -> &Self::Signature;
|
||||
|
||||
fn func_index(&self, defined_func_index: u32) -> u32;
|
||||
fn defined_func_index(&self, func_index: u32) -> Option<u32>;
|
||||
|
||||
fn defined_func_type(&self, defined_func_idx: u32) -> &Self::Signature {
|
||||
self.func_type(self.func_index(defined_func_idx))
|
||||
}
|
||||
|
||||
fn func_type(&self, func_idx: u32) -> &Self::Signature {
|
||||
self.signature(self.func_type_index(func_idx))
|
||||
}
|
||||
|
||||
fn emit_memory_bounds_check(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleContext for SimpleContext {
|
||||
type Signature = FuncType;
|
||||
type GlobalType = wasmparser::Type;
|
||||
|
||||
// TODO: We don't support external functions yet
|
||||
fn func_index(&self, func_idx: u32) -> u32 {
|
||||
func_idx
|
||||
}
|
||||
|
||||
fn defined_func_index(&self, func_idx: u32) -> Option<u32> {
|
||||
Some(func_idx)
|
||||
}
|
||||
|
||||
fn func_type_index(&self, func_idx: u32) -> u32 {
|
||||
self.func_ty_indicies[func_idx as usize]
|
||||
}
|
||||
|
||||
fn defined_global_index(&self, _index: u32) -> Option<u32> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn global_type(&self, _global_index: u32) -> &Self::GlobalType {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn signature(&self, index: u32) -> &Self::Signature {
|
||||
&self.types[index as usize]
|
||||
}
|
||||
|
||||
fn vmctx_vmglobal_definition(&self, _index: u32) -> u32 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn vmctx_vmglobal_import_from(&self, _index: u32) -> u32 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn defined_memory_index(&self, _index: u32) -> Option<u32> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn defined_table_index(&self, index: u32) -> Option<u32> {
|
||||
Some(index)
|
||||
}
|
||||
|
||||
fn vmctx_vmfunction_import_body(&self, _func_index: u32) -> u32 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn vmctx_vmfunction_import_vmctx(&self, _func_index: u32) -> u32 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn vmctx_vmtable_import_from(&self, _table_index: u32) -> u32 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn vmctx_vmmemory_definition(&self, _defined_memory_index: u32) -> u32 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn vmctx_vmmemory_import_from(&self, _memory_index: u32) -> u32 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn vmmemory_definition_base(&self) -> u8 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn vmmemory_definition_current_length(&self) -> u8 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn vmctx_vmmemory_definition_base(&self, defined_memory_index: u32) -> u32 {
|
||||
assert_eq!(defined_memory_index, 0);
|
||||
VmCtx::offset_of_memory_ptr()
|
||||
}
|
||||
|
||||
fn vmctx_vmmemory_definition_current_length(&self, defined_memory_index: u32) -> u32 {
|
||||
assert_eq!(defined_memory_index, 0);
|
||||
VmCtx::offset_of_memory_len()
|
||||
}
|
||||
|
||||
fn vmctx_vmtable_definition(&self, _defined_table_index: u32) -> u32 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn vmctx_vmtable_definition_base(&self, _defined_table_index: u32) -> u32 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn vmctx_vmtable_definition_current_elements(&self, _defined_table_index: u32) -> u32 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn vmtable_definition_base(&self) -> u8 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn vmtable_definition_current_elements(&self) -> u8 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn vmcaller_checked_anyfunc_vmctx(&self) -> u8 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn vmcaller_checked_anyfunc_type_index(&self) -> u8 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn vmcaller_checked_anyfunc_func_ptr(&self) -> u8 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn size_of_vmcaller_checked_anyfunc(&self) -> u8 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn vmctx_vmshared_signature_id(&self, _signature_idx: u32) -> u32 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// TODO: type of a global
|
||||
}
|
||||
|
||||
pub fn translate(data: &[u8]) -> Result<ExecutableModule, Error> {
|
||||
translate_only(data).map(|m| m.instantiate())
|
||||
}
|
||||
|
||||
/// Translate from a slice of bytes holding a wasm module.
|
||||
pub fn translate_only(data: &[u8]) -> Result<TranslatedModule, Error> {
|
||||
let mut reader = ModuleReader::new(data)?;
|
||||
let mut output = TranslatedModule::default();
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(output);
|
||||
}
|
||||
let mut section = reader.read()?;
|
||||
|
||||
if let SectionCode::Type = section.code {
|
||||
let types_reader = section.get_type_section_reader()?;
|
||||
output.ctx.types = translate_sections::type_(types_reader)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(output);
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Import = section.code {
|
||||
let imports = section.get_import_section_reader()?;
|
||||
translate_sections::import(imports)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(output);
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Function = section.code {
|
||||
let functions = section.get_function_section_reader()?;
|
||||
output.ctx.func_ty_indicies = translate_sections::function(functions)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(output);
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Table = section.code {
|
||||
let tables = section.get_table_section_reader()?;
|
||||
translate_sections::table(tables)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(output);
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Memory = section.code {
|
||||
let memories = section.get_memory_section_reader()?;
|
||||
let mem = translate_sections::memory(memories)?;
|
||||
|
||||
assert!(
|
||||
mem.len() <= 1,
|
||||
"Multiple memory sections not yet unimplemented"
|
||||
);
|
||||
|
||||
if !mem.is_empty() {
|
||||
let mem = mem[0];
|
||||
assert_eq!(Some(mem.limits.initial), mem.limits.maximum);
|
||||
output.memory = Some(mem);
|
||||
}
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(output);
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Global = section.code {
|
||||
let globals = section.get_global_section_reader()?;
|
||||
translate_sections::global(globals)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(output);
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Export = section.code {
|
||||
let exports = section.get_export_section_reader()?;
|
||||
translate_sections::export(exports)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(output);
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Start = section.code {
|
||||
let start = section.get_start_section_content()?;
|
||||
translate_sections::start(start)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(output);
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Element = section.code {
|
||||
let elements = section.get_element_section_reader()?;
|
||||
translate_sections::element(elements)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(output);
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Code = section.code {
|
||||
let code = section.get_code_section_reader()?;
|
||||
output.translated_code_section = Some(translate_sections::code(code, &output.ctx)?);
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(output);
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Data = section.code {
|
||||
let data = section.get_data_section_reader()?;
|
||||
translate_sections::data(data)?;
|
||||
}
|
||||
|
||||
assert!(reader.eof());
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
1066
crates/lightbeam/src/tests.rs
Normal file
1066
crates/lightbeam/src/tests.rs
Normal file
File diff suppressed because it is too large
Load Diff
130
crates/lightbeam/src/translate_sections.rs
Normal file
130
crates/lightbeam/src/translate_sections.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use crate::backend::{CodeGenSession, TranslatedCodeSection};
|
||||
use crate::error::Error;
|
||||
use crate::function_body;
|
||||
use crate::module::SimpleContext;
|
||||
use cranelift_codegen::{binemit, ir};
|
||||
use wasmparser::{
|
||||
CodeSectionReader, DataSectionReader, ElementSectionReader, ExportSectionReader, FuncType,
|
||||
FunctionSectionReader, GlobalSectionReader, ImportSectionReader, MemorySectionReader,
|
||||
MemoryType, TableSectionReader, TableType, TypeSectionReader,
|
||||
};
|
||||
|
||||
/// Parses the Type section of the wasm module.
|
||||
pub fn type_(types_reader: TypeSectionReader) -> Result<Vec<FuncType>, Error> {
|
||||
types_reader
|
||||
.into_iter()
|
||||
.map(|r| r.map_err(Into::into))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Parses the Import section of the wasm module.
|
||||
pub fn import(imports: ImportSectionReader) -> Result<(), Error> {
|
||||
for entry in imports {
|
||||
entry?; // TODO
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Function section of the wasm module.
|
||||
pub fn function(functions: FunctionSectionReader) -> Result<Vec<u32>, Error> {
|
||||
functions
|
||||
.into_iter()
|
||||
.map(|r| r.map_err(Into::into))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Parses the Table section of the wasm module.
|
||||
pub fn table(tables: TableSectionReader) -> Result<Vec<TableType>, Error> {
|
||||
tables.into_iter().map(|r| r.map_err(Into::into)).collect()
|
||||
}
|
||||
|
||||
/// Parses the Memory section of the wasm module.
|
||||
pub fn memory(memories: MemorySectionReader) -> Result<Vec<MemoryType>, Error> {
|
||||
memories
|
||||
.into_iter()
|
||||
.map(|r| r.map_err(Into::into))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Parses the Global section of the wasm module.
|
||||
pub fn global(globals: GlobalSectionReader) -> Result<(), Error> {
|
||||
for entry in globals {
|
||||
entry?; // TODO
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Export section of the wasm module.
|
||||
pub fn export(exports: ExportSectionReader) -> Result<(), Error> {
|
||||
for entry in exports {
|
||||
entry?; // TODO
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Start section of the wasm module.
|
||||
pub fn start(_index: u32) -> Result<(), Error> {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Element section of the wasm module.
|
||||
pub fn element(elements: ElementSectionReader) -> Result<(), Error> {
|
||||
for entry in elements {
|
||||
entry?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct UnimplementedRelocSink;
|
||||
|
||||
impl binemit::RelocSink for UnimplementedRelocSink {
|
||||
fn reloc_ebb(&mut self, _: binemit::CodeOffset, _: binemit::Reloc, _: binemit::CodeOffset) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn reloc_external(
|
||||
&mut self,
|
||||
_: binemit::CodeOffset,
|
||||
_: binemit::Reloc,
|
||||
_: &ir::ExternalName,
|
||||
_: binemit::Addend,
|
||||
) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn reloc_constant(&mut self, _: binemit::CodeOffset, _: binemit::Reloc, _: ir::ConstantOffset) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn reloc_jt(&mut self, _: binemit::CodeOffset, _: binemit::Reloc, _: ir::JumpTable) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the Code section of the wasm module.
|
||||
pub fn code(
|
||||
code: CodeSectionReader,
|
||||
translation_ctx: &SimpleContext,
|
||||
) -> Result<TranslatedCodeSection, Error> {
|
||||
let func_count = code.get_count();
|
||||
let mut session = CodeGenSession::new(func_count, translation_ctx);
|
||||
|
||||
for (idx, body) in code.into_iter().enumerate() {
|
||||
let body = body?;
|
||||
let mut relocs = UnimplementedRelocSink;
|
||||
|
||||
function_body::translate_wasm(&mut session, &mut relocs, idx as u32, &body)?;
|
||||
}
|
||||
|
||||
Ok(session.into_translated_code_section()?)
|
||||
}
|
||||
|
||||
/// Parses the Data section of the wasm module.
|
||||
pub fn data(data: DataSectionReader) -> Result<(), Error> {
|
||||
for entry in data {
|
||||
entry?; // TODO
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user