Browse Source
* Initial skeleton for Winch This commit introduces the initial skeleton for Winch, the "baseline" compiler. This skeleton contains mostly setup code for the ISA, ABI, registers, and compilation environment abstractions. It also includes the calculation of function local slots. As of this commit, the structure of these abstractions looks like the following: +------------------------+ | v +----------+ +-----+ +-----------+-----+-----------------+ | Compiler | --> | ISA | --> | Registers | ABI | Compilation Env | +----------+ +-----+ +-----------+-----+-----------------+ | ^ +------------------------------+ * Compilation environment will hold a reference to the function data * Add basic documentation to the ABI trait * Enable x86 and arm64 in cranelift-codegen * Add reg_name function for x64 * Introduce the concept of a MacroAssembler and Assembler This commit introduces the concept of a MacroAsesembler and Assembler. The MacroAssembler trait will provide a high enough interface across architectures so that each ISA implementation can use their own low-level Assembler implementation to fulfill the interface. Each Assembler will provide a 1-1 mapping to each ISA instruction. As of this commit, only a partial debug implementation is provided for the x64 Assembler. * Add a newtype over PReg Adds a newtype `Reg` over regalloc2::PReg; this ensures that Winch will operate only on the concept of `Reg`. This change is temporary until we have the necessary machinery to share a common Reg abstraction via `cranelift_asm` * Improvements to local calcuation - Add `LocalSlot::addressed_from_sp` - Use `u32` for local slot and local sizes calculation * Add helper methods to ABIArg Adds helper methods to retrieve register and type information from the argument * Make locals_size public in frame * Improve x64 register naming depending on size * Add new methods to the masm interface This commit introduces the ability for the MacroAssembler to reserve stack space, get the address of a given local and perform a stack store based on the concept of `Operand`s. There are several motivating factors to introduce the concept of an Operand: - Make the translation between Winch and Cranelift easier; - Make dispatching from the MacroAssembler to the underlying Assembler - easier by minimizing the amount of functions that we need to define - in order to satisfy the store/load combinations This commit also introduces the concept of a memory address, which essentially describes the addressing modes; as of this commit only one addressing mode is supported. We'll also need to verify that this structure will play nicely with arm64. * Blank masm implementation for arm64 * Implementation of reserve_stack, local_address, store and fp_offset for x64 * Implement function prologue and argument register spilling * Add structopt and wat * Fix debug instruction formatting * Make TargetISA trait publicly accessible * Modify the MacroAssembler finalize siganture to return a slice of strings * Introduce a simple CLI for Winch To be able to compile Wasm programs with Winch independently. Mostly meant for testing / debugging * Fix bug in x64 assembler mov_rm * Remove unused import * Move the stack slot calculation to the Frame This commit moves the calculation of the stack slots to the frame handler abstraction and also includes the calculation of the limits for the function defined locals, which will be used to zero the locals that are not associated to function arguments * Add i32 and i64 constructors to local slots * Introduce the concept of DefinedLocalsRange This commit introduces `DefinedLocalsRange` to track the stack offset at which the function-defined locals start and end; this is later used to zero-out that stack region * Add constructors for int and float registers * Add a placeholder stack implementation * Add a regset abstraction to track register availability Adds a bit set abstraction to track register availability for register allocation. The bit set has no specific knowledge about physical registers, it works on the register's hardware encoding as the source of truth. Each RegSet is expected to be created with the universe of allocatable registers per ISA when starting the compilation of a particular function. * Add an abstraction over register and immediate This is meant to be used as the source for stores. * Add a way to zero local slots and an initial skeletion of regalloc This commit introduces `zero_local_slots` to the MacroAssembler; which ensures that function defined locals are zeroed out when starting the function body. The algorithm divides the defined function locals stack range into 8 byte slots and stores a zero at each address. This process relies on register allocation if the amount of slots that need to be initialized is greater than 1. In such case, the next available register is requested to the register set and it's used to store a 0, which is then stored at every local slot * Update to wasmparser 0.92 * Correctly track if the regset has registers available * Add a result entry to the ABI signature This commuit introduces ABIResult as part of the ABISignature; this struct will track how function results are stored; initially it will consiste of a single register that will be requested to the register allocator at the end of the function; potentially causing a spill * Move zero local slots and add more granular methods to the masm This commit removes zeroing local slots from the MacroAssembler and instead adds more granular methods to it (e.g `zero`, `add`). This allows for better code sharing since most of the work done by the algorithm for zeroing slots will be the same in all targets, except for the binary emissions pieces, which is what gets delegated to the masm * Use wasmparser's visitor API and add initial support for const and add This commit adds initial support for the I32Const and I32 instructions; this involves adding a minimum for register allocation. Note that some regalloc pieces are still incomplete, since for the current set of supported instructions they are not needed. * Make the ty field public in Local * Add scratch_reg to the abi * Add a method to get a particular local from the Frame * Split the compilation environment abstraction This commit splits the compilation environment into two more concise abstractions: 1. CodeGen: the main abstraction for code generation 2. CodeGenContext: abstraction that shares the common pieces for compilation; these pieces are shared between the code generator and the register allocator * Add `push` and `load` to the MacroAssembler * Remove dead code warnings for unused paths * Map ISA features to cranelift-codegen ISA features * Apply formatting * Fix Cargo.toml after a bad rebase * Add component-compiler feature * Use clap instead of structopt * Add winch to publish.rs script * Minor formatting * Add tests to RegSet and fix two bugs when freeing and checking for register availability * Add tests to Stack * Free source register after a non-constant i32 add * Improve comments - Remove unneeded comments - And improve some of the TODO items * Update default features * Drop the ABI generic param and pass the word_size information directly To avoid dealing with dead code warnings this commit passes the word size information directly, since it's the only piece of information needed from the ABI by Codegen until now * Remove dead code This piece of code will be put back once we start integrating Winch with Wasmtime * Remove unused enum variant This variant doesn't get constructed; it should be added back once a backend is added and not enabled by default or when Winch gets integrated into Wasmtime * Fix unused code in regset tests * Update spec testsuite * Switch the visitor pattern for a simpler operator match This commit removes the usage of wasmparser's visitor pattern and instead defaults to a simpler operator matching approach. This removes the complexity of having to define all the visitor trait functions at once. * Use wasmparser's Visitor trait with a different macro strategy This commit puts back wasmparser's Visitor trait, with a sigle; simpler macro, only used for unsupported operators. * Restructure Winch This commit restuructures Winch's parts. It divides the initial approach into three main crates: `winch-codegen`,`wasmtime-winch` and `winch-tools`. `wasmtime-winch` is reponsible for the Wasmtime-Winch integration. `winch-codegen` is solely responsible for code generation. `winch-tools` is CLI tool to compile Wasm programs, mainly for testing purposes. * Refactor zero local slots This commit moves the logic of zeroing local slots from the codegen module into a method with a default implementation in the MacroAssembler trait: `zero_mem_range`. The refactored implementation is very similar to the previous implementation with the only difference that it doesn't allocates a general-purpose register; it instead uses the register allocator to retrieve the scratch register and uses this register to unroll the series of zero stores. * Tie the codegen creation to the ISA ABI This commit makes the relationship between the ISA ABI and the codegen explicit. This allows us to pass down ABI-specific bit and pieces to the codegeneration. In this case the only concrete piece that we need is the ABI word size. * Mark winch as publishable directory * Revamp winch docs This commit ensures that all the code comments in Winch are compliant with the syle used in the rest of Wasmtime's codebase. It also imptoves, generally the quality of the comments in some modules. * Panic when using multi-value when the target is aarch64 Similar to x64, this commit ensures that the abi signature of the current function doesn't use multi-value returns * Document the usage of directives * Use endianness instead of endianess in the ISA trait * Introduce a three-argument form in the MacroAssembler This commit introduces the usage of three-argument form for the MacroAssembler interface. This allows for a natural mapping for architectures like aarch64. In the case of x64, the implementation can simply restrict the implementation asserting for equality in two of the arguments of defaulting to a differnt set of instructions. As of this commit, the implementation of `add` panics if the destination and the first source arguments are not equal; internally the x64 assembler implementation will ensure that all the allowed combinations of `add` are satisfied. The reason for panicking and not emitting a `mov` followed by an `add` for example is simply because register allocation happens right before calling `add`, which ensures any register-to-register moves, if needed. This implementation will evolve in the future and this panic will be lifted if needed. * Improve the documentation for the MacroAssembler. Documents the usage of three-arg form and the intention around the high-level interface. * Format comments in remaining modules * Clean up Cargo.toml for winch pieces This commit adds missing fields to each of Winch's Cargo.toml. * Use `ModuleTranslation::get_types()` to derive the function type * Assert that start range is always word-size alignedpull/5148/head
Saúl Cabrera
2 years ago
committed by
GitHub
31 changed files with 3063 additions and 0 deletions
@ -0,0 +1,19 @@ |
|||
[package] |
|||
name = "wasmtime-winch" |
|||
description = "Integration between Wasmtime and Winch" |
|||
version.workspace = true |
|||
authors.workspace = true |
|||
edition.workspace = true |
|||
license = "Apache-2.0 WITH LLVM-exception" |
|||
repository = "https://github.com/bytecodealliance/wasmtime" |
|||
|
|||
[dependencies] |
|||
winch-codegen = { workspace = true } |
|||
target-lexicon = { workspace = true } |
|||
wasmtime-environ = { workspace = true } |
|||
anyhow = { workspace = true } |
|||
|
|||
[features] |
|||
default = ["all-arch", "component-model"] |
|||
component-model = ["wasmtime-environ/component-model"] |
|||
all-arch = ["winch-codegen/all-arch"] |
@ -0,0 +1,57 @@ |
|||
use crate::compiler::Compiler; |
|||
use anyhow::Result; |
|||
use std::sync::Arc; |
|||
use target_lexicon::Triple; |
|||
use wasmtime_environ::{CompilerBuilder, Setting}; |
|||
use winch_codegen::isa; |
|||
|
|||
struct Builder { |
|||
triple: Triple, |
|||
} |
|||
|
|||
pub fn builder() -> Box<dyn CompilerBuilder> { |
|||
Box::new(Builder { |
|||
triple: Triple::host(), |
|||
}) |
|||
} |
|||
|
|||
impl CompilerBuilder for Builder { |
|||
fn triple(&self) -> &target_lexicon::Triple { |
|||
&self.triple |
|||
} |
|||
|
|||
fn target(&mut self, target: target_lexicon::Triple) -> Result<()> { |
|||
self.triple = target; |
|||
Ok(()) |
|||
} |
|||
|
|||
fn set(&mut self, _name: &str, _val: &str) -> Result<()> { |
|||
Ok(()) |
|||
} |
|||
|
|||
fn enable(&mut self, _name: &str) -> Result<()> { |
|||
Ok(()) |
|||
} |
|||
|
|||
fn settings(&self) -> Vec<Setting> { |
|||
vec![] |
|||
} |
|||
|
|||
fn build(&self) -> Result<Box<dyn wasmtime_environ::Compiler>> { |
|||
let isa = isa::lookup(self.triple.clone())?; |
|||
Ok(Box::new(Compiler::new(isa))) |
|||
} |
|||
|
|||
fn enable_incremental_compilation( |
|||
&mut self, |
|||
_cache_store: Arc<dyn wasmtime_environ::CacheStore>, |
|||
) { |
|||
todo!() |
|||
} |
|||
} |
|||
|
|||
impl std::fmt::Debug for Builder { |
|||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
|||
write!(f, "Builder: {{ triple: {:?} }}", self.triple()) |
|||
} |
|||
} |
@ -0,0 +1,83 @@ |
|||
use anyhow::Result; |
|||
use wasmtime_environ::{ |
|||
CompileError, DefinedFuncIndex, FunctionBodyData, ModuleTranslation, ModuleTypes, Tunables, |
|||
}; |
|||
use winch_codegen::isa::TargetIsa; |
|||
|
|||
pub(crate) struct Compiler { |
|||
isa: Box<dyn TargetIsa>, |
|||
} |
|||
|
|||
impl Compiler { |
|||
pub fn new(isa: Box<dyn TargetIsa>) -> Self { |
|||
Self { isa } |
|||
} |
|||
} |
|||
|
|||
impl wasmtime_environ::Compiler for Compiler { |
|||
fn compile_function( |
|||
&self, |
|||
_translation: &ModuleTranslation<'_>, |
|||
_index: DefinedFuncIndex, |
|||
_data: FunctionBodyData<'_>, |
|||
_tunables: &Tunables, |
|||
_types: &ModuleTypes, |
|||
) -> Result<Box<dyn std::any::Any + Send>, CompileError> { |
|||
todo!() |
|||
} |
|||
|
|||
fn compile_host_to_wasm_trampoline( |
|||
&self, |
|||
_ty: &wasmtime_environ::WasmFuncType, |
|||
) -> Result<Box<dyn std::any::Any + Send>, CompileError> { |
|||
todo!() |
|||
} |
|||
|
|||
fn emit_obj( |
|||
&self, |
|||
_module: &ModuleTranslation, |
|||
_funcs: wasmtime_environ::PrimaryMap<DefinedFuncIndex, Box<dyn std::any::Any + Send>>, |
|||
_trampolines: Vec<Box<dyn std::any::Any + Send>>, |
|||
_tunables: &Tunables, |
|||
_obj: &mut wasmtime_environ::object::write::Object<'static>, |
|||
) -> Result<( |
|||
wasmtime_environ::PrimaryMap<DefinedFuncIndex, wasmtime_environ::FunctionInfo>, |
|||
Vec<wasmtime_environ::Trampoline>, |
|||
)> { |
|||
todo!() |
|||
} |
|||
|
|||
fn emit_trampoline_obj( |
|||
&self, |
|||
_ty: &wasmtime_environ::WasmFuncType, |
|||
_host_fn: usize, |
|||
_obj: &mut wasmtime_environ::object::write::Object<'static>, |
|||
) -> Result<(wasmtime_environ::Trampoline, wasmtime_environ::Trampoline)> { |
|||
todo!() |
|||
} |
|||
|
|||
fn triple(&self) -> &target_lexicon::Triple { |
|||
self.isa.triple() |
|||
} |
|||
|
|||
fn page_size_align(&self) -> u64 { |
|||
todo!() |
|||
} |
|||
|
|||
fn flags(&self) -> std::collections::BTreeMap<String, wasmtime_environ::FlagValue> { |
|||
todo!() |
|||
} |
|||
|
|||
fn isa_flags(&self) -> std::collections::BTreeMap<String, wasmtime_environ::FlagValue> { |
|||
todo!() |
|||
} |
|||
|
|||
fn is_branch_protection_enabled(&self) -> bool { |
|||
todo!() |
|||
} |
|||
|
|||
#[cfg(feature = "component-model")] |
|||
fn component_compiler(&self) -> &dyn wasmtime_environ::component::ComponentCompiler { |
|||
todo!() |
|||
} |
|||
} |
@ -0,0 +1,3 @@ |
|||
mod builder; |
|||
mod compiler; |
|||
pub use builder::builder; |
@ -0,0 +1,26 @@ |
|||
[package] |
|||
name = "winch-tools" |
|||
version = "0.1.0" |
|||
description = "Binaries for testing Winch" |
|||
license = "Apache-2.0 WITH LLVM-exception" |
|||
repository = "https://github.com/bytecodealliance/wasmtime" |
|||
publish = false |
|||
edition.workspace = true |
|||
|
|||
[[bin]] |
|||
name = "winch-tools" |
|||
path = "src/main.rs" |
|||
|
|||
|
|||
[dependencies] |
|||
winch-codegen = { workspace = true } |
|||
wasmtime-environ = { workspace = true } |
|||
target-lexicon = { workspace = true } |
|||
anyhow = { workspace = true } |
|||
wasmparser = { workspace = true } |
|||
clap = { workspace = true } |
|||
wat = { workspace = true } |
|||
|
|||
[features] |
|||
default = ["all-arch"] |
|||
all-arch = ["winch-codegen/all-arch"] |
@ -0,0 +1,27 @@ |
|||
[package] |
|||
name = "winch-codegen" |
|||
description = "Winch code generation library" |
|||
license = "Apache-2.0 WITH LLVM-exception" |
|||
repository = "https://github.com/bytecodealliance/wasmtime" |
|||
version = "0.1.0" |
|||
edition.workspace = true |
|||
|
|||
[dependencies] |
|||
wasmparser = { workspace = true } |
|||
smallvec = { workspace = true } |
|||
anyhow = { workspace = true } |
|||
target-lexicon = { workspace = true, features = ["std"] } |
|||
# The following two dependencies (cranelift-codegen, regalloc2) are temporary; |
|||
# mostly to have access to `PReg`s and the calling convention. |
|||
# In the next iteration we'll factor out the common bits so that they can be consumed |
|||
# by Cranelift and Winch. |
|||
cranelift-codegen = { workspace = true } |
|||
regalloc2 = "0.4.1" |
|||
|
|||
[features] |
|||
x64 = ["cranelift-codegen/x86"] |
|||
arm64 = ["cranelift-codegen/arm64"] |
|||
all-arch = [ |
|||
"x64", |
|||
"arm64", |
|||
] |
@ -0,0 +1,15 @@ |
|||
//! A generic representation for addressing memory.
|
|||
use crate::isa::reg::Reg; |
|||
|
|||
// TODO
|
|||
// Add the other modes
|
|||
#[derive(Debug, Copy, Clone)] |
|||
pub(crate) enum Address { |
|||
Base { base: Reg, imm: u32 }, |
|||
} |
|||
|
|||
impl Address { |
|||
pub fn base(base: Reg, imm: u32) -> Address { |
|||
Address::Base { base, imm } |
|||
} |
|||
} |
@ -0,0 +1,68 @@ |
|||
use wasmparser::ValType; |
|||
/// Base register used to address the local slot.
|
|||
///
|
|||
/// Slots for stack arguments are addressed from the frame pointer
|
|||
/// Slots for function-defined locals and for registers are addressed
|
|||
/// from the stack pointer.
|
|||
#[derive(Eq, PartialEq)] |
|||
enum Base { |
|||
FP, |
|||
SP, |
|||
} |
|||
|
|||
/// A local slot.
|
|||
///
|
|||
/// Represents the type, location and addressing mode of a local
|
|||
/// in the stack's local and argument area.
|
|||
pub(crate) struct LocalSlot { |
|||
/// The offset of the local slot.
|
|||
pub offset: u32, |
|||
/// The type contained by this local slot.
|
|||
pub ty: ValType, |
|||
/// Base register associated to this local slot.
|
|||
base: Base, |
|||
} |
|||
|
|||
impl LocalSlot { |
|||
/// Creates a local slot for a function defined local or
|
|||
/// for a spilled argument register.
|
|||
pub fn new(ty: ValType, offset: u32) -> Self { |
|||
Self { |
|||
ty, |
|||
offset, |
|||
base: Base::SP, |
|||
} |
|||
} |
|||
|
|||
/// Int32 shortcut for `new`.
|
|||
pub fn i32(offset: u32) -> Self { |
|||
Self { |
|||
ty: ValType::I32, |
|||
offset, |
|||
base: Base::SP, |
|||
} |
|||
} |
|||
|
|||
/// Int64 shortcut for `new`.
|
|||
pub fn i64(offset: u32) -> Self { |
|||
Self { |
|||
ty: ValType::I64, |
|||
offset, |
|||
base: Base::SP, |
|||
} |
|||
} |
|||
|
|||
/// Creates a local slot for a stack function argument.
|
|||
pub fn stack_arg(ty: ValType, offset: u32) -> Self { |
|||
Self { |
|||
ty, |
|||
offset, |
|||
base: Base::FP, |
|||
} |
|||
} |
|||
|
|||
/// Check if the local is addressed from the stack pointer.
|
|||
pub fn addressed_from_sp(&self) -> bool { |
|||
self.base == Base::SP |
|||
} |
|||
} |
@ -0,0 +1,149 @@ |
|||
use crate::isa::reg::Reg; |
|||
use smallvec::SmallVec; |
|||
use std::ops::{Add, BitAnd, Not, Sub}; |
|||
use wasmparser::{FuncType, ValType}; |
|||
|
|||
pub(crate) mod addressing_mode; |
|||
pub(crate) mod local; |
|||
|
|||
/// Trait implemented by a specific ISA and used to provide
|
|||
/// information about alignment, parameter passing, usage of
|
|||
/// specific registers, etc.
|
|||
pub(crate) trait ABI { |
|||
/// The required stack alignment.
|
|||
fn stack_align(&self) -> u8; |
|||
|
|||
/// The offset to the argument base, relative to the frame pointer.
|
|||
fn arg_base_offset(&self) -> u8; |
|||
|
|||
/// Construct the ABI-specific signature from a WebAssembly
|
|||
/// function type.
|
|||
fn sig(&self, wasm_sig: &FuncType) -> ABISig; |
|||
|
|||
/// Returns the number of bits in a word.
|
|||
fn word_bits() -> u32; |
|||
|
|||
/// Returns the number of bytes in a word.
|
|||
fn word_bytes() -> u32 { |
|||
Self::word_bits() / 8 |
|||
} |
|||
|
|||
/// Returns the designated scratch register.
|
|||
fn scratch_reg() -> Reg; |
|||
} |
|||
|
|||
/// ABI-specific representation of a function argument.
|
|||
#[derive(Debug)] |
|||
pub(crate) enum ABIArg { |
|||
/// A register argument.
|
|||
Reg { |
|||
/// Type of the argument.
|
|||
ty: ValType, |
|||
/// Register holding the argument.
|
|||
reg: Reg, |
|||
}, |
|||
/// A stack argument.
|
|||
Stack { |
|||
/// The type of the argument.
|
|||
ty: ValType, |
|||
/// Offset of the argument relative to the frame pointer.
|
|||
offset: u32, |
|||
}, |
|||
} |
|||
|
|||
impl ABIArg { |
|||
/// Allocate a new register abi arg.
|
|||
pub fn reg(reg: Reg, ty: ValType) -> Self { |
|||
Self::Reg { reg, ty } |
|||
} |
|||
|
|||
/// Allocate a new stack abi arg.
|
|||
pub fn stack_offset(offset: u32, ty: ValType) -> Self { |
|||
Self::Stack { ty, offset } |
|||
} |
|||
|
|||
/// Is this abi arg in a register.
|
|||
pub fn is_reg(&self) -> bool { |
|||
match *self { |
|||
ABIArg::Reg { .. } => true, |
|||
_ => false, |
|||
} |
|||
} |
|||
|
|||
/// Get the register associated to this arg.
|
|||
pub fn get_reg(&self) -> Option<Reg> { |
|||
match *self { |
|||
ABIArg::Reg { reg, .. } => Some(reg), |
|||
_ => None, |
|||
} |
|||
} |
|||
|
|||
/// Get the type associated to this arg.
|
|||
pub fn ty(&self) -> ValType { |
|||
match *self { |
|||
ABIArg::Reg { ty, .. } | ABIArg::Stack { ty, .. } => ty, |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// ABI-specific representation of the function result.
|
|||
pub(crate) enum ABIResult { |
|||
Reg { |
|||
/// Type of the result.
|
|||
ty: Option<ValType>, |
|||
/// Register to hold the result.
|
|||
reg: Reg, |
|||
}, |
|||
} |
|||
|
|||
impl ABIResult { |
|||
/// Create a register ABI result.
|
|||
pub fn reg(ty: Option<ValType>, reg: Reg) -> Self { |
|||
Self::Reg { ty, reg } |
|||
} |
|||
|
|||
/// Get the result reg.
|
|||
pub fn result_reg(&self) -> Reg { |
|||
match self { |
|||
Self::Reg { reg, .. } => *reg, |
|||
} |
|||
} |
|||
|
|||
/// Checks if the result is void.
|
|||
pub fn is_void(&self) -> bool { |
|||
match self { |
|||
Self::Reg { ty, .. } => ty.is_none(), |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// An ABI-specific representation of a function signature.
|
|||
pub(crate) struct ABISig { |
|||
/// Function parameters.
|
|||
pub params: SmallVec<[ABIArg; 6]>, |
|||
pub result: ABIResult, |
|||
} |
|||
|
|||
/// Returns the size in bytes of a given WebAssembly type.
|
|||
pub(crate) fn ty_size(ty: &ValType) -> u32 { |
|||
match *ty { |
|||
ValType::I32 | ValType::F32 => 4, |
|||
ValType::I64 | ValType::F64 => 8, |
|||
_ => panic!(), |
|||
} |
|||
} |
|||
|
|||
/// Align a value up to the given power-of-two-alignment.
|
|||
// See https://sites.google.com/site/theoryofoperatingsystems/labs/malloc/align8
|
|||
pub(crate) fn align_to<N>(value: N, alignment: N) -> N |
|||
where |
|||
N: Not<Output = N> |
|||
+ BitAnd<N, Output = N> |
|||
+ Add<N, Output = N> |
|||
+ Sub<N, Output = N> |
|||
+ From<u8> |
|||
+ Copy, |
|||
{ |
|||
let alignment_mask = alignment - 1.into(); |
|||
(value + alignment_mask) & !alignment_mask |
|||
} |
@ -0,0 +1,158 @@ |
|||
use crate::{ |
|||
abi::{ABISig, ABI}, |
|||
frame::Frame, |
|||
masm::{MacroAssembler, OperandSize}, |
|||
regalloc::RegAlloc, |
|||
stack::Stack, |
|||
}; |
|||
use anyhow::Result; |
|||
use wasmparser::{FuncValidator, FunctionBody, ValType, ValidatorResources}; |
|||
|
|||
/// The code generation context.
|
|||
pub(crate) struct CodeGenContext<'a, M> |
|||
where |
|||
M: MacroAssembler, |
|||
{ |
|||
pub masm: M, |
|||
pub stack: Stack, |
|||
pub frame: &'a Frame, |
|||
} |
|||
|
|||
impl<'a, M> CodeGenContext<'a, M> |
|||
where |
|||
M: MacroAssembler, |
|||
{ |
|||
pub fn new(masm: M, stack: Stack, frame: &'a Frame) -> Self { |
|||
Self { masm, stack, frame } |
|||
} |
|||
} |
|||
|
|||
/// The code generation abstraction.
|
|||
pub(crate) struct CodeGen<'a, M> |
|||
where |
|||
M: MacroAssembler, |
|||
{ |
|||
/// A reference to the function body.
|
|||
function: FunctionBody<'a>, |
|||
|
|||
/// The word size in bytes, extracted from the current ABI.
|
|||
word_size: u32, |
|||
|
|||
/// The ABI-specific representation of the function signature, excluding results.
|
|||
sig: ABISig, |
|||
|
|||
/// The code generation context.
|
|||
pub context: CodeGenContext<'a, M>, |
|||
|
|||
/// The register allocator.
|
|||
pub regalloc: RegAlloc, |
|||
|
|||
/// Function body validator.
|
|||
pub validator: FuncValidator<ValidatorResources>, |
|||
} |
|||
|
|||
impl<'a, M> CodeGen<'a, M> |
|||
where |
|||
M: MacroAssembler, |
|||
{ |
|||
pub fn new<A: ABI>( |
|||
context: CodeGenContext<'a, M>, |
|||
sig: ABISig, |
|||
function: FunctionBody<'a>, |
|||
validator: FuncValidator<ValidatorResources>, |
|||
regalloc: RegAlloc, |
|||
) -> Self { |
|||
Self { |
|||
function, |
|||
word_size: <A as ABI>::word_bytes(), |
|||
sig, |
|||
context, |
|||
regalloc, |
|||
validator, |
|||
} |
|||
} |
|||
|
|||
/// Emit the function body to machine code.
|
|||
pub fn emit(&mut self) -> Result<Vec<String>> { |
|||
self.emit_start() |
|||
.and(self.emit_body()) |
|||
.and(self.emit_end())?; |
|||
let buf = self.context.masm.finalize(); |
|||
let code = Vec::from(buf); |
|||
Ok(code) |
|||
} |
|||
|
|||
// TODO stack checks
|
|||
fn emit_start(&mut self) -> Result<()> { |
|||
self.context.masm.prologue(); |
|||
self.context |
|||
.masm |
|||
.reserve_stack(self.context.frame.locals_size); |
|||
Ok(()) |
|||
} |
|||
|
|||
fn emit_body(&mut self) -> Result<()> { |
|||
self.spill_register_arguments(); |
|||
let defined_locals_range = &self.context.frame.defined_locals_range; |
|||
self.context.masm.zero_mem_range( |
|||
defined_locals_range.as_range(), |
|||
self.word_size, |
|||
&mut self.regalloc, |
|||
); |
|||
|
|||
let mut reader = self.function.get_operators_reader()?; |
|||
while !reader.eof() { |
|||
reader.visit_with_offset(self)??; |
|||
} |
|||
|
|||
reader.ensure_end().map_err(|e| e.into()) |
|||
} |
|||
|
|||
// Emit the usual function end instruction sequence.
|
|||
pub fn emit_end(&mut self) -> Result<()> { |
|||
self.handle_abi_result(); |
|||
self.context.masm.epilogue(self.context.frame.locals_size); |
|||
Ok(()) |
|||
} |
|||
|
|||
fn spill_register_arguments(&mut self) { |
|||
// TODO
|
|||
// Revisit this once the implicit VMContext argument is introduced;
|
|||
// when that happens the mapping between local slots and abi args
|
|||
// is not going to be symmetric.
|
|||
self.sig |
|||
.params |
|||
.iter() |
|||
.enumerate() |
|||
.filter(|(_, a)| a.is_reg()) |
|||
.for_each(|(index, arg)| { |
|||
let ty = arg.ty(); |
|||
let local = self |
|||
.context |
|||
.frame |
|||
.get_local(index as u32) |
|||
.expect("valid local slot at location"); |
|||
let addr = self.context.masm.local_address(local); |
|||
let src = arg |
|||
.get_reg() |
|||
.expect("arg should be associated to a register"); |
|||
|
|||
match &ty { |
|||
ValType::I32 => self.context.masm.store(src.into(), addr, OperandSize::S32), |
|||
ValType::I64 => self.context.masm.store(src.into(), addr, OperandSize::S64), |
|||
_ => panic!("Unsupported type {:?}", ty), |
|||
} |
|||
}); |
|||
} |
|||
|
|||
pub fn handle_abi_result(&mut self) { |
|||
if self.sig.result.is_void() { |
|||
return; |
|||
} |
|||
let named_reg = self.sig.result.result_reg(); |
|||
let reg = self |
|||
.regalloc |
|||
.pop_to_named_reg(&mut self.context, named_reg, OperandSize::S64); |
|||
self.regalloc.free_gpr(reg); |
|||
} |
|||
} |
@ -0,0 +1,145 @@ |
|||
use crate::abi::{align_to, local::LocalSlot, ty_size, ABIArg, ABISig, ABI}; |
|||
use anyhow::Result; |
|||
use smallvec::SmallVec; |
|||
use std::ops::Range; |
|||
use wasmparser::{FuncValidator, FunctionBody, ValType, ValidatorResources}; |
|||
|
|||
// TODO:
|
|||
// SpiderMonkey's implementation uses 16;
|
|||
// (ref: https://searchfox.org/mozilla-central/source/js/src/wasm/WasmBCFrame.h#585)
|
|||
// during instrumentation we should measure to verify if this is a good default.
|
|||
pub(crate) type Locals = SmallVec<[LocalSlot; 16]>; |
|||
|
|||
/// Function defined locals start and end in the frame.
|
|||
pub(crate) struct DefinedLocalsRange(Range<u32>); |
|||
|
|||
impl DefinedLocalsRange { |
|||
/// Get a reference to the inner range.
|
|||
pub fn as_range(&self) -> &Range<u32> { |
|||
&self.0 |
|||
} |
|||
} |
|||
|
|||
/// Frame handler abstraction.
|
|||
pub(crate) struct Frame { |
|||
/// The size of the entire local area; the arguments plus the function defined locals.
|
|||
pub locals_size: u32, |
|||
|
|||
/// The range in the frame corresponding to the defined locals range.
|
|||
pub defined_locals_range: DefinedLocalsRange, |
|||
|
|||
/// The local slots for the current function.
|
|||
///
|
|||
/// Locals get calculated when allocating a frame and are readonly
|
|||
/// through the function compilation lifetime.
|
|||
pub locals: Locals, |
|||
} |
|||
|
|||
impl Frame { |
|||
/// Allocate a new Frame.
|
|||
pub fn new<A: ABI>( |
|||
sig: &ABISig, |
|||
function: &mut FunctionBody, |
|||
validator: &mut FuncValidator<ValidatorResources>, |
|||
abi: &A, |
|||
) -> Result<Self> { |
|||
let (mut locals, defined_locals_start) = Self::compute_arg_slots(sig, abi)?; |
|||
let (defined_slots, defined_locals_end) = |
|||
Self::compute_defined_slots(function, validator, defined_locals_start)?; |
|||
locals.extend(defined_slots); |
|||
let locals_size = align_to(defined_locals_end, abi.stack_align().into()); |
|||
|
|||
Ok(Self { |
|||
locals, |
|||
locals_size, |
|||
defined_locals_range: DefinedLocalsRange(defined_locals_start..defined_locals_end), |
|||
}) |
|||
} |
|||
|
|||
/// Get a local slot.
|
|||
pub fn get_local(&self, index: u32) -> Option<&LocalSlot> { |
|||
self.locals.get(index as usize) |
|||
} |
|||
|
|||
fn compute_arg_slots<A: ABI>(sig: &ABISig, abi: &A) -> Result<(Locals, u32)> { |
|||
// Go over the function ABI-signature and
|
|||
// calculate the stack slots.
|
|||
//
|
|||
// for each parameter p; when p
|
|||
//
|
|||
// Stack =>
|
|||
// The slot offset is calculated from the ABIArg offset
|
|||
// relative the to the frame pointer (and its inclusions, e.g.
|
|||
// return address).
|
|||
//
|
|||
// Register =>
|
|||
// The slot is calculated by accumulating into the `next_frame_size`
|
|||
// the size + alignment of the type that the register is holding.
|
|||
//
|
|||
// NOTE
|
|||
// This implementation takes inspiration from SpiderMonkey's implementation
|
|||
// to calculate local slots for function arguments
|
|||
// (https://searchfox.org/mozilla-central/source/js/src/wasm/WasmBCFrame.cpp#83).
|
|||
// The main difference is that SpiderMonkey's implementation
|
|||
// doesn't append any sort of metadata to the locals regarding stack
|
|||
// addressing mode (stack pointer or frame pointer), the offset is
|
|||
// declared negative if the local belongs to a stack argument;
|
|||
// that's enough to later calculate address of the local later on.
|
|||
//
|
|||
// Winch appends an addressing mode to each slot, in the end
|
|||
// we want positive addressing from the stack pointer
|
|||
// for both locals and stack arguments.
|
|||
|
|||
let arg_base_offset = abi.arg_base_offset().into(); |
|||
let mut next_stack = 0u32; |
|||
let slots: Locals = sig |
|||
.params |
|||
.iter() |
|||
.map(|arg| Self::abi_arg_slot(&arg, &mut next_stack, arg_base_offset)) |
|||
.collect(); |
|||
|
|||
Ok((slots, next_stack)) |
|||
} |
|||
|
|||
fn abi_arg_slot(arg: &ABIArg, next_stack: &mut u32, arg_base_offset: u32) -> LocalSlot { |
|||
match arg { |
|||
// Create a local slot, for input register spilling,
|
|||
// with type-size aligned access.
|
|||
ABIArg::Reg { ty, reg: _ } => { |
|||
let ty_size = ty_size(&ty); |
|||
*next_stack = align_to(*next_stack, ty_size) + ty_size; |
|||
LocalSlot::new(*ty, *next_stack) |
|||
} |
|||
// Create a local slot, with an offset from the arguments base in
|
|||
// the stack; which is the frame pointer + return address.
|
|||
ABIArg::Stack { ty, offset } => LocalSlot::stack_arg(*ty, offset + arg_base_offset), |
|||
} |
|||
} |
|||
|
|||
fn compute_defined_slots( |
|||
body_data: &mut FunctionBody, |
|||
validator: &mut FuncValidator<ValidatorResources>, |
|||
next_stack: u32, |
|||
) -> Result<(Locals, u32)> { |
|||
let mut next_stack = next_stack; |
|||
let mut reader = body_data.get_binary_reader(); |
|||
let local_count = reader.read_var_u32()?; |
|||
let mut slots: Locals = Default::default(); |
|||
|
|||
for _ in 0..local_count { |
|||
let position = reader.original_position(); |
|||
let count = reader.read_var_u32()?; |
|||
let ty = reader.read_val_type()?; |
|||
validator.define_locals(position, count, ty)?; |
|||
|
|||
let ty: ValType = ty.try_into()?; |
|||
for _ in 0..count { |
|||
let ty_size = ty_size(&ty); |
|||
next_stack = align_to(next_stack, ty_size) + ty_size; |
|||
slots.push(LocalSlot::new(ty, next_stack)); |
|||
} |
|||
} |
|||
|
|||
Ok((slots, next_stack)) |
|||
} |
|||
} |
@ -0,0 +1,210 @@ |
|||
use super::regs; |
|||
use crate::abi::{ABIArg, ABIResult, ABISig, ABI}; |
|||
use crate::isa::reg::Reg; |
|||
use smallvec::SmallVec; |
|||
use wasmparser::{FuncType, ValType}; |
|||
|
|||
#[derive(Default)] |
|||
pub(crate) struct Aarch64ABI; |
|||
|
|||
/// Helper environment to track argument-register
|
|||
/// assignment in aarch64.
|
|||
///
|
|||
/// The first element tracks the general purpose register index, capped at 7 (x0-x7).
|
|||
/// The second element tracks the floating point register index, capped at 7 (v0-v7).
|
|||
// Follows
|
|||
// https://github.com/ARM-software/abi-aa/blob/2021Q1/aapcs64/aapcs64.rst#64parameter-passing
|
|||
#[derive(Default)] |
|||
struct RegIndexEnv(u8, u8); |
|||
|
|||
impl RegIndexEnv { |
|||
fn next_xreg(&mut self) -> Option<u8> { |
|||
if self.0 < 8 { |
|||
return Some(Self::increment(&mut self.0)); |
|||
} |
|||
|
|||
None |
|||
} |
|||
|
|||
fn next_vreg(&mut self) -> Option<u8> { |
|||
if self.1 < 8 { |
|||
return Some(Self::increment(&mut self.1)); |
|||
} |
|||
|
|||
None |
|||
} |
|||
|
|||
fn increment(index: &mut u8) -> u8 { |
|||
let current = *index; |
|||
*index += 1; |
|||
current |
|||
} |
|||
} |
|||
|
|||
impl ABI for Aarch64ABI { |
|||
// TODO change to 16 once SIMD is supported
|
|||
fn stack_align(&self) -> u8 { |
|||
8 |
|||
} |
|||
|
|||
fn arg_base_offset(&self) -> u8 { |
|||
16 |
|||
} |
|||
|
|||
fn word_bits() -> u32 { |
|||
64 |
|||
} |
|||
|
|||
fn sig(&self, wasm_sig: &FuncType) -> ABISig { |
|||
if wasm_sig.results().len() > 1 { |
|||
panic!("multi-value not supported"); |
|||
} |
|||
|
|||
let mut stack_offset = 0; |
|||
let mut index_env = RegIndexEnv::default(); |
|||
|
|||
let params: SmallVec<[ABIArg; 6]> = wasm_sig |
|||
.params() |
|||
.iter() |
|||
.map(|arg| Self::to_abi_arg(arg, &mut stack_offset, &mut index_env)) |
|||
.collect(); |
|||
|
|||
let ty = wasm_sig.results().get(0).map(|e| e.clone()); |
|||
// NOTE temporarily defaulting to x0;
|
|||
let reg = regs::xreg(0); |
|||
let result = ABIResult::reg(ty, reg); |
|||
|
|||
ABISig { params, result } |
|||
} |
|||
|
|||
fn scratch_reg() -> Reg { |
|||
todo!() |
|||
} |
|||
} |
|||
|
|||
impl Aarch64ABI { |
|||
fn to_abi_arg( |
|||
wasm_arg: &ValType, |
|||
stack_offset: &mut u32, |
|||
index_env: &mut RegIndexEnv, |
|||
) -> ABIArg { |
|||
let (reg, ty) = match wasm_arg { |
|||
ty @ (ValType::I32 | ValType::I64) => (index_env.next_xreg().map(regs::xreg), ty), |
|||
|
|||
ty @ (ValType::F32 | ValType::F64) => (index_env.next_vreg().map(regs::vreg), ty), |
|||
|
|||
ty => unreachable!("Unsupported argument type {:?}", ty), |
|||
}; |
|||
|
|||
let ty = *ty; |
|||
let default = || { |
|||
let size = Self::word_bytes(); |
|||
let arg = ABIArg::stack_offset(*stack_offset, ty); |
|||
*stack_offset += size; |
|||
arg |
|||
}; |
|||
reg.map_or_else(default, |reg| ABIArg::Reg { ty, reg }) |
|||
} |
|||
} |
|||
|
|||
#[cfg(test)] |
|||
mod tests { |
|||
use super::{Aarch64ABI, RegIndexEnv}; |
|||
use crate::{ |
|||
abi::{ABIArg, ABI}, |
|||
isa::aarch64::regs, |
|||
isa::reg::Reg, |
|||
}; |
|||
use wasmparser::{ |
|||
FuncType, |
|||
ValType::{self, *}, |
|||
}; |
|||
|
|||
#[test] |
|||
fn test_get_next_reg_index() { |
|||
let mut index_env = RegIndexEnv::default(); |
|||
assert_eq!(index_env.next_xreg(), Some(0)); |
|||
assert_eq!(index_env.next_vreg(), Some(0)); |
|||
assert_eq!(index_env.next_xreg(), Some(1)); |
|||
assert_eq!(index_env.next_vreg(), Some(1)); |
|||
assert_eq!(index_env.next_xreg(), Some(2)); |
|||
assert_eq!(index_env.next_vreg(), Some(2)); |
|||
} |
|||
|
|||
#[test] |
|||
fn xreg_abi_sig() { |
|||
let wasm_sig = FuncType::new([I32, I64, I32, I64, I32, I32, I64, I32, I64], []); |
|||
|
|||
let abi = Aarch64ABI::default(); |
|||
let sig = abi.sig(&wasm_sig); |
|||
let params = sig.params; |
|||
|
|||
match_reg_arg(params.get(0).unwrap(), I32, regs::xreg(0)); |
|||
match_reg_arg(params.get(1).unwrap(), I64, regs::xreg(1)); |
|||
match_reg_arg(params.get(2).unwrap(), I32, regs::xreg(2)); |
|||
match_reg_arg(params.get(3).unwrap(), I64, regs::xreg(3)); |
|||
match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(4)); |
|||
match_reg_arg(params.get(5).unwrap(), I32, regs::xreg(5)); |
|||
match_reg_arg(params.get(6).unwrap(), I64, regs::xreg(6)); |
|||
match_reg_arg(params.get(7).unwrap(), I32, regs::xreg(7)); |
|||
match_stack_arg(params.get(8).unwrap(), I64, 0); |
|||
} |
|||
|
|||
#[test] |
|||
fn vreg_abi_sig() { |
|||
let wasm_sig = FuncType::new([F32, F64, F32, F64, F32, F32, F64, F32, F64], []); |
|||
|
|||
let abi = Aarch64ABI::default(); |
|||
let sig = abi.sig(&wasm_sig); |
|||
let params = sig.params; |
|||
|
|||
match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0)); |
|||
match_reg_arg(params.get(1).unwrap(), F64, regs::vreg(1)); |
|||
match_reg_arg(params.get(2).unwrap(), F32, regs::vreg(2)); |
|||
match_reg_arg(params.get(3).unwrap(), F64, regs::vreg(3)); |
|||
match_reg_arg(params.get(4).unwrap(), F32, regs::vreg(4)); |
|||
match_reg_arg(params.get(5).unwrap(), F32, regs::vreg(5)); |
|||
match_reg_arg(params.get(6).unwrap(), F64, regs::vreg(6)); |
|||
match_reg_arg(params.get(7).unwrap(), F32, regs::vreg(7)); |
|||
match_stack_arg(params.get(8).unwrap(), F64, 0); |
|||
} |
|||
|
|||
#[test] |
|||
fn mixed_abi_sig() { |
|||
let wasm_sig = FuncType::new([F32, I32, I64, F64, I32, F32, F64, F32, F64], []); |
|||
|
|||
let abi = Aarch64ABI::default(); |
|||
let sig = abi.sig(&wasm_sig); |
|||
let params = sig.params; |
|||
|
|||
match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0)); |
|||
match_reg_arg(params.get(1).unwrap(), I32, regs::xreg(0)); |
|||
match_reg_arg(params.get(2).unwrap(), I64, regs::xreg(1)); |
|||
match_reg_arg(params.get(3).unwrap(), F64, regs::vreg(1)); |
|||
match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(2)); |
|||
match_reg_arg(params.get(5).unwrap(), F32, regs::vreg(2)); |
|||
match_reg_arg(params.get(6).unwrap(), F64, regs::vreg(3)); |
|||
match_reg_arg(params.get(7).unwrap(), F32, regs::vreg(4)); |
|||
match_reg_arg(params.get(8).unwrap(), F64, regs::vreg(5)); |
|||
} |
|||
|
|||
fn match_reg_arg(abi_arg: &ABIArg, expected_ty: ValType, expected_reg: Reg) { |
|||
match abi_arg { |
|||
&ABIArg::Reg { reg, ty } => { |
|||
assert_eq!(reg, expected_reg); |
|||
assert_eq!(ty, expected_ty); |
|||
} |
|||
stack => panic!("Expected reg argument, got {:?}", stack), |
|||
} |
|||
} |
|||
|
|||
fn match_stack_arg(abi_arg: &ABIArg, expected_ty: ValType, expected_offset: u32) { |
|||
match abi_arg { |
|||
&ABIArg::Stack { offset, ty } => { |
|||
assert_eq!(offset, expected_offset); |
|||
assert_eq!(ty, expected_ty); |
|||
} |
|||
stack => panic!("Expected stack argument, got {:?}", stack), |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,56 @@ |
|||
use crate::{ |
|||
abi::{addressing_mode::Address, local::LocalSlot}, |
|||
isa::reg::Reg, |
|||
masm::{MacroAssembler as Masm, OperandSize, RegImm}, |
|||
}; |
|||
|
|||
#[derive(Default)] |
|||
pub(crate) struct MacroAssembler; |
|||
|
|||
impl Masm for MacroAssembler { |
|||
fn prologue(&mut self) { |
|||
todo!() |
|||
} |
|||
|
|||
fn epilogue(&mut self, _locals_size: u32) { |
|||
todo!() |
|||
} |
|||
|
|||
fn reserve_stack(&mut self, _bytes: u32) { |
|||
todo!() |
|||
} |
|||
|
|||
fn local_address(&mut self, _local: &LocalSlot) -> Address { |
|||
todo!() |
|||
} |
|||
|
|||
fn store(&mut self, _src: RegImm, _dst: Address, _size: OperandSize) { |
|||
todo!() |
|||
} |
|||
|
|||
fn load(&mut self, _src: Address, _dst: Reg, _size: OperandSize) {} |
|||
|
|||
fn sp_offset(&mut self) -> u32 { |
|||
0u32 |
|||
} |
|||
|
|||
fn finalize(&mut self) -> &[String] { |
|||
todo!() |
|||
} |
|||
|
|||
fn mov(&mut self, _src: RegImm, _dst: RegImm, _size: OperandSize) { |
|||
todo!() |
|||
} |
|||
|
|||
fn add(&mut self, _dst: RegImm, __lhs: RegImm, __rhs: RegImm, _size: OperandSize) { |
|||
todo!() |
|||
} |
|||
|
|||
fn zero(&mut self, _reg: Reg) { |
|||
todo!() |
|||
} |
|||
|
|||
fn push(&mut self, _reg: Reg) -> u32 { |
|||
todo!() |
|||
} |
|||
} |
@ -0,0 +1,42 @@ |
|||
use crate::isa::TargetIsa; |
|||
use anyhow::Result; |
|||
use target_lexicon::Triple; |
|||
use wasmparser::{FuncType, FuncValidator, FunctionBody, ValidatorResources}; |
|||
|
|||
mod abi; |
|||
mod masm; |
|||
mod regs; |
|||
|
|||
/// Create an ISA from the given triple.
|
|||
pub(crate) fn isa_from(triple: Triple) -> Aarch64 { |
|||
Aarch64::new(triple) |
|||
} |
|||
|
|||
pub(crate) struct Aarch64 { |
|||
triple: Triple, |
|||
} |
|||
|
|||
impl Aarch64 { |
|||
pub fn new(triple: Triple) -> Self { |
|||
Self { triple } |
|||
} |
|||
} |
|||
|
|||
impl TargetIsa for Aarch64 { |
|||
fn name(&self) -> &'static str { |
|||
"aarch64" |
|||
} |
|||
|
|||
fn triple(&self) -> &Triple { |
|||
&self.triple |
|||
} |
|||
|
|||
fn compile_function( |
|||
&self, |
|||
_sig: &FuncType, |
|||
mut _body: FunctionBody, |
|||
mut _validator: FuncValidator<ValidatorResources>, |
|||
) -> Result<Vec<String>> { |
|||
todo!() |
|||
} |
|||
} |
@ -0,0 +1,16 @@ |
|||
//! AArch64 register definition
|
|||
|
|||
use crate::isa::reg::Reg; |
|||
use regalloc2::{PReg, RegClass}; |
|||
|
|||
/// Construct a X-register from an index.
|
|||
pub(crate) const fn xreg(num: u8) -> Reg { |
|||
assert!(num < 31); |
|||
Reg::new(PReg::new(num as usize, RegClass::Int)) |
|||
} |
|||
|
|||
/// Construct a V-register from an index.
|
|||
pub(crate) const fn vreg(num: u8) -> Reg { |
|||
assert!(num < 32); |
|||
Reg::new(PReg::new(num as usize, RegClass::Float)) |
|||
} |
@ -0,0 +1,112 @@ |
|||
use anyhow::{anyhow, Result}; |
|||
use core::fmt::Formatter; |
|||
use cranelift_codegen::isa::CallConv; |
|||
use std::{ |
|||
error, |
|||
fmt::{self, Debug, Display}, |
|||
}; |
|||
use target_lexicon::{Architecture, Triple}; |
|||
use wasmparser::{FuncType, FuncValidator, FunctionBody, ValidatorResources}; |
|||
|
|||
#[cfg(feature = "x64")] |
|||
pub(crate) mod x64; |
|||
|
|||
#[cfg(feature = "arm64")] |
|||
pub(crate) mod aarch64; |
|||
|
|||
pub(crate) mod reg; |
|||
|
|||
macro_rules! isa { |
|||
($name: ident, $cfg_terms: tt, $triple: ident) => {{ |
|||
#[cfg $cfg_terms] |
|||
{ |
|||
Ok(Box::new($name::isa_from($triple))) |
|||
} |
|||
#[cfg(not $cfg_terms)] |
|||
{ |
|||
Err(anyhow!(LookupError::SupportDisabled)) |
|||
} |
|||
}}; |
|||
} |
|||
|
|||
/// Look for an ISA for the given target triple.
|
|||
//
|
|||
// The ISA, as it's currently implemented in Cranelift
|
|||
// needs a builder since it adds settings
|
|||
// depending on those available in the host architecture.
|
|||
// I'm intentionally skipping the builder for now.
|
|||
// The lookup method will return the ISA directly.
|
|||
//
|
|||
// Once features like SIMD are supported, returning a builder
|
|||
// will make more sense.
|
|||
pub fn lookup(triple: Triple) -> Result<Box<dyn TargetIsa>> { |
|||
match triple.architecture { |
|||
Architecture::X86_64 => { |
|||
isa!(x64, (feature = "x64"), triple) |
|||
} |
|||
Architecture::Aarch64 { .. } => { |
|||
isa!(aarch64, (feature = "arm64"), triple) |
|||
} |
|||
|
|||
_ => Err(anyhow!(LookupError::Unsupported)), |
|||
} |
|||
} |
|||
|
|||
impl error::Error for LookupError {} |
|||
impl Display for LookupError { |
|||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
|||
match self { |
|||
LookupError::Unsupported => write!(f, "This target is not supported yet"), |
|||
LookupError::SupportDisabled => write!(f, "Support for this target was disabled"), |
|||
} |
|||
} |
|||
} |
|||
|
|||
#[derive(Debug)] |
|||
pub(crate) enum LookupError { |
|||
Unsupported, |
|||
// This directive covers the case in which the consumer
|
|||
// enables the `all-arch` feature; in such case, this variant
|
|||
// will never be used. This is most likely going to change
|
|||
// in the future; this is one of the simplest options for now.
|
|||
#[allow(dead_code)] |
|||
SupportDisabled, |
|||
} |
|||
|
|||
/// A trait representing commonalities between the supported
|
|||
/// instruction set architectures.
|
|||
pub trait TargetIsa: Send + Sync { |
|||
/// Get the name of the ISA.
|
|||
fn name(&self) -> &'static str; |
|||
|
|||
/// Get the target triple of the ISA.
|
|||
fn triple(&self) -> &Triple; |
|||
|
|||
fn compile_function( |
|||
&self, |
|||
sig: &FuncType, |
|||
body: FunctionBody, |
|||
validator: FuncValidator<ValidatorResources>, |
|||
) -> Result<Vec<String>>; |
|||
|
|||
/// Get the default calling convention of the underlying target triple.
|
|||
fn call_conv(&self) -> CallConv { |
|||
CallConv::triple_default(&self.triple()) |
|||
} |
|||
|
|||
/// Get the endianess of the underlying target triple.
|
|||
fn endianness(&self) -> target_lexicon::Endianness { |
|||
self.triple().endianness().unwrap() |
|||
} |
|||
} |
|||
|
|||
impl Debug for &dyn TargetIsa { |
|||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
|||
write!( |
|||
f, |
|||
"Target ISA {{ triple: {:?}, calling convention: {:?} }}", |
|||
self.triple(), |
|||
self.call_conv() |
|||
) |
|||
} |
|||
} |
@ -0,0 +1,45 @@ |
|||
use regalloc2::{PReg, RegClass}; |
|||
|
|||
/// A newtype abstraction on top of a physical register.
|
|||
//
|
|||
// NOTE
|
|||
// This is temporary; the intention behind this newtype
|
|||
// is to keep the usage of PReg contained to this module
|
|||
// so that the rest of Winch should only need to operate
|
|||
// on top of the concept of `Reg`.
|
|||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
|||
pub(crate) struct Reg(PReg); |
|||
|
|||
impl Reg { |
|||
/// Create a new register from a physical register.
|
|||
pub const fn new(raw: PReg) -> Self { |
|||
Reg(raw) |
|||
} |
|||
|
|||
/// Create a new general purpose register from encoding.
|
|||
pub fn int(enc: usize) -> Self { |
|||
Self::new(PReg::new(enc, RegClass::Int)) |
|||
} |
|||
|
|||
/// Create a new floating point register from encoding.
|
|||
#[allow(dead_code)] |
|||
pub fn float(enc: usize) -> Self { |
|||
Self::new(PReg::new(enc, RegClass::Float)) |
|||
} |
|||
|
|||
/// Get the class of the underlying register.
|
|||
pub fn class(self) -> RegClass { |
|||
self.0.class() |
|||
} |
|||
|
|||
/// Get the encoding of the underlying register.
|
|||
pub fn hw_enc(self) -> u8 { |
|||
self.0.hw_enc() as u8 |
|||
} |
|||
} |
|||
|
|||
impl std::fmt::Debug for Reg { |
|||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
|||
write!(f, "{}", self.0) |
|||
} |
|||
} |
@ -0,0 +1,227 @@ |
|||
use super::regs; |
|||
use crate::{ |
|||
abi::{ABIArg, ABIResult, ABISig, ABI}, |
|||
isa::reg::Reg, |
|||
}; |
|||
use smallvec::SmallVec; |
|||
use wasmparser::{FuncType, ValType}; |
|||
|
|||
/// Helper environment to track argument-register
|
|||
/// assignment in x64.
|
|||
///
|
|||
/// The first element tracks the general purpose register index.
|
|||
/// The second element tracks the floating point register index.
|
|||
#[derive(Default)] |
|||
struct RegIndexEnv(u8, u8); |
|||
|
|||
impl RegIndexEnv { |
|||
fn next_gpr(&mut self) -> u8 { |
|||
Self::increment(&mut self.0) |
|||
} |
|||
|
|||
fn next_fpr(&mut self) -> u8 { |
|||
Self::increment(&mut self.1) |
|||
} |
|||
|
|||
fn increment(index: &mut u8) -> u8 { |
|||
let current = *index; |
|||
*index += 1; |
|||
current |
|||
} |
|||
} |
|||
|
|||
#[derive(Default)] |
|||
pub(crate) struct X64ABI; |
|||
|
|||
impl ABI for X64ABI { |
|||
// TODO: change to 16 once SIMD is supported
|
|||
fn stack_align(&self) -> u8 { |
|||
8 |
|||
} |
|||
|
|||
fn arg_base_offset(&self) -> u8 { |
|||
16 |
|||
} |
|||
|
|||
fn word_bits() -> u32 { |
|||
64 |
|||
} |
|||
|
|||
fn sig(&self, wasm_sig: &FuncType) -> ABISig { |
|||
if wasm_sig.results().len() > 1 { |
|||
panic!("multi-value not supported"); |
|||
} |
|||
|
|||
let mut stack_offset = 0; |
|||
let mut index_env = RegIndexEnv::default(); |
|||
|
|||
let params: SmallVec<[ABIArg; 6]> = wasm_sig |
|||
.params() |
|||
.iter() |
|||
.map(|arg| Self::to_abi_arg(arg, &mut stack_offset, &mut index_env)) |
|||
.collect(); |
|||
|
|||
let ty = wasm_sig.results().get(0).map(|e| e.clone()); |
|||
// NOTE temporarily defaulting to rax.
|
|||
let reg = regs::rax(); |
|||
let result = ABIResult::reg(ty, reg); |
|||
|
|||
ABISig { params, result } |
|||
} |
|||
|
|||
fn scratch_reg() -> Reg { |
|||
regs::scratch() |
|||
} |
|||
} |
|||
|
|||
impl X64ABI { |
|||
fn to_abi_arg( |
|||
wasm_arg: &ValType, |
|||
stack_offset: &mut u32, |
|||
index_env: &mut RegIndexEnv, |
|||
) -> ABIArg { |
|||
let (reg, ty) = match wasm_arg { |
|||
ty @ (ValType::I32 | ValType::I64) => (Self::int_reg_for(index_env.next_gpr()), ty), |
|||
|
|||
ty @ (ValType::F32 | ValType::F64) => (Self::float_reg_for(index_env.next_fpr()), ty), |
|||
|
|||
ty => unreachable!("Unsupported argument type {:?}", ty), |
|||
}; |
|||
|
|||
let default = || { |
|||
let arg = ABIArg::stack_offset(*stack_offset, *ty); |
|||
let size = Self::word_bytes(); |
|||
*stack_offset += size; |
|||
arg |
|||
}; |
|||
|
|||
reg.map_or_else(default, |reg| ABIArg::reg(reg, *ty)) |
|||
} |
|||
|
|||
fn int_reg_for(index: u8) -> Option<Reg> { |
|||
match index { |
|||
0 => Some(regs::rdi()), |
|||
1 => Some(regs::rsi()), |
|||
2 => Some(regs::rdx()), |
|||
3 => Some(regs::rcx()), |
|||
4 => Some(regs::r8()), |
|||
5 => Some(regs::r9()), |
|||
_ => None, |
|||
} |
|||
} |
|||
|
|||
fn float_reg_for(index: u8) -> Option<Reg> { |
|||
match index { |
|||
0 => Some(regs::xmm0()), |
|||
1 => Some(regs::xmm1()), |
|||
2 => Some(regs::xmm2()), |
|||
3 => Some(regs::xmm3()), |
|||
4 => Some(regs::xmm4()), |
|||
5 => Some(regs::xmm5()), |
|||
6 => Some(regs::xmm6()), |
|||
7 => Some(regs::xmm7()), |
|||
_ => None, |
|||
} |
|||
} |
|||
} |
|||
|
|||
#[cfg(test)] |
|||
mod tests { |
|||
use super::{RegIndexEnv, X64ABI}; |
|||
use crate::{ |
|||
abi::{ABIArg, ABI}, |
|||
isa::reg::Reg, |
|||
isa::x64::regs, |
|||
}; |
|||
use wasmparser::{ |
|||
FuncType, |
|||
ValType::{self, *}, |
|||
}; |
|||
|
|||
#[test] |
|||
fn test_get_next_reg_index() { |
|||
let mut index_env = RegIndexEnv::default(); |
|||
assert_eq!(index_env.next_fpr(), 0); |
|||
assert_eq!(index_env.next_gpr(), 0); |
|||
assert_eq!(index_env.next_fpr(), 1); |
|||
assert_eq!(index_env.next_gpr(), 1); |
|||
assert_eq!(index_env.next_fpr(), 2); |
|||
assert_eq!(index_env.next_gpr(), 2); |
|||
} |
|||
|
|||
#[test] |
|||
fn int_abi_sig() { |
|||
let wasm_sig = FuncType::new([I32, I64, I32, I64, I32, I32, I64, I32], []); |
|||
|
|||
let abi = X64ABI::default(); |
|||
let sig = abi.sig(&wasm_sig); |
|||
let params = sig.params; |
|||
|
|||
match_reg_arg(params.get(0).unwrap(), I32, regs::rdi()); |
|||
match_reg_arg(params.get(1).unwrap(), I64, regs::rsi()); |
|||
match_reg_arg(params.get(2).unwrap(), I32, regs::rdx()); |
|||
match_reg_arg(params.get(3).unwrap(), I64, regs::rcx()); |
|||
match_reg_arg(params.get(4).unwrap(), I32, regs::r8()); |
|||
match_reg_arg(params.get(5).unwrap(), I32, regs::r9()); |
|||
match_stack_arg(params.get(6).unwrap(), I64, 0); |
|||
match_stack_arg(params.get(7).unwrap(), I32, 8); |
|||
} |
|||
|
|||
#[test] |
|||
fn float_abi_sig() { |
|||
let wasm_sig = FuncType::new([F32, F64, F32, F64, F32, F32, F64, F32, F64], []); |
|||
|
|||
let abi = X64ABI::default(); |
|||
let sig = abi.sig(&wasm_sig); |
|||
let params = sig.params; |
|||
|
|||
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0()); |
|||
match_reg_arg(params.get(1).unwrap(), F64, regs::xmm1()); |
|||
match_reg_arg(params.get(2).unwrap(), F32, regs::xmm2()); |
|||
match_reg_arg(params.get(3).unwrap(), F64, regs::xmm3()); |
|||
match_reg_arg(params.get(4).unwrap(), F32, regs::xmm4()); |
|||
match_reg_arg(params.get(5).unwrap(), F32, regs::xmm5()); |
|||
match_reg_arg(params.get(6).unwrap(), F64, regs::xmm6()); |
|||
match_reg_arg(params.get(7).unwrap(), F32, regs::xmm7()); |
|||
match_stack_arg(params.get(8).unwrap(), F64, 0); |
|||
} |
|||
|
|||
#[test] |
|||
fn mixed_abi_sig() { |
|||
let wasm_sig = FuncType::new([F32, I32, I64, F64, I32, F32, F64, F32, F64], []); |
|||
|
|||
let abi = X64ABI::default(); |
|||
let sig = abi.sig(&wasm_sig); |
|||
let params = sig.params; |
|||
|
|||
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0()); |
|||
match_reg_arg(params.get(1).unwrap(), I32, regs::rdi()); |
|||
match_reg_arg(params.get(2).unwrap(), I64, regs::rsi()); |
|||
match_reg_arg(params.get(3).unwrap(), F64, regs::xmm1()); |
|||
match_reg_arg(params.get(4).unwrap(), I32, regs::rdx()); |
|||
match_reg_arg(params.get(5).unwrap(), F32, regs::xmm2()); |
|||
match_reg_arg(params.get(6).unwrap(), F64, regs::xmm3()); |
|||
match_reg_arg(params.get(7).unwrap(), F32, regs::xmm4()); |
|||
match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5()); |
|||
} |
|||
|
|||
fn match_reg_arg(abi_arg: &ABIArg, expected_ty: ValType, expected_reg: Reg) { |
|||
match abi_arg { |
|||
&ABIArg::Reg { reg, ty } => { |
|||
assert_eq!(reg, expected_reg); |
|||
assert_eq!(ty, expected_ty); |
|||
} |
|||
stack => panic!("Expected reg argument, got {:?}", stack), |
|||
} |
|||
} |
|||
|
|||
fn match_stack_arg(abi_arg: &ABIArg, expected_ty: ValType, expected_offset: u32) { |
|||
match abi_arg { |
|||
&ABIArg::Stack { offset, ty } => { |
|||
assert_eq!(offset, expected_offset); |
|||
assert_eq!(ty, expected_ty); |
|||
} |
|||
stack => panic!("Expected stack argument, got {:?}", stack), |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,440 @@ |
|||
use super::regs::{rbp, reg_name, rsp}; |
|||
use crate::abi::addressing_mode::Address; |
|||
use crate::abi::local::LocalSlot; |
|||
use crate::isa::reg::Reg; |
|||
use crate::masm::{MacroAssembler as Masm, OperandSize, RegImm}; |
|||
|
|||
pub(crate) struct MacroAssembler { |
|||
sp_offset: u32, |
|||
asm: Assembler, |
|||
} |
|||
|
|||
impl Masm for MacroAssembler { |
|||
fn prologue(&mut self) { |
|||
let frame_pointer = rbp(); |
|||
let stack_pointer = rsp(); |
|||
|
|||
self.asm.push_r(frame_pointer); |
|||
self.asm.mov_rr(stack_pointer, frame_pointer); |
|||
} |
|||
|
|||
fn push(&mut self, reg: Reg) -> u32 { |
|||
self.asm.push_r(reg); |
|||
// In x64 the push instruction takes either
|
|||
// 2 or 8 bytes; in our case we're always
|
|||
// assuming 8 bytes per push.
|
|||
self.increment_sp(8); |
|||
|
|||
self.sp_offset |
|||
} |
|||
|
|||
fn reserve_stack(&mut self, bytes: u32) { |
|||
if bytes == 0 { |
|||
return; |
|||
} |
|||
|
|||
self.asm.sub_ir(bytes, rsp()); |
|||
self.increment_sp(bytes); |
|||
} |
|||
|
|||
fn local_address(&mut self, local: &LocalSlot) -> Address { |
|||
let (reg, offset) = local |
|||
.addressed_from_sp() |
|||
.then(|| { |
|||
let offset = self.sp_offset.checked_sub(local.offset).expect(&format!( |
|||
"Invalid local offset = {}; sp offset = {}", |
|||
local.offset, self.sp_offset |
|||
)); |
|||
(rsp(), offset) |
|||
}) |
|||
.unwrap_or((rbp(), local.offset)); |
|||
|
|||
Address::base(reg, offset) |
|||
} |
|||
|
|||
fn store(&mut self, src: RegImm, dst: Address, size: OperandSize) { |
|||
let src: Operand = src.into(); |
|||
let dst: Operand = dst.into(); |
|||
|
|||
match size { |
|||
OperandSize::S32 => { |
|||
self.asm.movl(src, dst); |
|||
} |
|||
OperandSize::S64 => { |
|||
self.asm.mov(src, dst); |
|||
} |
|||
} |
|||
} |
|||
|
|||
fn load(&mut self, src: Address, dst: Reg, size: OperandSize) { |
|||
let src = src.into(); |
|||
let dst = dst.into(); |
|||
|
|||
match size { |
|||
OperandSize::S32 => self.asm.movl(src, dst), |
|||
OperandSize::S64 => self.asm.mov(src, dst), |
|||
} |
|||
} |
|||
|
|||
fn sp_offset(&mut self) -> u32 { |
|||
self.sp_offset |
|||
} |
|||
|
|||
fn zero(&mut self, reg: Reg) { |
|||
self.asm.xorl_rr(reg, reg); |
|||
} |
|||
|
|||
fn mov(&mut self, src: RegImm, dst: RegImm, size: OperandSize) { |
|||
let src: Operand = src.into(); |
|||
let dst: Operand = dst.into(); |
|||
|
|||
match size { |
|||
OperandSize::S32 => { |
|||
self.asm.movl(src, dst); |
|||
} |
|||
OperandSize::S64 => { |
|||
self.asm.mov(src, dst); |
|||
} |
|||
} |
|||
} |
|||
|
|||
fn add(&mut self, dst: RegImm, lhs: RegImm, rhs: RegImm, size: OperandSize) { |
|||
let (src, dst): (Operand, Operand) = if dst == lhs { |
|||
(rhs.into(), dst.into()) |
|||
} else { |
|||
panic!( |
|||
"the destination and first source argument must be the same, dst={:?}, lhs={:?}", |
|||
dst, lhs |
|||
); |
|||
}; |
|||
|
|||
match size { |
|||
OperandSize::S32 => { |
|||
self.asm.addl(src, dst); |
|||
} |
|||
OperandSize::S64 => { |
|||
self.asm.add(src, dst); |
|||
} |
|||
} |
|||
} |
|||
|
|||
fn epilogue(&mut self, locals_size: u32) { |
|||
let rsp = rsp(); |
|||
if locals_size > 0 { |
|||
self.asm.add_ir(locals_size as i32, rsp); |
|||
} |
|||
self.asm.pop_r(rbp()); |
|||
self.asm.ret(); |
|||
} |
|||
|
|||
fn finalize(&mut self) -> &[String] { |
|||
self.asm.finalize() |
|||
} |
|||
} |
|||
|
|||
impl MacroAssembler { |
|||
/// Crate a x64 MacroAssembler
|
|||
pub fn new() -> Self { |
|||
Self { |
|||
sp_offset: 0, |
|||
asm: Default::default(), |
|||
} |
|||
} |
|||
|
|||
fn increment_sp(&mut self, bytes: u32) { |
|||
self.sp_offset += bytes; |
|||
} |
|||
|
|||
#[allow(dead_code)] |
|||
fn decrement_sp(&mut self, bytes: u32) { |
|||
assert!( |
|||
self.sp_offset >= bytes, |
|||
"sp offset = {}; bytes = {}", |
|||
self.sp_offset, |
|||
bytes |
|||
); |
|||
self.sp_offset -= bytes; |
|||
} |
|||
} |
|||
|
|||
/// A x64 instruction operand.
|
|||
#[derive(Debug, Copy, Clone)] |
|||
enum Operand { |
|||
Reg(Reg), |
|||
Mem(Address), |
|||
Imm(i32), |
|||
} |
|||
|
|||
/// Low level assembler implementation for x64
|
|||
/// All instructions denote a 64 bit size, unless
|
|||
/// otherwise specified by the corresponding function
|
|||
/// name suffix.
|
|||
|
|||
// NOTE
|
|||
// This is an interim, debug approach; the long term idea
|
|||
// is to make each ISA assembler available through
|
|||
// `cranelift_asm`. The literal representation of the
|
|||
// instructions use intel syntax for easier manual verification.
|
|||
// This shouldn't be an issue, once we plug in Cranelift's backend
|
|||
// we are going to be able to properly disassemble.
|
|||
#[derive(Default)] |
|||
struct Assembler { |
|||
buffer: Vec<String>, |
|||
} |
|||
|
|||
impl Assembler { |
|||
pub fn push_r(&mut self, reg: Reg) { |
|||
self.buffer.push(format!("push {}", reg_name(reg, 8))); |
|||
} |
|||
|
|||
pub fn pop_r(&mut self, reg: Reg) { |
|||
self.buffer.push(format!("pop {}", reg_name(reg, 8))); |
|||
} |
|||
|
|||
pub fn ret(&mut self) { |
|||
self.buffer.push("ret".into()); |
|||
} |
|||
|
|||
pub fn mov(&mut self, src: Operand, dst: Operand) { |
|||
// r, r
|
|||
// r, m (displacement)
|
|||
// r, m (displace,ent, index)
|
|||
// i, r
|
|||
// i, m (displacement)
|
|||
// i, m (displacement, index)
|
|||
// load combinations
|
|||
match &(src, dst) { |
|||
(Operand::Reg(lhs), Operand::Reg(rhs)) => self.mov_rr(*lhs, *rhs), |
|||
(Operand::Reg(r), Operand::Mem(addr)) => match addr { |
|||
Address::Base { base, imm } => self.mov_rm(*r, *base, *imm), |
|||
}, |
|||
(Operand::Imm(op), Operand::Mem(addr)) => match addr { |
|||
Address::Base { base, imm } => self.mov_im(*op, *base, *imm), |
|||
}, |
|||
(Operand::Imm(imm), Operand::Reg(reg)) => self.mov_ir(*imm, *reg), |
|||
(Operand::Mem(addr), Operand::Reg(reg)) => match addr { |
|||
Address::Base { base, imm } => self.mov_mr(*base, *imm, *reg), |
|||
}, |
|||
_ => panic!( |
|||
"Invalid operand combination for mov; src = {:?}; dst = {:?}", |
|||
src, dst |
|||
), |
|||
} |
|||
} |
|||
|
|||
pub fn mov_rr(&mut self, src: Reg, dst: Reg) { |
|||
let dst = reg_name(dst, 8); |
|||
let src = reg_name(src, 8); |
|||
|
|||
self.buffer.push(format!("mov {}, {}", dst, src)); |
|||
} |
|||
|
|||
pub fn mov_rm(&mut self, src: Reg, base: Reg, disp: u32) { |
|||
let src = reg_name(src, 8); |
|||
let dst = reg_name(base, 8); |
|||
|
|||
let addr = if disp == 0 { |
|||
format!("[{}]", dst) |
|||
} else { |
|||
format!("[{} + {}]", dst, disp) |
|||
}; |
|||
|
|||
self.buffer.push(format!("mov {}, {}", addr, src)); |
|||
} |
|||
|
|||
pub fn mov_im(&mut self, imm: i32, base: Reg, disp: u32) { |
|||
let reg = reg_name(base, 8); |
|||
|
|||
let addr = if disp == 0 { |
|||
format!("[{}]", reg) |
|||
} else { |
|||
format!("[{} + {}]", reg, disp) |
|||
}; |
|||
|
|||
self.buffer.push(format!("mov qword {}, {}", addr, imm)); |
|||
} |
|||
|
|||
pub fn mov_ir(&mut self, imm: i32, dst: Reg) { |
|||
let reg = reg_name(dst, 8); |
|||
|
|||
self.buffer.push(format!("mov {}, {}", reg, imm)); |
|||
} |
|||
|
|||
pub fn mov_mr(&mut self, base: Reg, disp: u32, dst: Reg) { |
|||
let base = reg_name(base, 8); |
|||
let dst = reg_name(dst, 8); |
|||
|
|||
let addr = if disp == 0 { |
|||
format!("[{}]", base) |
|||
} else { |
|||
format!("[{} + {}]", base, disp) |
|||
}; |
|||
|
|||
self.buffer.push(format!("mov {}, {}", dst, addr)); |
|||
} |
|||
|
|||
pub fn movl(&mut self, src: Operand, dst: Operand) { |
|||
// r, r
|
|||
// r, m (displacement)
|
|||
// r, m (displace,ent, index)
|
|||
// i, r
|
|||
// i, m (displacement)
|
|||
// i, m (displacement, index)
|
|||
// load combinations
|
|||
match &(src, dst) { |
|||
(Operand::Reg(lhs), Operand::Reg(rhs)) => self.movl_rr(*lhs, *rhs), |
|||
(Operand::Reg(r), Operand::Mem(addr)) => match addr { |
|||
Address::Base { base, imm } => self.movl_rm(*r, *base, *imm), |
|||
}, |
|||
(Operand::Imm(op), Operand::Mem(addr)) => match addr { |
|||
Address::Base { base, imm } => self.movl_im(*op, *base, *imm), |
|||
}, |
|||
(Operand::Imm(imm), Operand::Reg(reg)) => self.movl_ir(*imm, *reg), |
|||
(Operand::Mem(addr), Operand::Reg(reg)) => match addr { |
|||
Address::Base { base, imm } => self.movl_mr(*base, *imm, *reg), |
|||
}, |
|||
|
|||
_ => panic!( |
|||
"Invalid operand combination for movl; src = {:?}; dst = {:?}", |
|||
src, dst |
|||
), |
|||
} |
|||
} |
|||
|
|||
pub fn movl_rr(&mut self, src: Reg, dst: Reg) { |
|||
let dst = reg_name(dst, 4); |
|||
let src = reg_name(src, 4); |
|||
|
|||
self.buffer.push(format!("mov {}, {}", dst, src)); |
|||
} |
|||
|
|||
pub fn movl_rm(&mut self, src: Reg, base: Reg, disp: u32) { |
|||
let src = reg_name(src, 4); |
|||
let dst = reg_name(base, 8); |
|||
|
|||
let addr = if disp == 0 { |
|||
format!("[{}]", dst) |
|||
} else { |
|||
format!("[{} + {}]", dst, disp) |
|||
}; |
|||
|
|||
self.buffer.push(format!("mov {}, {}", addr, src)); |
|||
} |
|||
|
|||
pub fn movl_im(&mut self, imm: i32, base: Reg, disp: u32) { |
|||
let reg = reg_name(base, 8); |
|||
|
|||
let addr = if disp == 0 { |
|||
format!("[{}]", reg) |
|||
} else { |
|||
format!("[{} + {}]", reg, disp) |
|||
}; |
|||
|
|||
self.buffer.push(format!("mov dword {}, {}", addr, imm)); |
|||
} |
|||
|
|||
pub fn movl_ir(&mut self, imm: i32, dst: Reg) { |
|||
let reg = reg_name(dst, 4); |
|||
|
|||
self.buffer.push(format!("mov {}, {}", reg, imm)); |
|||
} |
|||
|
|||
pub fn movl_mr(&mut self, base: Reg, disp: u32, dst: Reg) { |
|||
let base = reg_name(base, 8); |
|||
let dst = reg_name(dst, 4); |
|||
|
|||
let addr = if disp == 0 { |
|||
format!("[{}]", base) |
|||
} else { |
|||
format!("[{} + {}]", base, disp) |
|||
}; |
|||
|
|||
self.buffer.push(format!("mov {}, {}", dst, addr)); |
|||
} |
|||
|
|||
pub fn sub_ir(&mut self, imm: u32, dst: Reg) { |
|||
let dst = reg_name(dst, 8); |
|||
self.buffer.push(format!("sub {}, {}", dst, imm)); |
|||
} |
|||
|
|||
pub fn add(&mut self, src: Operand, dst: Operand) { |
|||
match &(src, dst) { |
|||
(Operand::Imm(imm), Operand::Reg(dst)) => self.add_ir(*imm, *dst), |
|||
(Operand::Reg(src), Operand::Reg(dst)) => self.add_rr(*src, *dst), |
|||
_ => panic!( |
|||
"Invalid operand combination for add; src = {:?} dst = {:?}", |
|||
src, dst |
|||
), |
|||
} |
|||
} |
|||
|
|||
pub fn add_ir(&mut self, imm: i32, dst: Reg) { |
|||
let dst = reg_name(dst, 8); |
|||
|
|||
self.buffer.push(format!("add {}, {}", dst, imm)); |
|||
} |
|||
|
|||
pub fn add_rr(&mut self, src: Reg, dst: Reg) { |
|||
let src = reg_name(src, 8); |
|||
let dst = reg_name(dst, 8); |
|||
|
|||
self.buffer.push(format!("add {}, {}", dst, src)); |
|||
} |
|||
|
|||
pub fn addl(&mut self, src: Operand, dst: Operand) { |
|||
match &(src, dst) { |
|||
(Operand::Imm(imm), Operand::Reg(dst)) => self.addl_ir(*imm, *dst), |
|||
(Operand::Reg(src), Operand::Reg(dst)) => self.addl_rr(*src, *dst), |
|||
_ => panic!( |
|||
"Invalid operand combination for add; src = {:?} dst = {:?}", |
|||
src, dst |
|||
), |
|||
} |
|||
} |
|||
|
|||
pub fn addl_ir(&mut self, imm: i32, dst: Reg) { |
|||
let dst = reg_name(dst, 4); |
|||
|
|||
self.buffer.push(format!("add {}, {}", dst, imm)); |
|||
} |
|||
|
|||
pub fn addl_rr(&mut self, src: Reg, dst: Reg) { |
|||
let src = reg_name(src, 4); |
|||
let dst = reg_name(dst, 4); |
|||
|
|||
self.buffer.push(format!("add {}, {}", dst, src)); |
|||
} |
|||
|
|||
pub fn xorl_rr(&mut self, src: Reg, dst: Reg) { |
|||
let src = reg_name(src, 4); |
|||
let dst = reg_name(dst, 4); |
|||
|
|||
self.buffer.push(format!("xor {}, {}", dst, src)); |
|||
} |
|||
|
|||
/// Return the emitted code
|
|||
pub fn finalize(&mut self) -> &[String] { |
|||
&self.buffer |
|||
} |
|||
} |
|||
|
|||
impl From<RegImm> for Operand { |
|||
fn from(rimm: RegImm) -> Self { |
|||
match rimm { |
|||
RegImm::Reg(r) => r.into(), |
|||
RegImm::Imm(imm) => Operand::Imm(imm), |
|||
} |
|||
} |
|||
} |
|||
|
|||
impl From<Reg> for Operand { |
|||
fn from(reg: Reg) -> Self { |
|||
Operand::Reg(reg) |
|||
} |
|||
} |
|||
|
|||
impl From<Address> for Operand { |
|||
fn from(addr: Address) -> Self { |
|||
Operand::Mem(addr) |
|||
} |
|||
} |
@ -0,0 +1,66 @@ |
|||
use crate::abi::ABI; |
|||
use crate::codegen::{CodeGen, CodeGenContext}; |
|||
use crate::frame::Frame; |
|||
use crate::isa::x64::masm::MacroAssembler; |
|||
use crate::regalloc::RegAlloc; |
|||
use crate::stack::Stack; |
|||
use crate::{isa::TargetIsa, regset::RegSet}; |
|||
use anyhow::Result; |
|||
use target_lexicon::Triple; |
|||
use wasmparser::{FuncType, FuncValidator, FunctionBody, ValidatorResources}; |
|||
|
|||
use self::regs::ALL_GPR; |
|||
|
|||
mod abi; |
|||
mod masm; |
|||
// Not all the fpr and gpr constructors are used at the moment;
|
|||
// in that sense, this directive is a temporary measure to avoid
|
|||
// dead code warnings.
|
|||
#[allow(dead_code)] |
|||
mod regs; |
|||
|
|||
/// Create an ISA from the given triple.
|
|||
pub(crate) fn isa_from(triple: Triple) -> X64 { |
|||
X64::new(triple) |
|||
} |
|||
|
|||
pub(crate) struct X64 { |
|||
triple: Triple, |
|||
} |
|||
|
|||
impl X64 { |
|||
pub fn new(triple: Triple) -> Self { |
|||
Self { triple } |
|||
} |
|||
} |
|||
|
|||
impl TargetIsa for X64 { |
|||
fn name(&self) -> &'static str { |
|||
"x64" |
|||
} |
|||
|
|||
fn triple(&self) -> &Triple { |
|||
&self.triple |
|||
} |
|||
|
|||
// Temporarily returns a Vec<String>
|
|||
fn compile_function( |
|||
&self, |
|||
sig: &FuncType, |
|||
mut body: FunctionBody, |
|||
mut validator: FuncValidator<ValidatorResources>, |
|||
) -> Result<Vec<String>> { |
|||
let masm = MacroAssembler::new(); |
|||
let stack = Stack::new(); |
|||
let abi = abi::X64ABI::default(); |
|||
let abi_sig = abi.sig(sig); |
|||
let frame = Frame::new(&abi_sig, &mut body, &mut validator, &abi)?; |
|||
// TODO Add in floating point bitmask
|
|||
let regalloc = RegAlloc::new(RegSet::new(ALL_GPR, 0), regs::scratch()); |
|||
let codegen_context = CodeGenContext::new(masm, stack, &frame); |
|||
let mut codegen = |
|||
CodeGen::new::<abi::X64ABI>(codegen_context, abi_sig, body, validator, regalloc); |
|||
|
|||
codegen.emit() |
|||
} |
|||
} |
@ -0,0 +1,238 @@ |
|||
//! X64 register definition.
|
|||
|
|||
use crate::isa::reg::Reg; |
|||
use regalloc2::{PReg, RegClass}; |
|||
|
|||
const ENC_RAX: u8 = 0; |
|||
const ENC_RCX: u8 = 1; |
|||
const ENC_RDX: u8 = 2; |
|||
const ENC_RBX: u8 = 3; |
|||
const ENC_RSP: u8 = 4; |
|||
const ENC_RBP: u8 = 5; |
|||
const ENC_RSI: u8 = 6; |
|||
const ENC_RDI: u8 = 7; |
|||
const ENC_R8: u8 = 8; |
|||
const ENC_R9: u8 = 9; |
|||
const ENC_R10: u8 = 10; |
|||
const ENC_R11: u8 = 11; |
|||
const ENC_R12: u8 = 12; |
|||
const ENC_R13: u8 = 13; |
|||
const ENC_R14: u8 = 14; |
|||
const ENC_R15: u8 = 15; |
|||
|
|||
fn gpr(enc: u8) -> Reg { |
|||
Reg::new(PReg::new(enc as usize, RegClass::Int)) |
|||
} |
|||
|
|||
/// Constructors for GPR.
|
|||
|
|||
pub(crate) fn rsi() -> Reg { |
|||
gpr(ENC_RSI) |
|||
} |
|||
pub(crate) fn rdi() -> Reg { |
|||
gpr(ENC_RDI) |
|||
} |
|||
pub(crate) fn rax() -> Reg { |
|||
gpr(ENC_RAX) |
|||
} |
|||
pub(crate) fn rcx() -> Reg { |
|||
gpr(ENC_RCX) |
|||
} |
|||
pub(crate) fn rdx() -> Reg { |
|||
gpr(ENC_RDX) |
|||
} |
|||
pub(crate) fn r8() -> Reg { |
|||
gpr(ENC_R8) |
|||
} |
|||
pub(crate) fn r9() -> Reg { |
|||
gpr(ENC_R9) |
|||
} |
|||
pub(crate) fn r10() -> Reg { |
|||
gpr(ENC_R10) |
|||
} |
|||
pub(crate) fn r11() -> Reg { |
|||
gpr(ENC_R11) |
|||
} |
|||
pub(crate) fn r12() -> Reg { |
|||
gpr(ENC_R12) |
|||
} |
|||
pub(crate) fn r13() -> Reg { |
|||
gpr(ENC_R13) |
|||
} |
|||
pub(crate) fn r14() -> Reg { |
|||
gpr(ENC_R14) |
|||
} |
|||
pub(crate) fn rbx() -> Reg { |
|||
gpr(ENC_RBX) |
|||
} |
|||
|
|||
pub(crate) fn r15() -> Reg { |
|||
gpr(ENC_R15) |
|||
} |
|||
|
|||
pub(crate) fn rsp() -> Reg { |
|||
gpr(ENC_RSP) |
|||
} |
|||
pub(crate) fn rbp() -> Reg { |
|||
gpr(ENC_RBP) |
|||
} |
|||
|
|||
pub(crate) fn scratch() -> Reg { |
|||
r11() |
|||
} |
|||
|
|||
fn fpr(enc: u8) -> Reg { |
|||
Reg::new(PReg::new(enc as usize, RegClass::Float)) |
|||
} |
|||
|
|||
/// Constructors for FPR.
|
|||
|
|||
pub(crate) fn xmm0() -> Reg { |
|||
fpr(0) |
|||
} |
|||
pub(crate) fn xmm1() -> Reg { |
|||
fpr(1) |
|||
} |
|||
pub(crate) fn xmm2() -> Reg { |
|||
fpr(2) |
|||
} |
|||
pub(crate) fn xmm3() -> Reg { |
|||
fpr(3) |
|||
} |
|||
pub(crate) fn xmm4() -> Reg { |
|||
fpr(4) |
|||
} |
|||
pub(crate) fn xmm5() -> Reg { |
|||
fpr(5) |
|||
} |
|||
pub(crate) fn xmm6() -> Reg { |
|||
fpr(6) |
|||
} |
|||
pub(crate) fn xmm7() -> Reg { |
|||
fpr(7) |
|||
} |
|||
pub(crate) fn xmm8() -> Reg { |
|||
fpr(8) |
|||
} |
|||
pub(crate) fn xmm9() -> Reg { |
|||
fpr(9) |
|||
} |
|||
pub(crate) fn xmm10() -> Reg { |
|||
fpr(10) |
|||
} |
|||
pub(crate) fn xmm11() -> Reg { |
|||
fpr(11) |
|||
} |
|||
pub(crate) fn xmm12() -> Reg { |
|||
fpr(12) |
|||
} |
|||
pub(crate) fn xmm13() -> Reg { |
|||
fpr(13) |
|||
} |
|||
pub(crate) fn xmm14() -> Reg { |
|||
fpr(14) |
|||
} |
|||
pub(crate) fn xmm15() -> Reg { |
|||
fpr(15) |
|||
} |
|||
|
|||
const GPR: u32 = 16; |
|||
const ALLOCATABLE_GPR: u32 = (1 << GPR) - 1; |
|||
const NON_ALLOCATABLE_GPR: u32 = (1 << ENC_RBP) | (1 << ENC_RSP) | (1 << ENC_R11); |
|||
|
|||
/// Bitmask to represent the available general purpose registers.
|
|||
pub(crate) const ALL_GPR: u32 = ALLOCATABLE_GPR & !NON_ALLOCATABLE_GPR; |
|||
|
|||
// Temporarily removing the % from the register name
|
|||
// for debugging purposes only until winch gets disasm
|
|||
// support.
|
|||
pub(crate) fn reg_name(reg: Reg, size: u8) -> &'static str { |
|||
match reg.class() { |
|||
RegClass::Int => match (reg.hw_enc() as u8, size) { |
|||
(ENC_RAX, 8) => "rax", |
|||
(ENC_RAX, 4) => "eax", |
|||
(ENC_RAX, 2) => "ax", |
|||
(ENC_RAX, 1) => "al", |
|||
(ENC_RBX, 8) => "rbx", |
|||
(ENC_RBX, 4) => "ebx", |
|||
(ENC_RBX, 2) => "bx", |
|||
(ENC_RBX, 1) => "bl", |
|||
(ENC_RCX, 8) => "rcx", |
|||
(ENC_RCX, 4) => "ecx", |
|||
(ENC_RCX, 2) => "cx", |
|||
(ENC_RCX, 1) => "cl", |
|||
(ENC_RDX, 8) => "rdx", |
|||
(ENC_RDX, 4) => "edx", |
|||
(ENC_RDX, 2) => "dx", |
|||
(ENC_RDX, 1) => "dl", |
|||
(ENC_RSI, 8) => "rsi", |
|||
(ENC_RSI, 4) => "esi", |
|||
(ENC_RSI, 2) => "si", |
|||
(ENC_RSI, 1) => "sil", |
|||
(ENC_RDI, 8) => "rdi", |
|||
(ENC_RDI, 4) => "edi", |
|||
(ENC_RDI, 2) => "di", |
|||
(ENC_RDI, 1) => "dil", |
|||
(ENC_RBP, 8) => "rbp", |
|||
(ENC_RBP, 4) => "ebp", |
|||
(ENC_RBP, 2) => "bp", |
|||
(ENC_RBP, 1) => "bpl", |
|||
(ENC_RSP, 8) => "rsp", |
|||
(ENC_RSP, 4) => "esp", |
|||
(ENC_RSP, 2) => "sp", |
|||
(ENC_RSP, 1) => "spl", |
|||
(ENC_R8, 8) => "r8", |
|||
(ENC_R8, 4) => "r8d", |
|||
(ENC_R8, 2) => "r8w", |
|||
(ENC_R8, 1) => "r8b", |
|||
(ENC_R9, 8) => "r9", |
|||
(ENC_R9, 4) => "r9d", |
|||
(ENC_R9, 2) => "r9w", |
|||
(ENC_R9, 1) => "r9b", |
|||
(ENC_R10, 8) => "r10", |
|||
(ENC_R10, 4) => "r10d", |
|||
(ENC_R10, 2) => "r10w", |
|||
(ENC_R10, 1) => "r10b", |
|||
(ENC_R11, 8) => "r11", |
|||
(ENC_R11, 4) => "r11d", |
|||
(ENC_R11, 2) => "r11w", |
|||
(ENC_R11, 1) => "r11b", |
|||
(ENC_R12, 8) => "r12", |
|||
(ENC_R12, 4) => "r12d", |
|||
(ENC_R12, 2) => "r12w", |
|||
(ENC_R12, 1) => "r12b", |
|||
(ENC_R13, 8) => "r13", |
|||
(ENC_R13, 4) => "r13d", |
|||
(ENC_R13, 2) => "r13w", |
|||
(ENC_R13, 1) => "r13b", |
|||
(ENC_R14, 8) => "r14", |
|||
(ENC_R14, 4) => "r14d", |
|||
(ENC_R14, 2) => "r14w", |
|||
(ENC_R14, 1) => "r14b", |
|||
(ENC_R15, 8) => "r15", |
|||
(ENC_R15, 4) => "r15d", |
|||
(ENC_R15, 2) => "r15w", |
|||
(ENC_R15, 1) => "r15b", |
|||
_ => panic!("Invalid Reg: {:?}", reg), |
|||
}, |
|||
RegClass::Float => match reg.hw_enc() { |
|||
0 => "xmm0", |
|||
1 => "xmm1", |
|||
2 => "xmm2", |
|||
3 => "xmm3", |
|||
4 => "xmm4", |
|||
5 => "xmm5", |
|||
6 => "xmm6", |
|||
7 => "xmm7", |
|||
8 => "xmm8", |
|||
9 => "xmm9", |
|||
10 => "xmm10", |
|||
11 => "xmm11", |
|||
12 => "xmm12", |
|||
13 => "xmm13", |
|||
14 => "xmm14", |
|||
15 => "xmm15", |
|||
_ => panic!("Invalid Reg: {:?}", reg), |
|||
}, |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
//! Code generation library for Winch.
|
|||
|
|||
// Unless this library is compiled with `all-arch`, the rust compiler
|
|||
// is going to emit dead code warnings. This directive is fine as long
|
|||
// as we configure to run CI at least once with the `all-arch` feature
|
|||
// enabled.
|
|||
#![cfg_attr(not(feature = "all-arch"), allow(dead_code))] |
|||
|
|||
mod abi; |
|||
mod codegen; |
|||
mod frame; |
|||
pub mod isa; |
|||
mod masm; |
|||
mod regalloc; |
|||
mod regset; |
|||
mod stack; |
|||
mod visitor; |
@ -0,0 +1,144 @@ |
|||
use crate::abi::align_to; |
|||
use crate::abi::{addressing_mode::Address, local::LocalSlot}; |
|||
use crate::isa::reg::Reg; |
|||
use crate::regalloc::RegAlloc; |
|||
use std::ops::Range; |
|||
|
|||
/// Operand size, in bits.
|
|||
#[derive(Copy, Clone)] |
|||
pub(crate) enum OperandSize { |
|||
/// 32 bits.
|
|||
S32, |
|||
/// 64 bits.
|
|||
S64, |
|||
} |
|||
|
|||
/// An abstraction over a register or immediate.
|
|||
#[derive(Copy, Clone, Debug, PartialEq, Eq)] |
|||
pub(crate) enum RegImm { |
|||
/// A register.
|
|||
Reg(Reg), |
|||
/// An immediate.
|
|||
Imm(i32), |
|||
} |
|||
|
|||
impl RegImm { |
|||
/// Register constructor.
|
|||
pub fn reg(r: Reg) -> Self { |
|||
RegImm::Reg(r) |
|||
} |
|||
|
|||
/// Immediate constructor.
|
|||
pub fn imm(imm: i32) -> Self { |
|||
RegImm::Imm(imm) |
|||
} |
|||
} |
|||
|
|||
impl From<Reg> for RegImm { |
|||
fn from(r: Reg) -> Self { |
|||
Self::Reg(r) |
|||
} |
|||
} |
|||
|
|||
/// Generic MacroAssembler interface used by the code generation.
|
|||
///
|
|||
/// The MacroAssembler trait aims to expose an interface, high-level enough,
|
|||
/// so that each ISA can provide its own lowering to machine code. For example,
|
|||
/// for WebAssembly operators that don't have a direct mapping to a machine
|
|||
/// a instruction, the interface defines a signature matching the WebAssembly
|
|||
/// operator, allowing each implementation to lower such operator entirely.
|
|||
/// This approach attributes more responsibility to the MacroAssembler, but frees
|
|||
/// the caller from concerning about assembling the right sequence of
|
|||
/// instructions at the operator callsite.
|
|||
///
|
|||
/// The interface defaults to a three-argument form for binary operations;
|
|||
/// this allows a natural mapping to instructions for RISC architectures,
|
|||
/// that use three-argument form.
|
|||
/// This approach allows for a more general interface that can be restricted
|
|||
/// where needed, in the case of architectures that use a two-argument form.
|
|||
|
|||
pub(crate) trait MacroAssembler { |
|||
/// Emit the function prologue.
|
|||
fn prologue(&mut self); |
|||
|
|||
/// Emit the function epilogue.
|
|||
fn epilogue(&mut self, locals_size: u32); |
|||
|
|||
/// Reserve stack space.
|
|||
fn reserve_stack(&mut self, bytes: u32); |
|||
|
|||
/// Get the address of a local slot.
|
|||
fn local_address(&mut self, local: &LocalSlot) -> Address; |
|||
|
|||
/// Get stack pointer offset.
|
|||
fn sp_offset(&mut self) -> u32; |
|||
|
|||
/// Perform a stack store.
|
|||
fn store(&mut self, src: RegImm, dst: Address, size: OperandSize); |
|||
|
|||
/// Perform a stack load.
|
|||
fn load(&mut self, src: Address, dst: Reg, size: OperandSize); |
|||
|
|||
/// Perform a move.
|
|||
fn mov(&mut self, src: RegImm, dst: RegImm, size: OperandSize); |
|||
|
|||
/// Perform add operation.
|
|||
fn add(&mut self, dst: RegImm, lhs: RegImm, rhs: RegImm, size: OperandSize); |
|||
|
|||
/// Push the register to the stack, returning the offset.
|
|||
fn push(&mut self, src: Reg) -> u32; |
|||
|
|||
/// Finalize the assembly and return the result.
|
|||
// NOTE Interim, debug approach
|
|||
fn finalize(&mut self) -> &[String]; |
|||
|
|||
/// Zero a particular register.
|
|||
fn zero(&mut self, reg: Reg); |
|||
|
|||
/// Zero a given memory range.
|
|||
///
|
|||
/// The default implementation divides the given memory range
|
|||
/// into word-sized slots. Then it unrolls a series of store
|
|||
/// instructions, effectively assigning zero to each slot.
|
|||
fn zero_mem_range(&mut self, mem: &Range<u32>, word_size: u32, regalloc: &mut RegAlloc) { |
|||
if mem.is_empty() { |
|||
return; |
|||
} |
|||
|
|||
let start = if mem.start % word_size == 0 { |
|||
mem.start |
|||
} else { |
|||
// Ensure that the start of the range is at least 4-byte aligned.
|
|||
assert!(mem.start % 4 == 0); |
|||
let start = align_to(mem.start, word_size); |
|||
let addr = self.local_address(&LocalSlot::i32(start)); |
|||
self.store(RegImm::imm(0), addr, OperandSize::S32); |
|||
// Ensure that the new start of the range, is word-size aligned.
|
|||
assert!(start % word_size == 0); |
|||
start |
|||
}; |
|||
|
|||
let end = align_to(mem.end, word_size); |
|||
let slots = (end - start) / word_size; |
|||
|
|||
if slots == 1 { |
|||
let slot = LocalSlot::i64(start + word_size); |
|||
let addr = self.local_address(&slot); |
|||
self.store(RegImm::imm(0), addr, OperandSize::S64); |
|||
} else { |
|||
// TODO
|
|||
// Add an upper bound to this generation;
|
|||
// given a considerably large amount of slots
|
|||
// this will be inefficient.
|
|||
let zero = regalloc.scratch; |
|||
self.zero(zero); |
|||
let zero = RegImm::reg(zero); |
|||
|
|||
for step in (start..end).into_iter().step_by(word_size as usize) { |
|||
let slot = LocalSlot::i64(step + word_size); |
|||
let addr = self.local_address(&slot); |
|||
self.store(zero, addr, OperandSize::S64); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,146 @@ |
|||
use crate::{ |
|||
codegen::CodeGenContext, |
|||
frame::Frame, |
|||
isa::reg::Reg, |
|||
masm::{MacroAssembler, OperandSize, RegImm}, |
|||
regset::RegSet, |
|||
stack::Val, |
|||
}; |
|||
|
|||
/// The register allocator.
|
|||
///
|
|||
/// The register allocator uses a single-pass algorithm;
|
|||
/// its implementation uses a bitset as a freelist
|
|||
/// to track per-class register availability.
|
|||
///
|
|||
/// If a particular register is not available upon request
|
|||
/// the register allocation will perform a "spill", essentially
|
|||
/// moving Local and Register values in the stack to memory.
|
|||
/// This processs ensures that whenever a register is requested,
|
|||
/// it is going to be available.
|
|||
pub(crate) struct RegAlloc { |
|||
pub scratch: Reg, |
|||
regset: RegSet, |
|||
} |
|||
|
|||
impl RegAlloc { |
|||
/// Create a new register allocator
|
|||
/// from a register set.
|
|||
pub fn new(regset: RegSet, scratch: Reg) -> Self { |
|||
Self { regset, scratch } |
|||
} |
|||
|
|||
/// Loads the stack top value into a register, if it isn't already one;
|
|||
/// spilling if there are no registers available.
|
|||
pub fn pop_to_reg<M: MacroAssembler>( |
|||
&mut self, |
|||
context: &mut CodeGenContext<M>, |
|||
size: OperandSize, |
|||
) -> Reg { |
|||
if let Some(reg) = context.stack.pop_reg() { |
|||
return reg; |
|||
} |
|||
|
|||
let dst = self.any_gpr(context); |
|||
let val = context.stack.pop().expect("a value at stack top"); |
|||
Self::move_val_to_reg(val, dst, &mut context.masm, context.frame, size); |
|||
dst |
|||
} |
|||
|
|||
/// Checks if the stack top contains the given register. The register
|
|||
/// gets allocated otherwise, potentially causing a spill.
|
|||
/// Once the requested register is allocated, the value at the top of the stack
|
|||
/// gets loaded into the register.
|
|||
pub fn pop_to_named_reg<M: MacroAssembler>( |
|||
&mut self, |
|||
context: &mut CodeGenContext<M>, |
|||
named: Reg, |
|||
size: OperandSize, |
|||
) -> Reg { |
|||
if let Some(reg) = context.stack.pop_named_reg(named) { |
|||
return reg; |
|||
} |
|||
|
|||
let dst = self.gpr(context, named); |
|||
let val = context.stack.pop().expect("a value at stack top"); |
|||
Self::move_val_to_reg(val, dst, &mut context.masm, context.frame, size); |
|||
dst |
|||
} |
|||
|
|||
fn move_val_to_reg<M: MacroAssembler>( |
|||
src: Val, |
|||
dst: Reg, |
|||
masm: &mut M, |
|||
frame: &Frame, |
|||
size: OperandSize, |
|||
) { |
|||
match src { |
|||
Val::Reg(src) => masm.mov(RegImm::reg(src), RegImm::reg(dst), size), |
|||
Val::I32(imm) => masm.mov(RegImm::imm(imm), RegImm::reg(dst), size), |
|||
Val::Local(index) => { |
|||
let slot = frame |
|||
.get_local(index) |
|||
.expect(&format!("valid locat at index = {}", index)); |
|||
let addr = masm.local_address(&slot); |
|||
masm.load(addr, dst, slot.ty.into()); |
|||
} |
|||
v => panic!("Unsupported value {:?}", v), |
|||
}; |
|||
} |
|||
|
|||
/// Allocate the next available general purpose register,
|
|||
/// spilling if none available.
|
|||
pub fn any_gpr<M: MacroAssembler>(&mut self, context: &mut CodeGenContext<M>) -> Reg { |
|||
self.regset.any_gpr().unwrap_or_else(|| { |
|||
self.spill(context); |
|||
self.regset.any_gpr().expect("any gpr to be available") |
|||
}) |
|||
} |
|||
|
|||
/// Request a specific general purpose register,
|
|||
/// spilling if not available.
|
|||
pub fn gpr<M: MacroAssembler>(&mut self, context: &mut CodeGenContext<M>, named: Reg) -> Reg { |
|||
self.regset.gpr(named).unwrap_or_else(|| { |
|||
self.spill(context); |
|||
self.regset |
|||
.gpr(named) |
|||
.expect(&format!("gpr {:?} to be available", named)) |
|||
}) |
|||
} |
|||
|
|||
/// Mark a particular general purpose register as available.
|
|||
pub fn free_gpr(&mut self, reg: Reg) { |
|||
self.regset.free_gpr(reg); |
|||
} |
|||
|
|||
/// Spill locals and registers to memory.
|
|||
// TODO optimize the spill range;
|
|||
//
|
|||
// At any point in the program, the stack
|
|||
// might already contain Memory entries;
|
|||
// we could effectively ignore that range;
|
|||
// only focusing on the range that contains
|
|||
// spillable values.
|
|||
fn spill<M: MacroAssembler>(&mut self, context: &mut CodeGenContext<M>) { |
|||
context.stack.inner_mut().iter_mut().for_each(|v| match v { |
|||
Val::Reg(r) => { |
|||
let offset = context.masm.push(*r); |
|||
self.free_gpr(*r); |
|||
*v = Val::Memory(offset); |
|||
} |
|||
Val::Local(index) => { |
|||
let slot = context |
|||
.frame |
|||
.get_local(*index) |
|||
.expect("valid local at slot"); |
|||
let addr = context.masm.local_address(&slot); |
|||
context |
|||
.masm |
|||
.store(RegImm::reg(self.scratch), addr, slot.ty.into()); |
|||
let offset = context.masm.push(self.scratch); |
|||
*v = Val::Memory(offset); |
|||
} |
|||
_ => {} |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,88 @@ |
|||
use crate::isa::reg::Reg; |
|||
|
|||
/// A bit set to track regiter availability.
|
|||
pub(crate) struct RegSet { |
|||
/// Bitset to track general purpose register availability.
|
|||
gpr: u32, |
|||
/// Bitset to track floating-point register availability.
|
|||
_fpr: u32, |
|||
} |
|||
|
|||
impl RegSet { |
|||
/// Create a new register set.
|
|||
pub fn new(gpr: u32, fpr: u32) -> Self { |
|||
Self { gpr, _fpr: fpr } |
|||
} |
|||
|
|||
/// Request a general purpose register.
|
|||
pub fn any_gpr(&mut self) -> Option<Reg> { |
|||
self.gpr_available().then(|| { |
|||
let index = self.gpr.trailing_zeros(); |
|||
self.allocate(index); |
|||
Reg::int(index as usize) |
|||
}) |
|||
} |
|||
|
|||
/// Request a specific general purpose register.
|
|||
pub fn gpr(&mut self, reg: Reg) -> Option<Reg> { |
|||
let index = reg.hw_enc(); |
|||
self.named_gpr_available(index as u32).then(|| { |
|||
self.allocate(index as u32); |
|||
Reg::int(index as usize) |
|||
}) |
|||
} |
|||
|
|||
/// Free the given general purpose register.
|
|||
pub fn free_gpr(&mut self, reg: Reg) { |
|||
let index = reg.hw_enc() as u32; |
|||
self.gpr |= 1 << index; |
|||
} |
|||
|
|||
fn named_gpr_available(&self, index: u32) -> bool { |
|||
let index = 1 << index; |
|||
(!self.gpr & index) == 0 |
|||
} |
|||
|
|||
fn gpr_available(&self) -> bool { |
|||
self.gpr != 0 |
|||
} |
|||
|
|||
fn allocate(&mut self, index: u32) { |
|||
self.gpr &= !(1 << index); |
|||
} |
|||
} |
|||
|
|||
#[cfg(test)] |
|||
mod tests { |
|||
use super::{Reg, RegSet}; |
|||
|
|||
const UNIVERSE: u32 = (1 << 16) - 1; |
|||
|
|||
#[test] |
|||
fn test_any_gpr() { |
|||
let mut set = RegSet::new(UNIVERSE, 0); |
|||
for _ in 0..16 { |
|||
let gpr = set.any_gpr(); |
|||
assert!(gpr.is_some()) |
|||
} |
|||
|
|||
assert!(!set.gpr_available()); |
|||
assert!(set.any_gpr().is_none()) |
|||
} |
|||
|
|||
#[test] |
|||
fn test_gpr() { |
|||
let all = UNIVERSE & !(1 << 5); |
|||
let target = Reg::int(5); |
|||
let mut set = RegSet::new(all, 0); |
|||
assert!(set.gpr(target).is_none()); |
|||
} |
|||
|
|||
#[test] |
|||
fn test_free_gpr() { |
|||
let mut set = RegSet::new(UNIVERSE, 0); |
|||
let gpr = set.any_gpr().unwrap(); |
|||
set.free_gpr(gpr); |
|||
assert!(set.gpr(gpr).is_some()); |
|||
} |
|||
} |
@ -0,0 +1,172 @@ |
|||
use crate::isa::reg::Reg; |
|||
use std::collections::VecDeque; |
|||
|
|||
/// Value definition to be used within the shadow stack.
|
|||
#[derive(Debug, Eq, PartialEq)] |
|||
pub(crate) enum Val { |
|||
/// I32 Constant.
|
|||
I32(i32), |
|||
/// A register.
|
|||
Reg(Reg), |
|||
/// A local slot.
|
|||
Local(u32), |
|||
/// Offset to a memory location.
|
|||
Memory(u32), |
|||
} |
|||
|
|||
impl Val { |
|||
/// Create a new I32 constant value.
|
|||
pub fn i32(v: i32) -> Self { |
|||
Self::I32(v) |
|||
} |
|||
|
|||
/// Create a new Reg value.
|
|||
pub fn reg(r: Reg) -> Self { |
|||
Self::Reg(r) |
|||
} |
|||
|
|||
/// Create a new Local value.
|
|||
pub fn local(index: u32) -> Self { |
|||
Self::Local(index) |
|||
} |
|||
|
|||
/// Check whether the value is a register.
|
|||
pub fn is_reg(&self) -> bool { |
|||
match *self { |
|||
Self::Reg(_) => true, |
|||
_ => false, |
|||
} |
|||
} |
|||
|
|||
/// Get the register representation of the value.
|
|||
///
|
|||
/// # Panics
|
|||
/// This method will panic if the value is not a register.
|
|||
pub fn get_reg(&self) -> Reg { |
|||
match self { |
|||
Self::Reg(r) => *r, |
|||
v => panic!("expected value {:?} to be a register", v), |
|||
} |
|||
} |
|||
|
|||
/// Get the integer representation of the value.
|
|||
///
|
|||
/// # Panics
|
|||
/// This method will panic if the value is not an i32.
|
|||
pub fn get_i32(&self) -> i32 { |
|||
match self { |
|||
Self::I32(v) => *v, |
|||
v => panic!("expected value {:?} to be i32", v), |
|||
} |
|||
} |
|||
|
|||
/// Check whether the value is an i32 constant.
|
|||
pub fn is_i32_const(&self) -> bool { |
|||
match *self { |
|||
Self::I32(_) => true, |
|||
_ => false, |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// The shadow stack used for compilation.
|
|||
#[derive(Default, Debug)] |
|||
pub(crate) struct Stack { |
|||
inner: VecDeque<Val>, |
|||
} |
|||
|
|||
impl Stack { |
|||
/// Allocate a new stack.
|
|||
pub fn new() -> Self { |
|||
Self { |
|||
inner: Default::default(), |
|||
} |
|||
} |
|||
|
|||
/// Push a value to the stack.
|
|||
pub fn push(&mut self, val: Val) { |
|||
self.inner.push_back(val); |
|||
} |
|||
|
|||
/// Peek into the top in the stack.
|
|||
pub fn peek(&mut self) -> Option<&Val> { |
|||
self.inner.back() |
|||
} |
|||
|
|||
/// Pops the top element of the stack, if any.
|
|||
pub fn pop(&mut self) -> Option<Val> { |
|||
self.inner.pop_back() |
|||
} |
|||
|
|||
/// Pops the element at the top of the stack if it is a const;
|
|||
/// returns `None` otherwise.
|
|||
pub fn pop_i32_const(&mut self) -> Option<i32> { |
|||
match self.peek() { |
|||
Some(v) => v.is_i32_const().then(|| self.pop().unwrap().get_i32()), |
|||
_ => None, |
|||
} |
|||
} |
|||
|
|||
/// Pops the element at the top of the stack if it is a register;
|
|||
/// returns `None` otherwise.
|
|||
pub fn pop_reg(&mut self) -> Option<Reg> { |
|||
match self.peek() { |
|||
Some(v) => v.is_reg().then(|| self.pop().unwrap().get_reg()), |
|||
_ => None, |
|||
} |
|||
} |
|||
|
|||
/// Pops the given register if it is at the top of the stack;
|
|||
/// returns `None` otherwise.
|
|||
pub fn pop_named_reg(&mut self, reg: Reg) -> Option<Reg> { |
|||
match self.peek() { |
|||
Some(v) => (v.is_reg() && v.get_reg() == reg).then(|| self.pop().unwrap().get_reg()), |
|||
_ => None, |
|||
} |
|||
} |
|||
|
|||
/// Get a mutable reference to the inner stack representation.
|
|||
pub fn inner_mut(&mut self) -> &mut VecDeque<Val> { |
|||
&mut self.inner |
|||
} |
|||
} |
|||
|
|||
#[cfg(test)] |
|||
mod tests { |
|||
use super::{Stack, Val}; |
|||
use crate::isa::reg::Reg; |
|||
|
|||
#[test] |
|||
fn test_pop_i32_const() { |
|||
let mut stack = Stack::new(); |
|||
stack.push(Val::i32(33i32)); |
|||
assert_eq!(33, stack.pop_i32_const().unwrap()); |
|||
|
|||
stack.push(Val::local(10)); |
|||
assert!(stack.pop_i32_const().is_none()); |
|||
} |
|||
|
|||
#[test] |
|||
fn test_pop_reg() { |
|||
let mut stack = Stack::new(); |
|||
let reg = Reg::int(2usize); |
|||
stack.push(Val::reg(reg)); |
|||
stack.push(Val::i32(4)); |
|||
|
|||
assert_eq!(None, stack.pop_reg()); |
|||
let _ = stack.pop().unwrap(); |
|||
assert_eq!(reg, stack.pop_reg().unwrap()); |
|||
} |
|||
|
|||
#[test] |
|||
fn test_pop_named_reg() { |
|||
let mut stack = Stack::new(); |
|||
let reg = Reg::int(2usize); |
|||
stack.push(Val::reg(reg)); |
|||
stack.push(Val::reg(Reg::int(4))); |
|||
|
|||
assert_eq!(None, stack.pop_named_reg(reg)); |
|||
let _ = stack.pop().unwrap(); |
|||
assert_eq!(reg, stack.pop_named_reg(reg).unwrap()); |
|||
} |
|||
} |
@ -0,0 +1,176 @@ |
|||
//! This module is the central place for machine code emission.
|
|||
//! It defines an implementation of wasmparser's Visitor trait
|
|||
//! for `CodeGen`; which defines a visitor per op-code,
|
|||
//! which validates and dispatches to the corresponding
|
|||
//! machine code emitter.
|
|||
|
|||
use crate::codegen::CodeGen; |
|||
use crate::masm::{MacroAssembler, OperandSize, RegImm}; |
|||
use crate::stack::Val; |
|||
use anyhow::Result; |
|||
use wasmparser::ValType; |
|||
use wasmparser::VisitOperator; |
|||
|
|||
impl<'a, M> CodeGen<'a, M> |
|||
where |
|||
M: MacroAssembler, |
|||
{ |
|||
fn emit_i32_add(&mut self) -> Result<()> { |
|||
let is_const = self |
|||
.context |
|||
.stack |
|||
.peek() |
|||
.expect("value at stack top") |
|||
.is_i32_const(); |
|||
|
|||
if is_const { |
|||
self.add_imm_i32(); |
|||
} else { |
|||
self.add_i32(); |
|||
} |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
fn add_imm_i32(&mut self) { |
|||
let val = self |
|||
.context |
|||
.stack |
|||
.pop_i32_const() |
|||
.expect("i32 constant at stack top"); |
|||
let reg = self |
|||
.regalloc |
|||
.pop_to_reg(&mut self.context, OperandSize::S32); |
|||
|
|||
let dst = RegImm::reg(reg); |
|||
self.context |
|||
.masm |
|||
.add(dst, dst, RegImm::imm(val), OperandSize::S32); |
|||
self.context.stack.push(Val::reg(reg)); |
|||
} |
|||
|
|||
fn add_i32(&mut self) { |
|||
let src = self |
|||
.regalloc |
|||
.pop_to_reg(&mut self.context, OperandSize::S32); |
|||
let dst = self |
|||
.regalloc |
|||
.pop_to_reg(&mut self.context, OperandSize::S32); |
|||
|
|||
let lhs = RegImm::reg(dst); |
|||
self.context |
|||
.masm |
|||
.add(lhs, lhs, RegImm::reg(src), OperandSize::S32); |
|||
|
|||
self.regalloc.free_gpr(src); |
|||
self.context.stack.push(Val::reg(dst)); |
|||
} |
|||
|
|||
fn emit_i32_const(&mut self, val: i32) -> Result<()> { |
|||
self.context.stack.push(Val::i32(val)); |
|||
Ok(()) |
|||
} |
|||
|
|||
fn emit_local_get(&mut self, index: u32) -> Result<()> { |
|||
let context = &mut self.context; |
|||
let slot = context |
|||
.frame |
|||
.get_local(index) |
|||
.expect(&format!("valid local at slot = {}", index)); |
|||
match slot.ty { |
|||
ValType::I32 | ValType::I64 => context.stack.push(Val::local(index)), |
|||
_ => panic!("Unsupported type {:?} for local", slot.ty), |
|||
} |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
// TODO verify the case where the target local is on the stack.
|
|||
fn emit_local_set(&mut self, index: u32) -> Result<()> { |
|||
let context = &mut self.context; |
|||
let frame = context.frame; |
|||
let slot = frame |
|||
.get_local(index) |
|||
.expect(&format!("vald local at slot = {}", index)); |
|||
let size: OperandSize = slot.ty.into(); |
|||
let src = self.regalloc.pop_to_reg(context, size); |
|||
let addr = context.masm.local_address(&slot); |
|||
context.masm.store(RegImm::reg(src), addr, size); |
|||
self.regalloc.free_gpr(src); |
|||
|
|||
Ok(()) |
|||
} |
|||
} |
|||
|
|||
/// A macro to define unsupported WebAssembly operators.
|
|||
///
|
|||
/// This macro calls itself recursively;
|
|||
/// 1. It no-ops when matching a supported operator.
|
|||
/// 2. Defines the visitor function and panics when
|
|||
/// matching an unsupported operator.
|
|||
macro_rules! def_unsupported { |
|||
($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => { |
|||
$( |
|||
def_unsupported!( |
|||
emit |
|||
$op |
|||
|
|||
fn $visit(&mut self, _offset: usize $($(,$arg: $argty)*)?) -> Self::Output { |
|||
$($(drop($arg);)*)? |
|||
todo!(stringify!($op)) |
|||
} |
|||
); |
|||
)* |
|||
}; |
|||
|
|||
(emit I32Const $($rest:tt)*) => {}; |
|||
(emit I32Add $($rest:tt)*) => {}; |
|||
(emit LocalGet $($rest:tt)*) => {}; |
|||
(emit LocalSet $($rest:tt)*) => {}; |
|||
(emit End $($rest:tt)*) => {}; |
|||
|
|||
(emit $unsupported:tt $($rest:tt)*) => {$($rest)*}; |
|||
} |
|||
|
|||
impl<'a, M> VisitOperator<'a> for CodeGen<'a, M> |
|||
where |
|||
M: MacroAssembler, |
|||
{ |
|||
type Output = Result<()>; |
|||
|
|||
fn visit_i32_const(&mut self, offset: usize, value: i32) -> Result<()> { |
|||
self.validator.visit_i32_const(offset, value)?; |
|||
self.emit_i32_const(value) |
|||
} |
|||
|
|||
fn visit_i32_add(&mut self, offset: usize) -> Result<()> { |
|||
self.validator.visit_i32_add(offset)?; |
|||
self.emit_i32_add() |
|||
} |
|||
|
|||
fn visit_end(&mut self, offset: usize) -> Result<()> { |
|||
self.validator.visit_end(offset).map_err(|e| e.into()) |
|||
} |
|||
|
|||
fn visit_local_get(&mut self, offset: usize, local_index: u32) -> Result<()> { |
|||
self.validator.visit_local_get(offset, local_index)?; |
|||
self.emit_local_get(local_index) |
|||
} |
|||
|
|||
fn visit_local_set(&mut self, offset: usize, local_index: u32) -> Result<()> { |
|||
self.validator.visit_local_set(offset, local_index)?; |
|||
self.emit_local_set(local_index) |
|||
} |
|||
|
|||
wasmparser::for_each_operator!(def_unsupported); |
|||
} |
|||
|
|||
impl From<ValType> for OperandSize { |
|||
fn from(ty: ValType) -> OperandSize { |
|||
match ty { |
|||
ValType::I32 => OperandSize::S32, |
|||
ValType::I64 => OperandSize::S64, |
|||
ty => todo!("unsupported type {:?}", ty), |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,73 @@ |
|||
//! Winch CLI tool, meant mostly for testing purposes.
|
|||
//!
|
|||
//! Reads Wasm in binary/text format and compiles them
|
|||
//! to any of the supported architectures using Winch.
|
|||
|
|||
use anyhow::{Context, Result}; |
|||
use clap::Parser; |
|||
use std::{fs, path::PathBuf, str::FromStr}; |
|||
use target_lexicon::Triple; |
|||
use wasmtime_environ::{ |
|||
wasmparser::{types::Types, Parser as WasmParser, Validator}, |
|||
DefinedFuncIndex, FunctionBodyData, Module, ModuleEnvironment, Tunables, |
|||
}; |
|||
use winch_codegen::isa::{self, TargetIsa}; |
|||
|
|||
#[derive(Parser, Debug)] |
|||
struct Options { |
|||
/// The input file.
|
|||
input: PathBuf, |
|||
|
|||
/// The target architecture.
|
|||
#[clap(long = "target")] |
|||
target: String, |
|||
} |
|||
|
|||
fn main() -> Result<()> { |
|||
let opt = Options::from_args(); |
|||
let bytes = fs::read(&opt.input) |
|||
.with_context(|| format!("Failed to read input file {}", opt.input.display()))?; |
|||
let bytes = wat::parse_bytes(&bytes)?; |
|||
let triple = Triple::from_str(&opt.target)?; |
|||
let isa = isa::lookup(triple)?; |
|||
let mut validator = Validator::new(); |
|||
let parser = WasmParser::new(0); |
|||
let mut types = Default::default(); |
|||
let tunables = Tunables::default(); |
|||
let mut translation = ModuleEnvironment::new(&tunables, &mut validator, &mut types) |
|||
.translate(parser, &bytes) |
|||
.context("Failed to translate WebAssembly module")?; |
|||
let _ = types.finish(); |
|||
|
|||
let body_inputs = std::mem::take(&mut translation.function_body_inputs); |
|||
let module = &translation.module; |
|||
let types = translation.get_types(); |
|||
|
|||
body_inputs |
|||
.into_iter() |
|||
.try_for_each(|func| compile(&*isa, module, types, func))?; |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
fn compile( |
|||
isa: &dyn TargetIsa, |
|||
module: &Module, |
|||
types: &Types, |
|||
f: (DefinedFuncIndex, FunctionBodyData<'_>), |
|||
) -> Result<()> { |
|||
let index = module.func_index(f.0); |
|||
let sig = types |
|||
.func_type_at(index.as_u32()) |
|||
.expect(&format!("function type at index {:?}", index.as_u32())); |
|||
let FunctionBodyData { body, validator } = f.1; |
|||
let validator = validator.into_validator(Default::default()); |
|||
let buffer = isa |
|||
.compile_function(&sig, body, validator) |
|||
.expect("Couldn't compile function"); |
|||
for i in buffer { |
|||
println!("{}", i); |
|||
} |
|||
|
|||
Ok(()) |
|||
} |
Loading…
Reference in new issue