From b42696f207e42e473ec7e8782827ea9c4a885e9b Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Mon, 19 Nov 2018 21:37:12 +0100 Subject: [PATCH] Add a simple boilerplate. --- src/backend.rs | 21 +++++++- src/function_body.rs | 115 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 128 insertions(+), 8 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index c3b5636d42..a5345496f7 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -2,7 +2,7 @@ use error::Error; use dynasmrt::x64::Assembler; -use dynasmrt::{DynasmApi, AssemblyOffset, ExecutableBuffer}; +use dynasmrt::{DynasmApi, DynasmLabelApi, AssemblyOffset, ExecutableBuffer, DynamicLabel}; /// Size of a pointer on the target in bytes. const WORD_SIZE: u32 = 8; @@ -82,6 +82,10 @@ impl Registers { } } +/// Label in code. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Label(DynamicLabel); + /// Describes location of a argument. enum ArgLocation { /// Argument is passed via some register. @@ -175,6 +179,19 @@ impl<'a> Context<'a> { } } +/// Create a new undefined label. +pub fn create_label(ctx: &mut Context) -> Label { + Label(ctx.asm.new_dynamic_label()) +} + +/// Define the given label at the current position. +/// +/// 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); +} + fn push_i32(ctx: &mut Context, gpr: GPR) { // For now, do an actual push (and pop below). In the future, we could // do on-the-fly register allocation here. @@ -284,7 +301,7 @@ pub fn epilogue(ctx: &mut Context) { ); } -pub fn unsupported_opcode(ctx: &mut Context) { +pub fn trap(ctx: &mut Context) { dynasm!(ctx.asm ; ud2 ); diff --git a/src/function_body.rs b/src/function_body.rs index 752e80eeb9..b9f365df33 100644 --- a/src/function_body.rs +++ b/src/function_body.rs @@ -2,6 +2,73 @@ use backend::*; use error::Error; use wasmparser::{FunctionBody, Operator}; +/// Type of a control frame. +#[derive(Debug, Copy, Clone, PartialEq)] +enum ControlFrameKind { + /// A regular block frame. + /// + /// Can be used for an implicit function block. + Block { end_label: Label }, + /// Loop frame (branching to the beginning of block). + Loop { header: Label }, + /// True-subblock of if expression. + IfTrue { + /// If jump happens inside the if-true block then control will + /// land on this label. + end_label: Label, + + /// 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: Label, + }, + /// False-subblock of if expression. + IfFalse { end_label: Label }, +} + +impl ControlFrameKind { + /// Returns a label which should be used as a branch destination. + fn br_destination(&self) -> Label { + match *self { + ControlFrameKind::Block { end_label } => end_label, + ControlFrameKind::Loop { header } => header, + ControlFrameKind::IfTrue { end_label, .. } => end_label, + ControlFrameKind::IfFalse { end_label } => end_label, + } + } + + /// Returns `true` if this block of a loop kind. + fn is_loop(&self) -> bool { + match *self { + ControlFrameKind::Loop { .. } => true, + _ => false, + } + } +} + +struct ControlFrame { + kind: ControlFrameKind, + /// 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. + stack_polymorphic: bool, + // TODO: type, stack height, etc +} + +impl ControlFrame { + pub fn new(kind: ControlFrameKind) -> ControlFrame { + ControlFrame { + kind, + stack_polymorphic: false, + } + } + + /// Marks this control frame as reached stack-polymorphic state. + pub fn mark_stack_polymorphic(&mut self) { + self.stack_polymorphic = true; + } +} + pub fn translate(session: &mut CodeGenSession, body: &FunctionBody) -> Result<(), Error> { let locals = body.get_locals_reader()?; @@ -24,24 +91,60 @@ pub fn translate(session: &mut CodeGenSession, body: &FunctionBody) -> Result<() copy_incoming_arg(&mut ctx, arg_pos); } + let mut control_frames = Vec::new(); + + // Upon entering the function implicit frame for function body is pushed. It has the same + // result type as the function itself. Branching to it is equivalent to returning from the function. + let epilogue_label = create_label(&mut ctx); + control_frames.push(ControlFrame::new( + ControlFrameKind::Block { + end_label: epilogue_label, + }, + )); + for op in operators { match op? { + Operator::Unreachable => { + control_frames + .last_mut() + .expect("control stack is never empty") + .mark_stack_polymorphic(); + trap(&mut ctx); + } + Operator::If { ty } => { + let end_label = create_label(&mut ctx); + let if_not = create_label(&mut ctx); + control_frames.push(ControlFrame::new( + ControlFrameKind::IfTrue { + end_label, + if_not, + }, + )); + + // TODO: Generate code that pops a value and executes the if_true part if the value + // is not equal to zero. + } + Operator::End => { + let control_frame = control_frames.pop().expect("control stack is never empty"); + if !control_frame.kind.is_loop() { + // Branches to a control frame with block type directs control flow to the header of the loop + // and we don't need to resolve it here. Branching to other control frames always lead + // control flow to the corresponding `end`. + define_label(&mut ctx, control_frame.kind.br_destination()); + } + } Operator::I32Add => { add_i32(&mut ctx); } Operator::GetLocal { local_index } => { get_local_i32(&mut ctx, local_index); } - Operator::End => { - // TODO: This is super naive and makes a lot of unfounded assumptions - // but will do for the start. - prepare_return_value(&mut ctx); - } _ => { - unsupported_opcode(&mut ctx); + trap(&mut ctx); } } } + prepare_return_value(&mut ctx); epilogue(&mut ctx); Ok(())