This commit is contained in:
Sergey Pepyakin 2018-06-12 22:13:37 +03:00
parent 1702372696
commit 5653e2809f
10 changed files with 1462 additions and 493 deletions

View File

@ -7,6 +7,8 @@ pub const DEFAULT_MEMORY_INDEX: u32 = 0;
/// Index of default table.
pub const DEFAULT_TABLE_INDEX: u32 = 0;
// TODO: Move BlockFrame under validation.
/// Control stack frame.
#[derive(Debug, Clone)]
pub struct BlockFrame {

View File

@ -1,12 +1,12 @@
use std::rc::{Rc, Weak};
use std::fmt;
use std::collections::HashMap;
use parity_wasm::elements::{Local, Opcodes};
use parity_wasm::elements::Local;
use {Trap, TrapKind, Signature};
use host::Externals;
use runner::{check_function_args, Interpreter};
use value::RuntimeValue;
use module::ModuleInstance;
use isa;
/// Reference to a function (See [`FuncInstance`] for details).
///
@ -158,6 +158,5 @@ impl FuncInstance {
#[derive(Clone, Debug)]
pub struct FuncBody {
pub locals: Vec<Local>,
pub opcodes: Opcodes,
pub labels: HashMap<usize, usize>,
pub code: isa::Instructions,
}

View File

@ -1,241 +0,0 @@
/// Enumeration of instruction set used by wasmi.
///
/// The instruction set is mostly derived from Wasm. However,
/// there is a substantial difference.
///
/// # Structured Stack Machine vs Traditional One
///
/// Wasm is a structured stack machine. Wasm encodes control flow in structures
/// similar to that commonly found in a programming languages
/// such as if, while. That contrasts to a traditional stack machine which
/// encodes all control flow with goto-like instructions.
///
/// Structured stack machine code aligns well with goals of Wasm,
/// namely providing fast validation of Wasm code and compilation to native code.
///
/// Unfortunately, the downside of structured stack machine code is
/// that it is less convenient to interpret. For example, let's look at
/// the following example in hypothetical structured stack machine:
///
/// ```
/// loop
/// ...
/// if_true_jump_to_end
/// ...
/// end
/// ```
///
/// To execute `if_true_jump_to_end` , the interpreter needs to skip all instructions
/// until it reaches the *matching* `end`. That's quite inefficient compared
/// to a plain goto to the specific position.
///
/// # Differences from Wasm
///
/// - There is no `nop` instruction.
/// - All control flow strucutres are flattened to plain gotos.
/// - Implicit returns via reaching function scope `End` are replaced with an explicit `return` instruction.
/// - Locals live on the value stack now.
enum Instruction {
/// Keep the specified amount of operands and then
/// drop the rest.
DropKeep {
drop: u32,
keep: u32,
},
/// Push a local variable or an argument from the specified depth.
GetLocal {
depth: u32
},
/// Pop a value and put it in at the specified depth.
SetLocal {
depth: u32
},
/// Copy a value to the specified depth.
TeeLocal(u32),
/// Similar to the Wasm ones, but instead of a label depth
/// they specify direct PC.
Br(u32),
BrIf(u32),
BrTable(Box<[u32]>, u32),
Unreachable,
Return,
Call(u32),
CallIndirect(u32, u8),
Drop,
Select,
GetGlobal(u32),
SetGlobal(u32),
// All store/load instructions operate with 'memory immediates'
// which represented here as (flag, offset) tuple
I32Load(u32, u32),
I64Load(u32, u32),
F32Load(u32, u32),
F64Load(u32, u32),
I32Load8S(u32, u32),
I32Load8U(u32, u32),
I32Load16S(u32, u32),
I32Load16U(u32, u32),
I64Load8S(u32, u32),
I64Load8U(u32, u32),
I64Load16S(u32, u32),
I64Load16U(u32, u32),
I64Load32S(u32, u32),
I64Load32U(u32, u32),
I32Store(u32, u32),
I64Store(u32, u32),
F32Store(u32, u32),
F64Store(u32, u32),
I32Store8(u32, u32),
I32Store16(u32, u32),
I64Store8(u32, u32),
I64Store16(u32, u32),
I64Store32(u32, u32),
CurrentMemory(u8),
GrowMemory(u8),
I32Const(i32),
I64Const(i64),
F32Const(u32),
F64Const(u64),
I32Eqz,
I32Eq,
I32Ne,
I32LtS,
I32LtU,
I32GtS,
I32GtU,
I32LeS,
I32LeU,
I32GeS,
I32GeU,
I64Eqz,
I64Eq,
I64Ne,
I64LtS,
I64LtU,
I64GtS,
I64GtU,
I64LeS,
I64LeU,
I64GeS,
I64GeU,
F32Eq,
F32Ne,
F32Lt,
F32Gt,
F32Le,
F32Ge,
F64Eq,
F64Ne,
F64Lt,
F64Gt,
F64Le,
F64Ge,
I32Clz,
I32Ctz,
I32Popcnt,
I32Add,
I32Sub,
I32Mul,
I32DivS,
I32DivU,
I32RemS,
I32RemU,
I32And,
I32Or,
I32Xor,
I32Shl,
I32ShrS,
I32ShrU,
I32Rotl,
I32Rotr,
I64Clz,
I64Ctz,
I64Popcnt,
I64Add,
I64Sub,
I64Mul,
I64DivS,
I64DivU,
I64RemS,
I64RemU,
I64And,
I64Or,
I64Xor,
I64Shl,
I64ShrS,
I64ShrU,
I64Rotl,
I64Rotr,
F32Abs,
F32Neg,
F32Ceil,
F32Floor,
F32Trunc,
F32Nearest,
F32Sqrt,
F32Add,
F32Sub,
F32Mul,
F32Div,
F32Min,
F32Max,
F32Copysign,
F64Abs,
F64Neg,
F64Ceil,
F64Floor,
F64Trunc,
F64Nearest,
F64Sqrt,
F64Add,
F64Sub,
F64Mul,
F64Div,
F64Min,
F64Max,
F64Copysign,
I32WrapI64,
I32TruncSF32,
I32TruncUF32,
I32TruncSF64,
I32TruncUF64,
I64ExtendSI32,
I64ExtendUI32,
I64TruncSF32,
I64TruncUF32,
I64TruncSF64,
I64TruncUF64,
F32ConvertSI32,
F32ConvertUI32,
F32ConvertSI64,
F32ConvertUI64,
F32DemoteF64,
F64ConvertSI32,
F64ConvertUI32,
F64ConvertSI64,
F64ConvertUI64,
F64PromoteF32,
I32ReinterpretF32,
I64ReinterpretF64,
F32ReinterpretI32,
F64ReinterpretI64,
}

256
src/isa.rs Normal file
View File

@ -0,0 +1,256 @@
//! An instruction set used by wasmi.
//!
//! The instruction set is mostly derived from Wasm. However,
//! there is a substantial difference.
//!
//! # Structured Stack Machine vs Traditional One
//!
//! Wasm is a structured stack machine. Wasm encodes control flow in structures
//! similar to that commonly found in a programming languages
//! such as if, while. That contrasts to a traditional stack machine which
//! encodes all control flow with goto-like instructions.
//!
//! Structured stack machine code aligns well with goals of Wasm,
//! namely providing fast validation of Wasm code and compilation to native code.
//!
//! Unfortunately, the downside of structured stack machine code is
//! that it is less convenient to interpret. For example, let's look at
//! the following example in hypothetical structured stack machine:
//!
//! ```
//! loop
//! ...
//! if_true_jump_to_end
//! ...
//! end
//! ```
//!
//! To execute `if_true_jump_to_end` , the interpreter needs to skip all instructions
//! until it reaches the *matching* `end`. That's quite inefficient compared
//! to a plain goto to the specific position.
//!
//! # Differences from Wasm
//!
//! - There is no `nop` instruction.
//! - All control flow strucutres are flattened to plain gotos.
//! - Implicit returns via reaching function scope `End` are replaced with an explicit `return` instruction.
//! - Locals live on the value stack now.
//! - Load/store instructions doesn't take `align` parameter.
//! - *.const store value in straight encoding.
//! - Reserved immediates are ignored for `call_indirect`, `current_memory`, `grow_memory`.
//!
#[derive(Debug, Clone)]
pub struct Target {
pub dst_pc: u32,
pub drop: u32,
pub keep: u8,
}
#[allow(unused)] // TODO: Remove
#[derive(Debug, Clone)]
pub enum Instruction {
/// Push a local variable or an argument from the specified depth.
GetLocal {
depth: u32
},
/// Pop a value and put it in at the specified depth.
SetLocal {
depth: u32
},
/// Copy a value to the specified depth.
TeeLocal { depth: u32 },
/// Similar to the Wasm ones, but instead of a label depth
/// they specify direct PC.
Br(Target),
BrIfEqz(Target),
BrIfNez(Target),
/// Last one is the default.
///
/// Can be less than zero.
BrTable(Box<[Target]>),
Unreachable,
Return,
Call(u32),
CallIndirect(u32),
Drop,
Select,
GetGlobal(u32),
SetGlobal(u32),
I32Load(u32),
I64Load(u32),
F32Load(u32),
F64Load(u32),
I32Load8S(u32),
I32Load8U(u32),
I32Load16S(u32),
I32Load16U(u32),
I64Load8S(u32),
I64Load8U(u32),
I64Load16S(u32),
I64Load16U(u32),
I64Load32S(u32),
I64Load32U(u32),
I32Store(u32),
I64Store(u32),
F32Store(u32),
F64Store(u32),
I32Store8(u32),
I32Store16(u32),
I64Store8(u32),
I64Store16(u32),
I64Store32(u32),
CurrentMemory,
GrowMemory,
I32Const(i32),
I64Const(i64),
F32Const(u32),
F64Const(u64),
I32Eqz,
I32Eq,
I32Ne,
I32LtS,
I32LtU,
I32GtS,
I32GtU,
I32LeS,
I32LeU,
I32GeS,
I32GeU,
I64Eqz,
I64Eq,
I64Ne,
I64LtS,
I64LtU,
I64GtS,
I64GtU,
I64LeS,
I64LeU,
I64GeS,
I64GeU,
F32Eq,
F32Ne,
F32Lt,
F32Gt,
F32Le,
F32Ge,
F64Eq,
F64Ne,
F64Lt,
F64Gt,
F64Le,
F64Ge,
I32Clz,
I32Ctz,
I32Popcnt,
I32Add,
I32Sub,
I32Mul,
I32DivS,
I32DivU,
I32RemS,
I32RemU,
I32And,
I32Or,
I32Xor,
I32Shl,
I32ShrS,
I32ShrU,
I32Rotl,
I32Rotr,
I64Clz,
I64Ctz,
I64Popcnt,
I64Add,
I64Sub,
I64Mul,
I64DivS,
I64DivU,
I64RemS,
I64RemU,
I64And,
I64Or,
I64Xor,
I64Shl,
I64ShrS,
I64ShrU,
I64Rotl,
I64Rotr,
F32Abs,
F32Neg,
F32Ceil,
F32Floor,
F32Trunc,
F32Nearest,
F32Sqrt,
F32Add,
F32Sub,
F32Mul,
F32Div,
F32Min,
F32Max,
F32Copysign,
F64Abs,
F64Neg,
F64Ceil,
F64Floor,
F64Trunc,
F64Nearest,
F64Sqrt,
F64Add,
F64Sub,
F64Mul,
F64Div,
F64Min,
F64Max,
F64Copysign,
I32WrapI64,
I32TruncSF32,
I32TruncUF32,
I32TruncSF64,
I32TruncUF64,
I64ExtendSI32,
I64ExtendUI32,
I64TruncSF32,
I64TruncUF32,
I64TruncSF64,
I64TruncUF64,
F32ConvertSI32,
F32ConvertUI32,
F32ConvertSI64,
F32ConvertUI64,
F32DemoteF64,
F64ConvertSI32,
F64ConvertUI32,
F64ConvertSI64,
F64ConvertUI64,
F64PromoteF32,
I32ReinterpretF32,
I64ReinterpretF64,
F32ReinterpretI32,
F64ReinterpretI64,
}
#[derive(Debug, Clone)]
pub struct Instructions {
pub code: Vec<Instruction>,
}

View File

@ -356,7 +356,7 @@ mod imports;
mod global;
mod func;
mod types;
mod instructions;
mod isa;
#[cfg(test)]
mod tests;
@ -379,7 +379,7 @@ pub mod memory_units {
/// Deserialized module prepared for instantiation.
pub struct Module {
labels: HashMap<usize, HashMap<usize, usize>>,
code_map: Vec<isa::Instructions>,
module: parity_wasm::elements::Module,
}
@ -419,12 +419,12 @@ impl Module {
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
use validation::{validate_module, ValidatedModule};
let ValidatedModule {
labels,
code_map,
module,
} = validate_module(module)?;
Ok(Module {
labels,
code_map,
module,
})
}
@ -525,7 +525,7 @@ impl Module {
&self.module
}
pub(crate) fn labels(&self) -> &HashMap<usize, HashMap<usize, usize>> {
&self.labels
pub(crate) fn code(&self) -> &Vec<isa::Instructions> {
&self.code_map
}
}

View File

@ -291,7 +291,7 @@ impl ModuleInstance {
}
}
let labels = loaded_module.labels();
let code = loaded_module.code();
{
let funcs = module.function_section().map(|fs| fs.entries()).unwrap_or(
&[],
@ -308,13 +308,12 @@ impl ModuleInstance {
let signature = instance.signature_by_index(ty.type_ref()).expect(
"Due to validation type should exists",
);
let labels = labels.get(&index).expect(
let code = code.get(index).expect(
"At func validation time labels are collected; Collected labels are added by index; qed",
).clone();
let func_body = FuncBody {
locals: body.locals().to_vec(),
opcodes: body.code().clone(),
labels: labels,
code: code,
};
let func_instance =
FuncInstance::alloc_internal(Rc::downgrade(&instance.0), signature, func_body);

View File

@ -84,7 +84,7 @@ impl<'a, E: Externals> Interpreter<'a, E> {
if !function_context.is_initialized() {
let return_type = function_context.return_type;
function_context.initialize(&function_body.locals);
function_context.push_frame(&function_body.labels, BlockFrameType::Function, return_type).map_err(Trap::new)?;
function_context.push_frame(BlockFrameType::Function, return_type).map_err(Trap::new)?;
}
let function_return = self.do_run_function(&mut function_context, function_body.opcodes.elements(), &function_body.labels).map_err(Trap::new)?;
@ -127,7 +127,7 @@ impl<'a, E: Externals> Interpreter<'a, E> {
}
}
fn do_run_function(&mut self, function_context: &mut FunctionContext, function_body: &[Opcode], function_labels: &HashMap<usize, usize>) -> Result<RunResult, TrapKind> {
fn do_run_function(&mut self, function_context: &mut FunctionContext, function_body: &[Opcode]) -> Result<RunResult, TrapKind> {
loop {
let instruction = &function_body[function_context.position];
@ -400,7 +400,7 @@ impl<'a, E: Externals> Interpreter<'a, E> {
Ok(InstructionOutcome::RunNextInstruction)
}
fn run_else(&mut self, context: &mut FunctionContext, labels: &HashMap<usize, usize>) -> Result<InstructionOutcome, TrapKind> {
fn run_else(&mut self, context: &mut FunctionContext) -> Result<InstructionOutcome, TrapKind> {
let end_pos = labels[&context.position];
context.pop_frame(false)?;
context.position = end_pos;
@ -1144,7 +1144,7 @@ impl FunctionContext {
&self.frame_stack
}
pub fn push_frame(&mut self, labels: &HashMap<usize, usize>, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), TrapKind> {
pub fn push_frame(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), TrapKind> {
let begin_position = self.position;
let branch_position = match frame_type {
BlockFrameType::Function => usize::MAX,

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@ use common::stack;
use self::context::ModuleContextBuilder;
use self::func::Validator;
use memory_units::Pages;
use isa;
mod context;
mod func;
@ -40,7 +41,7 @@ impl From<stack::Error> for Error {
#[derive(Clone)]
pub struct ValidatedModule {
pub labels: HashMap<usize, HashMap<usize, usize>>,
pub code_map: Vec<isa::Instructions>,
pub module: Module,
}
@ -167,7 +168,7 @@ pub fn deny_floating_point(module: &Module) -> Result<(), Error> {
pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
let mut context_builder = ModuleContextBuilder::new();
let mut imported_globals = Vec::new();
let mut labels = HashMap::new();
let mut code_map = Vec::new();
// Copy types from module as is.
context_builder.set_types(
@ -257,12 +258,12 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
index
)),
)?;
let func_labels = Validator::validate_function(&context, function, function_body)
let code = Validator::validate_function(&context, function, function_body)
.map_err(|e| {
let Error(ref msg) = e;
Error(format!("Function #{} validation error: {}", index, msg))
})?;
labels.insert(index, func_labels);
code_map.push(code);
}
}
@ -374,7 +375,7 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
Ok(ValidatedModule {
module,
labels
code_map,
})
}

View File

@ -20,6 +20,21 @@ impl<'a> Locals<'a> {
}
}
/// Returns total count of all declared locals and paramaterers.
///
/// Returns `Err` if count overflows 32-bit value.
pub fn count(&self) -> Result<u32, Error> {
let mut acc = self.params.len() as u32;
for locals_group in self.local_groups {
acc = acc
.checked_add(locals_group.count())
.ok_or_else(||
Error(String::from("Locals range no in 32-bit range"))
)?;
}
Ok(acc)
}
/// Returns the type of a local variable (either a declared local or a param).
///
/// Returns `Err` in the case of overflow or when idx falls out of range.