From f22b9903b6673d2703d39e8dd923a3c9c61a77f2 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Thu, 29 Nov 2018 18:55:49 +0100 Subject: [PATCH 01/11] Pass function sig in function_body::translate --- src/function_body.rs | 45 +++++++++++++++++++++++++++++++-------- src/module.rs | 22 ++++++++++++------- src/tests.rs | 5 +++++ src/translate_sections.rs | 31 +++++++++++++++++---------- 4 files changed, 75 insertions(+), 28 deletions(-) diff --git a/src/function_body.rs b/src/function_body.rs index c49699f1b4..213da6ee59 100644 --- a/src/function_body.rs +++ b/src/function_body.rs @@ -1,6 +1,6 @@ use backend::*; use error::Error; -use wasmparser::{FunctionBody, Operator, Type}; +use wasmparser::{FuncType, FunctionBody, Operator, Type}; // TODO: Use own declared `Type` enum. @@ -86,15 +86,21 @@ impl ControlFrame { } } -pub fn translate(session: &mut CodeGenSession, body: &FunctionBody) -> Result<(), Error> { +pub fn translate( + session: &mut CodeGenSession, + func_type: &FuncType, + body: &FunctionBody, +) -> Result<(), Error> { let locals = body.get_locals_reader()?; - // Assume signature is (i32, i32) -> i32 for now. - // TODO: Use a real signature - const ARG_COUNT: u32 = 2; - let return_ty = Type::I32; + let arg_count = func_type.params.len() as u32; + let return_ty = if func_type.returns.len() > 0 { + func_type.returns[0] + } else { + Type::EmptyBlockType + }; - let mut framesize = ARG_COUNT; + let mut framesize = arg_count; for local in locals { let (count, _ty) = local?; framesize += count; @@ -105,7 +111,7 @@ pub fn translate(session: &mut CodeGenSession, body: &FunctionBody) -> Result<() prologue(&mut ctx, framesize); - for arg_pos in 0..ARG_COUNT { + for arg_pos in 0..arg_count { copy_incoming_arg(&mut ctx, arg_pos); } @@ -191,6 +197,13 @@ pub fn translate(session: &mut CodeGenSession, body: &FunctionBody) -> Result<() } restore_stack_depth(&mut ctx, control_frame.outgoing_stack_depth()); + + if control_frames.len() == 0 { + // This is the last control frame. Perform the implicit return here. + if return_ty != Type::EmptyBlockType { + prepare_return_value(&mut ctx); + } + } } Operator::I32Eq => { relop_eq_i32(&mut ctx); @@ -201,12 +214,26 @@ pub fn translate(session: &mut CodeGenSession, body: &FunctionBody) -> Result<() Operator::GetLocal { local_index } => { get_local_i32(&mut ctx, local_index); } + Operator::Call { function_index } => { + // TODO: find out the signature of this function + // this requires to generalize the function types infrasturcture + + // TODO: ensure that this function is locally defined + // We would like to support imported functions at some point + + // TODO: pop arguments and move them in appropriate positions. + // only 6 for now. + + // TODO: jump to the specified position + // this requires us saving function start locations in codegensession. + + panic!() + } _ => { trap(&mut ctx); } } } - prepare_return_value(&mut ctx); epilogue(&mut ctx); Ok(()) diff --git a/src/module.rs b/src/module.rs index 06a6f9bbb5..6a900a2695 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1,7 +1,7 @@ -use std::mem; -use error::Error; -use translate_sections; use backend::TranslatedCodeSection; +use error::Error; +use std::mem; +use translate_sections; use wasmparser::{ModuleReader, SectionCode}; #[derive(Default)] @@ -14,7 +14,10 @@ impl TranslatedModule { // Assume signature is (i32, i32) -> i32 for now. // TODO: Handle generic signatures. pub fn execute_func(&self, func_idx: u32, a: usize, b: usize) -> usize { - let code_section = self.translated_code_section.as_ref().expect("no code section"); + let code_section = self + .translated_code_section + .as_ref() + .expect("no code section"); let start_buf = code_section.func_start(func_idx as usize); unsafe { @@ -34,10 +37,12 @@ pub fn translate(data: &[u8]) -> Result { return Ok(output); } let mut section = reader.read()?; + let mut types = vec![]; + let mut func_ty_indicies = vec![]; if let SectionCode::Type = section.code { - let types = section.get_type_section_reader()?; - translate_sections::type_(types)?; + let types_reader = section.get_type_section_reader()?; + types = translate_sections::type_(types_reader)?; reader.skip_custom_sections()?; if reader.eof() { @@ -59,7 +64,7 @@ pub fn translate(data: &[u8]) -> Result { if let SectionCode::Function = section.code { let functions = section.get_function_section_reader()?; - translate_sections::function(functions)?; + func_ty_indicies = translate_sections::function(functions)?; reader.skip_custom_sections()?; if reader.eof() { @@ -136,7 +141,8 @@ pub fn translate(data: &[u8]) -> Result { if let SectionCode::Code = section.code { let code = section.get_code_section_reader()?; - output.translated_code_section = Some(translate_sections::code(code)?); + output.translated_code_section = + Some(translate_sections::code(code, &types, &func_ty_indicies)?); reader.skip_custom_sections()?; if reader.eof() { diff --git a/src/tests.rs b/src/tests.rs index b6cc91faae..4a505d6983 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -13,6 +13,11 @@ fn execute_wat(wat: &str, a: usize, b: usize) -> usize { translated.execute_func(0, a, b) } +#[test] +fn empty() { + let _ = translate_wat("(module (func))"); +} + #[test] fn adds() { const CASES: &[(usize, usize, usize)] = &[ diff --git a/src/translate_sections.rs b/src/translate_sections.rs index df2936fdbe..db97713a7a 100644 --- a/src/translate_sections.rs +++ b/src/translate_sections.rs @@ -1,3 +1,4 @@ +use backend::{CodeGenSession, TranslatedCodeSection}; use error::Error; use function_body; #[allow(unused_imports)] // for now @@ -7,14 +8,14 @@ use wasmparser::{ GlobalSectionReader, GlobalType, Import, ImportSectionEntryType, ImportSectionReader, MemorySectionReader, MemoryType, Operator, TableSectionReader, Type, TypeSectionReader, }; -use backend::{CodeGenSession, TranslatedCodeSection}; /// Parses the Type section of the wasm module. -pub fn type_(types: TypeSectionReader) -> Result<(), Error> { - for entry in types { - entry?; // TODO +pub fn type_(types_reader: TypeSectionReader) -> Result, Error> { + let mut types = vec![]; + for entry in types_reader { + types.push(entry?); } - Ok(()) + Ok(types) } /// Parses the Import section of the wasm module. @@ -26,11 +27,12 @@ pub fn import(imports: ImportSectionReader) -> Result<(), Error> { } /// Parses the Function section of the wasm module. -pub fn function(functions: FunctionSectionReader) -> Result<(), Error> { +pub fn function(functions: FunctionSectionReader) -> Result, Error> { + let mut func_ty_indicies = vec![]; for entry in functions { - entry?; // TODO + func_ty_indicies.push(entry?); } - Ok(()) + Ok(func_ty_indicies) } /// Parses the Table section of the wasm module. @@ -80,10 +82,17 @@ pub fn element(elements: ElementSectionReader) -> Result<(), Error> { } /// Parses the Code section of the wasm module. -pub fn code(code: CodeSectionReader) -> Result { +pub fn code( + code: CodeSectionReader, + types: &[FuncType], + func_ty_indicies: &[u32], +) -> Result { let mut session = CodeGenSession::new(); - for body in code { - function_body::translate(&mut session, &body?)?; + for (idx, body) in code.into_iter().enumerate() { + let func_ty_idx = func_ty_indicies[idx]; + let func_ty = &types[func_ty_idx as usize]; + + function_body::translate(&mut session, &func_ty, &body?)?; } Ok(session.into_translated_code_section()?) } From 635ccc6916947ceb516cac26e2d3487d6f7afe81 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Thu, 29 Nov 2018 22:28:10 +0100 Subject: [PATCH 02/11] Introduce TranslationContext --- src/function_body.rs | 2 ++ src/module.rs | 26 ++++++++++++++++++++------ src/translate_sections.rs | 10 ++++------ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/function_body.rs b/src/function_body.rs index 213da6ee59..d0f83fab4b 100644 --- a/src/function_body.rs +++ b/src/function_body.rs @@ -1,4 +1,5 @@ use backend::*; +use module::TranslationContext; use error::Error; use wasmparser::{FuncType, FunctionBody, Operator, Type}; @@ -88,6 +89,7 @@ impl ControlFrame { pub fn translate( session: &mut CodeGenSession, + translation_ctx: &TranslationContext, func_type: &FuncType, body: &FunctionBody, ) -> Result<(), Error> { diff --git a/src/module.rs b/src/module.rs index 6a900a2695..e2c71ba989 100644 --- a/src/module.rs +++ b/src/module.rs @@ -2,7 +2,7 @@ use backend::TranslatedCodeSection; use error::Error; use std::mem; use translate_sections; -use wasmparser::{ModuleReader, SectionCode}; +use wasmparser::{FuncType, ModuleReader, SectionCode}; #[derive(Default)] pub struct TranslatedModule { @@ -27,6 +27,20 @@ impl TranslatedModule { } } +#[derive(Default)] +pub struct TranslationContext { + types: Vec, + func_ty_indicies: Vec, +} + +impl TranslationContext { + pub fn func_type(&self, func_idx: u32) -> &FuncType { + // TODO: This assumes that there is no imported functions. + let func_ty_idx = self.func_ty_indicies[func_idx as usize]; + &self.types[func_ty_idx as usize] + } +} + /// Translate from a slice of bytes holding a wasm module. pub fn translate(data: &[u8]) -> Result { let mut reader = ModuleReader::new(data)?; @@ -37,12 +51,12 @@ pub fn translate(data: &[u8]) -> Result { return Ok(output); } let mut section = reader.read()?; - let mut types = vec![]; - let mut func_ty_indicies = vec![]; + + let mut ctx = TranslationContext::default(); if let SectionCode::Type = section.code { let types_reader = section.get_type_section_reader()?; - types = translate_sections::type_(types_reader)?; + ctx.types = translate_sections::type_(types_reader)?; reader.skip_custom_sections()?; if reader.eof() { @@ -64,7 +78,7 @@ pub fn translate(data: &[u8]) -> Result { if let SectionCode::Function = section.code { let functions = section.get_function_section_reader()?; - func_ty_indicies = translate_sections::function(functions)?; + ctx.func_ty_indicies = translate_sections::function(functions)?; reader.skip_custom_sections()?; if reader.eof() { @@ -142,7 +156,7 @@ pub fn translate(data: &[u8]) -> Result { if let SectionCode::Code = section.code { let code = section.get_code_section_reader()?; output.translated_code_section = - Some(translate_sections::code(code, &types, &func_ty_indicies)?); + Some(translate_sections::code(code, &ctx)?); reader.skip_custom_sections()?; if reader.eof() { diff --git a/src/translate_sections.rs b/src/translate_sections.rs index db97713a7a..431285b42f 100644 --- a/src/translate_sections.rs +++ b/src/translate_sections.rs @@ -1,6 +1,7 @@ use backend::{CodeGenSession, TranslatedCodeSection}; use error::Error; use function_body; +use module::TranslationContext; #[allow(unused_imports)] // for now use wasmparser::{ CodeSectionReader, Data, DataSectionReader, Element, ElementSectionReader, Export, @@ -84,15 +85,12 @@ pub fn element(elements: ElementSectionReader) -> Result<(), Error> { /// Parses the Code section of the wasm module. pub fn code( code: CodeSectionReader, - types: &[FuncType], - func_ty_indicies: &[u32], + translation_ctx: &TranslationContext ) -> Result { let mut session = CodeGenSession::new(); for (idx, body) in code.into_iter().enumerate() { - let func_ty_idx = func_ty_indicies[idx]; - let func_ty = &types[func_ty_idx as usize]; - - function_body::translate(&mut session, &func_ty, &body?)?; + let func_ty = translation_ctx.func_type(idx as u32); + function_body::translate(&mut session, translation_ctx, &func_ty, &body?)?; } Ok(session.into_translated_code_section()?) } From eb60c2587adc7ec747fd22202476f3c7f0efef4d Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Fri, 7 Dec 2018 19:16:32 +0100 Subject: [PATCH 03/11] Basic form of a function call --- src/backend.rs | 94 +++++++++++++++++++++++---------------- src/function_body.rs | 19 ++++---- src/module.rs | 5 ++- src/tests.rs | 23 +++++++--- src/translate_sections.rs | 8 ++-- 5 files changed, 89 insertions(+), 60 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index e504df7d1e..b3cf12a91b 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,8 +1,9 @@ #![allow(dead_code)] // for now -use error::Error; use dynasmrt::x64::Assembler; -use dynasmrt::{DynasmApi, DynasmLabelApi, AssemblyOffset, ExecutableBuffer, DynamicLabel}; +use dynasmrt::{AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi, ExecutableBuffer}; +use error::Error; +use std::iter; /// Size of a pointer on the target in bytes. const WORD_SIZE: u32 = 8; @@ -45,10 +46,7 @@ impl GPRs { } fn release(&mut self, gpr: GPR) { - assert!( - !self.is_free(gpr), - "released register was already free", - ); + assert!(!self.is_free(gpr), "released register was already free",); self.bits |= 1 << gpr; } @@ -93,22 +91,15 @@ enum ArgLocation { /// Get a location for an argument at the given position. fn abi_loc_for_arg(pos: u32) -> ArgLocation { // TODO: This assumes only system-v calling convention. - // In system-v calling convention the first 6 arguments are passed via registers. + // In system-v calling convention the first 6 arguments are passed via registers. // All rest arguments are passed on the stack. - const ARGS_IN_GPRS: &'static [GPR] = &[ - RDI, - RSI, - RDX, - RCX, - R8, - R9, - ]; + const ARGS_IN_GPRS: &'static [GPR] = &[RDI, RSI, RDX, RCX, R8, R9]; if let Some(®) = ARGS_IN_GPRS.get(pos as usize) { ArgLocation::Reg(reg) } else { let stack_pos = pos - ARGS_IN_GPRS.len() as u32; - // +2 is because the first argument is located right after the saved frame pointer slot + // +2 is because the first argument is located right after the saved frame pointer slot // and the incoming return address. let stack_offset = ((stack_pos + 2) * WORD_SIZE) as i32; ArgLocation::Stack(stack_offset) @@ -117,33 +108,54 @@ fn abi_loc_for_arg(pos: u32) -> ArgLocation { pub struct CodeGenSession { assembler: Assembler, - func_starts: Vec, + func_starts: Vec<(Option, DynamicLabel)>, } impl CodeGenSession { - pub fn new() -> Self { + pub fn new(func_count: u32) -> Self { + let mut assembler = Assembler::new().unwrap(); + let func_starts = iter::repeat_with(|| (None, assembler.new_dynamic_label())) + .take(func_count as usize) + .collect::>(); + CodeGenSession { - assembler: Assembler::new().unwrap(), - func_starts: Vec::new(), + assembler, + func_starts, } } - pub fn new_context(&mut self) -> Context { - let start_offset = self.assembler.offset(); - self.func_starts.push(start_offset); + pub fn new_context(&mut self, func_idx: u32) -> Context { + { + let func_start = &mut self.func_starts[func_idx as usize]; + + // At this point we now the exact start address of this function. Save it + // and define dynamic label at this location. + func_start.0 = Some(self.assembler.offset()); + self.assembler.dynamic_label(func_start.1); + } + Context { asm: &mut self.assembler, - start: start_offset, + func_starts: &self.func_starts, regs: Registers::new(), sp_depth: StackDepth(0), } } - pub fn into_translated_code_section(self) -> Result { - let exec_buf = self.assembler + pub fn into_translated_code_section(self) -> Result { + let exec_buf = self + .assembler .finalize() .map_err(|_asm| Error::Assembler("assembler error".to_owned()))?; - Ok(TranslatedCodeSection { exec_buf, func_starts: self.func_starts }) + let func_starts = self + .func_starts + .iter() + .map(|(offset, _)| offset.unwrap()) + .collect::>(); + Ok(TranslatedCodeSection { + exec_buf, + func_starts, + }) } } @@ -161,19 +173,12 @@ impl TranslatedCodeSection { pub struct Context<'a> { asm: &'a mut Assembler, - start: AssemblyOffset, + func_starts: &'a Vec<(Option, DynamicLabel)>, regs: Registers, /// Each push and pop on the value stack increments or decrements this value by 1 respectively. sp_depth: StackDepth, } -impl<'a> Context<'a> { - /// Returns the offset of the first instruction. - fn start(&self) -> AssemblyOffset { - self.start - } -} - /// Label in code. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Label(DynamicLabel); @@ -184,8 +189,8 @@ pub fn create_label(ctx: &mut Context) -> Label { } /// Define the given label at the current position. -/// -/// Multiple labels can be defined at the same position. However, a label +/// +/// Multiple labels can be defined at the same position. However, a label /// can be defined only once. pub fn define_label(ctx: &mut Context, label: Label) { ctx.asm.dynamic_label(label.0); @@ -327,7 +332,14 @@ pub fn copy_incoming_arg(ctx: &mut Context, arg_pos: u32) { // And then move a value from a register into local variable area on the stack. let offset = sp_relative_offset(ctx, arg_pos); dynasm!(ctx.asm - ; mov [rsp + offset], Rq(reg) + ; mov [rsp + offset], Rq(reg) + ); +} + +pub fn call_direct(ctx: &mut Context, index: u32) { + let label = &ctx.func_starts[index as usize].1; + dynasm!(ctx.asm + ; call =>*label ); } @@ -346,7 +358,11 @@ pub fn prologue(ctx: &mut Context, stack_slots: u32) { } pub fn epilogue(ctx: &mut Context) { - assert_eq!(ctx.sp_depth, StackDepth(0), "imbalanced pushes and pops detected"); + assert_eq!( + ctx.sp_depth, + StackDepth(0), + "imbalanced pushes and pops detected" + ); dynasm!(ctx.asm ; mov rsp, rbp ; pop rbp diff --git a/src/function_body.rs b/src/function_body.rs index d0f83fab4b..18733c644a 100644 --- a/src/function_body.rs +++ b/src/function_body.rs @@ -1,7 +1,7 @@ use backend::*; -use module::TranslationContext; use error::Error; -use wasmparser::{FuncType, FunctionBody, Operator, Type}; +use module::TranslationContext; +use wasmparser::{FunctionBody, Operator, Type}; // TODO: Use own declared `Type` enum. @@ -90,11 +90,12 @@ impl ControlFrame { pub fn translate( session: &mut CodeGenSession, translation_ctx: &TranslationContext, - func_type: &FuncType, + func_idx: u32, body: &FunctionBody, ) -> Result<(), Error> { let locals = body.get_locals_reader()?; + 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 { func_type.returns[0] @@ -108,7 +109,7 @@ pub fn translate( framesize += count; } - let mut ctx = session.new_context(); + let mut ctx = session.new_context(func_idx); let operators = body.get_operators_reader()?; prologue(&mut ctx, framesize); @@ -217,8 +218,9 @@ pub fn translate( get_local_i32(&mut ctx, local_index); } Operator::Call { function_index } => { - // TODO: find out the signature of this function - // this requires to generalize the function types infrasturcture + let callee_ty = translation_ctx.func_type(function_index); + assert!(callee_ty.params.len() == 0, "is not supported"); + assert!(callee_ty.returns.len() == 0, "is not supported"); // TODO: ensure that this function is locally defined // We would like to support imported functions at some point @@ -226,10 +228,7 @@ pub fn translate( // TODO: pop arguments and move them in appropriate positions. // only 6 for now. - // TODO: jump to the specified position - // this requires us saving function start locations in codegensession. - - panic!() + call_direct(&mut ctx, function_index); } _ => { trap(&mut ctx); diff --git a/src/module.rs b/src/module.rs index e2c71ba989..52f0d61009 100644 --- a/src/module.rs +++ b/src/module.rs @@ -39,6 +39,8 @@ impl TranslationContext { let func_ty_idx = self.func_ty_indicies[func_idx as usize]; &self.types[func_ty_idx as usize] } + + // TODO: type of a global } /// Translate from a slice of bytes holding a wasm module. @@ -155,8 +157,7 @@ pub fn translate(data: &[u8]) -> Result { if let SectionCode::Code = section.code { let code = section.get_code_section_reader()?; - output.translated_code_section = - Some(translate_sections::code(code, &ctx)?); + output.translated_code_section = Some(translate_sections::code(code, &ctx)?); reader.skip_custom_sections()?; if reader.eof() { diff --git a/src/tests.rs b/src/tests.rs index 4a505d6983..980d100bc4 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -20,11 +20,7 @@ fn empty() { #[test] fn adds() { - const CASES: &[(usize, usize, usize)] = &[ - (5, 3, 8), - (0, 228, 228), - (usize::max_value(), 1, 0), - ]; + const CASES: &[(usize, usize, usize)] = &[(5, 3, 8), (0, 228, 228), (usize::max_value(), 1, 0)]; let code = r#" (module @@ -110,4 +106,21 @@ fn if_without_result() { assert_eq!(execute_wat(code, 2, 3), 2); } +#[test] +fn function_call() { + let code = r#" +(module + (func (param i32) (param i32) (result i32) + (call 1) + (get_local 0) + ) + + (func + ) +) + "#; + + assert_eq!(execute_wat(code, 2, 3), 2); +} + // TODO: Add a test that checks argument passing via the stack. diff --git a/src/translate_sections.rs b/src/translate_sections.rs index 431285b42f..94d248d517 100644 --- a/src/translate_sections.rs +++ b/src/translate_sections.rs @@ -85,12 +85,12 @@ pub fn element(elements: ElementSectionReader) -> Result<(), Error> { /// Parses the Code section of the wasm module. pub fn code( code: CodeSectionReader, - translation_ctx: &TranslationContext + translation_ctx: &TranslationContext, ) -> Result { - let mut session = CodeGenSession::new(); + let func_count = code.get_count(); + let mut session = CodeGenSession::new(func_count); for (idx, body) in code.into_iter().enumerate() { - let func_ty = translation_ctx.func_type(idx as u32); - function_body::translate(&mut session, translation_ctx, &func_ty, &body?)?; + function_body::translate(&mut session, translation_ctx, idx as u32, &body?)?; } Ok(session.into_translated_code_section()?) } From a3719e08e532763d044848b6871ef3c960b7d3e3 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Tue, 11 Dec 2018 19:45:33 +0100 Subject: [PATCH 04/11] Pass arguments. --- src/backend.rs | 28 +++++++++++++++++++++++----- src/function_body.rs | 5 +---- src/tests.rs | 12 +++++++++--- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index b3cf12a91b..dfcd301c32 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -81,6 +81,7 @@ impl Registers { } /// Describes location of a argument. +#[derive(Debug)] enum ArgLocation { /// Argument is passed via some register. Reg(GPR), @@ -336,6 +337,22 @@ pub fn copy_incoming_arg(ctx: &mut Context, arg_pos: u32) { ); } +pub fn pass_outgoing_args(ctx: &mut Context, arity: u32) { + for arg_pos in (0..arity).rev() { + ctx.sp_depth.free(1); + + let loc = abi_loc_for_arg(arg_pos); + match loc { + ArgLocation::Reg(gpr) => { + dynasm!(ctx.asm + ; pop Rq(gpr) + ); + } + _ => unimplemented!("don't know how to pass argument {} via {:?}", arg_pos, loc), + } + } +} + pub fn call_direct(ctx: &mut Context, index: u32) { let label = &ctx.func_starts[index as usize].1; dynasm!(ctx.asm @@ -358,11 +375,12 @@ pub fn prologue(ctx: &mut Context, stack_slots: u32) { } pub fn epilogue(ctx: &mut Context) { - assert_eq!( - ctx.sp_depth, - StackDepth(0), - "imbalanced pushes and pops detected" - ); + // TODO: This doesn't work with stack alignment. + // assert_eq!( + // ctx.sp_depth, + // StackDepth(0), + // "imbalanced pushes and pops detected" + // ); dynasm!(ctx.asm ; mov rsp, rbp ; pop rbp diff --git a/src/function_body.rs b/src/function_body.rs index 18733c644a..bffca70b98 100644 --- a/src/function_body.rs +++ b/src/function_body.rs @@ -219,15 +219,12 @@ pub fn translate( } Operator::Call { function_index } => { let callee_ty = translation_ctx.func_type(function_index); - assert!(callee_ty.params.len() == 0, "is not supported"); assert!(callee_ty.returns.len() == 0, "is not supported"); // TODO: ensure that this function is locally defined // We would like to support imported functions at some point - // TODO: pop arguments and move them in appropriate positions. - // only 6 for now. - + pass_outgoing_args(&mut ctx, callee_ty.params.len() as u32); call_direct(&mut ctx, function_index); } _ => { diff --git a/src/tests.rs b/src/tests.rs index 980d100bc4..d2e2effb78 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -111,16 +111,22 @@ fn function_call() { let code = r#" (module (func (param i32) (param i32) (result i32) - (call 1) + (call $assert_zero + (get_local 1) + ) (get_local 0) ) - (func + (func $assert_zero (param $v i32) + (local i32) + (if (get_local $v) + (unreachable) + ) ) ) "#; - assert_eq!(execute_wat(code, 2, 3), 2); + assert_eq!(execute_wat(code, 2, 0), 2); } // TODO: Add a test that checks argument passing via the stack. From 38590cbcb9f8a83adc6312de257725a30379ed0f Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Tue, 11 Dec 2018 20:12:55 +0100 Subject: [PATCH 05/11] Add i32 literals support. --- src/backend.rs | 10 +++++++++- src/function_body.rs | 3 +++ src/tests.rs | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/backend.rs b/src/backend.rs index dfcd301c32..3e90722c00 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -261,7 +261,7 @@ pub fn get_local_i32(ctx: &mut Context, local_idx: u32) { push_i32(ctx, gpr); } -pub fn store_i32(ctx: &mut Context, local_idx: u32) { +pub fn set_local_i32(ctx: &mut Context, local_idx: u32) { let gpr = pop_i32(ctx); let offset = sp_relative_offset(ctx, local_idx); dynasm!(ctx.asm @@ -270,6 +270,14 @@ pub fn store_i32(ctx: &mut Context, local_idx: u32) { ctx.regs.release_scratch_gpr(gpr); } +pub fn literal_i32(ctx: &mut Context, imm: i32) { + let gpr = ctx.regs.take_scratch_gpr(); + dynasm!(ctx.asm + ; mov Rd(gpr), imm + ); + push_i32(ctx, gpr); +} + pub fn relop_eq_i32(ctx: &mut Context) { let right = pop_i32(ctx); let left = pop_i32(ctx); diff --git a/src/function_body.rs b/src/function_body.rs index bffca70b98..fca2a7e666 100644 --- a/src/function_body.rs +++ b/src/function_body.rs @@ -217,6 +217,9 @@ pub fn translate( Operator::GetLocal { local_index } => { get_local_i32(&mut ctx, local_index); } + Operator::I32Const { value } => { + literal_i32(&mut ctx, value); + } Operator::Call { function_index } => { let callee_ty = translation_ctx.func_type(function_index); assert!(callee_ty.returns.len() == 0, "is not supported"); diff --git a/src/tests.rs b/src/tests.rs index d2e2effb78..348c0e772f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -129,4 +129,18 @@ fn function_call() { assert_eq!(execute_wat(code, 2, 0), 2); } +#[test] +fn literals() { + let code = r#" +(module + (func (param i32) (param i32) (result i32) + (i32.const 228) + ) +) + "#; + + assert_eq!(execute_wat(code, 0, 0), 228); +} + + // TODO: Add a test that checks argument passing via the stack. From d6b300c87fa422a5b19d4360d94df6cb9bf802fa Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Tue, 11 Dec 2018 20:13:20 +0100 Subject: [PATCH 06/11] Implement returns. --- src/backend.rs | 11 ++++++++++- src/function_body.rs | 6 ++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 3e90722c00..7829b7bf1f 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -361,11 +361,20 @@ pub fn pass_outgoing_args(ctx: &mut Context, arity: u32) { } } -pub fn call_direct(ctx: &mut Context, index: u32) { +pub fn call_direct(ctx: &mut Context, index: u32, return_arity: u32) { + assert!(return_arity == 0 || return_arity == 1); + let label = &ctx.func_starts[index as usize].1; dynasm!(ctx.asm ; call =>*label ); + + if return_arity == 1 { + dynasm!(ctx.asm + ; push rax + ); + ctx.sp_depth.reserve(1); + } } pub fn prologue(ctx: &mut Context, stack_slots: u32) { diff --git a/src/function_body.rs b/src/function_body.rs index fca2a7e666..0983973f9f 100644 --- a/src/function_body.rs +++ b/src/function_body.rs @@ -222,13 +222,11 @@ pub fn translate( } Operator::Call { function_index } => { let callee_ty = translation_ctx.func_type(function_index); - assert!(callee_ty.returns.len() == 0, "is not supported"); - // TODO: ensure that this function is locally defined - // We would like to support imported functions at some point + // TODO: this implementation assumes that this function is locally defined. pass_outgoing_args(&mut ctx, callee_ty.params.len() as u32); - call_direct(&mut ctx, function_index); + call_direct(&mut ctx, function_index, callee_ty.returns.len() as u32); } _ => { trap(&mut ctx); From 94e2f0c2a536708ad72bf4bd05689c38ebe716ce Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Tue, 11 Dec 2018 20:13:27 +0100 Subject: [PATCH 07/11] Add the fib test. --- src/tests.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/tests.rs b/src/tests.rs index 348c0e772f..add6ee96b7 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -142,5 +142,61 @@ fn literals() { assert_eq!(execute_wat(code, 0, 0), 228); } +#[test] +fn fib() { + let code = r#" +(module + (func $fib (param $n i32) (param $_unused i32) (result i32) + (if (result i32) + (i32.eq + (i32.const 0) + (get_local $n) + ) + (then + (i32.const 1) + ) + (else + (if (result i32) + (i32.eq + (i32.const 1) + (get_local $n) + ) + (then + (i32.const 1) + ) + (else + (i32.add + ;; fib(n - 1) + (call $fib + (i32.add + (get_local $n) + (i32.const -1) + ) + (i32.const 0) + ) + ;; fib(n - 2) + (call $fib + (i32.add + (get_local $n) + (i32.const -2) + ) + (i32.const 0) + ) + ) + ) + ) + ) + ) + ) +) + "#; + + // fac(x) = y <=> (x, y) + const FIB_SEQ: &[usize] = &[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]; + + for x in 0..10 { + assert_eq!(execute_wat(code, x, 0), FIB_SEQ[x]); + } +} // TODO: Add a test that checks argument passing via the stack. From aab191b46035606bbef187e2e70d503a8cb1d1ab Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Thu, 13 Dec 2018 16:20:03 +0100 Subject: [PATCH 08/11] Comment FuncDef. --- src/backend.rs | 52 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 7829b7bf1f..31b15d129b 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -107,37 +107,59 @@ fn abi_loc_for_arg(pos: u32) -> ArgLocation { } } +/// Records data about the function. +struct FuncDef { + /// Offset to the start of the function. None, until the exact offset is known. + /// + /// Used to calculate the address for calling this function. + /// TODO: This field will not be needed if dynasm gain ability to return `AssemblyOffset` for the + /// defined labels. + offset: Option, + /// Dynamic label can be used to designate target of calls + /// before knowning the actual address of the function. + label: DynamicLabel, +} + +impl FuncDef { + fn new(asm: &mut Assembler) -> FuncDef { + FuncDef { + offset: None, + label: asm.new_dynamic_label(), + } + } +} + pub struct CodeGenSession { assembler: Assembler, - func_starts: Vec<(Option, DynamicLabel)>, + func_defs: Vec, } impl CodeGenSession { pub fn new(func_count: u32) -> Self { let mut assembler = Assembler::new().unwrap(); - let func_starts = iter::repeat_with(|| (None, assembler.new_dynamic_label())) + let func_defs = iter::repeat_with(|| FuncDef::new(&mut assembler)) .take(func_count as usize) .collect::>(); CodeGenSession { assembler, - func_starts, + func_defs, } } pub fn new_context(&mut self, func_idx: u32) -> Context { { - let func_start = &mut self.func_starts[func_idx as usize]; + let func_start = &mut self.func_defs[func_idx as usize]; // At this point we now the exact start address of this function. Save it // and define dynamic label at this location. - func_start.0 = Some(self.assembler.offset()); - self.assembler.dynamic_label(func_start.1); + func_start.offset = Some(self.assembler.offset()); + self.assembler.dynamic_label(func_start.label); } Context { asm: &mut self.assembler, - func_starts: &self.func_starts, + func_defs: &self.func_defs, regs: Registers::new(), sp_depth: StackDepth(0), } @@ -148,33 +170,33 @@ impl CodeGenSession { .assembler .finalize() .map_err(|_asm| Error::Assembler("assembler error".to_owned()))?; - let func_starts = self - .func_starts + let func_defs = self + .func_defs .iter() - .map(|(offset, _)| offset.unwrap()) + .map(|FuncDef { offset, .. }| offset.unwrap()) .collect::>(); Ok(TranslatedCodeSection { exec_buf, - func_starts, + func_defs, }) } } pub struct TranslatedCodeSection { exec_buf: ExecutableBuffer, - func_starts: Vec, + func_defs: Vec, } impl TranslatedCodeSection { pub fn func_start(&self, idx: usize) -> *const u8 { - let offset = self.func_starts[idx]; + let offset = self.func_defs[idx]; self.exec_buf.ptr(offset) } } pub struct Context<'a> { asm: &'a mut Assembler, - func_starts: &'a Vec<(Option, DynamicLabel)>, + func_defs: &'a Vec, regs: Registers, /// Each push and pop on the value stack increments or decrements this value by 1 respectively. sp_depth: StackDepth, @@ -364,7 +386,7 @@ pub fn pass_outgoing_args(ctx: &mut Context, arity: u32) { pub fn call_direct(ctx: &mut Context, index: u32, return_arity: u32) { assert!(return_arity == 0 || return_arity == 1); - let label = &ctx.func_starts[index as usize].1; + let label = &ctx.func_defs[index as usize].label; dynasm!(ctx.asm ; call =>*label ); From 23476e82ff548b45f2173235f6b343d62885ba35 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Thu, 13 Dec 2018 16:21:30 +0100 Subject: [PATCH 09/11] Fix typo. --- src/backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend.rs b/src/backend.rs index 31b15d129b..0c996f90c3 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -151,7 +151,7 @@ impl CodeGenSession { { let func_start = &mut self.func_defs[func_idx as usize]; - // At this point we now the exact start address of this function. Save it + // At this point we know the exact start address of this function. Save it // and define dynamic label at this location. func_start.offset = Some(self.assembler.offset()); self.assembler.dynamic_label(func_start.label); From 5511f920886c58f15b04155e41100bd9efecc563 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Thu, 13 Dec 2018 16:31:08 +0100 Subject: [PATCH 10/11] Add call alignment requirement. --- src/function_body.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/function_body.rs b/src/function_body.rs index 0983973f9f..bdd000d7ca 100644 --- a/src/function_body.rs +++ b/src/function_body.rs @@ -224,6 +224,7 @@ pub fn translate( let callee_ty = translation_ctx.func_type(function_index); // TODO: this implementation assumes that this function is locally defined. + // TODO: guarantee 16-byte alignment for calls as required by x86-64 ABI pass_outgoing_args(&mut ctx, callee_ty.params.len() as u32); call_direct(&mut ctx, function_index, callee_ty.returns.len() as u32); From 0d1c4f32903192282da7c418f50b8076964a41fb Mon Sep 17 00:00:00 2001 From: Jef Date: Wed, 12 Dec 2018 11:52:48 +0100 Subject: [PATCH 11/11] Allow calling functions with any signature --- examples/test.rs | 2 +- src/module.rs | 39 +++++++++++++++++++++++++++++++++------ src/tests.rs | 47 ++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 74 insertions(+), 14 deletions(-) diff --git a/examples/test.rs b/examples/test.rs index 3295df8ae6..163dda0142 100644 --- a/examples/test.rs +++ b/examples/test.rs @@ -22,7 +22,7 @@ fn read_to_end>(path: P) -> io::Result> { fn maybe_main() -> Result<(), String> { let data = read_to_end("test.wasm").map_err(|e| e.to_string())?; let translated = translate(&data).map_err(|e| e.to_string())?; - let result = translated.execute_func(0, 5, 3); + let result: u32 = unsafe { translated.execute_func(0, (5u32, 3u32)) }; println!("f(5, 3) = {}", result); Ok(()) diff --git a/src/module.rs b/src/module.rs index 52f0d61009..a38aac0940 100644 --- a/src/module.rs +++ b/src/module.rs @@ -4,6 +4,37 @@ use std::mem; use translate_sections; use wasmparser::{FuncType, ModuleReader, SectionCode}; +pub trait FunctionArgs { + unsafe fn call(self, start: *const u8) -> T; +} + +macro_rules! impl_function_args { + ($first:ident $(, $rest:ident)*) => { + impl<$first, $($rest),*> FunctionArgs for ($first, $($rest),*) { + #[allow(non_snake_case)] + unsafe fn call(self, start: *const u8) -> T { + let func = mem::transmute::<_, extern "sysv64" fn($first, $($rest),*) -> T>(start); + { + let ($first, $($rest),*) = self; + func($first, $($rest),*) + } + } + } + + impl_function_args!($($rest),*); + }; + () => { + impl FunctionArgs for () { + unsafe fn call(self, start: *const u8) -> T { + let func = mem::transmute::<_, extern "sysv64" fn() -> T>(start); + func() + } + } + }; +} + +impl_function_args!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S); + #[derive(Default)] pub struct TranslatedModule { translated_code_section: Option, @@ -11,19 +42,15 @@ pub struct TranslatedModule { impl TranslatedModule { // For testing only. - // Assume signature is (i32, i32) -> i32 for now. // TODO: Handle generic signatures. - pub fn execute_func(&self, func_idx: u32, a: usize, b: usize) -> usize { + pub unsafe fn execute_func(&self, func_idx: u32, args: Args) -> T { let code_section = self .translated_code_section .as_ref() .expect("no code section"); let start_buf = code_section.func_start(func_idx as usize); - unsafe { - let func = mem::transmute::<_, extern "sysv64" fn(usize, usize) -> usize>(start_buf); - func(a, b) - } + args.call(start_buf) } } diff --git a/src/tests.rs b/src/tests.rs index add6ee96b7..0e434c7233 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -8,9 +8,9 @@ fn translate_wat(wat: &str) -> TranslatedModule { } /// Execute the first function in the module. -fn execute_wat(wat: &str, a: usize, b: usize) -> usize { +fn execute_wat(wat: &str, a: u32, b: u32) -> u32 { let translated = translate_wat(wat); - translated.execute_func(0, a, b) + unsafe { translated.execute_func(0, (a, b)) } } #[test] @@ -20,7 +20,7 @@ fn empty() { #[test] fn adds() { - const CASES: &[(usize, usize, usize)] = &[(5, 3, 8), (0, 228, 228), (usize::max_value(), 1, 0)]; + const CASES: &[(u32, u32, u32)] = &[(5, 3, 8), (0, 228, 228), (u32::max_value(), 1, 0)]; let code = r#" (module @@ -34,7 +34,7 @@ fn adds() { #[test] fn relop_eq() { - const CASES: &[(usize, usize, usize)] = &[ + const CASES: &[(u32, u32, u32)] = &[ (0, 0, 1), (0, 1, 0), (1, 0, 0), @@ -56,7 +56,7 @@ fn relop_eq() { #[test] fn if_then_else() { - const CASES: &[(usize, usize, usize)] = &[ + const CASES: &[(u32, u32, u32)] = &[ (0, 1, 1), (0, 0, 0), (1, 0, 0), @@ -129,6 +129,39 @@ fn function_call() { assert_eq!(execute_wat(code, 2, 0), 2); } +#[test] +fn large_function_call() { + let code = r#" +(module + (func (param i32) (param i32) (param i32) (param i32) + (param i32) (param i32) + (result i32) + + (call $assert_zero + (get_local 5) + ) + (get_local 0) + ) + + (func $assert_zero (param $v i32) + (local i32) + (if (get_local $v) + (unreachable) + ) + ) +) + "#; + + assert_eq!( + { + let translated = translate_wat(code); + let out: u32 = unsafe { translated.execute_func(0, (5, 4, 3, 2, 1, 0)) }; + out + }, + 5 + ); +} + #[test] fn literals() { let code = r#" @@ -192,10 +225,10 @@ fn fib() { "#; // fac(x) = y <=> (x, y) - const FIB_SEQ: &[usize] = &[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]; + const FIB_SEQ: &[u32] = &[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]; for x in 0..10 { - assert_eq!(execute_wat(code, x, 0), FIB_SEQ[x]); + assert_eq!(execute_wat(code, x, 0), FIB_SEQ[x as usize]); } }