Signature checking for call_indirect, integrate with wasmtime

This commit is contained in:
Jef
2019-02-08 11:46:25 +01:00
parent 2fad984a0d
commit 7e5c3c567f
9 changed files with 472 additions and 235 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "wasmparser.rs"]
path = wasmparser.rs
url = git@github.com:yurydelendik/wasmparser.rs

View File

@@ -18,6 +18,7 @@ itertools = "0.8"
capstone = "0.5.0" capstone = "0.5.0"
failure = "0.1.3" failure = "0.1.3"
failure_derive = "0.1.3" failure_derive = "0.1.3"
cranelift-codegen = "0.28"
wabt = "0.7" wabt = "0.7"
lazy_static = "1.2" lazy_static = "1.2"
quickcheck = "0.7" quickcheck = "0.7"

View File

@@ -4,12 +4,13 @@
// small maximum size and so we can consider iterating over them to be essentially constant-time. // small maximum size and so we can consider iterating over them to be essentially constant-time.
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use self::registers::*;
use dynasmrt::x64::Assembler; use dynasmrt::x64::Assembler;
use dynasmrt::{AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi, ExecutableBuffer}; use dynasmrt::{AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi, ExecutableBuffer};
use error::Error; use error::Error;
use std::{iter, mem}; use std::{iter, mem};
use module::{RuntimeFunc, VmCtx}; use module::{ModuleContext, RuntimeFunc};
/// Size of a pointer on the target in bytes. /// Size of a pointer on the target in bytes.
const WORD_SIZE: u32 = 8; const WORD_SIZE: u32 = 8;
@@ -27,23 +28,25 @@ impl GPRs {
} }
} }
const RAX: u8 = 0; pub mod registers {
const RCX: u8 = 1; pub const RAX: u8 = 0;
const RDX: u8 = 2; pub const RCX: u8 = 1;
const RBX: u8 = 3; pub const RDX: u8 = 2;
const RSP: u8 = 4; pub const RBX: u8 = 3;
const RBP: u8 = 5; pub const RSP: u8 = 4;
const RSI: u8 = 6; pub const RBP: u8 = 5;
const RDI: u8 = 7; pub const RSI: u8 = 6;
const R8: u8 = 8; pub const RDI: u8 = 7;
const R9: u8 = 9; pub const R8: u8 = 8;
const R10: u8 = 10; pub const R9: u8 = 9;
const R11: u8 = 11; pub const R10: u8 = 10;
const R12: u8 = 12; pub const R11: u8 = 11;
const R13: u8 = 13; pub const R12: u8 = 12;
const R14: u8 = 14; pub const R13: u8 = 13;
const R15: u8 = 15; pub const R14: u8 = 14;
const NUM_GPRS: u8 = 16; pub const R15: u8 = 15;
pub const NUM_GPRS: u8 = 16;
}
extern "sysv64" fn println(len: u64, args: *const u8) { extern "sysv64" fn println(len: u64, args: *const u8) {
println!("{}", unsafe { println!("{}", unsafe {
@@ -205,14 +208,14 @@ pub struct FunctionEnd {
should_generate_epilogue: bool, should_generate_epilogue: bool,
} }
pub struct CodeGenSession { pub struct CodeGenSession<'a, M> {
assembler: Assembler, assembler: Assembler,
pub module_context: &'a M,
func_starts: Vec<(Option<AssemblyOffset>, DynamicLabel)>, func_starts: Vec<(Option<AssemblyOffset>, DynamicLabel)>,
has_memory: bool,
} }
impl CodeGenSession { impl<'a, M> CodeGenSession<'a, M> {
pub fn new(func_count: u32, has_memory: bool) -> Self { pub fn new(func_count: u32, module_context: &'a M) -> Self {
let mut assembler = Assembler::new().unwrap(); let mut assembler = Assembler::new().unwrap();
let func_starts = iter::repeat_with(|| (None, assembler.new_dynamic_label())) let func_starts = iter::repeat_with(|| (None, assembler.new_dynamic_label()))
.take(func_count as usize) .take(func_count as usize)
@@ -221,11 +224,11 @@ impl CodeGenSession {
CodeGenSession { CodeGenSession {
assembler, assembler,
func_starts, func_starts,
has_memory, module_context,
} }
} }
pub fn new_context(&mut self, func_idx: u32) -> Context { pub fn new_context(&mut self, func_idx: u32) -> Context<'_, M> {
{ {
let func_start = &mut self.func_starts[func_idx as usize]; let func_start = &mut self.func_starts[func_idx as usize];
@@ -238,9 +241,9 @@ impl CodeGenSession {
Context { Context {
asm: &mut self.assembler, asm: &mut self.assembler,
func_starts: &self.func_starts, func_starts: &self.func_starts,
has_memory: self.has_memory,
trap_label: None, trap_label: None,
block_state: Default::default(), block_state: Default::default(),
module_context: self.module_context,
} }
} }
@@ -292,6 +295,24 @@ impl TranslatedCodeSection {
self.exec_buf.ptr(offset) self.exec_buf.ptr(offset)
} }
pub fn func_range(&self, idx: usize) -> std::ops::Range<usize> {
let end = self
.func_starts
.get(idx + 1)
.map(|i| i.0)
.unwrap_or(self.exec_buf.len());
self.func_starts[idx].0..end
}
pub fn funcs<'a>(&'a self) -> impl Iterator<Item = std::ops::Range<usize>> + 'a {
(0..self.func_starts.len()).map(move |i| self.func_range(i))
}
pub fn buffer(&self) -> &[u8] {
&*self.exec_buf
}
pub fn disassemble(&self) { pub fn disassemble(&self) {
::disassemble::disassemble(&*self.exec_buf).unwrap(); ::disassemble::disassemble(&*self.exec_buf).unwrap();
} }
@@ -494,10 +515,10 @@ impl Locals {
pub struct BlockState { pub struct BlockState {
stack: Stack, stack: Stack,
// TODO: `BitVec` // TODO: `BitVec`
stack_map: Vec<bool>, pub stack_map: Vec<bool>,
depth: StackDepth, pub depth: StackDepth,
return_register: Option<GPR>, pub return_register: Option<GPR>,
regs: Registers, pub regs: Registers,
/// This is the _current_ locals, since we can shuffle them about during function calls. /// This is the _current_ locals, since we can shuffle them about during function calls.
pub locals: Locals, pub locals: Locals,
/// In non-linear control flow (ifs and loops) we have to set the locals to the state that /// In non-linear control flow (ifs and loops) we have to set the locals to the state that
@@ -521,13 +542,13 @@ pub enum MemoryAccessMode {
Unchecked, Unchecked,
} }
pub struct Context<'a> { pub struct Context<'a, M> {
asm: &'a mut Assembler, asm: &'a mut Assembler,
module_context: &'a M,
func_starts: &'a Vec<(Option<AssemblyOffset>, DynamicLabel)>, func_starts: &'a Vec<(Option<AssemblyOffset>, DynamicLabel)>,
/// Each push and pop on the value stack increments or decrements this value by 1 respectively. /// Each push and pop on the value stack increments or decrements this value by 1 respectively.
pub block_state: BlockState, pub block_state: BlockState,
trap_label: Option<Label>, trap_label: Option<Label>,
has_memory: bool,
} }
/// Label in code. /// Label in code.
@@ -630,7 +651,11 @@ macro_rules! shift {
} }
} }
_ => { _ => {
if self.block_state.regs.is_free(RCX) { if self.block_state.regs.is_free(RCX) &&
!self.block_state.locals.register_locals.contains(
&ArgLoc::Register(RCX)
)
{
None None
} else { } else {
dynasm!(self.asm dynasm!(self.asm
@@ -972,39 +997,34 @@ macro_rules! commutative_binop_i64 {
macro_rules! load { macro_rules! load {
($name:ident, $reg_ty:ident, $instruction_name:expr) => { ($name:ident, $reg_ty:ident, $instruction_name:expr) => {
pub fn $name(&mut self, offset: u32) -> Result<(), Error> { pub fn $name(&mut self, offset: u32) -> Result<(), Error> {
fn load_to_reg( fn load_to_reg<_M: ModuleContext>(
ctx: &mut Context, ctx: &mut Context<_M>,
dst: GPR, dst: GPR,
vmctx: GPR, vmctx: GPR,
(offset, runtime_offset): (i32, Result<i32, GPR>) (offset, runtime_offset): (i32, Result<i32, GPR>)
) { ) {
let vmctx_mem_offset = VmCtx::offset_of_memory() as i32; let vmctx_mem_ptr_offset = ctx.module_context.offset_of_memory_ptr() as i32;
let mem_ptr_reg = ctx.block_state.regs.take_scratch_gpr();
dynasm!(ctx.asm
; mov Rq(mem_ptr_reg), [Rq(vmctx) + vmctx_mem_ptr_offset]
);
match runtime_offset { match runtime_offset {
Ok(imm) => { Ok(imm) => {
dynasm!(ctx.asm dynasm!(ctx.asm
; mov $reg_ty(dst), [Rq(vmctx) + offset + imm + vmctx_mem_offset] ; mov $reg_ty(dst), [Rq(mem_ptr_reg) + offset + imm]
); );
} }
Err(offset_reg) => { Err(offset_reg) => {
dynasm!(ctx.asm dynasm!(ctx.asm
; mov $reg_ty(dst), [Rq(vmctx) + Rq(offset_reg) + offset + vmctx_mem_offset] ; mov $reg_ty(dst), [Rq(mem_ptr_reg) + Rq(offset_reg) + offset]
); );
} }
} }
ctx.block_state.regs.release_scratch_gpr(mem_ptr_reg);
} }
assert!(offset <= i32::max_value() as u32); assert!(offset <= i32::max_value() as u32);
if !self.has_memory {
return Err(Error::Input(
concat!(
"Unexpected ",
$instruction_name,
", this module has no memory section"
).into()
));
}
let base = self.pop(); let base = self.pop();
let vmctx_idx = self.block_state.locals.vmctx_index(); let vmctx_idx = self.block_state.locals.vmctx_index();
@@ -1057,41 +1077,34 @@ macro_rules! load {
macro_rules! store { macro_rules! store {
($name:ident, $reg_ty:ident, $size:ident, $instruction_name:expr) => { ($name:ident, $reg_ty:ident, $size:ident, $instruction_name:expr) => {
pub fn $name(&mut self, offset: u32) -> Result<(), Error> { pub fn $name(&mut self, offset: u32) -> Result<(), Error> {
fn store_from_reg( fn store_from_reg<_M: ModuleContext>(
ctx: &mut Context, ctx: &mut Context<_M>,
src: GPR, src: GPR,
vmctx: GPR, vmctx: GPR,
(offset, runtime_offset): (i32, Result<i32, GPR>) (offset, runtime_offset): (i32, Result<i32, GPR>)
) { ) {
let vmctx_mem_offset = VmCtx::offset_of_memory() as i32; let vmctx_mem_ptr_offset = ctx.module_context.offset_of_memory_ptr() as i32;
let mem_ptr_reg = ctx.block_state.regs.take_scratch_gpr();
dynasm!(ctx.asm
; mov Rq(mem_ptr_reg), [Rq(vmctx) + vmctx_mem_ptr_offset]
);
match runtime_offset { match runtime_offset {
Ok(imm) => { Ok(imm) => {
dynasm!(ctx.asm dynasm!(ctx.asm
; mov [Rq(vmctx) + offset + imm + vmctx_mem_offset], $reg_ty(src) ; mov [Rq(mem_ptr_reg) + offset + imm], $reg_ty(src)
); );
} }
Err(offset_reg) => { Err(offset_reg) => {
dynasm!(ctx.asm dynasm!(ctx.asm
; mov [Rq(vmctx) + Rq(offset_reg) + offset + vmctx_mem_offset], $reg_ty(src) ; mov [Rq(mem_ptr_reg) + Rq(offset_reg) + offset], $reg_ty(src)
); );
} }
} }
ctx.block_state.regs.release_scratch_gpr(mem_ptr_reg);
} }
assert!(offset <= i32::max_value() as u32); assert!(offset <= i32::max_value() as u32);
// TODO: Is this necessary or is this ensured by the validation step?
// In other places we assume the wasm is validated.
if !self.has_memory {
return Err(Error::Input(
concat!(
"Unexpected ",
$instruction_name,
", this module has no memory section"
).into()
));
}
let src = self.pop(); let src = self.pop();
let base = self.pop(); let base = self.pop();
let vmctx_idx = self.block_state.locals.vmctx_index(); let vmctx_idx = self.block_state.locals.vmctx_index();
@@ -1173,7 +1186,7 @@ impl TryInto<i32> for i64 {
} }
} }
impl Context<'_> { impl<M: ModuleContext> Context<'_, M> {
/// Create a new undefined label. /// Create a new undefined label.
pub fn create_label(&mut self) -> Label { pub fn create_label(&mut self) -> Label {
Label(self.asm.new_dynamic_label()) Label(self.asm.new_dynamic_label())
@@ -1952,6 +1965,8 @@ impl Context<'_> {
} }
} }
// TODO: This is wildly unsound, we don't actually check if the
// local was written first. Would be fixed by Microwasm.
pub fn get_local(&mut self, local_idx: u32) { pub fn get_local(&mut self, local_idx: u32) {
let index = self.adjusted_local_idx(local_idx); let index = self.adjusted_local_idx(local_idx);
self.push(Value::Local(index)); self.push(Value::Local(index));
@@ -2097,6 +2112,7 @@ impl Context<'_> {
match self.block_state.locals.register_locals[i] { match self.block_state.locals.register_locals[i] {
ArgLoc::Register(reg) => { ArgLoc::Register(reg) => {
if ARGS_IN_GPRS.contains(&reg) { if ARGS_IN_GPRS.contains(&reg) {
// We do `- 1` because of `vmctx`
let offset = let offset =
((self.block_state.locals.num_local_stack_slots - 1 - i as u32) ((self.block_state.locals.num_local_stack_slots - 1 - i as u32)
* WORD_SIZE) as _; * WORD_SIZE) as _;
@@ -2289,10 +2305,10 @@ impl Context<'_> {
// TODO: Consider generating a single trap function and jumping to that instead. // TODO: Consider generating a single trap function and jumping to that instead.
dynasm!(self.asm dynasm!(self.asm
; cmp Rq(callee), [Rq(vmctx_reg) + VmCtx::offset_of_funcs_len() as i32] ; cmp Rq(callee), [Rq(vmctx_reg) + self.module_context.offset_of_funcs_len() as i32]
; jae =>fail ; jae =>fail
; imul Rq(callee), Rq(callee), mem::size_of::<RuntimeFunc>() as i32 ; imul Rq(callee), Rq(callee), mem::size_of::<RuntimeFunc>() as i32
; mov Rq(temp0), [Rq(vmctx_reg) + VmCtx::offset_of_funcs_ptr() as i32] ; mov Rq(temp0), [Rq(vmctx_reg) + self.module_context.offset_of_funcs_ptr() as i32]
; mov Rd(temp1), [ ; mov Rd(temp1), [
Rq(temp0) + Rq(temp0) +
Rq(callee) + Rq(callee) +
@@ -2379,11 +2395,19 @@ impl Context<'_> {
// We need space to store the register arguments if we need to call a function // We need space to store the register arguments if we need to call a function
// and overwrite these registers so we add `reg_args.len()` // and overwrite these registers so we add `reg_args.len()`
//
// We do `- 1` because we don't need to store `vmctx` (TODO: I believe we actually
// do need to store `vmctx` when calling host functions, although we might be able
// to have the hidden argument/hidden return `vmctx` stuff encoded in our trampoline,
// which would allow the `vmctx` to be changed while we're in a host function)
let stack_slots = locals + reg_args.len() as u32 + reg_locals.len() as u32; let stack_slots = locals + reg_args.len() as u32 + reg_locals.len() as u32;
// Align stack slots to the nearest even number. This is required // TODO: The x86_64 ABI requires that rsp be aligned to 16 bytes. Originally
// by x86-64 ABI. // we aligned stack slots to the nearest even number to support this
let aligned_stack_slots = (stack_slots + 1) & !1; // but this actually doesn't help, since `push` and `pop` change rsp
let frame_size: i32 = aligned_stack_slots as i32 * WORD_SIZE as i32; // by 8 bytes at a time. We need a better solution if we want to support
// calling functions that use SIMD (for our use-case this is mostly just
// host functions).
let frame_size: i32 = stack_slots as i32 * WORD_SIZE as i32;
self.block_state.locals.register_locals = reg_args self.block_state.locals.register_locals = reg_args
.iter() .iter()
@@ -2395,8 +2419,9 @@ impl Context<'_> {
self.block_state.locals.num_local_stack_slots = stack_slots; self.block_state.locals.num_local_stack_slots = stack_slots;
self.block_state.return_register = Some(RAX); self.block_state.return_register = Some(RAX);
// TODO: This isn't enough to ensure that we're aligned when calling functions
// self.block_state.depth.reserve(aligned_stack_slots - locals); // self.block_state.depth.reserve(aligned_stack_slots - locals);
let should_generate_epilogue = frame_size > 0; let should_generate_epilogue = stack_slots > 1;
if should_generate_epilogue { if should_generate_epilogue {
dynasm!(self.asm dynasm!(self.asm
; push rbp ; push rbp
@@ -2416,10 +2441,6 @@ impl Context<'_> {
// We don't need to clean up the stack - RSP is restored and // We don't need to clean up the stack - RSP is restored and
// the calling function has its own register stack and will // the calling function has its own register stack and will
// stomp on the registers from our stack if necessary. // stomp on the registers from our stack if necessary.
// TODO: Because every function has a hidden argument this is currently
// always called. We should lazily initialise the stack to avoid
// this but it introduces complexity around branch points. We
// should latch on to the `end_locals` code for this.
if func.should_generate_epilogue { if func.should_generate_epilogue {
dynasm!(self.asm dynasm!(self.asm
; mov rsp, rbp ; mov rsp, rbp

View File

@@ -1,6 +1,6 @@
use backend::*; use backend::*;
use error::Error; use error::Error;
use module::{quickhash, FuncTyStore}; use module::{quickhash, ModuleContext, Signature};
use wasmparser::{FunctionBody, Operator, Type}; use wasmparser::{FunctionBody, Operator, Type};
// TODO: Use own declared `Type` enum. // TODO: Use own declared `Type` enum.
@@ -77,7 +77,7 @@ struct ControlFrame {
/// State specific to the block (free temp registers, stack etc) which should be replaced /// State specific to the block (free temp registers, stack etc) which should be replaced
/// at the end of the block /// at the end of the block
block_state: BlockState, block_state: BlockState,
ty: Type, arity: u32,
} }
fn arity(ty: Type) -> u32 { fn arity(ty: Type) -> u32 {
@@ -89,17 +89,17 @@ fn arity(ty: Type) -> u32 {
} }
impl ControlFrame { impl ControlFrame {
pub fn new(kind: ControlFrameKind, block_state: BlockState, ty: Type) -> ControlFrame { pub fn new(kind: ControlFrameKind, block_state: BlockState, arity: u32) -> ControlFrame {
ControlFrame { ControlFrame {
kind, kind,
block_state, block_state,
ty, arity,
unreachable: false, unreachable: false,
} }
} }
pub fn arity(&self) -> u32 { pub fn arity(&self) -> u32 {
arity(self.ty) self.arity
} }
/// Marks this control frame as reached stack-polymorphic state. /// Marks this control frame as reached stack-polymorphic state.
@@ -108,14 +108,13 @@ impl ControlFrame {
} }
} }
pub fn translate( pub fn translate<M: ModuleContext>(
session: &mut CodeGenSession, session: &mut CodeGenSession<M>,
translation_ctx: &FuncTyStore,
func_idx: u32, func_idx: u32,
body: &FunctionBody, body: &FunctionBody,
) -> Result<(), Error> { ) -> Result<(), Error> {
fn break_from_control_frame_with_id( fn break_from_control_frame_with_id<_M: ModuleContext>(
ctx: &mut Context, ctx: &mut Context<_M>,
control_frames: &mut Vec<ControlFrame>, control_frames: &mut Vec<ControlFrame>,
idx: usize, idx: usize,
) { ) {
@@ -149,15 +148,9 @@ pub fn translate(
let locals = body.get_locals_reader()?; let locals = body.get_locals_reader()?;
let func_type = translation_ctx.func_type(func_idx); let func_type = session.module_context.func_type(func_idx);
let arg_count = func_type.params.len() as u32; let arg_count = func_type.params().len() as u32;
let return_ty = if func_type.returns.len() == 1 { let return_arity = func_type.returns().len() as u32;
func_type.returns[0]
} else if func_type.returns.len() == 0 {
Type::EmptyBlockType
} else {
panic!("We don't support multiple returns yet");
};
let mut num_locals = 0; let mut num_locals = 0;
for local in locals { for local in locals {
@@ -168,7 +161,8 @@ pub fn translate(
let ctx = &mut session.new_context(func_idx); let ctx = &mut session.new_context(func_idx);
let operators = body.get_operators_reader()?; let operators = body.get_operators_reader()?;
// We must add 1 here to supply `vmctx` // TODO: Do we need this `function_block_state`? If we transformed to use an arbitrary
// CFG all this code would become way simpler.
let func = ctx.start_function(arg_count, num_locals); let func = ctx.start_function(arg_count, num_locals);
let mut control_frames = Vec::new(); let mut control_frames = Vec::new();
@@ -176,13 +170,16 @@ pub fn translate(
// Upon entering the function implicit frame for function body is pushed. It has the same // Upon entering the function implicit frame for function body is pushed. It has the same
// result type as the function itself. Branching to it is equivalent to returning from the function. // result type as the function itself. Branching to it is equivalent to returning from the function.
let epilogue_label = ctx.create_label(); let epilogue_label = ctx.create_label();
let function_block_state = ctx.start_block(); // TODO: I want to ideally not have the concept of "returning" at all and model everything as a CFG,
// with "returning" being modelled as "calling the end of the function". That means that passing
// arguments in argument registers and returning values in return registers are modelled
// identically.
control_frames.push(ControlFrame::new( control_frames.push(ControlFrame::new(
ControlFrameKind::Block { ControlFrameKind::Block {
end_label: epilogue_label, end_label: epilogue_label,
}, },
function_block_state, Default::default(),
return_ty, return_arity,
)); ));
let mut operators = itertools::put_back(operators.into_iter()); let mut operators = itertools::put_back(operators.into_iter());
@@ -191,6 +188,15 @@ pub fn translate(
// can coelesce multiple `end`s and optimise break-at-end-of-block into noop. // can coelesce multiple `end`s and optimise break-at-end-of-block into noop.
// TODO: Does coelescing multiple `end`s matter since at worst this really only elides a single move at // TODO: Does coelescing multiple `end`s matter since at worst this really only elides a single move at
// the end of a function, and this is probably a no-op anyway due to register renaming. // the end of a function, and this is probably a no-op anyway due to register renaming.
loop {
if control_frames
.last()
.map(|c| c.unreachable)
.unwrap_or(false)
{
use self::Operator::{Block, Else, End, If, Loop};
let mut depth = 0;
loop { loop {
let op = if let Some(op) = operators.next() { let op = if let Some(op) = operators.next() {
op? op?
@@ -199,18 +205,32 @@ pub fn translate(
}; };
match op { match op {
Operator::End | Operator::Else => {} If { .. } | Block { .. } | Loop { .. } => depth += 1,
_ => { End => {
if control_frames if depth == 0 {
.last() operators.put_back(Ok(op));
.expect("Control stack never empty") break;
.unreachable } else {
{ depth -= 1;
continue; }
}
Else => {
if depth == 0 {
operators.put_back(Ok(op));
break;
}
}
_ => {}
} }
} }
} }
let op = if let Some(op) = operators.next() {
op?
} else {
break;
};
match op { match op {
Operator::Unreachable => { Operator::Unreachable => {
control_frames control_frames
@@ -225,7 +245,7 @@ pub fn translate(
control_frames.push(ControlFrame::new( control_frames.push(ControlFrame::new(
ControlFrameKind::Block { end_label: label }, ControlFrameKind::Block { end_label: label },
state, state,
ty, arity(ty),
)); ));
} }
Operator::Return => { Operator::Return => {
@@ -267,7 +287,7 @@ pub fn translate(
control_frames.push(ControlFrame::new( control_frames.push(ControlFrame::new(
ControlFrameKind::IfTrue { end_label, if_not }, ControlFrameKind::IfTrue { end_label, if_not },
state, state,
ty, arity(ty),
)); ));
} }
Operator::Loop { ty } => { Operator::Loop { ty } => {
@@ -279,18 +299,21 @@ pub fn translate(
control_frames.push(ControlFrame::new( control_frames.push(ControlFrame::new(
ControlFrameKind::Loop { header }, ControlFrameKind::Loop { header },
state, state,
ty, arity(ty),
)); ));
} }
Operator::Else => { Operator::Else => {
match control_frames.pop() { match control_frames.pop() {
Some(ControlFrame { Some(ControlFrame {
kind: ControlFrameKind::IfTrue { if_not, end_label }, kind: ControlFrameKind::IfTrue { if_not, end_label },
ty, arity,
block_state, block_state,
.. unreachable,
}) => { }) => {
ctx.return_from_block(arity(ty)); if !unreachable {
ctx.return_from_block(arity);
}
ctx.reset_block(block_state.clone()); ctx.reset_block(block_state.clone());
// Finalize `then` block by jumping to the `end_label`. // Finalize `then` block by jumping to the `end_label`.
@@ -308,7 +331,7 @@ pub fn translate(
let mut frame = ControlFrame::new( let mut frame = ControlFrame::new(
ControlFrameKind::IfFalse { end_label }, ControlFrameKind::IfFalse { end_label },
block_state, block_state,
ty, arity,
); );
control_frames.push(frame); control_frames.push(frame);
} }
@@ -325,6 +348,7 @@ pub fn translate(
// a kind of state machine. // a kind of state machine.
let mut control_frame = control_frames.pop().expect("control stack is never empty"); let mut control_frame = control_frames.pop().expect("control stack is never empty");
let mut labels = control_frame.kind.end_labels().collect::<Vec<_>>(); let mut labels = control_frame.kind.end_labels().collect::<Vec<_>>();
let mut unreachable = control_frame.unreachable;
let mut end = control_frame.block_state.end_locals.take(); let mut end = control_frame.block_state.end_locals.take();
@@ -342,6 +366,7 @@ pub fn translate(
control_frames.pop().expect("control stack is never empty"); control_frames.pop().expect("control stack is never empty");
labels.extend(control_frame.kind.end_labels()); labels.extend(control_frame.kind.end_labels());
unreachable = unreachable || control_frame.unreachable;
end = control_frame.block_state.end_locals.take().or(end); end = control_frame.block_state.end_locals.take().or(end);
} }
@@ -355,7 +380,7 @@ pub fn translate(
let arity = control_frame.arity(); let arity = control_frame.arity();
// Don't bother generating this code if we're in unreachable code // Don't bother generating this code if we're in unreachable code
if !control_frame.unreachable { if !unreachable {
ctx.return_from_block(arity); ctx.return_from_block(arity);
// If there are no remaining frames we've hit the end of the function - we don't need to // If there are no remaining frames we've hit the end of the function - we don't need to
@@ -436,27 +461,27 @@ pub fn translate(
Operator::I32Store { memarg } => ctx.i32_store(memarg.offset)?, Operator::I32Store { memarg } => ctx.i32_store(memarg.offset)?,
Operator::I64Store { memarg } => ctx.i64_store(memarg.offset)?, Operator::I64Store { memarg } => ctx.i64_store(memarg.offset)?,
Operator::Call { function_index } => { Operator::Call { function_index } => {
let callee_ty = translation_ctx.func_type(function_index); let callee_ty = session.module_context.func_type(function_index);
// TODO: this implementation assumes that this function is locally defined. // TODO: this implementation assumes that this function is locally defined.
ctx.call_direct( ctx.call_direct(
function_index, function_index,
callee_ty.params.len() as u32, callee_ty.params().len() as u32,
callee_ty.returns.len() as u32, callee_ty.returns().len() as u32,
); );
} }
Operator::CallIndirect { index, table_index } => { Operator::CallIndirect { index, table_index } => {
assert_eq!(table_index, 0); assert_eq!(table_index, 0);
let callee_ty = translation_ctx.signature(index); let callee_ty = session.module_context.signature(index);
// TODO: this implementation assumes that this function is locally defined. // TODO: this implementation assumes that this function is locally defined.
ctx.call_indirect( ctx.call_indirect(
quickhash(callee_ty) as u32, quickhash(callee_ty) as u32,
callee_ty.params.len() as u32, callee_ty.params().len() as u32,
callee_ty.returns.len() as u32, callee_ty.returns().len() as u32,
); );
} }
Operator::Nop => {} Operator::Nop => {}

View File

@@ -1,4 +1,11 @@
#![feature(plugin, test, const_slice_len, never_type, alloc_layout_extra)] #![feature(
plugin,
test,
const_slice_len,
never_type,
alloc_layout_extra,
try_from
)]
#![plugin(dynasm)] #![plugin(dynasm)]
extern crate test; extern crate test;
@@ -6,7 +13,7 @@ extern crate test;
extern crate arrayvec; extern crate arrayvec;
extern crate capstone; extern crate capstone;
extern crate failure; extern crate failure;
extern crate wasmparser; pub extern crate wasmparser;
#[macro_use] #[macro_use]
extern crate failure_derive; extern crate failure_derive;
#[macro_use] #[macro_use]
@@ -20,6 +27,8 @@ extern crate lazy_static;
#[macro_use] #[macro_use]
extern crate quickcheck; extern crate quickcheck;
extern crate wabt; extern crate wabt;
// Just so we can implement `Signature` for `cranelift_codegen::ir::Signature`
extern crate cranelift_codegen;
mod backend; mod backend;
mod disassemble; mod disassemble;
@@ -31,4 +40,6 @@ mod translate_sections;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub use module::{translate, ExecutableModule, TranslatedModule}; pub use backend::CodeGenSession;
pub use function_body::translate as translate_function;
pub use module::{translate, ExecutableModule, ModuleContext, Signature, TranslatedModule};

View File

@@ -1,6 +1,11 @@
use backend::TranslatedCodeSection; use backend::TranslatedCodeSection;
use cranelift_codegen::{
ir::{self, AbiParam, Signature as CraneliftSignature},
isa,
};
use error::Error; use error::Error;
use std::{ use std::{
convert::TryInto,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
mem, mem,
}; };
@@ -96,7 +101,7 @@ impl_function_args!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
#[derive(Default)] #[derive(Default)]
pub struct TranslatedModule { pub struct TranslatedModule {
translated_code_section: Option<TranslatedCodeSection>, translated_code_section: Option<TranslatedCodeSection>,
types: FuncTyStore, types: SimpleContext,
// TODO: Should we wrap this in a `Mutex` so that calling functions from multiple // TODO: Should we wrap this in a `Mutex` so that calling functions from multiple
// threads doesn't cause data races? // threads doesn't cause data races?
table: Option<(TableType, Vec<u32>)>, table: Option<(TableType, Vec<u32>)>,
@@ -111,9 +116,7 @@ pub fn quickhash<H: Hash>(h: H) -> u64 {
impl TranslatedModule { impl TranslatedModule {
pub fn instantiate(mut self) -> ExecutableModule { pub fn instantiate(mut self) -> ExecutableModule {
use std::alloc::{self, Layout}; let table = {
let slice = {
let code_section = self let code_section = self
.translated_code_section .translated_code_section
.as_ref() .as_ref()
@@ -123,7 +126,7 @@ impl TranslatedModule {
self.table self.table
.as_mut() .as_mut()
.map(|&mut (_, ref mut idxs)| { .map(|&mut (_, ref mut idxs)| {
let mut initial = idxs let initial = idxs
.iter() .iter()
.map(|i| { .map(|i| {
let start = code_section.func_start(*i as _); let start = code_section.func_start(*i as _);
@@ -135,12 +138,7 @@ impl TranslatedModule {
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
initial.shrink_to_fit(); let out = BoxSlice::from(initial.into_boxed_slice());
let out = BoxSlice {
ptr: initial.as_mut_ptr(),
len: initial.len(),
};
mem::forget(initial);
out out
}) })
.unwrap_or(BoxSlice { .unwrap_or(BoxSlice {
@@ -150,25 +148,12 @@ impl TranslatedModule {
}; };
let mem_size = self.memory.map(|m| m.limits.initial).unwrap_or(0) as usize; let mem_size = self.memory.map(|m| m.limits.initial).unwrap_or(0) as usize;
let (layout, _mem_offset) = Layout::new::<VmCtx>() let mem: BoxSlice<_> = vec![0u8; mem_size * WASM_PAGE_SIZE]
.extend(Layout::array::<u8>(mem_size * WASM_PAGE_SIZE).unwrap()) .into_boxed_slice()
.unwrap(); .into();
let ctx = if mem_size > 0 || slice.len > 0 { let ctx = if mem.len > 0 || table.len > 0 {
let ptr = unsafe { alloc::alloc_zeroed(layout) } as *mut VmCtx; Some(Box::new(VmCtx { table, mem }))
if ptr.is_null() {
alloc::handle_alloc_error(layout);
}
unsafe {
*ptr = VmCtx {
table: slice,
mem_size,
}
}
Some(Allocation { ptr, layout })
} else { } else {
None None
}; };
@@ -187,24 +172,6 @@ impl TranslatedModule {
} }
} }
struct Allocation<T> {
ptr: *mut T,
layout: std::alloc::Layout,
}
unsafe impl<T> Send for Allocation<T> where T: Send {}
unsafe impl<T> Sync for Allocation<T> where T: Sync {}
impl<T> Drop for Allocation<T> {
fn drop(&mut self) {
if mem::needs_drop::<T>() {
unsafe { std::ptr::drop_in_place::<T>(self.ptr) };
}
unsafe { std::alloc::dealloc(self.ptr as _, self.layout) };
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ExecutionError { pub enum ExecutionError {
FuncIndexOutOfBounds, FuncIndexOutOfBounds,
@@ -213,7 +180,7 @@ pub enum ExecutionError {
pub struct ExecutableModule { pub struct ExecutableModule {
module: TranslatedModule, module: TranslatedModule,
context: Option<Allocation<VmCtx>>, context: Option<Box<VmCtx>>,
} }
impl ExecutableModule { impl ExecutableModule {
@@ -247,7 +214,7 @@ impl ExecutableModule {
Args::into_func(start_buf), Args::into_func(start_buf),
self.context self.context
.as_ref() .as_ref()
.map(|ctx| ctx.ptr as *const VmCtx as *const u8) .map(|ctx| (&**ctx) as *const VmCtx as *const u8)
.unwrap_or(std::ptr::null()), .unwrap_or(std::ptr::null()),
) )
}) })
@@ -265,6 +232,9 @@ pub struct RuntimeFunc {
func_start: FuncRef, func_start: FuncRef,
} }
unsafe impl Send for RuntimeFunc {}
unsafe impl Sync for RuntimeFunc {}
impl RuntimeFunc { impl RuntimeFunc {
pub fn offset_of_sig_hash() -> usize { pub fn offset_of_sig_hash() -> usize {
offset_of!(Self, sig_hash) offset_of!(Self, sig_hash)
@@ -280,55 +250,142 @@ struct BoxSlice<T> {
ptr: *mut T, ptr: *mut T,
} }
#[repr(C)] impl<T> From<Box<[T]>> for BoxSlice<T> {
pub struct VmCtx { fn from(mut other: Box<[T]>) -> Self {
table: BoxSlice<RuntimeFunc>, let out = BoxSlice {
mem_size: usize, len: other.len(),
} ptr: other.as_mut_ptr(),
};
unsafe impl Send for VmCtx {} mem::forget(other);
unsafe impl Sync for VmCtx {} out
impl VmCtx {
pub fn offset_of_memory() -> usize {
mem::size_of::<Self>()
}
pub fn offset_of_funcs_ptr() -> usize {
offset_of!(Self, table.ptr)
}
pub fn offset_of_funcs_len() -> usize {
offset_of!(Self, table.len)
} }
} }
unsafe impl<T: Send> Send for BoxSlice<T> {}
unsafe impl<T: Sync> Sync for BoxSlice<T> {}
impl<T> Drop for BoxSlice<T> { impl<T> Drop for BoxSlice<T> {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { Vec::from_raw_parts(self.ptr, self.len, self.len) }; unsafe { Vec::from_raw_parts(self.ptr, self.len, self.len) };
} }
} }
pub struct VmCtx {
table: BoxSlice<RuntimeFunc>,
mem: BoxSlice<u8>,
}
impl VmCtx {
pub fn offset_of_memory_ptr() -> u8 {
offset_of!(Self, mem.ptr)
.try_into()
.expect("Offset exceeded size of u8")
}
pub fn offset_of_memory_len() -> u8 {
offset_of!(Self, mem.len)
.try_into()
.expect("Offset exceeded size of u8")
}
pub fn offset_of_funcs_ptr() -> u8 {
offset_of!(Self, table.ptr)
.try_into()
.expect("Offset exceeded size of u8")
}
pub fn offset_of_funcs_len() -> u8 {
offset_of!(Self, table.len)
.try_into()
.expect("Offset exceeded size of u8")
}
}
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct FuncTyStore { pub struct SimpleContext {
types: Vec<FuncType>, types: Vec<FuncType>,
func_ty_indicies: Vec<u32>, func_ty_indicies: Vec<u32>,
} }
const WASM_PAGE_SIZE: usize = 65_536; const WASM_PAGE_SIZE: usize = 65_536;
impl FuncTyStore { pub trait Signature {
pub fn func_type_index(&self, func_idx: u32) -> u32 { type Type;
fn params(&self) -> &[Self::Type];
fn returns(&self) -> &[Self::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 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 + Hash;
fn func_type_index(&self, func_idx: u32) -> u32;
fn signature(&self, index: u32) -> &Self::Signature;
fn offset_of_memory_ptr(&self) -> u8;
fn offset_of_memory_len(&self) -> u8;
fn offset_of_funcs_ptr(&self) -> u8;
fn offset_of_funcs_len(&self) -> u8;
fn func_type(&self, func_idx: u32) -> &Self::Signature {
// TODO: This assumes that there are no imported functions.
self.signature(self.func_type_index(func_idx))
}
}
impl ModuleContext for SimpleContext {
type Signature = FuncType;
fn func_type_index(&self, func_idx: u32) -> u32 {
self.func_ty_indicies[func_idx as usize] self.func_ty_indicies[func_idx as usize]
} }
pub fn signature(&self, index: u32) -> &FuncType { fn signature(&self, index: u32) -> &Self::Signature {
&self.types[index as usize] &self.types[index as usize]
} }
pub fn func_type(&self, func_idx: u32) -> &FuncType { fn offset_of_memory_ptr(&self) -> u8 {
// TODO: This assumes that there are no imported functions. VmCtx::offset_of_memory_ptr()
self.signature(self.func_type_index(func_idx)) }
fn offset_of_memory_len(&self) -> u8 {
VmCtx::offset_of_memory_len()
}
fn offset_of_funcs_ptr(&self) -> u8 {
VmCtx::offset_of_funcs_ptr()
}
fn offset_of_funcs_len(&self) -> u8 {
VmCtx::offset_of_funcs_len()
} }
// TODO: type of a global // TODO: type of a global
@@ -471,11 +528,7 @@ pub fn translate_only(data: &[u8]) -> Result<TranslatedModule, Error> {
if let SectionCode::Code = section.code { if let SectionCode::Code = section.code {
let code = section.get_code_section_reader()?; let code = section.get_code_section_reader()?;
output.translated_code_section = Some(translate_sections::code( output.translated_code_section = Some(translate_sections::code(code, &output.types)?);
code,
&output.types,
output.memory.is_some(),
)?);
reader.skip_custom_sections()?; reader.skip_custom_sections()?;
if reader.eof() { if reader.eof() {

View File

@@ -87,10 +87,12 @@ mod op32 {
use std::sync::Once; use std::sync::Once;
lazy_static! { lazy_static! {
static ref AS_PARAM: ExecutableModule = translate_wat( static ref AS_PARAM: ExecutableModule = translate_wat(concat!(
concat!("(module (func (param i32) (result i32) "(module (func (param i32) (result i32)
(i32.",stringify!($name)," (get_local 0))))"), (i32.",
); stringify!($name),
" (get_local 0))))"
),);
} }
quickcheck! { quickcheck! {
@@ -110,7 +112,7 @@ mod op32 {
} }
} }
} }
} };
} }
unop_test!(clz, u32::leading_zeros); unop_test!(clz, u32::leading_zeros);
@@ -208,10 +210,14 @@ mod op64 {
use std::sync::Once; use std::sync::Once;
lazy_static! { lazy_static! {
static ref AS_PARAM: ExecutableModule = translate_wat( static ref AS_PARAM: ExecutableModule = translate_wat(concat!(
concat!("(module (func (param i64) (result ",stringify!($out_ty),") "(module (func (param i64) (result ",
(i64.",stringify!($name)," (get_local 0))))"), stringify!($out_ty),
); ")
(i64.",
stringify!($name),
" (get_local 0))))"
),);
} }
quickcheck! { quickcheck! {
@@ -231,7 +237,7 @@ mod op64 {
} }
} }
} }
} };
} }
unop_test!(clz, |a: u64| a.leading_zeros() as _); unop_test!(clz, |a: u64| a.leading_zeros() as _);
@@ -1081,3 +1087,120 @@ fn bench_fibonacci_baseline(b: &mut test::Bencher) {
b.iter(|| test::black_box(fib(test::black_box(20)))); b.iter(|| test::black_box(fib(test::black_box(20))));
} }
#[test]
fn test_recursive_factorial() {
let code = r#"
(module
(func (export "fac-rec") (param i64) (result i64)
(if (result i64) (i64.eq (get_local 0) (i64.const 0))
(then (i64.const 1))
(else
(i64.mul (get_local 0) (call 0 (i64.sub (get_local 0) (i64.const 1))))
)
)
)
)
"#;
assert_eq!(translate_wat(code).execute_func::<_, u64>(0, (25u64,)).unwrap(), 7034535277573963776u64);
}
#[test]
fn test_recursive_factorial_named() {
let code = r#"
(module
(func $fac-rec-named (export "fac-rec-named") (param $n i64) (result i64)
(if (result i64) (i64.eq (get_local $n) (i64.const 0))
(then (i64.const 1))
(else
(i64.mul
(get_local $n)
(call $fac-rec-named (i64.sub (get_local $n) (i64.const 1)))
)
)
)
)
)
"#;
assert_eq!(translate_wat(code).execute_func::<_, u64>(0, (25u64,)).unwrap(), 7034535277573963776u64);
}
#[test]
fn test_iterative_factorial() {
let code = r#"
(module
(func (export "fac-iter") (param i64) (result i64)
(local i64 i64)
(set_local 1 (get_local 0))
(set_local 2 (i64.const 1))
(block
(loop
(if
(i64.eq (get_local 1) (i64.const 0))
(then (br 2))
(else
(set_local 2 (i64.mul (get_local 1) (get_local 2)))
(set_local 1 (i64.sub (get_local 1) (i64.const 1)))
)
)
(br 0)
)
)
(get_local 2)
)
)
"#;
assert_eq!(translate_wat(code).execute_func::<_, u64>(0, (25u64,)).unwrap(), 7034535277573963776u64);
}
#[test]
fn test_iterative_factorial_named() {
let code = r#"
(module
(func (export "fac-iter-named") (param $n i64) (result i64)
(local $i i64)
(local $res i64)
(set_local $i (get_local $n))
(set_local $res (i64.const 1))
(block $done
(loop $loop
(if
(i64.eq (get_local $i) (i64.const 0))
(then (br $done))
(else
(set_local $res (i64.mul (get_local $i) (get_local $res)))
(set_local $i (i64.sub (get_local $i) (i64.const 1)))
)
)
(br $loop)
)
)
(get_local $res)
)
)
"#;
assert_eq!(translate_wat(code).execute_func::<_, u64>(0, (25u64,)).unwrap(), 7034535277573963776u64);
}
#[test]
fn test_optimized_factorial() {
let code = r#"
(module
(func (export "fac-opt") (param i64) (result i64)
(local i64)
(set_local 1 (i64.const 1))
(block
(br_if 0 (i64.lt_s (get_local 0) (i64.const 2)))
(loop
(set_local 1 (i64.mul (get_local 1) (get_local 0)))
(set_local 0 (i64.add (get_local 0) (i64.const -1)))
(br_if 0 (i64.gt_s (get_local 0) (i64.const 1)))
)
)
(get_local 1)
)
)
"#;
assert_eq!(translate_wat(code).execute_func::<_, u64>(0, (25u64,)).unwrap(), 7034535277573963776u64);
}

View File

@@ -1,7 +1,7 @@
use backend::{CodeGenSession, TranslatedCodeSection}; use backend::{CodeGenSession, TranslatedCodeSection};
use error::Error; use error::Error;
use function_body; use function_body;
use module::FuncTyStore; use module::SimpleContext;
#[allow(unused_imports)] // for now #[allow(unused_imports)] // for now
use wasmparser::{ use wasmparser::{
CodeSectionReader, Data, DataSectionReader, Element, ElementSectionReader, Export, CodeSectionReader, Data, DataSectionReader, Element, ElementSectionReader, Export,
@@ -107,13 +107,12 @@ pub fn element(elements: ElementSectionReader) -> Result<Vec<u32>, Error> {
/// Parses the Code section of the wasm module. /// Parses the Code section of the wasm module.
pub fn code( pub fn code(
code: CodeSectionReader, code: CodeSectionReader,
translation_ctx: &FuncTyStore, translation_ctx: &SimpleContext,
has_memory: bool,
) -> Result<TranslatedCodeSection, Error> { ) -> Result<TranslatedCodeSection, Error> {
let func_count = code.get_count(); let func_count = code.get_count();
let mut session = CodeGenSession::new(func_count, has_memory); let mut session = CodeGenSession::new(func_count, translation_ctx);
for (idx, body) in code.into_iter().enumerate() { for (idx, body) in code.into_iter().enumerate() {
function_body::translate(&mut session, translation_ctx, idx as u32, &body?)?; function_body::translate(&mut session, idx as u32, &body?)?;
} }
Ok(session.into_translated_code_section()?) Ok(session.into_translated_code_section()?)
} }

1
wasmparser.rs Submodule

Submodule wasmparser.rs added at 4002d32c25