Signature checking for call_indirect, integrate with wasmtime
This commit is contained in:
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "wasmparser.rs"]
|
||||
path = wasmparser.rs
|
||||
url = git@github.com:yurydelendik/wasmparser.rs
|
||||
@@ -18,6 +18,7 @@ itertools = "0.8"
|
||||
capstone = "0.5.0"
|
||||
failure = "0.1.3"
|
||||
failure_derive = "0.1.3"
|
||||
cranelift-codegen = "0.28"
|
||||
wabt = "0.7"
|
||||
lazy_static = "1.2"
|
||||
quickcheck = "0.7"
|
||||
|
||||
173
src/backend.rs
173
src/backend.rs
@@ -4,12 +4,13 @@
|
||||
// small maximum size and so we can consider iterating over them to be essentially constant-time.
|
||||
use arrayvec::ArrayVec;
|
||||
|
||||
use self::registers::*;
|
||||
use dynasmrt::x64::Assembler;
|
||||
use dynasmrt::{AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi, ExecutableBuffer};
|
||||
use error::Error;
|
||||
use std::{iter, mem};
|
||||
|
||||
use module::{RuntimeFunc, VmCtx};
|
||||
use module::{ModuleContext, RuntimeFunc};
|
||||
|
||||
/// Size of a pointer on the target in bytes.
|
||||
const WORD_SIZE: u32 = 8;
|
||||
@@ -27,23 +28,25 @@ impl GPRs {
|
||||
}
|
||||
}
|
||||
|
||||
const RAX: u8 = 0;
|
||||
const RCX: u8 = 1;
|
||||
const RDX: u8 = 2;
|
||||
const RBX: u8 = 3;
|
||||
const RSP: u8 = 4;
|
||||
const RBP: u8 = 5;
|
||||
const RSI: u8 = 6;
|
||||
const RDI: u8 = 7;
|
||||
const R8: u8 = 8;
|
||||
const R9: u8 = 9;
|
||||
const R10: u8 = 10;
|
||||
const R11: u8 = 11;
|
||||
const R12: u8 = 12;
|
||||
const R13: u8 = 13;
|
||||
const R14: u8 = 14;
|
||||
const R15: u8 = 15;
|
||||
const NUM_GPRS: u8 = 16;
|
||||
pub mod registers {
|
||||
pub const RAX: u8 = 0;
|
||||
pub const RCX: u8 = 1;
|
||||
pub const RDX: u8 = 2;
|
||||
pub const RBX: u8 = 3;
|
||||
pub const RSP: u8 = 4;
|
||||
pub const RBP: u8 = 5;
|
||||
pub const RSI: u8 = 6;
|
||||
pub const RDI: u8 = 7;
|
||||
pub const R8: u8 = 8;
|
||||
pub const R9: u8 = 9;
|
||||
pub const R10: u8 = 10;
|
||||
pub const R11: u8 = 11;
|
||||
pub const R12: u8 = 12;
|
||||
pub const R13: u8 = 13;
|
||||
pub const R14: u8 = 14;
|
||||
pub const R15: u8 = 15;
|
||||
pub const NUM_GPRS: u8 = 16;
|
||||
}
|
||||
|
||||
extern "sysv64" fn println(len: u64, args: *const u8) {
|
||||
println!("{}", unsafe {
|
||||
@@ -205,14 +208,14 @@ pub struct FunctionEnd {
|
||||
should_generate_epilogue: bool,
|
||||
}
|
||||
|
||||
pub struct CodeGenSession {
|
||||
pub struct CodeGenSession<'a, M> {
|
||||
assembler: Assembler,
|
||||
pub module_context: &'a M,
|
||||
func_starts: Vec<(Option<AssemblyOffset>, DynamicLabel)>,
|
||||
has_memory: bool,
|
||||
}
|
||||
|
||||
impl CodeGenSession {
|
||||
pub fn new(func_count: u32, has_memory: bool) -> Self {
|
||||
impl<'a, M> CodeGenSession<'a, M> {
|
||||
pub fn new(func_count: u32, module_context: &'a M) -> Self {
|
||||
let mut assembler = Assembler::new().unwrap();
|
||||
let func_starts = iter::repeat_with(|| (None, assembler.new_dynamic_label()))
|
||||
.take(func_count as usize)
|
||||
@@ -221,11 +224,11 @@ impl CodeGenSession {
|
||||
CodeGenSession {
|
||||
assembler,
|
||||
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];
|
||||
|
||||
@@ -238,9 +241,9 @@ impl CodeGenSession {
|
||||
Context {
|
||||
asm: &mut self.assembler,
|
||||
func_starts: &self.func_starts,
|
||||
has_memory: self.has_memory,
|
||||
trap_label: None,
|
||||
block_state: Default::default(),
|
||||
module_context: self.module_context,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,6 +295,24 @@ impl TranslatedCodeSection {
|
||||
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) {
|
||||
::disassemble::disassemble(&*self.exec_buf).unwrap();
|
||||
}
|
||||
@@ -494,10 +515,10 @@ impl Locals {
|
||||
pub struct BlockState {
|
||||
stack: Stack,
|
||||
// TODO: `BitVec`
|
||||
stack_map: Vec<bool>,
|
||||
depth: StackDepth,
|
||||
return_register: Option<GPR>,
|
||||
regs: Registers,
|
||||
pub stack_map: Vec<bool>,
|
||||
pub depth: StackDepth,
|
||||
pub return_register: Option<GPR>,
|
||||
pub regs: Registers,
|
||||
/// This is the _current_ locals, since we can shuffle them about during function calls.
|
||||
pub locals: Locals,
|
||||
/// 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,
|
||||
}
|
||||
|
||||
pub struct Context<'a> {
|
||||
pub struct Context<'a, M> {
|
||||
asm: &'a mut Assembler,
|
||||
module_context: &'a M,
|
||||
func_starts: &'a Vec<(Option<AssemblyOffset>, DynamicLabel)>,
|
||||
/// Each push and pop on the value stack increments or decrements this value by 1 respectively.
|
||||
pub block_state: BlockState,
|
||||
trap_label: Option<Label>,
|
||||
has_memory: bool,
|
||||
}
|
||||
|
||||
/// 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
|
||||
} else {
|
||||
dynasm!(self.asm
|
||||
@@ -972,39 +997,34 @@ macro_rules! commutative_binop_i64 {
|
||||
macro_rules! load {
|
||||
($name:ident, $reg_ty:ident, $instruction_name:expr) => {
|
||||
pub fn $name(&mut self, offset: u32) -> Result<(), Error> {
|
||||
fn load_to_reg(
|
||||
ctx: &mut Context,
|
||||
fn load_to_reg<_M: ModuleContext>(
|
||||
ctx: &mut Context<_M>,
|
||||
dst: GPR,
|
||||
vmctx: 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 {
|
||||
Ok(imm) => {
|
||||
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) => {
|
||||
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);
|
||||
|
||||
if !self.has_memory {
|
||||
return Err(Error::Input(
|
||||
concat!(
|
||||
"Unexpected ",
|
||||
$instruction_name,
|
||||
", this module has no memory section"
|
||||
).into()
|
||||
));
|
||||
}
|
||||
|
||||
let base = self.pop();
|
||||
let vmctx_idx = self.block_state.locals.vmctx_index();
|
||||
|
||||
@@ -1057,41 +1077,34 @@ macro_rules! load {
|
||||
macro_rules! store {
|
||||
($name:ident, $reg_ty:ident, $size:ident, $instruction_name:expr) => {
|
||||
pub fn $name(&mut self, offset: u32) -> Result<(), Error> {
|
||||
fn store_from_reg(
|
||||
ctx: &mut Context,
|
||||
fn store_from_reg<_M: ModuleContext>(
|
||||
ctx: &mut Context<_M>,
|
||||
src: GPR,
|
||||
vmctx: 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 {
|
||||
Ok(imm) => {
|
||||
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) => {
|
||||
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);
|
||||
|
||||
// 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 base = self.pop();
|
||||
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.
|
||||
pub fn create_label(&mut self) -> 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) {
|
||||
let index = self.adjusted_local_idx(local_idx);
|
||||
self.push(Value::Local(index));
|
||||
@@ -2097,6 +2112,7 @@ impl Context<'_> {
|
||||
match self.block_state.locals.register_locals[i] {
|
||||
ArgLoc::Register(reg) => {
|
||||
if ARGS_IN_GPRS.contains(®) {
|
||||
// We do `- 1` because of `vmctx`
|
||||
let offset =
|
||||
((self.block_state.locals.num_local_stack_slots - 1 - i as u32)
|
||||
* WORD_SIZE) as _;
|
||||
@@ -2289,10 +2305,10 @@ impl Context<'_> {
|
||||
|
||||
// TODO: Consider generating a single trap function and jumping to that instead.
|
||||
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
|
||||
; 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), [
|
||||
Rq(temp0) +
|
||||
Rq(callee) +
|
||||
@@ -2379,11 +2395,19 @@ impl Context<'_> {
|
||||
|
||||
// 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()`
|
||||
//
|
||||
// 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;
|
||||
// Align stack slots to the nearest even number. This is required
|
||||
// by x86-64 ABI.
|
||||
let aligned_stack_slots = (stack_slots + 1) & !1;
|
||||
let frame_size: i32 = aligned_stack_slots as i32 * WORD_SIZE as i32;
|
||||
// TODO: The x86_64 ABI requires that rsp be aligned to 16 bytes. Originally
|
||||
// we aligned stack slots to the nearest even number to support this
|
||||
// but this actually doesn't help, since `push` and `pop` change rsp
|
||||
// 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
|
||||
.iter()
|
||||
@@ -2395,8 +2419,9 @@ impl Context<'_> {
|
||||
self.block_state.locals.num_local_stack_slots = stack_slots;
|
||||
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);
|
||||
let should_generate_epilogue = frame_size > 0;
|
||||
let should_generate_epilogue = stack_slots > 1;
|
||||
if should_generate_epilogue {
|
||||
dynasm!(self.asm
|
||||
; push rbp
|
||||
@@ -2416,10 +2441,6 @@ impl Context<'_> {
|
||||
// We don't need to clean up the stack - RSP is restored and
|
||||
// the calling function has its own register stack and will
|
||||
// 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 {
|
||||
dynasm!(self.asm
|
||||
; mov rsp, rbp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use backend::*;
|
||||
use error::Error;
|
||||
use module::{quickhash, FuncTyStore};
|
||||
use module::{quickhash, ModuleContext, Signature};
|
||||
use wasmparser::{FunctionBody, Operator, Type};
|
||||
|
||||
// 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
|
||||
/// at the end of the block
|
||||
block_state: BlockState,
|
||||
ty: Type,
|
||||
arity: u32,
|
||||
}
|
||||
|
||||
fn arity(ty: Type) -> u32 {
|
||||
@@ -89,17 +89,17 @@ fn arity(ty: Type) -> u32 {
|
||||
}
|
||||
|
||||
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 {
|
||||
kind,
|
||||
block_state,
|
||||
ty,
|
||||
arity,
|
||||
unreachable: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arity(&self) -> u32 {
|
||||
arity(self.ty)
|
||||
self.arity
|
||||
}
|
||||
|
||||
/// Marks this control frame as reached stack-polymorphic state.
|
||||
@@ -108,14 +108,13 @@ impl ControlFrame {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn translate(
|
||||
session: &mut CodeGenSession,
|
||||
translation_ctx: &FuncTyStore,
|
||||
pub fn translate<M: ModuleContext>(
|
||||
session: &mut CodeGenSession<M>,
|
||||
func_idx: u32,
|
||||
body: &FunctionBody,
|
||||
) -> Result<(), Error> {
|
||||
fn break_from_control_frame_with_id(
|
||||
ctx: &mut Context,
|
||||
fn break_from_control_frame_with_id<_M: ModuleContext>(
|
||||
ctx: &mut Context<_M>,
|
||||
control_frames: &mut Vec<ControlFrame>,
|
||||
idx: usize,
|
||||
) {
|
||||
@@ -149,15 +148,9 @@ pub fn translate(
|
||||
|
||||
let locals = body.get_locals_reader()?;
|
||||
|
||||
let func_type = translation_ctx.func_type(func_idx);
|
||||
let arg_count = func_type.params.len() as u32;
|
||||
let return_ty = if func_type.returns.len() == 1 {
|
||||
func_type.returns[0]
|
||||
} else if func_type.returns.len() == 0 {
|
||||
Type::EmptyBlockType
|
||||
} else {
|
||||
panic!("We don't support multiple returns yet");
|
||||
};
|
||||
let func_type = session.module_context.func_type(func_idx);
|
||||
let arg_count = func_type.params().len() as u32;
|
||||
let return_arity = func_type.returns().len() as u32;
|
||||
|
||||
let mut num_locals = 0;
|
||||
for local in locals {
|
||||
@@ -168,7 +161,8 @@ pub fn translate(
|
||||
let ctx = &mut session.new_context(func_idx);
|
||||
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 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
|
||||
// result type as the function itself. Branching to it is equivalent to returning from the function.
|
||||
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(
|
||||
ControlFrameKind::Block {
|
||||
end_label: epilogue_label,
|
||||
},
|
||||
function_block_state,
|
||||
return_ty,
|
||||
Default::default(),
|
||||
return_arity,
|
||||
));
|
||||
|
||||
let mut operators = itertools::put_back(operators.into_iter());
|
||||
@@ -192,25 +189,48 @@ pub fn translate(
|
||||
// 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.
|
||||
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 {
|
||||
let op = if let Some(op) = operators.next() {
|
||||
op?
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
|
||||
match op {
|
||||
If { .. } | Block { .. } | Loop { .. } => depth += 1,
|
||||
End => {
|
||||
if depth == 0 {
|
||||
operators.put_back(Ok(op));
|
||||
break;
|
||||
} else {
|
||||
depth -= 1;
|
||||
}
|
||||
}
|
||||
Else => {
|
||||
if depth == 0 {
|
||||
operators.put_back(Ok(op));
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let op = if let Some(op) = operators.next() {
|
||||
op?
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
|
||||
match op {
|
||||
Operator::End | Operator::Else => {}
|
||||
_ => {
|
||||
if control_frames
|
||||
.last()
|
||||
.expect("Control stack never empty")
|
||||
.unreachable
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match op {
|
||||
Operator::Unreachable => {
|
||||
control_frames
|
||||
@@ -225,7 +245,7 @@ pub fn translate(
|
||||
control_frames.push(ControlFrame::new(
|
||||
ControlFrameKind::Block { end_label: label },
|
||||
state,
|
||||
ty,
|
||||
arity(ty),
|
||||
));
|
||||
}
|
||||
Operator::Return => {
|
||||
@@ -267,7 +287,7 @@ pub fn translate(
|
||||
control_frames.push(ControlFrame::new(
|
||||
ControlFrameKind::IfTrue { end_label, if_not },
|
||||
state,
|
||||
ty,
|
||||
arity(ty),
|
||||
));
|
||||
}
|
||||
Operator::Loop { ty } => {
|
||||
@@ -279,18 +299,21 @@ pub fn translate(
|
||||
control_frames.push(ControlFrame::new(
|
||||
ControlFrameKind::Loop { header },
|
||||
state,
|
||||
ty,
|
||||
arity(ty),
|
||||
));
|
||||
}
|
||||
Operator::Else => {
|
||||
match control_frames.pop() {
|
||||
Some(ControlFrame {
|
||||
kind: ControlFrameKind::IfTrue { if_not, end_label },
|
||||
ty,
|
||||
arity,
|
||||
block_state,
|
||||
..
|
||||
unreachable,
|
||||
}) => {
|
||||
ctx.return_from_block(arity(ty));
|
||||
if !unreachable {
|
||||
ctx.return_from_block(arity);
|
||||
}
|
||||
|
||||
ctx.reset_block(block_state.clone());
|
||||
|
||||
// Finalize `then` block by jumping to the `end_label`.
|
||||
@@ -308,7 +331,7 @@ pub fn translate(
|
||||
let mut frame = ControlFrame::new(
|
||||
ControlFrameKind::IfFalse { end_label },
|
||||
block_state,
|
||||
ty,
|
||||
arity,
|
||||
);
|
||||
control_frames.push(frame);
|
||||
}
|
||||
@@ -325,6 +348,7 @@ pub fn translate(
|
||||
// a kind of state machine.
|
||||
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 unreachable = control_frame.unreachable;
|
||||
|
||||
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");
|
||||
|
||||
labels.extend(control_frame.kind.end_labels());
|
||||
unreachable = unreachable || control_frame.unreachable;
|
||||
|
||||
end = control_frame.block_state.end_locals.take().or(end);
|
||||
}
|
||||
@@ -355,7 +380,7 @@ pub fn translate(
|
||||
let arity = control_frame.arity();
|
||||
|
||||
// Don't bother generating this code if we're in unreachable code
|
||||
if !control_frame.unreachable {
|
||||
if !unreachable {
|
||||
ctx.return_from_block(arity);
|
||||
|
||||
// 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::I64Store { memarg } => ctx.i64_store(memarg.offset)?,
|
||||
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.
|
||||
|
||||
ctx.call_direct(
|
||||
function_index,
|
||||
callee_ty.params.len() as u32,
|
||||
callee_ty.returns.len() as u32,
|
||||
callee_ty.params().len() as u32,
|
||||
callee_ty.returns().len() as u32,
|
||||
);
|
||||
}
|
||||
Operator::CallIndirect { index, table_index } => {
|
||||
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.
|
||||
|
||||
ctx.call_indirect(
|
||||
quickhash(callee_ty) as u32,
|
||||
callee_ty.params.len() as u32,
|
||||
callee_ty.returns.len() as u32,
|
||||
callee_ty.params().len() as u32,
|
||||
callee_ty.returns().len() as u32,
|
||||
);
|
||||
}
|
||||
Operator::Nop => {}
|
||||
|
||||
17
src/lib.rs
17
src/lib.rs
@@ -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)]
|
||||
|
||||
extern crate test;
|
||||
@@ -6,7 +13,7 @@ extern crate test;
|
||||
extern crate arrayvec;
|
||||
extern crate capstone;
|
||||
extern crate failure;
|
||||
extern crate wasmparser;
|
||||
pub extern crate wasmparser;
|
||||
#[macro_use]
|
||||
extern crate failure_derive;
|
||||
#[macro_use]
|
||||
@@ -20,6 +27,8 @@ extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate quickcheck;
|
||||
extern crate wabt;
|
||||
// Just so we can implement `Signature` for `cranelift_codegen::ir::Signature`
|
||||
extern crate cranelift_codegen;
|
||||
|
||||
mod backend;
|
||||
mod disassemble;
|
||||
@@ -31,4 +40,6 @@ mod translate_sections;
|
||||
#[cfg(test)]
|
||||
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};
|
||||
|
||||
215
src/module.rs
215
src/module.rs
@@ -1,6 +1,11 @@
|
||||
use backend::TranslatedCodeSection;
|
||||
use cranelift_codegen::{
|
||||
ir::{self, AbiParam, Signature as CraneliftSignature},
|
||||
isa,
|
||||
};
|
||||
use error::Error;
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
hash::{Hash, Hasher},
|
||||
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)]
|
||||
pub struct TranslatedModule {
|
||||
translated_code_section: Option<TranslatedCodeSection>,
|
||||
types: FuncTyStore,
|
||||
types: SimpleContext,
|
||||
// TODO: Should we wrap this in a `Mutex` so that calling functions from multiple
|
||||
// threads doesn't cause data races?
|
||||
table: Option<(TableType, Vec<u32>)>,
|
||||
@@ -111,9 +116,7 @@ pub fn quickhash<H: Hash>(h: H) -> u64 {
|
||||
|
||||
impl TranslatedModule {
|
||||
pub fn instantiate(mut self) -> ExecutableModule {
|
||||
use std::alloc::{self, Layout};
|
||||
|
||||
let slice = {
|
||||
let table = {
|
||||
let code_section = self
|
||||
.translated_code_section
|
||||
.as_ref()
|
||||
@@ -123,7 +126,7 @@ impl TranslatedModule {
|
||||
self.table
|
||||
.as_mut()
|
||||
.map(|&mut (_, ref mut idxs)| {
|
||||
let mut initial = idxs
|
||||
let initial = idxs
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let start = code_section.func_start(*i as _);
|
||||
@@ -135,12 +138,7 @@ impl TranslatedModule {
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
initial.shrink_to_fit();
|
||||
let out = BoxSlice {
|
||||
ptr: initial.as_mut_ptr(),
|
||||
len: initial.len(),
|
||||
};
|
||||
mem::forget(initial);
|
||||
let out = BoxSlice::from(initial.into_boxed_slice());
|
||||
out
|
||||
})
|
||||
.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 (layout, _mem_offset) = Layout::new::<VmCtx>()
|
||||
.extend(Layout::array::<u8>(mem_size * WASM_PAGE_SIZE).unwrap())
|
||||
.unwrap();
|
||||
let mem: BoxSlice<_> = vec![0u8; mem_size * WASM_PAGE_SIZE]
|
||||
.into_boxed_slice()
|
||||
.into();
|
||||
|
||||
let ctx = if mem_size > 0 || slice.len > 0 {
|
||||
let ptr = unsafe { alloc::alloc_zeroed(layout) } as *mut VmCtx;
|
||||
|
||||
if ptr.is_null() {
|
||||
alloc::handle_alloc_error(layout);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*ptr = VmCtx {
|
||||
table: slice,
|
||||
mem_size,
|
||||
}
|
||||
}
|
||||
|
||||
Some(Allocation { ptr, layout })
|
||||
let ctx = if mem.len > 0 || table.len > 0 {
|
||||
Some(Box::new(VmCtx { table, mem }))
|
||||
} else {
|
||||
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)]
|
||||
pub enum ExecutionError {
|
||||
FuncIndexOutOfBounds,
|
||||
@@ -213,7 +180,7 @@ pub enum ExecutionError {
|
||||
|
||||
pub struct ExecutableModule {
|
||||
module: TranslatedModule,
|
||||
context: Option<Allocation<VmCtx>>,
|
||||
context: Option<Box<VmCtx>>,
|
||||
}
|
||||
|
||||
impl ExecutableModule {
|
||||
@@ -247,7 +214,7 @@ impl ExecutableModule {
|
||||
Args::into_func(start_buf),
|
||||
self.context
|
||||
.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()),
|
||||
)
|
||||
})
|
||||
@@ -265,6 +232,9 @@ pub struct RuntimeFunc {
|
||||
func_start: FuncRef,
|
||||
}
|
||||
|
||||
unsafe impl Send for RuntimeFunc {}
|
||||
unsafe impl Sync for RuntimeFunc {}
|
||||
|
||||
impl RuntimeFunc {
|
||||
pub fn offset_of_sig_hash() -> usize {
|
||||
offset_of!(Self, sig_hash)
|
||||
@@ -280,55 +250,142 @@ struct BoxSlice<T> {
|
||||
ptr: *mut T,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct VmCtx {
|
||||
table: BoxSlice<RuntimeFunc>,
|
||||
mem_size: usize,
|
||||
}
|
||||
|
||||
unsafe impl Send for VmCtx {}
|
||||
unsafe impl Sync for VmCtx {}
|
||||
|
||||
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)
|
||||
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) };
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
pub struct FuncTyStore {
|
||||
pub struct SimpleContext {
|
||||
types: Vec<FuncType>,
|
||||
func_ty_indicies: Vec<u32>,
|
||||
}
|
||||
|
||||
const WASM_PAGE_SIZE: usize = 65_536;
|
||||
|
||||
impl FuncTyStore {
|
||||
pub fn func_type_index(&self, func_idx: u32) -> u32 {
|
||||
pub trait Signature {
|
||||
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]
|
||||
}
|
||||
|
||||
pub fn signature(&self, index: u32) -> &FuncType {
|
||||
fn signature(&self, index: u32) -> &Self::Signature {
|
||||
&self.types[index as usize]
|
||||
}
|
||||
|
||||
pub fn func_type(&self, func_idx: u32) -> &FuncType {
|
||||
// TODO: This assumes that there are no imported functions.
|
||||
self.signature(self.func_type_index(func_idx))
|
||||
fn offset_of_memory_ptr(&self) -> u8 {
|
||||
VmCtx::offset_of_memory_ptr()
|
||||
}
|
||||
|
||||
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
|
||||
@@ -471,11 +528,7 @@ pub fn translate_only(data: &[u8]) -> Result<TranslatedModule, Error> {
|
||||
|
||||
if let SectionCode::Code = section.code {
|
||||
let code = section.get_code_section_reader()?;
|
||||
output.translated_code_section = Some(translate_sections::code(
|
||||
code,
|
||||
&output.types,
|
||||
output.memory.is_some(),
|
||||
)?);
|
||||
output.translated_code_section = Some(translate_sections::code(code, &output.types)?);
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
|
||||
147
src/tests.rs
147
src/tests.rs
@@ -87,10 +87,12 @@ mod op32 {
|
||||
use std::sync::Once;
|
||||
|
||||
lazy_static! {
|
||||
static ref AS_PARAM: ExecutableModule = translate_wat(
|
||||
concat!("(module (func (param i32) (result i32)
|
||||
(i32.",stringify!($name)," (get_local 0))))"),
|
||||
);
|
||||
static ref AS_PARAM: ExecutableModule = translate_wat(concat!(
|
||||
"(module (func (param i32) (result i32)
|
||||
(i32.",
|
||||
stringify!($name),
|
||||
" (get_local 0))))"
|
||||
),);
|
||||
}
|
||||
|
||||
quickcheck! {
|
||||
@@ -99,7 +101,7 @@ mod op32 {
|
||||
}
|
||||
|
||||
fn lit(a: u32) -> bool {
|
||||
let translated = translate_wat(&format!(concat!("
|
||||
let translated = translate_wat(&format!(concat!("
|
||||
(module (func (result i32)
|
||||
(i32.",stringify!($name)," (i32.const {val}))))
|
||||
"), val = a));
|
||||
@@ -110,7 +112,7 @@ mod op32 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unop_test!(clz, u32::leading_zeros);
|
||||
@@ -208,10 +210,14 @@ mod op64 {
|
||||
use std::sync::Once;
|
||||
|
||||
lazy_static! {
|
||||
static ref AS_PARAM: ExecutableModule = translate_wat(
|
||||
concat!("(module (func (param i64) (result ",stringify!($out_ty),")
|
||||
(i64.",stringify!($name)," (get_local 0))))"),
|
||||
);
|
||||
static ref AS_PARAM: ExecutableModule = translate_wat(concat!(
|
||||
"(module (func (param i64) (result ",
|
||||
stringify!($out_ty),
|
||||
")
|
||||
(i64.",
|
||||
stringify!($name),
|
||||
" (get_local 0))))"
|
||||
),);
|
||||
}
|
||||
|
||||
quickcheck! {
|
||||
@@ -220,7 +226,7 @@ mod op64 {
|
||||
}
|
||||
|
||||
fn lit(a: u64) -> bool {
|
||||
let translated = translate_wat(&format!(concat!("
|
||||
let translated = translate_wat(&format!(concat!("
|
||||
(module (func (result ",stringify!($out_ty),")
|
||||
(i64.",stringify!($name)," (i64.const {val}))))
|
||||
"), val = a));
|
||||
@@ -231,7 +237,7 @@ mod op64 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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))));
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use backend::{CodeGenSession, TranslatedCodeSection};
|
||||
use error::Error;
|
||||
use function_body;
|
||||
use module::FuncTyStore;
|
||||
use module::SimpleContext;
|
||||
#[allow(unused_imports)] // for now
|
||||
use wasmparser::{
|
||||
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.
|
||||
pub fn code(
|
||||
code: CodeSectionReader,
|
||||
translation_ctx: &FuncTyStore,
|
||||
has_memory: bool,
|
||||
translation_ctx: &SimpleContext,
|
||||
) -> Result<TranslatedCodeSection, Error> {
|
||||
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() {
|
||||
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()?)
|
||||
}
|
||||
|
||||
1
wasmparser.rs
Submodule
1
wasmparser.rs
Submodule
Submodule wasmparser.rs added at 4002d32c25
Reference in New Issue
Block a user