Fix use of locals
This commit is contained in:
@@ -13,7 +13,7 @@ enum ControlFrameKind {
|
||||
/// Can be used for an implicit function block.
|
||||
Block { end_label: Label },
|
||||
/// Loop frame (branching to the beginning of block).
|
||||
Loop { header: Label, break_: Label },
|
||||
Loop { header: Label },
|
||||
/// True-subblock of if expression.
|
||||
IfTrue {
|
||||
/// If jump happens inside the if-true block then control will
|
||||
@@ -31,20 +31,21 @@ enum ControlFrameKind {
|
||||
|
||||
impl ControlFrameKind {
|
||||
/// Returns a label which should be used as a branch destination.
|
||||
fn block_end(&self) -> Label {
|
||||
fn block_end(&self) -> Option<Label> {
|
||||
match *self {
|
||||
ControlFrameKind::Block { end_label } => end_label,
|
||||
ControlFrameKind::Loop { break_, .. } => break_,
|
||||
ControlFrameKind::IfTrue { end_label, .. } => end_label,
|
||||
ControlFrameKind::IfFalse { end_label } => end_label,
|
||||
ControlFrameKind::Block { end_label } => Some(end_label),
|
||||
ControlFrameKind::IfTrue { end_label, .. } => Some(end_label),
|
||||
ControlFrameKind::IfFalse { end_label } => Some(end_label),
|
||||
ControlFrameKind::Loop { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this block of a loop kind.
|
||||
fn is_loop(&self) -> bool {
|
||||
fn branch_target(&self) -> Label {
|
||||
match *self {
|
||||
ControlFrameKind::Loop { .. } => true,
|
||||
_ => false,
|
||||
ControlFrameKind::Block { end_label } => end_label,
|
||||
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
|
||||
/// becomes polymorphic only after an instruction that never passes control further is executed,
|
||||
/// 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
|
||||
/// at the end of the block
|
||||
block_state: BlockState,
|
||||
ty: Type,
|
||||
}
|
||||
|
||||
fn arity(ty: Type) -> u32 {
|
||||
if ty == Type::EmptyBlockType {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlFrame {
|
||||
pub fn new(kind: ControlFrameKind, block_state: BlockState, ty: Type) -> ControlFrame {
|
||||
ControlFrame {
|
||||
kind,
|
||||
block_state,
|
||||
ty,
|
||||
stack_polymorphic: false,
|
||||
unreachable: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arity(&self) -> u32 {
|
||||
arity(self.ty)
|
||||
}
|
||||
|
||||
/// Marks this control frame as reached stack-polymorphic state.
|
||||
pub fn mark_stack_polymorphic(&mut self) {
|
||||
self.stack_polymorphic = true;
|
||||
pub fn mark_unreachable(&mut self) {
|
||||
self.unreachable = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,10 +100,12 @@ pub fn translate(
|
||||
|
||||
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() > 0 {
|
||||
let return_ty = if func_type.returns.len() == 1 {
|
||||
func_type.returns[0]
|
||||
} else {
|
||||
} else if func_type.returns.len() == 0 {
|
||||
Type::EmptyBlockType
|
||||
} else {
|
||||
panic!("We don't support multiple returns yet");
|
||||
};
|
||||
|
||||
let mut num_locals = 0;
|
||||
@@ -102,52 +117,101 @@ pub fn translate(
|
||||
let ctx = &mut session.new_context(func_idx);
|
||||
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();
|
||||
|
||||
// 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 = create_label(ctx);
|
||||
let function_block_state = start_block(ctx, arity(return_ty));
|
||||
control_frames.push(ControlFrame::new(
|
||||
ControlFrameKind::Block {
|
||||
end_label: epilogue_label,
|
||||
},
|
||||
current_block_state(&ctx),
|
||||
function_block_state,
|
||||
return_ty,
|
||||
));
|
||||
|
||||
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 => {
|
||||
control_frames
|
||||
.last_mut()
|
||||
.expect("control stack is never empty")
|
||||
.mark_stack_polymorphic();
|
||||
.mark_unreachable();
|
||||
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 } => {
|
||||
let end_label = 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(
|
||||
ControlFrameKind::IfTrue { end_label, if_not },
|
||||
current_block_state(&ctx),
|
||||
state,
|
||||
ty,
|
||||
));
|
||||
}
|
||||
Operator::Loop { ty } => {
|
||||
let header = create_label(ctx);
|
||||
let break_ = create_label(ctx);
|
||||
|
||||
let state = start_block(ctx, arity(ty));
|
||||
define_label(ctx, header);
|
||||
pop_and_breq(ctx, break_);
|
||||
|
||||
control_frames.push(ControlFrame::new(
|
||||
ControlFrameKind::Loop { header, break_ },
|
||||
current_block_state(&ctx),
|
||||
ControlFrameKind::Loop { header },
|
||||
state,
|
||||
ty,
|
||||
));
|
||||
}
|
||||
@@ -159,18 +223,16 @@ pub fn translate(
|
||||
block_state,
|
||||
..
|
||||
}) => {
|
||||
if ty != Type::EmptyBlockType {
|
||||
return_from_block(ctx);
|
||||
}
|
||||
return_from_block(ctx, arity(ty), false);
|
||||
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);
|
||||
|
||||
// Define `if_not` label here, so if the corresponding `if` block receives
|
||||
// 0 it will branch here.
|
||||
// After that reset stack depth to the value before entering `if` block.
|
||||
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`
|
||||
// is encountered.
|
||||
@@ -190,28 +252,23 @@ pub fn translate(
|
||||
Operator::End => {
|
||||
let control_frame = control_frames.pop().expect("control stack is never empty");
|
||||
|
||||
if control_frame.ty != Type::EmptyBlockType && !control_frames.is_empty() {
|
||||
return_from_block(ctx);
|
||||
let arity = control_frame.arity();
|
||||
|
||||
// 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 {
|
||||
br(ctx, header);
|
||||
}
|
||||
end_block(ctx, control_frame.block_state, arity);
|
||||
|
||||
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 {
|
||||
// this is `if .. end` construction. Define the `if_not` label here.
|
||||
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::I32Add => i32_add(ctx),
|
||||
@@ -220,6 +277,8 @@ pub fn translate(
|
||||
Operator::I32Or => i32_or(ctx),
|
||||
Operator::I32Xor => i32_xor(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::I32Const { value } => literal_i32(ctx, value),
|
||||
Operator::Call { function_index } => {
|
||||
@@ -233,14 +292,14 @@ pub fn translate(
|
||||
callee_ty.params.len() as u32,
|
||||
callee_ty.returns.len() as u32,
|
||||
);
|
||||
push_return_value(ctx);
|
||||
}
|
||||
_ => {
|
||||
trap(ctx);
|
||||
Operator::Nop => {}
|
||||
op => {
|
||||
unimplemented!("{:?}", op);
|
||||
}
|
||||
}
|
||||
}
|
||||
epilogue(ctx);
|
||||
epilogue(ctx, func);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user