Fix use of locals

This commit is contained in:
Jef
2018-12-18 12:12:17 +01:00
parent 53841cdb07
commit 74ffb8560c
3 changed files with 490 additions and 113 deletions

View File

@@ -14,7 +14,7 @@ const WORD_SIZE: u32 = 8;
type GPR = u8; type GPR = u8;
#[derive(Copy, Clone)] #[derive(Debug, Copy, Clone)]
struct GPRs { struct GPRs {
bits: u16, bits: u16,
} }
@@ -70,7 +70,7 @@ impl GPRs {
} }
} }
#[derive(Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Registers { pub struct Registers {
scratch: GPRs, scratch: GPRs,
} }
@@ -94,6 +94,10 @@ impl Registers {
result result
} }
pub fn mark_used(&mut self, gpr: GPR) {
self.scratch.mark_used(gpr);
}
// TODO: Add function that takes a scratch register if possible // TODO: Add function that takes a scratch register if possible
// but otherwise gives a fresh stack location. // but otherwise gives a fresh stack location.
pub fn take_scratch_gpr(&mut self) -> GPR { pub fn take_scratch_gpr(&mut self) -> GPR {
@@ -136,7 +140,7 @@ const ARGS_IN_GPRS: &[GPR] = &[RDI, RSI, RDX, RCX, R8, R9];
// allow us to call instructions that require specific registers. // allow us to call instructions that require specific registers.
// //
// List of scratch registers taken from https://wiki.osdev.org/System_V_ABI // List of scratch registers taken from https://wiki.osdev.org/System_V_ABI
const SCRATCH_REGS: &[GPR] = &[R10, R11]; const SCRATCH_REGS: &[GPR] = &[RAX, R10, R11];
pub struct CodeGenSession { pub struct CodeGenSession {
assembler: Assembler, assembler: Assembler,
@@ -170,7 +174,6 @@ impl CodeGenSession {
asm: &mut self.assembler, asm: &mut self.assembler,
func_starts: &self.func_starts, func_starts: &self.func_starts,
block_state: Default::default(), block_state: Default::default(),
original_locals: Default::default(),
} }
} }
@@ -209,7 +212,7 @@ impl TranslatedCodeSection {
} }
// TODO: Immediates? We could implement on-the-fly const folding // TODO: Immediates? We could implement on-the-fly const folding
#[derive(Copy, Clone)] #[derive(Debug, Copy, Clone, PartialEq)]
enum Value { enum Value {
Local(u32), Local(u32),
Temp(GPR), Temp(GPR),
@@ -252,7 +255,7 @@ impl StackValue {
} }
} }
#[derive(Default, Clone)] #[derive(Debug, Default, Clone)]
struct Locals { struct Locals {
register_arguments: ArrayVec<[ValueLocation; ARGS_IN_GPRS.len()]>, register_arguments: ArrayVec<[ValueLocation; ARGS_IN_GPRS.len()]>,
num_stack_args: u32, num_stack_args: u32,
@@ -278,15 +281,18 @@ impl Locals {
} }
} }
#[derive(Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct BlockState { pub struct BlockState {
stack: Stack, stack: Stack,
pub depth: StackDepth, // TODO: `BitVec`
stack_map: Vec<bool>,
depth: StackDepth,
regs: Registers, 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.
/// We will restore this to be the same state as the `Locals` in `Context` at the end /// We will restore this to be the same state as the `Locals` in `Context` at the end
/// of a block. /// of a block.
locals: Locals, locals: Locals,
parent_locals: Locals,
} }
fn adjusted_offset(ctx: &mut Context, offset: i32) -> i32 { fn adjusted_offset(ctx: &mut Context, offset: i32) -> i32 {
@@ -300,7 +306,6 @@ pub struct Context<'a> {
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.
block_state: BlockState, block_state: BlockState,
original_locals: Locals,
} }
impl<'a> Context<'a> {} impl<'a> Context<'a> {}
@@ -336,25 +341,124 @@ impl StackDepth {
} }
} }
pub fn current_block_state(ctx: &Context) -> BlockState { fn expand_stack(ctx: &mut Context, by: u32) {
ctx.block_state.clone() use std::iter;
if by == 0 {
return;
} }
pub fn return_from_block(ctx: &mut Context) { let new_stack_size = (ctx.block_state.stack_map.len() + by as usize).next_power_of_two();
free_return_register(ctx, 1); let additional_elements = new_stack_size - ctx.block_state.stack_map.len();
pop_i32_into(ctx, ValueLocation::Reg(RAX)) ctx.block_state
.stack_map
.extend(iter::repeat(false).take(additional_elements));
dynasm!(ctx.asm
; sub rsp, additional_elements as i32
);
} }
pub fn push_block_return_value(ctx: &mut Context) { // TODO: Make this generic over `Vec` or `ArrayVec`?
ctx.block_state.stack.push(StackValue::Temp(RAX)); fn stack_slots(ctx: &mut Context, count: u32) -> Vec<i32> {
let mut out = Vec::with_capacity(count as usize);
let offset_if_taken = |(i, is_taken): (usize, bool)| {
if !is_taken {
Some(i as i32 * WORD_SIZE as i32)
} else {
None
}
};
out.extend(
ctx.block_state
.stack_map
.iter()
.cloned()
.enumerate()
.filter_map(offset_if_taken),
);
let remaining = count as usize - out.len();
if remaining > 0 {
expand_stack(ctx, remaining as u32);
out.extend(
ctx.block_state
.stack_map
.iter()
.cloned()
.enumerate()
.filter_map(offset_if_taken),
);
} }
pub fn end_block(ctx: &mut Context, parent_block_state: BlockState) { out
}
fn stack_slot(ctx: &mut Context) -> i32 {
if let Some(pos) = ctx
.block_state
.stack_map
.iter()
.position(|is_taken| !is_taken)
{
ctx.block_state.stack_map[pos] = true;
pos as i32 * WORD_SIZE as i32
} else {
expand_stack(ctx, 1);
stack_slot(ctx)
}
}
// We use `put` instead of `pop` since with `BrIf` it's possible
// that the block will continue after returning.
pub fn return_from_block(ctx: &mut Context, arity: u32, is_function_end: bool) {
// This should just be an optimisation, passing `false` should always result
// in correct code.
if !is_function_end {
restore_locals(ctx); restore_locals(ctx);
ctx.block_state = parent_block_state;
} }
if arity == 0 {
return;
}
let stack_top = *ctx.block_state.stack.last().expect("Stack is empty");
put_stack_val_into(ctx, stack_top, ValueLocation::Reg(RAX))
}
pub fn start_block(ctx: &mut Context, arity: u32) -> BlockState {
free_return_register(ctx, arity);
let current_state = ctx.block_state.clone();
ctx.block_state.parent_locals = ctx.block_state.locals.clone();
current_state
}
pub fn end_block(ctx: &mut Context, parent_block_state: BlockState, arity: u32) {
// TODO: This is currently never called, but is important for if we want to
// have a more complex stack spilling scheme.
if ctx.block_state.depth != parent_block_state.depth {
dynasm!(ctx.asm
; add rsp, (ctx.block_state.depth.0 - parent_block_state.depth.0) as i32
);
}
ctx.block_state = parent_block_state;
if arity > 0 {
push_return_value(ctx);
}
}
// TODO: We should be able to have arbitrary return registers. For blocks with multiple
// return points we can just choose the first one that we encounter and then always
// use that one. This will mean that `(block ...)` is no less efficient than `...`
// alone, and you only pay for the shuffling of registers in the case that you use
// `BrIf` or similar.
pub fn push_return_value(ctx: &mut Context) { pub fn push_return_value(ctx: &mut Context) {
ctx.block_state.regs.mark_used(RAX);
ctx.block_state.stack.push(StackValue::Temp(RAX)); ctx.block_state.stack.push(StackValue::Temp(RAX));
} }
@@ -365,7 +469,7 @@ fn restore_locals(ctx: &mut Context) {
.register_arguments .register_arguments
.clone() .clone()
.iter() .iter()
.zip(&ctx.original_locals.register_arguments.clone()) .zip(&ctx.block_state.parent_locals.register_arguments.clone())
{ {
copy_value(ctx, *src, *dst); copy_value(ctx, *src, *dst);
} }
@@ -380,6 +484,7 @@ fn push_i32(ctx: &mut Context, value: Value) {
StackValue::Temp(gpr) StackValue::Temp(gpr)
} else { } else {
ctx.block_state.depth.reserve(1); ctx.block_state.depth.reserve(1);
// TODO: Proper stack allocation scheme
dynasm!(ctx.asm dynasm!(ctx.asm
; push Rq(gpr) ; push Rq(gpr)
); );
@@ -408,8 +513,10 @@ fn pop_i32(ctx: &mut Context) -> Value {
} }
} }
fn pop_i32_into(ctx: &mut Context, dst: ValueLocation) { /// Warning: this _will_ pop the runtime stack, but will _not_ pop the compile-time
let to_move = match ctx.block_state.stack.pop().expect("Stack is empty") { /// stack. It's specifically for mid-block breaks like `Br` and `BrIf`.
fn put_stack_val_into(ctx: &mut Context, val: StackValue, dst: ValueLocation) {
let to_move = match val {
StackValue::Local(loc) => Value::Local(loc), StackValue::Local(loc) => Value::Local(loc),
StackValue::Immediate(i) => Value::Immediate(i), StackValue::Immediate(i) => Value::Immediate(i),
StackValue::Temp(reg) => Value::Temp(reg), StackValue::Temp(reg) => Value::Temp(reg),
@@ -434,12 +541,30 @@ fn pop_i32_into(ctx: &mut Context, dst: ValueLocation) {
}; };
let src = to_move.location(&ctx.block_state.locals); let src = to_move.location(&ctx.block_state.locals);
println!("{:?}, {:?}", src, dst);
copy_value(ctx, src, dst); copy_value(ctx, src, dst);
free_val(ctx, to_move); if src != dst {
free_value(ctx, to_move);
}
} }
fn free_val(ctx: &mut Context, val: Value) { pub fn drop(ctx: &mut Context) {
match ctx.block_state.stack.pop().expect("Stack is empty") {
StackValue::Pop => {
dynasm!(ctx.asm
; add rsp, WORD_SIZE as i32
);
}
StackValue::Temp(gpr) => free_value(ctx, Value::Temp(gpr)),
_ => {}
}
}
fn pop_i32_into(ctx: &mut Context, dst: ValueLocation) {
let val = ctx.block_state.stack.pop().expect("Stack is empty");
put_stack_val_into(ctx, val, dst);
}
fn free_value(ctx: &mut Context, val: Value) {
match val { match val {
Value::Temp(reg) => ctx.block_state.regs.release_scratch_gpr(reg), Value::Temp(reg) => ctx.block_state.regs.release_scratch_gpr(reg),
Value::Local(_) | Value::Immediate(_) => {} Value::Local(_) | Value::Immediate(_) => {}
@@ -546,12 +671,13 @@ macro_rules! commutative_binop {
} }
ctx.block_state.stack.push(StackValue::Temp(op1)); ctx.block_state.stack.push(StackValue::Temp(op1));
free_val(ctx, op0); free_value(ctx, op0);
} }
} }
} }
commutative_binop!(i32_add, add, i32::wrapping_add); commutative_binop!(i32_add, add, i32::wrapping_add);
commutative_binop!(i32_and, and, |a, b| a & b); commutative_binop!(i32_and, and, |a, b| a & b);
commutative_binop!(i32_or, or, |a, b| a | b); commutative_binop!(i32_or, or, |a, b| a | b);
commutative_binop!(i32_xor, xor, |a, b| a ^ b); commutative_binop!(i32_xor, xor, |a, b| a ^ b);
@@ -592,7 +718,7 @@ pub fn i32_sub(ctx: &mut Context) {
} }
ctx.block_state.stack.push(StackValue::Temp(op1)); ctx.block_state.stack.push(StackValue::Temp(op1));
free_val(ctx, op0); free_value(ctx, op0);
} }
pub fn get_local_i32(ctx: &mut Context, local_idx: u32) { pub fn get_local_i32(ctx: &mut Context, local_idx: u32) {
@@ -604,7 +730,7 @@ pub fn get_local_i32(ctx: &mut Context, local_idx: u32) {
pub fn set_local_i32(ctx: &mut Context, local_idx: u32) { pub fn set_local_i32(ctx: &mut Context, local_idx: u32) {
let val = pop_i32(ctx); let val = pop_i32(ctx);
let val_loc = val.location(&ctx.block_state.locals); let val_loc = val.location(&ctx.block_state.locals);
let dst_loc = ctx.original_locals.get(local_idx); let dst_loc = ctx.block_state.parent_locals.get(local_idx);
if let Some(cur) = ctx if let Some(cur) = ctx
.block_state .block_state
@@ -616,7 +742,7 @@ pub fn set_local_i32(ctx: &mut Context, local_idx: u32) {
} }
copy_value(ctx, val_loc, dst_loc); copy_value(ctx, val_loc, dst_loc);
free_val(ctx, val); free_value(ctx, val);
} }
pub fn literal_i32(ctx: &mut Context, imm: i32) { pub fn literal_i32(ctx: &mut Context, imm: i32) {
@@ -681,13 +807,13 @@ pub fn relop_eq_i32(ctx: &mut Context) {
} }
push_i32(ctx, Value::Temp(result)); push_i32(ctx, Value::Temp(result));
free_val(ctx, left); free_value(ctx, left);
free_val(ctx, right); free_value(ctx, right);
} }
/// Pops i32 predicate and branches to the specified label /// Pops i32 predicate and branches to the specified label
/// if the predicate is equal to zero. /// if the predicate is equal to zero.
pub fn pop_and_breq(ctx: &mut Context, label: Label) { pub fn jump_if_equal_zero(ctx: &mut Context, label: Label) {
let val = pop_i32(ctx); let val = pop_i32(ctx);
let predicate = into_temp_reg(ctx, val); let predicate = into_temp_reg(ctx, val);
dynasm!(ctx.asm dynasm!(ctx.asm
@@ -704,10 +830,6 @@ pub fn br(ctx: &mut Context, label: Label) {
); );
} }
pub fn prepare_return_value(ctx: &mut Context) {
pop_i32_into(ctx, ValueLocation::Reg(RAX));
}
fn copy_value(ctx: &mut Context, src: ValueLocation, dst: ValueLocation) { fn copy_value(ctx: &mut Context, src: ValueLocation, dst: ValueLocation) {
match (src, dst) { match (src, dst) {
(ValueLocation::Stack(in_offset), ValueLocation::Stack(out_offset)) => { (ValueLocation::Stack(in_offset), ValueLocation::Stack(out_offset)) => {
@@ -773,16 +895,15 @@ fn free_arg_registers(ctx: &mut Context, count: u32) {
return; return;
} }
// This is bound to the maximum size of the `ArrayVec` amd so preserves linear runtime // This is bound to the maximum size of the `ArrayVec` amd so can be considered to have constant
// runtime
for i in 0..ctx.block_state.locals.register_arguments.len() { for i in 0..ctx.block_state.locals.register_arguments.len() {
match ctx.block_state.locals.register_arguments[i] { match ctx.block_state.locals.register_arguments[i] {
ValueLocation::Reg(reg) => { ValueLocation::Reg(reg) => {
if ARGS_IN_GPRS.contains(&reg) { if ARGS_IN_GPRS.contains(&reg) {
let offset = adjusted_offset(ctx, (i as u32 * WORD_SIZE) as _); let dst = ValueLocation::Stack((i as u32 * WORD_SIZE) as _);
dynasm!(ctx.asm copy_value(ctx, ValueLocation::Reg(reg), dst);
; mov [rsp + offset], Rq(reg) ctx.block_state.locals.register_arguments[i] = dst;
);
ctx.block_state.locals.register_arguments[i] = ValueLocation::Stack(offset);
} }
} }
_ => {} _ => {}
@@ -795,19 +916,62 @@ fn free_return_register(ctx: &mut Context, count: u32) {
return; return;
} }
for stack_val in &mut ctx.block_state.stack { free_register(ctx, RAX);
}
fn free_register(ctx: &mut Context, reg: GPR) {
let mut to_repush = 0;
let mut out = None;
if ctx.block_state.regs.is_free(reg) {
return;
}
for stack_val in ctx.block_state.stack.iter_mut().rev() {
match stack_val.location(&ctx.block_state.locals) { match stack_val.location(&ctx.block_state.locals) {
// For now it's impossible for a local to be in RAX but that might be // For now it's impossible for a local to be in RAX but that might be
// possible in the future, so we check both cases. // possible in the future, so we check both cases.
Some(ValueLocation::Reg(RAX)) => { Some(ValueLocation::Reg(r)) if r == reg => {
let scratch = ctx.block_state.regs.take_scratch_gpr(); *stack_val = if ctx.block_state.regs.free_scratch() > 1 {
let gpr = ctx.block_state.regs.take_scratch_gpr();
assert!(gpr != RAX, "RAX in stack but marked as free");
StackValue::Temp(gpr)
} else {
ctx.block_state.depth.reserve(1);
StackValue::Pop
};
out = Some(*stack_val);
break;
}
Some(_) => {}
None => {
to_repush += 1;
}
}
}
if let Some(out) = out {
match out {
StackValue::Temp(gpr) => {
dynasm!(ctx.asm dynasm!(ctx.asm
; mov Rq(scratch), rax ; mov Rq(gpr), rax
); );
*stack_val = StackValue::Temp(scratch);
} }
_ => {} StackValue::Pop => {
// TODO: Ideally we should do proper stack allocation so we
// don't have to check this at all (i.e. order on the
// physical stack and order on the logical stack should
// be independent).
assert_eq!(to_repush, 0);
dynasm!(ctx.asm
; push rax
);
} }
_ => unreachable!(),
}
ctx.block_state.regs.release_scratch_gpr(RAX);
} }
} }
@@ -900,7 +1064,9 @@ pub fn call_direct(ctx: &mut Context, index: u32, arg_arity: u32, return_arity:
); );
free_arg_registers(ctx, arg_arity); free_arg_registers(ctx, arg_arity);
if return_arity > 0 {
free_return_register(ctx, return_arity); free_return_register(ctx, return_arity);
}
let cleanup = pass_outgoing_args(ctx, arg_arity); let cleanup = pass_outgoing_args(ctx, arg_arity);
@@ -910,13 +1076,22 @@ pub fn call_direct(ctx: &mut Context, index: u32, arg_arity: u32, return_arity:
); );
post_call_cleanup(ctx, cleanup); post_call_cleanup(ctx, cleanup);
if return_arity > 0 {
push_return_value(ctx);
}
}
#[must_use]
pub struct Function {
should_generate_epilogue: bool,
} }
// TODO: Reserve space to store RBX, RBP, and R12..R15 so we can use them // TODO: Reserve space to store RBX, RBP, and R12..R15 so we can use them
// as scratch registers // as scratch registers
// TODO: Allow use of unused argument registers as scratch registers. // TODO: Allow use of unused argument registers as scratch registers.
/// Writes the function prologue and stores the arguments as locals /// Writes the function prologue and stores the arguments as locals
pub fn start_function(ctx: &mut Context, arguments: u32, locals: u32) { pub fn start_function(ctx: &mut Context, arguments: u32, locals: u32) -> Function {
let reg_args = &ARGS_IN_GPRS[..(arguments as usize).min(ARGS_IN_GPRS.len())]; let reg_args = &ARGS_IN_GPRS[..(arguments as usize).min(ARGS_IN_GPRS.len())];
// 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
@@ -927,34 +1102,41 @@ pub fn start_function(ctx: &mut Context, arguments: u32, locals: u32) {
let aligned_stack_slots = (locals + 1) & !1; let aligned_stack_slots = (locals + 1) & !1;
let frame_size: i32 = aligned_stack_slots as i32 * WORD_SIZE as i32; let frame_size: i32 = aligned_stack_slots as i32 * WORD_SIZE as i32;
ctx.original_locals.register_arguments = ctx.block_state.locals.register_arguments =
reg_args.iter().cloned().map(ValueLocation::Reg).collect(); reg_args.iter().cloned().map(ValueLocation::Reg).collect();
ctx.original_locals.num_stack_args = arguments.saturating_sub(ARGS_IN_GPRS.len() as _); ctx.block_state.locals.num_stack_args = arguments.saturating_sub(ARGS_IN_GPRS.len() as _);
ctx.original_locals.num_local_stack_slots = locals; ctx.block_state.locals.num_local_stack_slots = locals;
ctx.block_state.locals = ctx.original_locals.clone(); ctx.block_state.parent_locals = ctx.block_state.locals.clone();
// ctx.block_state.depth.reserve(aligned_stack_slots - locals);
let should_generate_epilogue = frame_size > 0;
if should_generate_epilogue {
dynasm!(ctx.asm dynasm!(ctx.asm
; push rbp ; push rbp
; mov rbp, rsp ; mov rbp, rsp
);
// ctx.block_state.depth.reserve(aligned_stack_slots - locals);
if frame_size > 0 {
dynasm!(ctx.asm
; sub rsp, frame_size ; sub rsp, frame_size
); );
} }
Function {
should_generate_epilogue,
}
} }
/// Writes the function epilogue, restoring the stack pointer and returning to the /// Writes the function epilogue, restoring the stack pointer and returning to the
/// caller. /// caller.
pub fn epilogue(ctx: &mut Context) { pub fn epilogue(ctx: &mut Context, func: Function) {
// 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.
if func.should_generate_epilogue {
dynasm!(ctx.asm dynasm!(ctx.asm
; mov rsp, rbp ; mov rsp, rbp
; pop rbp ; pop rbp
);
}
dynasm!(ctx.asm
; ret ; ret
); );
} }

View File

@@ -13,7 +13,7 @@ enum ControlFrameKind {
/// Can be used for an implicit function block. /// Can be used for an implicit function block.
Block { end_label: Label }, Block { end_label: Label },
/// Loop frame (branching to the beginning of block). /// Loop frame (branching to the beginning of block).
Loop { header: Label, break_: Label }, Loop { header: Label },
/// True-subblock of if expression. /// True-subblock of if expression.
IfTrue { IfTrue {
/// If jump happens inside the if-true block then control will /// If jump happens inside the if-true block then control will
@@ -31,20 +31,21 @@ enum ControlFrameKind {
impl ControlFrameKind { impl ControlFrameKind {
/// Returns a label which should be used as a branch destination. /// Returns a label which should be used as a branch destination.
fn block_end(&self) -> Label { fn block_end(&self) -> Option<Label> {
match *self { match *self {
ControlFrameKind::Block { end_label } => end_label, ControlFrameKind::Block { end_label } => Some(end_label),
ControlFrameKind::Loop { break_, .. } => break_, ControlFrameKind::IfTrue { end_label, .. } => Some(end_label),
ControlFrameKind::IfTrue { end_label, .. } => end_label, ControlFrameKind::IfFalse { end_label } => Some(end_label),
ControlFrameKind::IfFalse { end_label } => end_label, ControlFrameKind::Loop { .. } => None,
} }
} }
/// Returns `true` if this block of a loop kind. fn branch_target(&self) -> Label {
fn is_loop(&self) -> bool {
match *self { match *self {
ControlFrameKind::Loop { .. } => true, ControlFrameKind::Block { end_label } => end_label,
_ => false, ControlFrameKind::IfTrue { end_label, .. } => end_label,
ControlFrameKind::IfFalse { end_label } => end_label,
ControlFrameKind::Loop { header } => header,
} }
} }
} }
@@ -54,26 +55,38 @@ struct ControlFrame {
/// Boolean which signals whether value stack became polymorphic. Value stack starts in non-polymorphic state and /// Boolean which signals whether value stack became polymorphic. Value stack starts in non-polymorphic state and
/// becomes polymorphic only after an instruction that never passes control further is executed, /// becomes polymorphic only after an instruction that never passes control further is executed,
/// i.e. `unreachable`, `br` (but not `br_if`!), etc. /// i.e. `unreachable`, `br` (but not `br_if`!), etc.
stack_polymorphic: bool, unreachable: bool,
/// 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, ty: Type,
} }
fn arity(ty: Type) -> u32 {
if ty == Type::EmptyBlockType {
0
} else {
1
}
}
impl ControlFrame { impl ControlFrame {
pub fn new(kind: ControlFrameKind, block_state: BlockState, ty: Type) -> ControlFrame { pub fn new(kind: ControlFrameKind, block_state: BlockState, ty: Type) -> ControlFrame {
ControlFrame { ControlFrame {
kind, kind,
block_state, block_state,
ty, ty,
stack_polymorphic: false, unreachable: false,
} }
} }
pub fn arity(&self) -> u32 {
arity(self.ty)
}
/// Marks this control frame as reached stack-polymorphic state. /// Marks this control frame as reached stack-polymorphic state.
pub fn mark_stack_polymorphic(&mut self) { pub fn mark_unreachable(&mut self) {
self.stack_polymorphic = true; self.unreachable = true;
} }
} }
@@ -87,10 +100,12 @@ pub fn translate(
let func_type = translation_ctx.func_type(func_idx); let func_type = translation_ctx.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() > 0 { let return_ty = if func_type.returns.len() == 1 {
func_type.returns[0] func_type.returns[0]
} else { } else if func_type.returns.len() == 0 {
Type::EmptyBlockType Type::EmptyBlockType
} else {
panic!("We don't support multiple returns yet");
}; };
let mut num_locals = 0; let mut num_locals = 0;
@@ -102,52 +117,101 @@ 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()?;
start_function(ctx, arg_count, num_locals); let func = start_function(ctx, arg_count, num_locals);
let mut control_frames = Vec::new(); let mut control_frames = Vec::new();
// 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 = create_label(ctx); let epilogue_label = create_label(ctx);
let function_block_state = start_block(ctx, arity(return_ty));
control_frames.push(ControlFrame::new( control_frames.push(ControlFrame::new(
ControlFrameKind::Block { ControlFrameKind::Block {
end_label: epilogue_label, end_label: epilogue_label,
}, },
current_block_state(&ctx), function_block_state,
return_ty, return_ty,
)); ));
for op in operators { for op in operators {
match op? { let op = op?;
if let Operator::End = op {
} else {
if control_frames
.last()
.expect("Control stack never empty")
.unreachable
{
continue;
}
}
match op {
Operator::Unreachable => { Operator::Unreachable => {
control_frames control_frames
.last_mut() .last_mut()
.expect("control stack is never empty") .expect("control stack is never empty")
.mark_stack_polymorphic(); .mark_unreachable();
trap(ctx); trap(ctx);
} }
Operator::Block { ty } => {
let label = create_label(ctx);
let state = start_block(ctx, arity(ty));
control_frames.push(ControlFrame::new(
ControlFrameKind::Block { end_label: label },
state,
ty,
));
}
Operator::Br { relative_depth } => {
control_frames
.last_mut()
.expect("control stack is never empty")
.mark_unreachable();
let idx = control_frames.len() - 1 - relative_depth as usize;
let control_frame = control_frames.get(idx).expect("wrong depth");
return_from_block(ctx, control_frame.arity(), idx == 0);
br(ctx, control_frame.kind.branch_target());
}
Operator::BrIf { relative_depth } => {
let idx = control_frames.len() - 1 - relative_depth as usize;
let control_frame = control_frames.get(idx).expect("wrong depth");
let if_not = create_label(ctx);
jump_if_equal_zero(ctx, if_not);
return_from_block(ctx, control_frame.arity(), idx == 0);
br(ctx, control_frame.kind.branch_target());
define_label(ctx, if_not);
}
Operator::If { ty } => { Operator::If { ty } => {
let end_label = create_label(ctx); let end_label = create_label(ctx);
let if_not = create_label(ctx); let if_not = create_label(ctx);
pop_and_breq(ctx, if_not); jump_if_equal_zero(ctx, if_not);
let state = start_block(ctx, arity(ty));
control_frames.push(ControlFrame::new( control_frames.push(ControlFrame::new(
ControlFrameKind::IfTrue { end_label, if_not }, ControlFrameKind::IfTrue { end_label, if_not },
current_block_state(&ctx), state,
ty, ty,
)); ));
} }
Operator::Loop { ty } => { Operator::Loop { ty } => {
let header = create_label(ctx); let header = create_label(ctx);
let break_ = create_label(ctx);
let state = start_block(ctx, arity(ty));
define_label(ctx, header); define_label(ctx, header);
pop_and_breq(ctx, break_);
control_frames.push(ControlFrame::new( control_frames.push(ControlFrame::new(
ControlFrameKind::Loop { header, break_ }, ControlFrameKind::Loop { header },
current_block_state(&ctx), state,
ty, ty,
)); ));
} }
@@ -159,18 +223,16 @@ pub fn translate(
block_state, block_state,
.. ..
}) => { }) => {
if ty != Type::EmptyBlockType { return_from_block(ctx, arity(ty), false);
return_from_block(ctx); end_block(ctx, block_state.clone(), arity(ty));
}
// Finalize if..else block by jumping to the `end_label`. // Finalize `then` block by jumping to the `end_label`.
br(ctx, end_label); br(ctx, end_label);
// Define `if_not` label here, so if the corresponding `if` block receives // Define `if_not` label here, so if the corresponding `if` block receives
// 0 it will branch here. // 0 it will branch here.
// After that reset stack depth to the value before entering `if` block. // After that reset stack depth to the value before entering `if` block.
define_label(ctx, if_not); define_label(ctx, if_not);
end_block(ctx, block_state.clone());
// Carry over the `end_label`, so it will be resolved when the corresponding `end` // Carry over the `end_label`, so it will be resolved when the corresponding `end`
// is encountered. // is encountered.
@@ -190,28 +252,23 @@ pub fn translate(
Operator::End => { Operator::End => {
let control_frame = control_frames.pop().expect("control stack is never empty"); let control_frame = control_frames.pop().expect("control stack is never empty");
if control_frame.ty != Type::EmptyBlockType && !control_frames.is_empty() { let arity = control_frame.arity();
return_from_block(ctx);
// Don't bother generating this code if we're in unreachable code
if !control_frame.unreachable {
return_from_block(ctx, arity, control_frames.is_empty());
} }
if let ControlFrameKind::Loop { header, .. } = control_frame.kind { end_block(ctx, control_frame.block_state, arity);
br(ctx, header);
}
define_label(ctx, control_frame.kind.block_end()); if let Some(block_end) = control_frame.kind.block_end() {
define_label(ctx, block_end);
}
if let ControlFrameKind::IfTrue { if_not, .. } = control_frame.kind { if let ControlFrameKind::IfTrue { if_not, .. } = control_frame.kind {
// this is `if .. end` construction. Define the `if_not` label here. // this is `if .. end` construction. Define the `if_not` label here.
define_label(ctx, if_not); define_label(ctx, if_not);
} }
// This is the last control frame. Perform the implicit return here.
if control_frames.len() == 0 && return_ty != Type::EmptyBlockType {
prepare_return_value(ctx);
}
end_block(ctx, control_frame.block_state);
push_block_return_value(ctx);
} }
Operator::I32Eq => relop_eq_i32(ctx), Operator::I32Eq => relop_eq_i32(ctx),
Operator::I32Add => i32_add(ctx), Operator::I32Add => i32_add(ctx),
@@ -220,6 +277,8 @@ pub fn translate(
Operator::I32Or => i32_or(ctx), Operator::I32Or => i32_or(ctx),
Operator::I32Xor => i32_xor(ctx), Operator::I32Xor => i32_xor(ctx),
Operator::I32Mul => i32_mul(ctx), Operator::I32Mul => i32_mul(ctx),
Operator::Drop => drop(ctx),
Operator::SetLocal { local_index } => set_local_i32(ctx, local_index),
Operator::GetLocal { local_index } => get_local_i32(ctx, local_index), Operator::GetLocal { local_index } => get_local_i32(ctx, local_index),
Operator::I32Const { value } => literal_i32(ctx, value), Operator::I32Const { value } => literal_i32(ctx, value),
Operator::Call { function_index } => { Operator::Call { function_index } => {
@@ -233,14 +292,14 @@ pub fn translate(
callee_ty.params.len() as u32, callee_ty.params.len() as u32,
callee_ty.returns.len() as u32, callee_ty.returns.len() as u32,
); );
push_return_value(ctx);
} }
_ => { Operator::Nop => {}
trap(ctx); op => {
unimplemented!("{:?}", op);
} }
} }
} }
epilogue(ctx); epilogue(ctx, func);
Ok(()) Ok(())
} }

View File

@@ -159,6 +159,7 @@ fn large_function() {
assert_eq!( assert_eq!(
{ {
let translated = translate_wat(code); let translated = translate_wat(code);
translated.disassemble();
let out: u32 = unsafe { translated.execute_func(0, (5, 4, 3, 2, 1, 0)) }; let out: u32 = unsafe { translated.execute_func(0, (5, 4, 3, 2, 1, 0)) };
out out
}, },
@@ -192,6 +193,7 @@ fn function_read_args_spill_to_stack() {
assert_eq!( assert_eq!(
{ {
let translated = translate_wat(code); let translated = translate_wat(code);
translated.disassemble();
let out: u32 = unsafe { let out: u32 = unsafe {
translated.execute_func(0, (7u32, 6u32, 5u32, 4u32, 3u32, 2u32, 1u32, 0u32)) translated.execute_func(0, (7u32, 6u32, 5u32, 4u32, 3u32, 2u32, 1u32, 0u32))
}; };
@@ -258,6 +260,140 @@ fn function_write_args_spill_to_stack() {
11 11
); );
} }
#[test]
fn block() {
let code = r#"
(module
(func (param i32) (param i32) (result i32)
(block (result i32)
get_local 0
)
)
)
"#;
assert_eq!(execute_wat(code, 10, 20), 10);
}
#[test]
fn br_block() {
let code = r#"
(module
(func (param i32) (param i32) (result i32)
get_local 1
(block (result i32)
get_local 0
get_local 0
br 0
unreachable
)
i32.add
)
)
"#;
assert_eq!(execute_wat(code, 5, 7), 12);
}
// Tests discarding values on the value stack, while
// carrying over the result using a conditional branch.
#[test]
fn brif_block() {
let code = r#"
(module
(func (param i32) (param i32) (result i32)
get_local 1
(block (result i32)
get_local 0
get_local 0
br_if 0
unreachable
)
i32.add
)
)
"#;
assert_eq!(execute_wat(code, 5, 7), 12);
}
#[test]
fn spec_loop() {
let code = r#"
(module
(func
(call $assert-return (call $as-binary-operand) (i32.const 12))
(call $assert-return (call $break-bare) (i32.const 19))
(call $assert-return (call $break-value) (i32.const 18))
(call $assert-return (call $break-repeated) (i32.const 18))
(call $assert-return (call $break-inner) (i32.const 0x7))
)
(func $dummy)
(func $as-binary-operand (result i32)
(i32.mul
(loop (result i32) (call $dummy) (i32.const 3))
(loop (result i32) (call $dummy) (i32.const 4))
)
)
(func $break-bare (result i32)
(block (loop (br 1) (br 0) (unreachable)))
(block (loop (br_if 1 (i32.const 1)) (unreachable)))
(i32.const 19)
)
(func $break-value (result i32)
(block (result i32)
(loop (result i32) (br 1 (i32.const 18)) (br 0) (i32.const 19))
)
)
(func $break-repeated (result i32)
(block (result i32)
(loop (result i32)
(br 1 (i32.const 18))
(br 1 (i32.const 19))
(drop (br_if 1 (i32.const 20) (i32.const 0)))
(drop (br_if 1 (i32.const 20) (i32.const 1)))
(br 1 (i32.const 21))
(i32.const 21)
)
)
)
(func $break-inner (result i32)
(local i32)
(set_local 0 (i32.const 0))
(set_local 0 (i32.add (get_local 0) (block (result i32) (loop (result i32) (block (result i32) (br 2 (i32.const 0x1)))))))
(set_local 0 (i32.add (get_local 0) (block (result i32) (loop (result i32) (loop (result i32) (br 2 (i32.const 0x2)))))))
(set_local 0 (i32.add (get_local 0) (block (result i32) (loop (result i32) (block (result i32) (loop (result i32) (br 1 (i32.const 0x4))))))))
(get_local 0)
)
(func $assert-return (param i32) (param i32)
(if (i32.eq (get_local 0) (get_local 1)) (then) (else (unreachable)))
)
)
"#;
let translated = translate_wat(code);
translated.disassemble();
unsafe { translated.execute_func::<(), ()>(0, ()) }
}
// Tests that br_if keeps values in the case if the branch
// hasn't been taken.
#[test]
fn brif_block_passthru() {
let code = r#"
(module
(func (param i32) (param i32) (result i32)
(block (result i32)
get_local 1
get_local 0
br_if 0
get_local 1
i32.add
)
)
)
"#;
assert_eq!(execute_wat(code, 0, 3), 6);
}
#[test] #[test]
fn literals() { fn literals() {
let code = r#" let code = r#"