Working
This commit is contained in:
parent
5e20cc28f8
commit
a9bf01a60f
|
@ -1,4 +1,3 @@
|
||||||
use parity_wasm::elements::BlockType;
|
|
||||||
|
|
||||||
pub mod stack;
|
pub mod stack;
|
||||||
|
|
||||||
|
@ -8,39 +7,3 @@ pub const DEFAULT_MEMORY_INDEX: u32 = 0;
|
||||||
pub const DEFAULT_TABLE_INDEX: u32 = 0;
|
pub const DEFAULT_TABLE_INDEX: u32 = 0;
|
||||||
|
|
||||||
// TODO: Move BlockFrame under validation.
|
// TODO: Move BlockFrame under validation.
|
||||||
|
|
||||||
/// Control stack frame.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct BlockFrame {
|
|
||||||
/// Frame type.
|
|
||||||
pub frame_type: BlockFrameType,
|
|
||||||
/// A signature, which is a block signature type indicating the number and types of result values of the region.
|
|
||||||
pub block_type: BlockType,
|
|
||||||
/// A label for reference to block instruction.
|
|
||||||
pub begin_position: usize,
|
|
||||||
/// A label for reference from branch instructions.
|
|
||||||
pub branch_position: usize,
|
|
||||||
/// A label for reference from end instructions.
|
|
||||||
pub end_position: usize,
|
|
||||||
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
|
|
||||||
pub value_stack_len: usize,
|
|
||||||
/// 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.
|
|
||||||
pub polymorphic_stack: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type of block frame.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum BlockFrameType {
|
|
||||||
/// Function frame.
|
|
||||||
Function,
|
|
||||||
/// Usual block frame.
|
|
||||||
Block,
|
|
||||||
/// Loop frame (branching to the beginning of block).
|
|
||||||
Loop,
|
|
||||||
/// True-subblock of if expression.
|
|
||||||
IfTrue,
|
|
||||||
/// False-subblock of if expression.
|
|
||||||
IfFalse,
|
|
||||||
}
|
|
||||||
|
|
|
@ -109,7 +109,6 @@ extern crate nan_preserving_float;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// Error type which can thrown by wasm code or by host environment.
|
/// Error type which can thrown by wasm code or by host environment.
|
||||||
///
|
///
|
||||||
|
|
|
@ -21,8 +21,6 @@ use isa;
|
||||||
|
|
||||||
/// Maximum number of entries in value stack.
|
/// Maximum number of entries in value stack.
|
||||||
pub const DEFAULT_VALUE_STACK_LIMIT: usize = 16384;
|
pub const DEFAULT_VALUE_STACK_LIMIT: usize = 16384;
|
||||||
/// Maximum number of entries in frame stack.
|
|
||||||
pub const DEFAULT_FRAME_STACK_LIMIT: usize = 16384;
|
|
||||||
|
|
||||||
/// Function interpreter.
|
/// Function interpreter.
|
||||||
pub struct Interpreter<'a, E: Externals + 'a> {
|
pub struct Interpreter<'a, E: Externals + 'a> {
|
||||||
|
|
|
@ -24,8 +24,6 @@ struct BlockFrame {
|
||||||
block_type: BlockType,
|
block_type: BlockType,
|
||||||
/// A label for reference to block instruction.
|
/// A label for reference to block instruction.
|
||||||
begin_position: usize,
|
begin_position: usize,
|
||||||
// TODO:
|
|
||||||
branch_label: LabelId,
|
|
||||||
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
|
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
|
||||||
value_stack_len: usize,
|
value_stack_len: usize,
|
||||||
/// 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
|
||||||
|
@ -37,35 +35,63 @@ struct BlockFrame {
|
||||||
/// Type of block frame.
|
/// Type of block frame.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
enum BlockFrameType {
|
enum BlockFrameType {
|
||||||
/// Function frame.
|
|
||||||
Function,
|
|
||||||
/// Usual block frame.
|
/// Usual block frame.
|
||||||
Block,
|
///
|
||||||
|
/// Can be used for an implicit function block.
|
||||||
|
Block {
|
||||||
|
end_label: LabelId,
|
||||||
|
},
|
||||||
/// Loop frame (branching to the beginning of block).
|
/// Loop frame (branching to the beginning of block).
|
||||||
Loop,
|
Loop {
|
||||||
|
header: LabelId,
|
||||||
|
},
|
||||||
/// True-subblock of if expression.
|
/// True-subblock of if expression.
|
||||||
IfTrue,
|
IfTrue {
|
||||||
|
/// If jump happens inside the if-true block then control will
|
||||||
|
/// land on this label.
|
||||||
|
end_label: LabelId,
|
||||||
|
|
||||||
|
/// If the condition of the `if` statement is unsatisfied, control
|
||||||
|
/// will land on this label. This label might point to `else` block if it
|
||||||
|
/// exists. Otherwise it equal to `end_label`.
|
||||||
|
if_not: LabelId,
|
||||||
|
},
|
||||||
/// False-subblock of if expression.
|
/// False-subblock of if expression.
|
||||||
IfFalse,
|
IfFalse {
|
||||||
|
end_label: LabelId,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function validation context.
|
impl BlockFrameType {
|
||||||
struct FunctionValidationContext<'a> {
|
/// Returns a label which should be used as a branch destination.
|
||||||
/// Wasm module
|
fn br_destination(&self) -> LabelId {
|
||||||
module: &'a ModuleContext,
|
match *self {
|
||||||
/// Current instruction position.
|
BlockFrameType::Block { end_label } => end_label,
|
||||||
position: usize,
|
BlockFrameType::Loop { header } => header,
|
||||||
/// Local variables.
|
BlockFrameType::IfTrue { end_label, .. } => end_label,
|
||||||
locals: Locals<'a>,
|
BlockFrameType::IfFalse { end_label } => end_label,
|
||||||
/// Value stack.
|
}
|
||||||
value_stack: StackWithLimit<StackValueType>,
|
}
|
||||||
/// Frame stack.
|
|
||||||
frame_stack: StackWithLimit<BlockFrame>,
|
|
||||||
/// Function return type. None if validating expression.
|
|
||||||
return_type: Option<BlockType>,
|
|
||||||
|
|
||||||
// TODO: comment
|
/// Returns a label which should be resolved at the `End` opcode.
|
||||||
sink: Sink,
|
///
|
||||||
|
/// All block types have it except loops. Loops doesn't use end as a branch
|
||||||
|
/// destination.
|
||||||
|
fn end_label(&self) -> LabelId {
|
||||||
|
match *self {
|
||||||
|
BlockFrameType::Block { end_label } => end_label,
|
||||||
|
BlockFrameType::IfTrue { end_label, .. } => end_label,
|
||||||
|
BlockFrameType::IfFalse { end_label } => end_label,
|
||||||
|
BlockFrameType::Loop { .. } => panic!("loop doesn't use end label"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_loop(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
BlockFrameType::Loop { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Value type on the stack.
|
/// Value type on the stack.
|
||||||
|
@ -105,20 +131,19 @@ impl Validator {
|
||||||
result_ty,
|
result_ty,
|
||||||
);
|
);
|
||||||
|
|
||||||
let func_label = context.sink.new_label();
|
let end_label = context.sink.new_label();
|
||||||
context.push_label(BlockFrameType::Function, result_ty, func_label)?;
|
context.push_label(
|
||||||
|
BlockFrameType::Block {
|
||||||
|
end_label,
|
||||||
|
},
|
||||||
|
result_ty
|
||||||
|
)?;
|
||||||
Validator::validate_function_block(&mut context, body.code().elements())?;
|
Validator::validate_function_block(&mut context, body.code().elements())?;
|
||||||
|
|
||||||
while !context.frame_stack.is_empty() {
|
while !context.frame_stack.is_empty() {
|
||||||
let branch_label = context.top_label()?.branch_label;
|
context.pop_label()?;
|
||||||
context.sink.resolve_label(branch_label);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context.sink.emit(isa::Instruction::Return {
|
|
||||||
drop: 0,
|
|
||||||
keep: if result_ty == BlockType::NoResult { 0 } else { 1 },
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(context.into_code())
|
Ok(context.into_code())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +170,8 @@ impl Validator {
|
||||||
fn validate_instruction(context: &mut FunctionValidationContext, opcode: &Opcode) -> Result<InstructionOutcome, Error> {
|
fn validate_instruction(context: &mut FunctionValidationContext, opcode: &Opcode) -> Result<InstructionOutcome, Error> {
|
||||||
// TODO: use InstructionOutcome::*;
|
// TODO: use InstructionOutcome::*;
|
||||||
|
|
||||||
|
println!("opcode={:?}", opcode);
|
||||||
|
|
||||||
use self::Opcode::*;
|
use self::Opcode::*;
|
||||||
match *opcode {
|
match *opcode {
|
||||||
// Nop instruction doesn't do anything. It is safe to just skip it.
|
// Nop instruction doesn't do anything. It is safe to just skip it.
|
||||||
|
@ -155,15 +182,25 @@ impl Validator {
|
||||||
},
|
},
|
||||||
|
|
||||||
Block(block_type) => {
|
Block(block_type) => {
|
||||||
let label = context.sink.new_label();
|
let end_label = context.sink.new_label();
|
||||||
context.push_label(BlockFrameType::Block, block_type, label)?;
|
context.push_label(
|
||||||
|
BlockFrameType::Block {
|
||||||
|
end_label
|
||||||
|
},
|
||||||
|
block_type
|
||||||
|
)?;
|
||||||
},
|
},
|
||||||
Loop(block_type) => {
|
Loop(block_type) => {
|
||||||
// Resolve loop header right away.
|
// Resolve loop header right away.
|
||||||
let loop_header = context.top_label()?.branch_label;
|
let header = context.sink.new_label();
|
||||||
context.sink.resolve_label(loop_header);
|
context.sink.resolve_label(header);
|
||||||
|
|
||||||
context.push_label(BlockFrameType::Loop, block_type, loop_header)?;
|
context.push_label(
|
||||||
|
BlockFrameType::Loop {
|
||||||
|
header,
|
||||||
|
},
|
||||||
|
block_type
|
||||||
|
)?;
|
||||||
},
|
},
|
||||||
If(block_type) => {
|
If(block_type) => {
|
||||||
// if
|
// if
|
||||||
|
@ -172,84 +209,143 @@ impl Validator {
|
||||||
//
|
//
|
||||||
// translates to ->
|
// translates to ->
|
||||||
//
|
//
|
||||||
// br_if_not $end
|
// br_if_not $if_not
|
||||||
// ..
|
// ..
|
||||||
// $end
|
// $if_not:
|
||||||
|
|
||||||
|
// if_not will be resolved whenever `end` or `else` operator will be met.
|
||||||
|
let if_not = context.sink.new_label();
|
||||||
let end_label = context.sink.new_label();
|
let end_label = context.sink.new_label();
|
||||||
|
|
||||||
context.pop_value(ValueType::I32.into())?;
|
context.pop_value(ValueType::I32.into())?;
|
||||||
context.push_label(BlockFrameType::IfTrue, block_type, end_label)?;
|
context.push_label(
|
||||||
|
BlockFrameType::IfTrue {
|
||||||
|
if_not,
|
||||||
|
end_label,
|
||||||
|
},
|
||||||
|
block_type
|
||||||
|
)?;
|
||||||
|
|
||||||
|
context.sink.emit_br_eqz(Target {
|
||||||
|
label: if_not,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
Else => {
|
Else => {
|
||||||
|
let (block_type, if_not, end_label) = {
|
||||||
|
let top_frame = context.top_label()?;
|
||||||
|
|
||||||
|
let (if_not, end_label) = match top_frame.frame_type {
|
||||||
|
BlockFrameType::IfTrue { if_not, end_label } => (if_not, end_label),
|
||||||
|
_ => return Err(Error("Misplaced else instruction".into())),
|
||||||
|
};
|
||||||
|
(top_frame.block_type, if_not, end_label)
|
||||||
|
};
|
||||||
|
|
||||||
// First, we need to finish if-true block: add a jump from the end of the if-true block
|
// First, we need to finish if-true block: add a jump from the end of the if-true block
|
||||||
// to the "end_label" (it will be resolved at End).
|
// to the "end_label" (it will be resolved at End).
|
||||||
let end_target = context.require_target(0)?;
|
context.sink.emit_br(Target {
|
||||||
let end_label = end_target.label;
|
label: end_label,
|
||||||
context.sink.emit_br(end_target);
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resolve `if_not` to here so when if condition is unsatisfied control flow
|
||||||
|
// will jump to this label.
|
||||||
|
context.sink.resolve_label(if_not);
|
||||||
|
|
||||||
// Then, we validate. Validator will pop the if..else block and the push else..end block.
|
// Then, we validate. Validator will pop the if..else block and the push else..end block.
|
||||||
let block_type = {
|
|
||||||
let top_frame = context.top_label()?;
|
|
||||||
if top_frame.frame_type != BlockFrameType::IfTrue {
|
|
||||||
return Err(Error("Misplaced else instruction".into()));
|
|
||||||
}
|
|
||||||
top_frame.block_type
|
|
||||||
};
|
|
||||||
context.pop_label()?;
|
context.pop_label()?;
|
||||||
|
|
||||||
if let BlockType::Value(value_type) = block_type {
|
if let BlockType::Value(value_type) = block_type {
|
||||||
context.pop_value(value_type.into())?;
|
context.pop_value(value_type.into())?;
|
||||||
}
|
}
|
||||||
context.push_label(BlockFrameType::IfFalse, block_type, end_label)?;
|
context.push_label(
|
||||||
|
BlockFrameType::IfFalse {
|
||||||
|
end_label,
|
||||||
|
},
|
||||||
|
block_type,
|
||||||
|
)?;
|
||||||
},
|
},
|
||||||
End => {
|
End => {
|
||||||
{
|
{
|
||||||
let frame_type = context.top_label()?.frame_type;
|
let frame_type = context.top_label()?.frame_type;
|
||||||
|
if let BlockFrameType::IfTrue { if_not, .. } = frame_type {
|
||||||
|
if context.top_label()?.block_type != BlockType::NoResult {
|
||||||
|
return Err(
|
||||||
|
Error(
|
||||||
|
format!(
|
||||||
|
"If block without else required to have NoResult block type. But it have {:?} type",
|
||||||
|
context.top_label()?.block_type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// If this end for a non-loop frame then we resolve it's label location to here.
|
context.sink.resolve_label(if_not);
|
||||||
if frame_type != BlockFrameType::Loop {
|
|
||||||
let loop_header = context.top_label()?.branch_label;
|
|
||||||
context.sink.resolve_label(loop_header);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let top_frame = context.top_label()?;
|
let frame_type = context.top_label()?.frame_type;
|
||||||
if top_frame.frame_type == BlockFrameType::IfTrue {
|
|
||||||
if top_frame.block_type != BlockType::NoResult {
|
// If this end for a non-loop frame then we resolve it's label location to here.
|
||||||
return Err(Error(format!("If block without else required to have NoResult block type. But it have {:?} type", top_frame.block_type)));
|
if !frame_type.is_loop() {
|
||||||
}
|
let end_label = frame_type.end_label();
|
||||||
|
context.sink.resolve_label(end_label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.frame_stack.len() == 1 {
|
||||||
|
// We are about to close the last frame. Insert
|
||||||
|
// an explicit return.
|
||||||
|
let (drop, keep) = context.drop_keep_return()?;
|
||||||
|
context.sink.emit(isa::Instruction::Return {
|
||||||
|
drop,
|
||||||
|
keep,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
context.pop_label()?;
|
context.pop_label()?;
|
||||||
},
|
},
|
||||||
Br(depth) => {
|
Br(depth) => {
|
||||||
Validator::validate_br(context, depth)?;
|
|
||||||
let target = context.require_target(depth)?;
|
let target = context.require_target(depth)?;
|
||||||
context.sink.emit_br(target);
|
context.sink.emit_br(target);
|
||||||
|
|
||||||
|
Validator::validate_br(context, depth)?;
|
||||||
|
|
||||||
|
return Ok(InstructionOutcome::Unreachable);
|
||||||
},
|
},
|
||||||
BrIf(depth) => {
|
BrIf(depth) => {
|
||||||
Validator::validate_br_if(context, depth)?;
|
|
||||||
let target = context.require_target(depth)?;
|
let target = context.require_target(depth)?;
|
||||||
context.sink.emit_br_nez(target);
|
context.sink.emit_br_nez(target);
|
||||||
|
|
||||||
|
Validator::validate_br_if(context, depth)?;
|
||||||
},
|
},
|
||||||
BrTable(ref table, default) => {
|
BrTable(ref table, default) => {
|
||||||
Validator::validate_br_table(context, table, default)?;
|
|
||||||
let mut targets = Vec::new();
|
let mut targets = Vec::new();
|
||||||
for depth in table.iter() {
|
for depth in table.iter() {
|
||||||
let target = context.require_target(*depth)?;
|
let target = context.require_target(*depth)?;
|
||||||
targets.push(target);
|
targets.push(target);
|
||||||
}
|
}
|
||||||
let default = context.require_target(default)?;
|
let default_target = context.require_target(default)?;
|
||||||
context.sink.emit_br_table(&targets, default);
|
context.sink.emit_br_table(&targets, default_target);
|
||||||
|
|
||||||
|
Validator::validate_br_table(context, table, default)?;
|
||||||
|
|
||||||
|
return Ok(InstructionOutcome::Unreachable);
|
||||||
},
|
},
|
||||||
Return => {
|
Return => {
|
||||||
Validator::validate_return(context)?;
|
|
||||||
let (drop, keep) = context.drop_keep_return()?;
|
let (drop, keep) = context.drop_keep_return()?;
|
||||||
context.sink.emit(isa::Instruction::Return {
|
context.sink.emit(isa::Instruction::Return {
|
||||||
drop,
|
drop,
|
||||||
keep,
|
keep,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Validator::validate_return(context)?;
|
||||||
|
|
||||||
|
return Ok(InstructionOutcome::Unreachable);
|
||||||
},
|
},
|
||||||
|
|
||||||
Call(index) => {
|
Call(index) => {
|
||||||
|
@ -280,10 +376,8 @@ impl Validator {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
SetLocal(index) => {
|
SetLocal(index) => {
|
||||||
// We need to calculate relative depth before validation since
|
|
||||||
// it will change value stack size.
|
|
||||||
let depth = context.relative_local_depth(index)?;
|
|
||||||
Validator::validate_set_local(context, index)?;
|
Validator::validate_set_local(context, index)?;
|
||||||
|
let depth = context.relative_local_depth(index)?;
|
||||||
context.sink.emit(
|
context.sink.emit(
|
||||||
isa::Instruction::SetLocal(depth),
|
isa::Instruction::SetLocal(depth),
|
||||||
);
|
);
|
||||||
|
@ -1205,7 +1299,7 @@ impl Validator {
|
||||||
let frame = context.require_label(idx)?;
|
let frame = context.require_label(idx)?;
|
||||||
(frame.frame_type, frame.block_type)
|
(frame.frame_type, frame.block_type)
|
||||||
};
|
};
|
||||||
if frame_type != BlockFrameType::Loop {
|
if !frame_type.is_loop() {
|
||||||
if let BlockType::Value(value_type) = frame_block_type {
|
if let BlockType::Value(value_type) = frame_block_type {
|
||||||
context.tee_value(value_type.into())?;
|
context.tee_value(value_type.into())?;
|
||||||
}
|
}
|
||||||
|
@ -1220,7 +1314,7 @@ impl Validator {
|
||||||
let frame = context.require_label(idx)?;
|
let frame = context.require_label(idx)?;
|
||||||
(frame.frame_type, frame.block_type)
|
(frame.frame_type, frame.block_type)
|
||||||
};
|
};
|
||||||
if frame_type != BlockFrameType::Loop {
|
if !frame_type.is_loop() {
|
||||||
if let BlockType::Value(value_type) = frame_block_type {
|
if let BlockType::Value(value_type) = frame_block_type {
|
||||||
context.tee_value(value_type.into())?;
|
context.tee_value(value_type.into())?;
|
||||||
}
|
}
|
||||||
|
@ -1231,7 +1325,7 @@ impl Validator {
|
||||||
fn validate_br_table(context: &mut FunctionValidationContext, table: &[u32], default: u32) -> Result<InstructionOutcome, Error> {
|
fn validate_br_table(context: &mut FunctionValidationContext, table: &[u32], default: u32) -> Result<InstructionOutcome, Error> {
|
||||||
let required_block_type: BlockType = {
|
let required_block_type: BlockType = {
|
||||||
let default_block = context.require_label(default)?;
|
let default_block = context.require_label(default)?;
|
||||||
let required_block_type = if default_block.frame_type != BlockFrameType::Loop {
|
let required_block_type = if !default_block.frame_type.is_loop() {
|
||||||
default_block.block_type
|
default_block.block_type
|
||||||
} else {
|
} else {
|
||||||
BlockType::NoResult
|
BlockType::NoResult
|
||||||
|
@ -1239,7 +1333,7 @@ impl Validator {
|
||||||
|
|
||||||
for label in table {
|
for label in table {
|
||||||
let label_block = context.require_label(*label)?;
|
let label_block = context.require_label(*label)?;
|
||||||
let label_block_type = if label_block.frame_type != BlockFrameType::Loop {
|
let label_block_type = if !label_block.frame_type.is_loop() {
|
||||||
label_block.block_type
|
label_block.block_type
|
||||||
} else {
|
} else {
|
||||||
BlockType::NoResult
|
BlockType::NoResult
|
||||||
|
@ -1322,6 +1416,25 @@ impl Validator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Function validation context.
|
||||||
|
struct FunctionValidationContext<'a> {
|
||||||
|
/// Wasm module
|
||||||
|
module: &'a ModuleContext,
|
||||||
|
/// Current instruction position.
|
||||||
|
position: usize,
|
||||||
|
/// Local variables.
|
||||||
|
locals: Locals<'a>,
|
||||||
|
/// Value stack.
|
||||||
|
value_stack: StackWithLimit<StackValueType>,
|
||||||
|
/// Frame stack.
|
||||||
|
frame_stack: StackWithLimit<BlockFrame>,
|
||||||
|
/// Function return type.
|
||||||
|
return_type: BlockType,
|
||||||
|
|
||||||
|
// TODO: comment
|
||||||
|
sink: Sink,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> FunctionValidationContext<'a> {
|
impl<'a> FunctionValidationContext<'a> {
|
||||||
fn new(
|
fn new(
|
||||||
module: &'a ModuleContext,
|
module: &'a ModuleContext,
|
||||||
|
@ -1336,7 +1449,7 @@ impl<'a> FunctionValidationContext<'a> {
|
||||||
locals: locals,
|
locals: locals,
|
||||||
value_stack: StackWithLimit::with_limit(value_stack_limit),
|
value_stack: StackWithLimit::with_limit(value_stack_limit),
|
||||||
frame_stack: StackWithLimit::with_limit(frame_stack_limit),
|
frame_stack: StackWithLimit::with_limit(frame_stack_limit),
|
||||||
return_type: Some(return_type),
|
return_type: return_type,
|
||||||
sink: Sink::new(),
|
sink: Sink::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1395,12 +1508,11 @@ impl<'a> FunctionValidationContext<'a> {
|
||||||
Ok(self.frame_stack.top()?)
|
Ok(self.frame_stack.top()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType, branch_label: LabelId) -> Result<(), Error> {
|
fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), Error> {
|
||||||
Ok(self.frame_stack.push(BlockFrame {
|
Ok(self.frame_stack.push(BlockFrame {
|
||||||
frame_type: frame_type,
|
frame_type: frame_type,
|
||||||
block_type: block_type,
|
block_type: block_type,
|
||||||
begin_position: self.position,
|
begin_position: self.position,
|
||||||
branch_label,
|
|
||||||
value_stack_len: self.value_stack.len(),
|
value_stack_len: self.value_stack.len(),
|
||||||
polymorphic_stack: false,
|
polymorphic_stack: false,
|
||||||
})?)
|
})?)
|
||||||
|
@ -1420,6 +1532,11 @@ impl<'a> FunctionValidationContext<'a> {
|
||||||
|
|
||||||
let frame = self.frame_stack.pop()?;
|
let frame = self.frame_stack.pop()?;
|
||||||
if self.value_stack.len() != frame.value_stack_len {
|
if self.value_stack.len() != frame.value_stack_len {
|
||||||
|
if true {
|
||||||
|
panic!("Unexpected stack height {}, expected {}",
|
||||||
|
self.value_stack.len(),
|
||||||
|
frame.value_stack_len)
|
||||||
|
}
|
||||||
return Err(Error(format!(
|
return Err(Error(format!(
|
||||||
"Unexpected stack height {}, expected {}",
|
"Unexpected stack height {}, expected {}",
|
||||||
self.value_stack.len(),
|
self.value_stack.len(),
|
||||||
|
@ -1439,7 +1556,7 @@ impl<'a> FunctionValidationContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn return_type(&self) -> Result<BlockType, Error> {
|
fn return_type(&self) -> Result<BlockType, Error> {
|
||||||
self.return_type.ok_or(Error("Trying to return from expression".into()))
|
Ok(self.return_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn require_local(&self, idx: u32) -> Result<StackValueType, Error> {
|
fn require_local(&self, idx: u32) -> Result<StackValueType, Error> {
|
||||||
|
@ -1448,19 +1565,24 @@ impl<'a> FunctionValidationContext<'a> {
|
||||||
|
|
||||||
fn require_target(&self, depth: u32) -> Result<Target, Error> {
|
fn require_target(&self, depth: u32) -> Result<Target, Error> {
|
||||||
let frame = self.require_label(depth)?;
|
let frame = self.require_label(depth)?;
|
||||||
let label = frame.branch_label;
|
|
||||||
|
|
||||||
let keep: u8 = match (frame.frame_type, frame.block_type) {
|
let keep: u8 = match (frame.frame_type, frame.block_type) {
|
||||||
(BlockFrameType::Loop, _) => 0,
|
(BlockFrameType::Loop { .. }, _) => 0,
|
||||||
(_, BlockType::NoResult) => 0,
|
(_, BlockType::NoResult) => 0,
|
||||||
(_, BlockType::Value(_)) => 1,
|
(_, BlockType::Value(_)) => 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let value_stack_height = self.value_stack.len() as u32;
|
let value_stack_height = self.value_stack.len() as u32;
|
||||||
let drop = value_stack_height - frame.value_stack_len as u32 - keep as u32;
|
let drop = if frame.polymorphic_stack { 0 } else {
|
||||||
|
// TODO
|
||||||
|
println!("value_stack_height = {}", value_stack_height);
|
||||||
|
println!("frame.value_stack_len = {}", frame.value_stack_len);
|
||||||
|
println!("keep = {}", keep);
|
||||||
|
(value_stack_height - frame.value_stack_len as u32) - keep as u32
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Target {
|
Ok(Target {
|
||||||
label,
|
label: frame.frame_type.br_destination(),
|
||||||
keep,
|
keep,
|
||||||
drop,
|
drop,
|
||||||
})
|
})
|
||||||
|
@ -1468,8 +1590,11 @@ impl<'a> FunctionValidationContext<'a> {
|
||||||
|
|
||||||
fn drop_keep_return(&self) -> Result<(u32, u8), Error> {
|
fn drop_keep_return(&self) -> Result<(u32, u8), Error> {
|
||||||
// TODO: Refactor
|
// TODO: Refactor
|
||||||
let deepest = self.frame_stack.len();
|
let deepest = (self.frame_stack.len() - 1) as u32;
|
||||||
let target = self.require_target(deepest as u32)?;
|
let mut target = self.require_target(deepest)?;
|
||||||
|
|
||||||
|
// Drop all local variables and parameters upon exit.
|
||||||
|
target.drop += self.locals.count()?;
|
||||||
|
|
||||||
Ok((target.drop, target.keep))
|
Ok((target.drop, target.keep))
|
||||||
}
|
}
|
||||||
|
@ -1684,8 +1809,10 @@ impl Sink {
|
||||||
if let Label::Resolved(_) = self.labels[label.0] {
|
if let Label::Resolved(_) = self.labels[label.0] {
|
||||||
panic!("Trying to resolve already resolved label");
|
panic!("Trying to resolve already resolved label");
|
||||||
}
|
}
|
||||||
|
|
||||||
let dst_pc = self.cur_pc();
|
let dst_pc = self.cur_pc();
|
||||||
|
|
||||||
|
// Patch all relocations that was previously recorded for this
|
||||||
|
// particular label.
|
||||||
let unresolved_rels = self.unresolved.remove(&label).unwrap_or(Vec::new());
|
let unresolved_rels = self.unresolved.remove(&label).unwrap_or(Vec::new());
|
||||||
for reloc in unresolved_rels {
|
for reloc in unresolved_rels {
|
||||||
match reloc {
|
match reloc {
|
||||||
|
@ -1701,6 +1828,9 @@ impl Sink {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark this label as resolved.
|
||||||
|
self.labels[label.0] = Label::Resolved(dst_pc);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_inner(self) -> Vec<isa::Instruction> {
|
fn into_inner(self) -> Vec<isa::Instruction> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashSet;
|
||||||
use parity_wasm::elements::{
|
use parity_wasm::elements::{
|
||||||
BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Opcode,
|
BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Opcode,
|
||||||
ResizableLimits, TableType, ValueType, InitExpr, Type
|
ResizableLimits, TableType, ValueType, InitExpr, Type
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use super::validate_module;
|
use super::{validate_module, ValidatedModule};
|
||||||
use parity_wasm::builder::module;
|
use parity_wasm::builder::module;
|
||||||
use parity_wasm::elements::{
|
use parity_wasm::elements::{
|
||||||
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
|
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
|
||||||
Opcode, Opcodes, TableType, ValueType, BlockType, deserialize_buffer,
|
Opcode, Opcodes, TableType, ValueType, BlockType, deserialize_buffer,
|
||||||
Module,
|
Module,
|
||||||
};
|
};
|
||||||
use isa;
|
use isa;
|
||||||
|
@ -303,16 +303,21 @@ fn if_else_with_return_type_validation() {
|
||||||
validate_module(m).unwrap();
|
validate_module(m).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile(wat: &str) -> Vec<isa::Instruction> {
|
fn validate(wat: &str) -> ValidatedModule {
|
||||||
let wasm = wabt::wat2wasm(wat).unwrap();
|
let wasm = wabt::wat2wasm(wat).unwrap();
|
||||||
let module = deserialize_buffer::<Module>(&wasm).unwrap();
|
let module = deserialize_buffer::<Module>(&wasm).unwrap();
|
||||||
let validated_module = validate_module(module).unwrap();
|
let validated_module = validate_module(module).unwrap();
|
||||||
|
validated_module
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile(wat: &str) -> Vec<isa::Instruction> {
|
||||||
|
let validated_module = validate(wat);
|
||||||
let code = &validated_module.code_map[0];
|
let code = &validated_module.code_map[0];
|
||||||
code.code.clone()
|
code.code.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn explicit_return_no_value() {
|
fn implicit_return_no_value() {
|
||||||
let code = compile(r#"
|
let code = compile(r#"
|
||||||
(module
|
(module
|
||||||
(func (export "call")
|
(func (export "call")
|
||||||
|
@ -331,7 +336,7 @@ fn explicit_return_no_value() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn explicit_return_with_value() {
|
fn implicit_return_with_value() {
|
||||||
let code = compile(r#"
|
let code = compile(r#"
|
||||||
(module
|
(module
|
||||||
(func (export "call") (result i32)
|
(func (export "call") (result i32)
|
||||||
|
@ -352,8 +357,8 @@ fn explicit_return_with_value() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn explicit_return_param() {
|
fn implicit_return_param() {
|
||||||
let code = compile(r#"
|
let code = compile(r#"
|
||||||
(module
|
(module
|
||||||
(func (export "call") (param i32)
|
(func (export "call") (param i32)
|
||||||
)
|
)
|
||||||
|
@ -369,3 +374,408 @@ fn explicit_return_param() {
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_local() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (param i32) (result i32)
|
||||||
|
get_local 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::GetLocal(1),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn explicit_return() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (param i32) (result i32)
|
||||||
|
get_local 0
|
||||||
|
return
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::GetLocal(1),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 1,
|
||||||
|
},
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_params() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (param i32) (param i32) (result i32)
|
||||||
|
get_local 0
|
||||||
|
get_local 1
|
||||||
|
i32.add
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
// This is tricky. Locals are now loaded from the stack. The load
|
||||||
|
// happens from address relative of the current stack pointer. The first load
|
||||||
|
// takes the value below the previous one (i.e the second argument) and then, it increments
|
||||||
|
// the stack pointer. And then the same thing hapens with the value below the previous one
|
||||||
|
// (which happens to be the value loaded by the first get_local).
|
||||||
|
isa::Instruction::GetLocal(2),
|
||||||
|
isa::Instruction::GetLocal(2),
|
||||||
|
isa::Instruction::I32Add,
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 2,
|
||||||
|
keep: 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drop_locals() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (param i32)
|
||||||
|
(local i32)
|
||||||
|
get_local 0
|
||||||
|
set_local 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::GetLocal(2),
|
||||||
|
isa::Instruction::SetLocal(1),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 2,
|
||||||
|
keep: 0,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_without_else() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (param i32) (result i32)
|
||||||
|
i32.const 1
|
||||||
|
if
|
||||||
|
i32.const 2
|
||||||
|
return
|
||||||
|
end
|
||||||
|
i32.const 3
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfEqz(isa::Target {
|
||||||
|
dst_pc: 4,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1, // 1 param
|
||||||
|
keep: 1, // 1 result
|
||||||
|
},
|
||||||
|
isa::Instruction::I32Const(3),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_else() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call")
|
||||||
|
(local i32)
|
||||||
|
i32.const 1
|
||||||
|
if
|
||||||
|
i32.const 2
|
||||||
|
set_local 0
|
||||||
|
else
|
||||||
|
i32.const 3
|
||||||
|
set_local 0
|
||||||
|
end
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfEqz(isa::Target {
|
||||||
|
dst_pc: 5,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::SetLocal(1),
|
||||||
|
isa::Instruction::Br(isa::Target {
|
||||||
|
dst_pc: 7,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(3),
|
||||||
|
isa::Instruction::SetLocal(1),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_else_returns_result() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call")
|
||||||
|
i32.const 1
|
||||||
|
if (result i32)
|
||||||
|
i32.const 2
|
||||||
|
else
|
||||||
|
i32.const 3
|
||||||
|
end
|
||||||
|
drop
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfEqz(isa::Target {
|
||||||
|
dst_pc: 4,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::Br(isa::Target {
|
||||||
|
dst_pc: 5,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(3),
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_else_branch_from_true_branch() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call")
|
||||||
|
i32.const 1
|
||||||
|
if (result i32)
|
||||||
|
i32.const 1
|
||||||
|
i32.const 1
|
||||||
|
br_if 0
|
||||||
|
drop
|
||||||
|
i32.const 2
|
||||||
|
else
|
||||||
|
i32.const 3
|
||||||
|
end
|
||||||
|
drop
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfEqz(isa::Target {
|
||||||
|
dst_pc: 8,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfNez(isa::Target {
|
||||||
|
dst_pc: 9,
|
||||||
|
drop: 1, // TODO: Is this correct?
|
||||||
|
keep: 1, // TODO: Is this correct?
|
||||||
|
}),
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::Br(isa::Target {
|
||||||
|
dst_pc: 9,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(3),
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_else_branch_from_false_branch() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call")
|
||||||
|
i32.const 1
|
||||||
|
if (result i32)
|
||||||
|
i32.const 1
|
||||||
|
else
|
||||||
|
i32.const 2
|
||||||
|
i32.const 1
|
||||||
|
br_if 0
|
||||||
|
drop
|
||||||
|
i32.const 3
|
||||||
|
end
|
||||||
|
drop
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfEqz(isa::Target {
|
||||||
|
dst_pc: 4,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::Br(isa::Target {
|
||||||
|
dst_pc: 9,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfNez(isa::Target {
|
||||||
|
dst_pc: 9,
|
||||||
|
drop: 1, // TODO: Is this correct?
|
||||||
|
keep: 1,
|
||||||
|
}),
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::I32Const(3),
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_loop() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call")
|
||||||
|
loop (result i32)
|
||||||
|
i32.const 1
|
||||||
|
br_if 0
|
||||||
|
i32.const 2
|
||||||
|
end
|
||||||
|
drop
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfNez(isa::Target {
|
||||||
|
dst_pc: 0,
|
||||||
|
drop: 1,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Loop
|
||||||
|
// TODO: Empty loop?
|
||||||
|
// TODO: brtable
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wabt_example() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (param i32) (result i32)
|
||||||
|
block $exit
|
||||||
|
get_local 0
|
||||||
|
br_if $exit
|
||||||
|
i32.const 1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
i32.const 2
|
||||||
|
return
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::GetLocal(1),
|
||||||
|
isa::Instruction::BrIfNez(isa::Target {
|
||||||
|
dst_pc: 4,
|
||||||
|
keep: 0,
|
||||||
|
drop: 1,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1, // 1 parameter
|
||||||
|
keep: 1, // return value
|
||||||
|
},
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 1,
|
||||||
|
},
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -20,11 +20,16 @@ impl<'a> Locals<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns parameter count.
|
||||||
|
pub fn param_count(&self) -> u32 {
|
||||||
|
self.params.len() as u32
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns total count of all declared locals and paramaterers.
|
/// Returns total count of all declared locals and paramaterers.
|
||||||
///
|
///
|
||||||
/// Returns `Err` if count overflows 32-bit value.
|
/// Returns `Err` if count overflows 32-bit value.
|
||||||
pub fn count(&self) -> Result<u32, Error> {
|
pub fn count(&self) -> Result<u32, Error> {
|
||||||
let mut acc = self.params.len() as u32;
|
let mut acc = self.param_count();
|
||||||
for locals_group in self.local_groups {
|
for locals_group in self.local_groups {
|
||||||
acc = acc
|
acc = acc
|
||||||
.checked_add(locals_group.count())
|
.checked_add(locals_group.count())
|
||||||
|
@ -44,7 +49,7 @@ impl<'a> Locals<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If an index doesn't point to a param, then we have to look into local declarations.
|
// If an index doesn't point to a param, then we have to look into local declarations.
|
||||||
let mut start_idx = self.params.len() as u32;
|
let mut start_idx = self.param_count();
|
||||||
for locals_group in self.local_groups {
|
for locals_group in self.local_groups {
|
||||||
let end_idx = start_idx
|
let end_idx = start_idx
|
||||||
.checked_add(locals_group.count())
|
.checked_add(locals_group.count())
|
||||||
|
|
Loading…
Reference in New Issue