Track wasm reachability explicitly.

Maintain an explicit "reachable" flag when decoding wasm. Push placeholder
frames on the control-flow stack instead of just maintaining a count of
the stack depth in unreachable code, so that we can whether If blocks
have Elses, and whether block exits are branched to, in all contexts.

Fixes #217.
This commit is contained in:
Dan Gohman
2018-02-26 14:12:13 -08:00
parent 81c126619b
commit 0e22c74085
4 changed files with 211 additions and 129 deletions

View File

@@ -25,6 +25,7 @@
use cretonne::ir::{self, InstBuilder, MemFlags, JumpTableData};
use cretonne::ir::types::*;
use cretonne::ir::condcodes::{IntCC, FloatCC};
use cretonne::packed_option::ReservedValue;
use cton_frontend::FunctionBuilder;
use wasmparser::{Operator, MemoryImmediate};
use translation_utils::{f32_translation, f64_translation, type_to_type, num_return_values, Local};
@@ -42,7 +43,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
state: &mut TranslationState,
environ: &mut FE,
) {
if state.in_unreachable_code() {
if !state.reachable {
return translate_unreachable_operator(op, builder, state);
}
@@ -105,7 +106,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
// We use `trap user0` to indicate a user-generated trap.
// We could make the trap code configurable if need be.
builder.ins().trap(ir::TrapCode::User(0));
state.real_unreachable_stack_depth = 1;
state.reachable = false;
}
/***************************** Control flow blocks **********************************
* When starting a control flow block, we create a new `Ebb` that will hold the code
@@ -155,15 +156,24 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
// and push a new control frame with a new ebb for the code after the if/then/else
// At the end of the then clause we jump to the destination
let i = state.control_stack.len() - 1;
let (destination, return_count, branch_inst) = match state.control_stack[i] {
ControlStackFrame::If {
destination,
num_return_values,
branch_inst,
..
} => (destination, num_return_values, branch_inst),
_ => panic!("should not happen"),
};
let (destination, return_count, branch_inst, ref mut reachable_from_top) =
match state.control_stack[i] {
ControlStackFrame::If {
destination,
num_return_values,
branch_inst,
reachable_from_top,
..
} => (
destination,
num_return_values,
branch_inst,
reachable_from_top,
),
_ => panic!("should not happen"),
};
// The if has an else, so there's no branch to the end from the top.
*reachable_from_top = false;
builder.ins().jump(destination, state.peekn(return_count));
state.popn(return_count);
// We change the target of the branch instruction
@@ -219,7 +229,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
let (return_count, br_destination) = {
let frame = &mut state.control_stack[i];
// We signal that all the code that follows until the next End is unreachable
frame.set_reachable();
frame.set_branched_to_exit();
let return_count = if frame.is_loop() {
0
} else {
@@ -232,7 +242,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
state.peekn(return_count),
);
state.popn(return_count);
state.real_unreachable_stack_depth = 1 + relative_depth as usize;
state.reachable = false;
}
Operator::BrIf { relative_depth } => {
let val = state.pop1();
@@ -241,7 +251,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
let frame = &mut state.control_stack[i];
// The values returned by the branch are still available for the reachable
// code that comes after it
frame.set_reachable();
frame.set_branched_to_exit();
let return_count = if frame.is_loop() {
0
} else {
@@ -277,20 +287,23 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
let val = state.pop1();
let mut data = JumpTableData::with_capacity(depths.len());
for depth in depths {
let i = state.control_stack.len() - 1 - (depth as usize);
let frame = &mut state.control_stack[i];
let ebb = frame.br_destination();
let ebb = {
let i = state.control_stack.len() - 1 - (depth as usize);
let frame = &mut state.control_stack[i];
frame.set_branched_to_exit();
frame.br_destination()
};
data.push_entry(ebb);
frame.set_reachable();
}
let jt = builder.create_jump_table(data);
builder.ins().br_table(val, jt);
let i = state.control_stack.len() - 1 - (default as usize);
let frame = &mut state.control_stack[i];
let ebb = frame.br_destination();
let ebb = {
let i = state.control_stack.len() - 1 - (default as usize);
let frame = &mut state.control_stack[i];
frame.set_branched_to_exit();
frame.br_destination()
};
builder.ins().jump(ebb, &[]);
state.real_unreachable_stack_depth = 1 + min_depth as usize;
frame.set_reachable();
} else {
// Here we have jump arguments, but Cretonne's br_table doesn't support them
// We then proceed to split the edges going out of the br_table
@@ -312,29 +325,32 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
}
let jt = builder.create_jump_table(data);
builder.ins().br_table(val, jt);
let default_ebb = state.control_stack[state.control_stack.len() - 1 -
(default as usize)]
.br_destination();
let default_ebb = {
let i = state.control_stack.len() - 1 - (default as usize);
let frame = &mut state.control_stack[i];
frame.set_branched_to_exit();
frame.br_destination()
};
builder.ins().jump(default_ebb, state.peekn(return_count));
for (depth, dest_ebb) in dest_ebb_sequence {
builder.switch_to_block(dest_ebb);
builder.seal_block(dest_ebb);
let i = state.control_stack.len() - 1 - depth;
let real_dest_ebb = {
let i = state.control_stack.len() - 1 - depth;
let frame = &mut state.control_stack[i];
frame.set_reachable();
frame.set_branched_to_exit();
frame.br_destination()
};
builder.ins().jump(real_dest_ebb, state.peekn(return_count));
}
state.popn(return_count);
state.real_unreachable_stack_depth = 1 + min_depth as usize;
}
state.reachable = false;
}
Operator::Return => {
let (return_count, br_destination) = {
let frame = &mut state.control_stack[0];
frame.set_reachable();
frame.set_branched_to_exit();
let return_count = frame.num_return_values();
(return_count, frame.br_destination())
};
@@ -347,7 +363,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
}
}
state.popn(return_count);
state.real_unreachable_stack_depth = 1;
state.reachable = false;
}
/************************************ Calls ****************************************
* The call instructions pop off their arguments from the stack and append their
@@ -900,76 +916,74 @@ fn translate_unreachable_operator(
builder: &mut FunctionBuilder<Local>,
state: &mut TranslationState,
) {
let stack = &mut state.stack;
let control_stack = &mut state.control_stack;
// We don't translate because the code is unreachable
// Nevertheless we have to record a phantom stack for this code
// to know when the unreachable code ends
match *op {
Operator::If { ty: _ } |
Operator::If { ty: _ } => {
// Push a placeholder control stack entry. The if isn't reachable,
// so we don't have any branches anywhere.
state.push_if(ir::Inst::reserved_value(), ir::Ebb::reserved_value(), 0);
}
Operator::Loop { ty: _ } |
Operator::Block { ty: _ } => {
state.phantom_unreachable_stack_depth += 1;
}
Operator::End => {
if state.phantom_unreachable_stack_depth > 0 {
state.phantom_unreachable_stack_depth -= 1;
} else {
// This End corresponds to a real control stack frame
// We switch to the destination block but we don't insert
// a jump instruction since the code is still unreachable
let frame = control_stack.pop().unwrap();
builder.switch_to_block(frame.following_code());
builder.seal_block(frame.following_code());
match frame {
// If it is a loop we also have to seal the body loop block
ControlStackFrame::Loop { header, .. } => builder.seal_block(header),
// If it is an if then the code after is reachable again
ControlStackFrame::If { .. } => {
state.real_unreachable_stack_depth = 1;
}
_ => {}
}
if frame.is_reachable() {
state.real_unreachable_stack_depth = 1;
}
// Now we have to split off the stack the values not used
// by unreachable code that hasn't been translated
stack.truncate(frame.original_stack_size());
// And add the return values of the block but only if the next block is reachble
// (which corresponds to testing if the stack depth is 1)
if state.real_unreachable_stack_depth == 1 {
stack.extend_from_slice(builder.ebb_params(frame.following_code()));
}
state.real_unreachable_stack_depth -= 1;
}
state.push_block(ir::Ebb::reserved_value(), 0);
}
Operator::Else => {
if state.phantom_unreachable_stack_depth > 0 {
// This is part of a phantom if-then-else, we do nothing
} else {
// Encountering an real else means that the code in the else
// clause is reachable again
let (branch_inst, original_stack_size) = match control_stack[control_stack.len() -
1] {
ControlStackFrame::If {
branch_inst,
original_stack_size,
..
} => (branch_inst, original_stack_size),
_ => panic!("should not happen"),
};
// We change the target of the branch instruction
let else_ebb = builder.create_ebb();
builder.change_jump_destination(branch_inst, else_ebb);
builder.seal_block(else_ebb);
builder.switch_to_block(else_ebb);
// Now we have to split off the stack the values not used
// by unreachable code that hasn't been translated
stack.truncate(original_stack_size);
state.real_unreachable_stack_depth = 0;
let i = state.control_stack.len() - 1;
match state.control_stack[i] {
ControlStackFrame::If {
branch_inst,
ref mut reachable_from_top,
..
} => {
if *reachable_from_top {
// We have a branch from the top of the if to the else.
state.reachable = true;
// And because there's an else, there can no longer be a
// branch from the top directly to the end.
*reachable_from_top = false;
// We change the target of the branch instruction
let else_ebb = builder.create_ebb();
builder.change_jump_destination(branch_inst, else_ebb);
builder.seal_block(else_ebb);
builder.switch_to_block(else_ebb);
}
}
_ => {}
}
}
Operator::End => {
let stack = &mut state.stack;
let control_stack = &mut state.control_stack;
let frame = control_stack.pop().unwrap();
// Now we have to split off the stack the values not used
// by unreachable code that hasn't been translated
stack.truncate(frame.original_stack_size());
let reachable_anyway = match frame {
// If it is a loop we also have to seal the body loop block
ControlStackFrame::Loop { header, .. } => {
builder.seal_block(header);
// And loops can't have branches to the end.
false
}
ControlStackFrame::If { reachable_from_top, .. } => {
// A reachable if without an else has a branch from the top
// directly to the bottom.
reachable_from_top
}
// All other control constructs are already handled.
_ => false,
};
if frame.exit_is_branched_to() || reachable_anyway {
builder.switch_to_block(frame.following_code());
builder.seal_block(frame.following_code());
// And add the return values of the block but only if the next block is reachble
// (which corresponds to testing if the stack depth is 1)
stack.extend_from_slice(builder.ebb_params(frame.following_code()));
state.reachable = true;
}
}
_ => {