Browse Source

Initial skeleton for Winch (#4907)

* 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 aligned
pull/5148/head
Saúl Cabrera 2 years ago
committed by GitHub
parent
commit
835abbcd11
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 35
      Cargo.lock
  2. 5
      Cargo.toml
  3. 19
      crates/winch/Cargo.toml
  4. 57
      crates/winch/src/builder.rs
  5. 83
      crates/winch/src/compiler.rs
  6. 3
      crates/winch/src/lib.rs
  7. 5
      scripts/publish.rs
  8. 26
      winch/Cargo.toml
  9. 27
      winch/codegen/Cargo.toml
  10. 15
      winch/codegen/src/abi/addressing_mode.rs
  11. 68
      winch/codegen/src/abi/local.rs
  12. 149
      winch/codegen/src/abi/mod.rs
  13. 158
      winch/codegen/src/codegen.rs
  14. 145
      winch/codegen/src/frame/mod.rs
  15. 210
      winch/codegen/src/isa/aarch64/abi.rs
  16. 56
      winch/codegen/src/isa/aarch64/masm.rs
  17. 42
      winch/codegen/src/isa/aarch64/mod.rs
  18. 16
      winch/codegen/src/isa/aarch64/regs.rs
  19. 112
      winch/codegen/src/isa/mod.rs
  20. 45
      winch/codegen/src/isa/reg.rs
  21. 227
      winch/codegen/src/isa/x64/abi.rs
  22. 440
      winch/codegen/src/isa/x64/masm.rs
  23. 66
      winch/codegen/src/isa/x64/mod.rs
  24. 238
      winch/codegen/src/isa/x64/regs.rs
  25. 17
      winch/codegen/src/lib.rs
  26. 144
      winch/codegen/src/masm.rs
  27. 146
      winch/codegen/src/regalloc.rs
  28. 88
      winch/codegen/src/regset.rs
  29. 172
      winch/codegen/src/stack.rs
  30. 176
      winch/codegen/src/visitor.rs
  31. 73
      winch/src/main.rs

35
Cargo.lock

@ -3770,6 +3770,16 @@ dependencies = [
"wast 48.0.0",
]
[[package]]
name = "wasmtime-winch"
version = "3.0.0"
dependencies = [
"anyhow",
"target-lexicon",
"wasmtime-environ",
"winch-codegen",
]
[[package]]
name = "wast"
version = "35.0.2"
@ -3905,6 +3915,31 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winch-codegen"
version = "0.1.0"
dependencies = [
"anyhow",
"cranelift-codegen",
"regalloc2",
"smallvec",
"target-lexicon",
"wasmparser",
]
[[package]]
name = "winch-tools"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 3.2.8",
"target-lexicon",
"wasmparser",
"wasmtime-environ",
"wat",
"winch-codegen",
]
[[package]]
name = "windows-sys"
version = "0.36.1"

5
Cargo.toml

@ -85,10 +85,13 @@ members = [
"crates/cli-flags",
"crates/environ/fuzz",
"crates/jit-icache-coherence",
"crates/winch",
"examples/fib-debug/wasm",
"examples/wasi/wasm",
"examples/tokio/wasm",
"fuzz",
"winch",
"winch/codegen"
]
exclude = [
'crates/wasi-common/WASI/tools/witx-cli',
@ -146,6 +149,8 @@ cranelift-fuzzgen = { path = "cranelift/fuzzgen" }
cranelift-bforest = { path = "cranelift/bforest", version = "0.90.0" }
cranelift = { path = "cranelift/umbrella", version = "0.90.0" }
winch-codegen = { path = "winch/codegen", version = "=0.1.0" }
target-lexicon = { version = "0.12.3", default-features = false }
anyhow = "1.0.22"
wasmparser = "0.93.0"

19
crates/winch/Cargo.toml

@ -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"]

57
crates/winch/src/builder.rs

@ -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())
}
}

83
crates/winch/src/compiler.rs

@ -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!()
}
}

3
crates/winch/src/lib.rs

@ -0,0 +1,3 @@
mod builder;
mod compiler;
pub use builder::builder;

5
scripts/publish.rs

@ -41,6 +41,9 @@ const CRATES_TO_PUBLISH: &[&str] = &[
// wiggle
"wiggle-generate",
"wiggle-macro",
// winch
"winch-codegen",
"winch",
// wasmtime
"wasmtime-asm-macros",
"wasmtime-component-util",
@ -52,6 +55,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[
"wasmtime-cranelift",
"wasmtime-jit",
"wasmtime-cache",
"wasmtime-winch",
"wasmtime",
// wasi-common/wiggle
"wiggle",
@ -127,6 +131,7 @@ fn main() {
crates.push(root);
find_crates("crates".as_ref(), &ws, &mut crates);
find_crates("cranelift".as_ref(), &ws, &mut crates);
find_crates("winch".as_ref(), &ws, &mut crates);
let pos = CRATES_TO_PUBLISH
.iter()

26
winch/Cargo.toml

@ -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"]

27
winch/codegen/Cargo.toml

@ -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",
]

15
winch/codegen/src/abi/addressing_mode.rs

@ -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 }
}
}

68
winch/codegen/src/abi/local.rs

@ -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
}
}

149
winch/codegen/src/abi/mod.rs

@ -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
}

158
winch/codegen/src/codegen.rs

@ -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);
}
}

145
winch/codegen/src/frame/mod.rs

@ -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))
}
}

210
winch/codegen/src/isa/aarch64/abi.rs

@ -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),
}
}
}

56
winch/codegen/src/isa/aarch64/masm.rs

@ -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!()
}
}

42
winch/codegen/src/isa/aarch64/mod.rs

@ -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!()
}
}

16
winch/codegen/src/isa/aarch64/regs.rs

@ -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))
}

112
winch/codegen/src/isa/mod.rs

@ -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()
)
}
}

45
winch/codegen/src/isa/reg.rs

@ -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)
}
}

227
winch/codegen/src/isa/x64/abi.rs

@ -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),
}
}
}

440
winch/codegen/src/isa/x64/masm.rs

@ -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)
}
}

66
winch/codegen/src/isa/x64/mod.rs

@ -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()
}
}

238
winch/codegen/src/isa/x64/regs.rs

@ -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),
},
}
}

17
winch/codegen/src/lib.rs

@ -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;

144
winch/codegen/src/masm.rs

@ -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);
}
}
}
}

146
winch/codegen/src/regalloc.rs

@ -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);
}
_ => {}
});
}
}

88
winch/codegen/src/regset.rs

@ -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());
}
}

172
winch/codegen/src/stack.rs

@ -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());
}
}

176
winch/codegen/src/visitor.rs

@ -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),
}
}
}

73
winch/src/main.rs

@ -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…
Cancel
Save