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. /// Index of default table.
pub const DEFAULT_TABLE_INDEX: u32 = 0; pub const DEFAULT_TABLE_INDEX: u32 = 0;
// TODO: Move BlockFrame under validation.
/// Control stack frame. /// Control stack frame.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct BlockFrame { pub struct BlockFrame {

View File

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

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 global;
mod func; mod func;
mod types; mod types;
mod instructions; mod isa;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -379,7 +379,7 @@ pub mod memory_units {
/// Deserialized module prepared for instantiation. /// Deserialized module prepared for instantiation.
pub struct Module { pub struct Module {
labels: HashMap<usize, HashMap<usize, usize>>, code_map: Vec<isa::Instructions>,
module: parity_wasm::elements::Module, 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> { pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
use validation::{validate_module, ValidatedModule}; use validation::{validate_module, ValidatedModule};
let ValidatedModule { let ValidatedModule {
labels, code_map,
module, module,
} = validate_module(module)?; } = validate_module(module)?;
Ok(Module { Ok(Module {
labels, code_map,
module, module,
}) })
} }
@ -525,7 +525,7 @@ impl Module {
&self.module &self.module
} }
pub(crate) fn labels(&self) -> &HashMap<usize, HashMap<usize, usize>> { pub(crate) fn code(&self) -> &Vec<isa::Instructions> {
&self.labels &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( 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( let signature = instance.signature_by_index(ty.type_ref()).expect(
"Due to validation type should exists", "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", "At func validation time labels are collected; Collected labels are added by index; qed",
).clone(); ).clone();
let func_body = FuncBody { let func_body = FuncBody {
locals: body.locals().to_vec(), locals: body.locals().to_vec(),
opcodes: body.code().clone(), code: code,
labels: labels,
}; };
let func_instance = let func_instance =
FuncInstance::alloc_internal(Rc::downgrade(&instance.0), signature, func_body); 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() { if !function_context.is_initialized() {
let return_type = function_context.return_type; let return_type = function_context.return_type;
function_context.initialize(&function_body.locals); 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)?; 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 { loop {
let instruction = &function_body[function_context.position]; let instruction = &function_body[function_context.position];
@ -400,7 +400,7 @@ impl<'a, E: Externals> Interpreter<'a, E> {
Ok(InstructionOutcome::RunNextInstruction) 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]; let end_pos = labels[&context.position];
context.pop_frame(false)?; context.pop_frame(false)?;
context.position = end_pos; context.position = end_pos;
@ -1144,7 +1144,7 @@ impl FunctionContext {
&self.frame_stack &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 begin_position = self.position;
let branch_position = match frame_type { let branch_position = match frame_type {
BlockFrameType::Function => usize::MAX, 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::context::ModuleContextBuilder;
use self::func::Validator; use self::func::Validator;
use memory_units::Pages; use memory_units::Pages;
use isa;
mod context; mod context;
mod func; mod func;
@ -40,7 +41,7 @@ impl From<stack::Error> for Error {
#[derive(Clone)] #[derive(Clone)]
pub struct ValidatedModule { pub struct ValidatedModule {
pub labels: HashMap<usize, HashMap<usize, usize>>, pub code_map: Vec<isa::Instructions>,
pub module: Module, 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> { pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
let mut context_builder = ModuleContextBuilder::new(); let mut context_builder = ModuleContextBuilder::new();
let mut imported_globals = Vec::new(); let mut imported_globals = Vec::new();
let mut labels = HashMap::new(); let mut code_map = Vec::new();
// Copy types from module as is. // Copy types from module as is.
context_builder.set_types( context_builder.set_types(
@ -257,12 +258,12 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
index index
)), )),
)?; )?;
let func_labels = Validator::validate_function(&context, function, function_body) let code = Validator::validate_function(&context, function, function_body)
.map_err(|e| { .map_err(|e| {
let Error(ref msg) = e; let Error(ref msg) = e;
Error(format!("Function #{} validation error: {}", index, msg)) 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 { Ok(ValidatedModule {
module, 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 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. /// Returns `Err` in the case of overflow or when idx falls out of range.