diff --git a/Cargo.lock b/Cargo.lock index e63f27df44..44a54b450b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3783,6 +3783,7 @@ name = "wasmtime-winch" version = "6.0.0" dependencies = [ "anyhow", + "cranelift-codegen", "object", "target-lexicon", "wasmtime-environ", @@ -3951,7 +3952,9 @@ name = "winch-tools" version = "0.0.0" dependencies = [ "anyhow", + "capstone", "clap 3.2.8", + "cranelift-codegen", "target-lexicon", "wasmparser", "wasmtime-environ", diff --git a/Cargo.toml b/Cargo.toml index 24cb67026f..5b6e55a8c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -176,6 +176,7 @@ clap = { version = "3.2.0", features = ["color", "suggestions", "derive"] } hashbrown = "0.12" cap-std = "1.0.0" cap-rand = "1.0.0" +capstone = "0.9.0" once_cell = "1.12.0" smallvec = { version = "1.6.1", features = ["union"] } io-lifetimes = { version = "1.0.0", default-features = false } diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml index 9da470cb4b..a9f7809460 100644 --- a/cranelift/Cargo.toml +++ b/cranelift/Cargo.toml @@ -36,7 +36,7 @@ cranelift = { workspace = true } filecheck = "0.5.0" log = { workspace = true } termcolor = "1.1.2" -capstone = { version = "0.9.0", optional = true } +capstone = { workspace = true, optional = true } wat = { workspace = true, optional = true } target-lexicon = { workspace = true, features = ["std"] } pretty_env_logger = "0.4.0" diff --git a/crates/winch/Cargo.toml b/crates/winch/Cargo.toml index 7b3ff5be09..a8d618d741 100644 --- a/crates/winch/Cargo.toml +++ b/crates/winch/Cargo.toml @@ -13,6 +13,7 @@ target-lexicon = { workspace = true } wasmtime-environ = { workspace = true } anyhow = { workspace = true } object = { workspace = true } +cranelift-codegen = { workspace = true } [features] default = ["all-arch", "component-model"] diff --git a/crates/winch/src/builder.rs b/crates/winch/src/builder.rs index e10ac3414e..1d9a0a645b 100644 --- a/crates/winch/src/builder.rs +++ b/crates/winch/src/builder.rs @@ -1,17 +1,30 @@ use crate::compiler::Compiler; use anyhow::Result; +use cranelift_codegen::settings; use std::sync::Arc; use target_lexicon::Triple; use wasmtime_environ::{CompilerBuilder, Setting}; use winch_codegen::isa; +/// Compiler builder. struct Builder { + /// Target triple. triple: Triple, + /// Shared flags builder. + shared_flags: settings::Builder, + /// ISA builder. + isa_builder: isa::Builder, } pub fn builder() -> Box { + let triple = Triple::host(); Box::new(Builder { - triple: Triple::host(), + triple: triple.clone(), + shared_flags: settings::builder(), + // TODO: + // Either refactor and re-use `cranelift-native::builder()` or come up with a similar + // mechanism to lookup the host's architecture ISA and infer native flags. + isa_builder: isa::lookup(triple).expect("host architecture is not supported"), }) } @@ -38,8 +51,10 @@ impl CompilerBuilder for Builder { } fn build(&self) -> Result> { - let isa = isa::lookup(self.triple.clone())?; - Ok(Box::new(Compiler::new(isa))) + let flags = settings::Flags::new(self.shared_flags.clone()); + Ok(Box::new(Compiler::new( + self.isa_builder.clone().build(flags)?, + ))) } fn enable_incremental_compilation( diff --git a/crates/winch/src/compiler.rs b/crates/winch/src/compiler.rs index f3806c754c..c975d084a8 100644 --- a/crates/winch/src/compiler.rs +++ b/crates/winch/src/compiler.rs @@ -5,7 +5,7 @@ use wasmtime_environ::{ CompileError, DefinedFuncIndex, FuncIndex, FunctionBodyData, FunctionLoc, ModuleTranslation, ModuleTypes, PrimaryMap, Tunables, WasmFunctionInfo, }; -use winch_codegen::isa::TargetIsa; +use winch_codegen::TargetIsa; pub(crate) struct Compiler { isa: Box, diff --git a/winch/Cargo.toml b/winch/Cargo.toml index 5d7c6b8aa8..7f740e49b2 100644 --- a/winch/Cargo.toml +++ b/winch/Cargo.toml @@ -20,6 +20,8 @@ anyhow = { workspace = true } wasmparser = { workspace = true } clap = { workspace = true } wat = { workspace = true } +cranelift-codegen = { workspace = true } +capstone = { workspace = true } [features] default = ["all-arch"] diff --git a/winch/codegen/src/abi/mod.rs b/winch/codegen/src/abi/mod.rs index 43095d2bda..73a856c15f 100644 --- a/winch/codegen/src/abi/mod.rs +++ b/winch/codegen/src/abi/mod.rs @@ -4,7 +4,9 @@ use std::ops::{Add, BitAnd, Not, Sub}; use wasmparser::{FuncType, ValType}; pub(crate) mod addressing_mode; +pub(crate) use addressing_mode::*; pub(crate) mod local; +pub(crate) use local::*; /// Trait implemented by a specific ISA and used to provide /// information about alignment, parameter passing, usage of diff --git a/winch/codegen/src/codegen.rs b/winch/codegen/src/codegen.rs index d78f61b2fc..460e0089d2 100644 --- a/winch/codegen/src/codegen.rs +++ b/winch/codegen/src/codegen.rs @@ -13,7 +13,7 @@ pub(crate) struct CodeGenContext<'a, M> where M: MacroAssembler, { - pub masm: M, + pub masm: &'a mut M, pub stack: Stack, pub frame: &'a Frame, } @@ -22,7 +22,7 @@ impl<'a, M> CodeGenContext<'a, M> where M: MacroAssembler, { - pub fn new(masm: M, stack: Stack, frame: &'a Frame) -> Self { + pub fn new(masm: &'a mut M, stack: Stack, frame: &'a Frame) -> Self { Self { masm, stack, frame } } } @@ -63,13 +63,12 @@ where &mut self, body: &mut BinaryReader<'a>, validator: FuncValidator, - ) -> Result> { + ) -> Result<()> { self.emit_start() - .and(self.emit_body(body, validator)) - .and(self.emit_end())?; - let buf = self.context.masm.finalize(); - let code = Vec::from(buf); - Ok(code) + .and_then(|_| self.emit_body(body, validator)) + .and_then(|_| self.emit_end())?; + + Ok(()) } // TODO stack checks diff --git a/winch/codegen/src/frame/mod.rs b/winch/codegen/src/frame/mod.rs index 4945809db2..519917fe76 100644 --- a/winch/codegen/src/frame/mod.rs +++ b/winch/codegen/src/frame/mod.rs @@ -1,4 +1,4 @@ -use crate::abi::{align_to, local::LocalSlot, ty_size, ABIArg, ABISig, ABI}; +use crate::abi::{align_to, ty_size, ABIArg, ABISig, LocalSlot, ABI}; use anyhow::Result; use smallvec::SmallVec; use std::ops::Range; diff --git a/winch/codegen/src/isa/aarch64/masm.rs b/winch/codegen/src/isa/aarch64/masm.rs index 8027c96121..ad2c602e8a 100644 --- a/winch/codegen/src/isa/aarch64/masm.rs +++ b/winch/codegen/src/isa/aarch64/masm.rs @@ -3,6 +3,7 @@ use crate::{ isa::reg::Reg, masm::{MacroAssembler as Masm, OperandSize, RegImm}, }; +use cranelift_codegen::{Final, MachBufferFinalized}; #[derive(Default)] pub(crate) struct MacroAssembler; @@ -34,7 +35,7 @@ impl Masm for MacroAssembler { 0u32 } - fn finalize(&mut self) -> &[String] { + fn finalize(self) -> MachBufferFinalized { todo!() } diff --git a/winch/codegen/src/isa/aarch64/mod.rs b/winch/codegen/src/isa/aarch64/mod.rs index 803b4cdccb..8865afedee 100644 --- a/winch/codegen/src/isa/aarch64/mod.rs +++ b/winch/codegen/src/isa/aarch64/mod.rs @@ -1,5 +1,8 @@ -use crate::isa::TargetIsa; +use crate::isa::{Builder, TargetIsa}; use anyhow::Result; +use cranelift_codegen::{ + isa::aarch64::settings as aarch64_settings, settings::Flags, Final, MachBufferFinalized, +}; use target_lexicon::Triple; use wasmparser::{FuncType, FuncValidator, FunctionBody, ValidatorResources}; @@ -8,17 +11,38 @@ mod masm; mod regs; /// Create an ISA from the given triple. -pub(crate) fn isa_from(triple: Triple) -> Aarch64 { - Aarch64::new(triple) +pub(crate) fn isa_builder(triple: Triple) -> Builder { + Builder { + triple, + settings: aarch64_settings::builder(), + constructor: |triple, shared_flags, settings| { + let isa_flags = aarch64_settings::Flags::new(&shared_flags, settings); + let isa = Aarch64::new(triple, shared_flags, isa_flags); + Ok(Box::new(isa)) + }, + } } +/// Aarch64 ISA. +// Until Aarch64 emission is supported. +#[allow(dead_code)] pub(crate) struct Aarch64 { + /// The target triple. triple: Triple, + /// ISA specific flags. + isa_flags: aarch64_settings::Flags, + /// Shared flags. + shared_flags: Flags, } impl Aarch64 { - pub fn new(triple: Triple) -> Self { - Self { triple } + /// Create a Aarch64 ISA. + pub fn new(triple: Triple, shared_flags: Flags, isa_flags: aarch64_settings::Flags) -> Self { + Self { + isa_flags, + shared_flags, + triple, + } } } @@ -36,7 +60,7 @@ impl TargetIsa for Aarch64 { _sig: &FuncType, _body: &FunctionBody, mut _validator: FuncValidator, - ) -> Result> { + ) -> Result> { todo!() } } diff --git a/winch/codegen/src/isa/mod.rs b/winch/codegen/src/isa/mod.rs index 50350a3a78..ea0f93828e 100644 --- a/winch/codegen/src/isa/mod.rs +++ b/winch/codegen/src/isa/mod.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Result}; use core::fmt::Formatter; -use cranelift_codegen::isa::CallConv; +use cranelift_codegen::{isa::CallConv, settings, Final, MachBufferFinalized}; use std::{ error, fmt::{self, Debug, Display}, @@ -16,11 +16,11 @@ pub(crate) mod aarch64; pub(crate) mod reg; -macro_rules! isa { +macro_rules! isa_builder { ($name: ident, $cfg_terms: tt, $triple: ident) => {{ #[cfg $cfg_terms] { - Ok(Box::new($name::isa_from($triple))) + Ok($name::isa_builder($triple)) } #[cfg(not $cfg_terms)] { @@ -29,23 +29,33 @@ macro_rules! isa { }}; } -/// 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> { +/// The target ISA builder. +#[derive(Clone)] +pub struct Builder { + /// The target triple. + triple: Triple, + /// The ISA settings builder. + settings: settings::Builder, + /// The Target ISA constructor. + constructor: fn(Triple, settings::Flags, settings::Builder) -> Result>, +} + +impl Builder { + /// Create a TargetIsa by combining ISA-specific settings with the provided + /// shared flags. + pub fn build(self, shared_flags: settings::Flags) -> Result> { + (self.constructor)(self.triple, shared_flags, self.settings) + } +} + +/// Look for an ISA builder for the given target triple. +pub fn lookup(triple: Triple) -> Result { match triple.architecture { Architecture::X86_64 => { - isa!(x64, (feature = "x64"), triple) + isa_builder!(x64, (feature = "x64"), triple) } Architecture::Aarch64 { .. } => { - isa!(aarch64, (feature = "arm64"), triple) + isa_builder!(aarch64, (feature = "arm64"), triple) } _ => Err(anyhow!(LookupError::Unsupported)), @@ -87,7 +97,7 @@ pub trait TargetIsa: Send + Sync { sig: &FuncType, body: &FunctionBody, validator: FuncValidator, - ) -> Result>; + ) -> Result>; /// Get the default calling convention of the underlying target triple. fn call_conv(&self) -> CallConv { diff --git a/winch/codegen/src/isa/reg.rs b/winch/codegen/src/isa/reg.rs index c59f6c9408..d4a55c11ac 100644 --- a/winch/codegen/src/isa/reg.rs +++ b/winch/codegen/src/isa/reg.rs @@ -36,6 +36,17 @@ impl Reg { pub fn hw_enc(self) -> u8 { self.0.hw_enc() as u8 } + + /// Get the physical register representation. + pub(super) fn inner(&self) -> PReg { + self.0 + } +} + +impl From for cranelift_codegen::Reg { + fn from(reg: Reg) -> Self { + reg.inner().into() + } } impl std::fmt::Debug for Reg { diff --git a/winch/codegen/src/isa/x64/asm.rs b/winch/codegen/src/isa/x64/asm.rs new file mode 100644 index 0000000000..f20c88da92 --- /dev/null +++ b/winch/codegen/src/isa/x64/asm.rs @@ -0,0 +1,262 @@ +//! Assembler library implementation for x64. + +use crate::{abi::Address, isa::reg::Reg, masm::OperandSize}; +use cranelift_codegen::{ + isa::x64::{ + args::{ + self, AluRmiROpcode, Amode, ExtMode, FromWritableReg, Gpr, GprMem, GprMemImm, RegMem, + RegMemImm, SyntheticAmode, WritableGpr, + }, + settings as x64_settings, EmitInfo, EmitState, Inst, + }, + settings, Final, MachBuffer, MachBufferFinalized, MachInstEmit, Writable, +}; + +/// A x64 instruction operand. +#[derive(Debug, Copy, Clone)] +pub(crate) enum Operand { + /// Register. + Reg(Reg), + /// Memory address. + Mem(Address), + /// Immediate. + Imm(i32), +} + +// Conversions between winch-codegen x64 types and cranelift-codegen x64 types. + +impl From for RegMemImm { + fn from(reg: Reg) -> Self { + RegMemImm::reg(reg.into()) + } +} + +impl From for args::OperandSize { + fn from(size: OperandSize) -> Self { + match size { + OperandSize::S32 => Self::Size32, + OperandSize::S64 => Self::Size64, + } + } +} + +/// Low level assembler implementation for x64. +pub(crate) struct Assembler { + /// The machine instruction buffer. + buffer: MachBuffer, + /// Constant emission information. + emit_info: EmitInfo, + /// Emission state. + emit_state: EmitState, +} + +impl Assembler { + /// Create a new x64 assembler. + pub fn new(shared_flags: settings::Flags, isa_flags: x64_settings::Flags) -> Self { + Self { + buffer: MachBuffer::::new(), + emit_state: Default::default(), + emit_info: EmitInfo::new(shared_flags, isa_flags), + } + } + + /// Return the emitted code. + pub fn finalize(self) -> MachBufferFinalized { + let stencil = self.buffer.finish(); + stencil.apply_base_srcloc(Default::default()) + } + + fn emit(&mut self, inst: Inst) { + inst.emit(&[], &mut self.buffer, &self.emit_info, &mut self.emit_state); + } + + /// Push register. + pub fn push_r(&mut self, reg: Reg) { + let src = GprMemImm::new(reg.into()).expect("valid gpr"); + self.emit(Inst::Push64 { src }); + } + + /// Pop to register. + pub fn pop_r(&mut self, dst: Reg) { + let writable = Writable::from_reg(dst.into()); + let dst = WritableGpr::from_writable_reg(writable).expect("valid writable gpr"); + self.emit(Inst::Pop64 { dst }); + } + + /// Return instruction. + pub fn ret(&mut self) { + self.emit(Inst::Ret { rets: vec![] }); + } + + /// Move instruction variants. + pub fn mov(&mut self, src: Operand, dst: Operand, size: OperandSize) { + use self::Operand::*; + + match &(src, dst) { + (Reg(lhs), Reg(rhs)) => self.mov_rr(*lhs, *rhs, size), + (Reg(lhs), Mem(addr)) => match addr { + Address::Base { base, imm } => self.mov_rm(*lhs, *base, *imm, size), + }, + (Imm(imm), Mem(addr)) => match addr { + Address::Base { base, imm: disp } => self.mov_im(*imm as u64, *base, *disp, size), + }, + (Imm(imm), Reg(reg)) => self.mov_ir(*imm as u64, *reg, size), + (Mem(addr), Reg(reg)) => match addr { + Address::Base { base, imm } => self.mov_mr(*base, *imm, *reg, size), + }, + + _ => panic!( + "Invalid operand combination for mov; src={:?}, dst={:?}", + src, dst + ), + } + } + + /// Register-to-register move. + pub fn mov_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) { + let src = Gpr::new(src.into()).expect("valid gpr"); + let dst = WritableGpr::from_writable_reg(Writable::from_reg(dst.into())) + .expect("valid writable gpr"); + + self.emit(Inst::MovRR { + src, + dst, + size: size.into(), + }); + } + + /// Register-to-memory move. + pub fn mov_rm(&mut self, src: Reg, base: Reg, disp: u32, size: OperandSize) { + let src = Gpr::new(src.into()).expect("valid gpr"); + let dst = Amode::imm_reg(disp, base.into()); + + self.emit(Inst::MovRM { + size: size.into(), + src, + dst: SyntheticAmode::real(dst), + }); + } + + /// Immediate-to-memory move. + pub fn mov_im(&mut self, src: u64, base: Reg, disp: u32, size: OperandSize) { + let dst = Amode::imm_reg(disp, base.into()); + self.emit(Inst::MovImmM { + size: size.into(), + simm64: src, + dst: SyntheticAmode::real(dst), + }); + } + + /// Immediate-to-register move. + pub fn mov_ir(&mut self, imm: u64, dst: Reg, size: OperandSize) { + let dst = WritableGpr::from_writable_reg(Writable::from_reg(dst.into())) + .expect("valid writable gpr"); + + self.emit(Inst::Imm { + dst_size: size.into(), + simm64: imm, + dst, + }); + } + + /// Memory-to-register load. + pub fn mov_mr(&mut self, base: Reg, disp: u32, dst: Reg, size: OperandSize) { + use OperandSize::S64; + + let dst = WritableGpr::from_writable_reg(Writable::from_reg(dst.into())) + .expect("valid writable gpr"); + let amode = Amode::imm_reg(disp, base.into()); + let src = SyntheticAmode::real(amode); + + if size == S64 { + self.emit(Inst::Mov64MR { src, dst }); + } else { + let reg_mem = RegMem::mem(src); + self.emit(Inst::MovzxRmR { + ext_mode: ExtMode::LQ, + src: GprMem::new(reg_mem).expect("valid memory address"), + dst, + }); + } + } + + /// Subtact immediate register. + pub fn sub_ir(&mut self, imm: u32, dst: Reg, size: OperandSize) { + let writable = WritableGpr::from_writable_reg(Writable::from_reg(dst.into())) + .expect("valid writable gpr"); + let src = Gpr::new(dst.into()).expect("valid gpr"); + + let imm = RegMemImm::imm(imm); + + self.emit(Inst::AluRmiR { + size: size.into(), + op: AluRmiROpcode::Sub, + src1: src, + src2: GprMemImm::new(imm).expect("valid immediate"), + dst: writable, + }); + } + + /// Add instruction variants. + pub fn add(&mut self, src: Operand, dst: Operand, size: OperandSize) { + match &(src, dst) { + (Operand::Imm(imm), Operand::Reg(dst)) => self.add_ir(*imm, *dst, size), + (Operand::Reg(src), Operand::Reg(dst)) => self.add_rr(*src, *dst, size), + _ => panic!( + "Invalid operand combination for add; src = {:?} dst = {:?}", + src, dst + ), + } + } + + /// Add immediate and register. + pub fn add_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) { + let writable = WritableGpr::from_writable_reg(Writable::from_reg(dst.into())) + .expect("valid writable gpr"); + let src = Gpr::new(dst.into()).expect("valid gpr"); + + let imm = RegMemImm::imm(imm as u32); + + self.emit(Inst::AluRmiR { + size: size.into(), + op: AluRmiROpcode::Add, + src1: src, + src2: GprMemImm::new(imm).expect("valid immediate"), + dst: writable, + }); + } + + /// Add register and register. + pub fn add_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) { + let dest = WritableGpr::from_writable_reg(Writable::from_reg(dst.into())) + .expect("valid writable gpr"); + let src1 = Gpr::new(dst.into()).expect("valid gpr"); + + let src2 = RegMemImm::reg(src.into()); + + self.emit(Inst::AluRmiR { + size: size.into(), + op: AluRmiROpcode::Add, + src1, + src2: GprMemImm::new(src2).expect("valid gpr"), + dst: dest, + }); + } + + /// Logical exclusive or with registers. + pub fn xor_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) { + let dest = WritableGpr::from_writable_reg(Writable::from_reg(dst.into())) + .expect("valid writable gpr"); + let src1 = Gpr::new(dst.into()).expect("valid gpr"); + + let src2 = RegMemImm::reg(src.into()); + + self.emit(Inst::AluRmiR { + size: size.into(), + op: AluRmiROpcode::Xor, + src1, + src2: GprMemImm::new(src2).expect("valid gpr"), + dst: dest, + }); + } +} diff --git a/winch/codegen/src/isa/x64/masm.rs b/winch/codegen/src/isa/x64/masm.rs index 2607bbfe07..520f46db12 100644 --- a/winch/codegen/src/isa/x64/masm.rs +++ b/winch/codegen/src/isa/x64/masm.rs @@ -1,21 +1,51 @@ -use super::regs::{rbp, reg_name, rsp}; -use crate::abi::addressing_mode::Address; -use crate::abi::local::LocalSlot; +use super::{ + asm::{Assembler, Operand}, + regs::{rbp, rsp}, +}; +use crate::abi::{Address, LocalSlot}; use crate::isa::reg::Reg; use crate::masm::{MacroAssembler as Masm, OperandSize, RegImm}; +use cranelift_codegen::{isa::x64::settings as x64_settings, settings, Final, MachBufferFinalized}; +/// x64 MacroAssembler. pub(crate) struct MacroAssembler { + /// Stack pointer offset. sp_offset: u32, + /// Low level assembler. asm: Assembler, } +// Conversions between generic masm arguments and x64 operands. + +impl From for Operand { + fn from(rimm: RegImm) -> Self { + match rimm { + RegImm::Reg(r) => r.into(), + RegImm::Imm(imm) => Operand::Imm(imm), + } + } +} + +impl From for Operand { + fn from(reg: Reg) -> Self { + Operand::Reg(reg) + } +} + +impl From
for Operand { + fn from(addr: Address) -> Self { + Operand::Mem(addr) + } +} + 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); + self.asm + .mov_rr(stack_pointer, frame_pointer, OperandSize::S64); } fn push(&mut self, reg: Reg) -> u32 { @@ -33,7 +63,7 @@ impl Masm for MacroAssembler { return; } - self.asm.sub_ir(bytes, rsp()); + self.asm.sub_ir(bytes, rsp(), OperandSize::S64); self.increment_sp(bytes); } @@ -56,24 +86,13 @@ impl Masm for MacroAssembler { 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); - } - } + self.asm.mov(src, dst, size); } 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), - } + self.asm.mov(src, dst, size); } fn sp_offset(&mut self) -> u32 { @@ -81,21 +100,14 @@ impl Masm for MacroAssembler { } fn zero(&mut self, reg: Reg) { - self.asm.xorl_rr(reg, reg); + self.asm.xor_rr(reg, reg, OperandSize::S32); } 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); - } - } + self.asm.mov(src, dst, size); } fn add(&mut self, dst: RegImm, lhs: RegImm, rhs: RegImm, size: OperandSize) { @@ -108,36 +120,29 @@ impl Masm for MacroAssembler { ); }; - match size { - OperandSize::S32 => { - self.asm.addl(src, dst); - } - OperandSize::S64 => { - self.asm.add(src, dst); - } - } + self.asm.add(src, dst, size); } 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.add_ir(locals_size as i32, rsp, OperandSize::S64); } self.asm.pop_r(rbp()); self.asm.ret(); } - fn finalize(&mut self) -> &[String] { + fn finalize(self) -> MachBufferFinalized { self.asm.finalize() } } impl MacroAssembler { - /// Crate a x64 MacroAssembler - pub fn new() -> Self { + /// Create an x64 MacroAssembler. + pub fn new(shared_flags: settings::Flags, isa_flags: x64_settings::Flags) -> Self { Self { sp_offset: 0, - asm: Default::default(), + asm: Assembler::new(shared_flags, isa_flags), } } @@ -156,285 +161,3 @@ impl MacroAssembler { 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, -} - -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 for Operand { - fn from(rimm: RegImm) -> Self { - match rimm { - RegImm::Reg(r) => r.into(), - RegImm::Imm(imm) => Operand::Imm(imm), - } - } -} - -impl From for Operand { - fn from(reg: Reg) -> Self { - Operand::Reg(reg) - } -} - -impl From
for Operand { - fn from(addr: Address) -> Self { - Operand::Mem(addr) - } -} diff --git a/winch/codegen/src/isa/x64/mod.rs b/winch/codegen/src/isa/x64/mod.rs index 81362809f0..7412f9444a 100644 --- a/winch/codegen/src/isa/x64/mod.rs +++ b/winch/codegen/src/isa/x64/mod.rs @@ -1,17 +1,25 @@ use crate::abi::ABI; use crate::codegen::{CodeGen, CodeGenContext}; use crate::frame::Frame; -use crate::isa::x64::masm::MacroAssembler; +use crate::isa::x64::masm::MacroAssembler as X64Masm; +use crate::masm::MacroAssembler; use crate::regalloc::RegAlloc; use crate::stack::Stack; -use crate::{isa::TargetIsa, regset::RegSet}; +use crate::{ + isa::{Builder, TargetIsa}, + regset::RegSet, +}; use anyhow::Result; +use cranelift_codegen::{ + isa::x64::settings as x64_settings, settings::Flags, Final, MachBufferFinalized, +}; use target_lexicon::Triple; use wasmparser::{FuncType, FuncValidator, FunctionBody, ValidatorResources}; use self::regs::ALL_GPR; mod abi; +mod asm; 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 @@ -19,18 +27,39 @@ mod masm; #[allow(dead_code)] mod regs; -/// Create an ISA from the given triple. -pub(crate) fn isa_from(triple: Triple) -> X64 { - X64::new(triple) +/// Create an ISA builder. +pub(crate) fn isa_builder(triple: Triple) -> Builder { + Builder { + triple, + settings: x64_settings::builder(), + constructor: |triple, shared_flags, settings| { + // TODO: Once enabling/disabling flags is allowed, and once features like SIMD are supported + // ensure compatibility between shared flags and ISA flags. + let isa_flags = x64_settings::Flags::new(&shared_flags, settings); + let isa = X64::new(triple, shared_flags, isa_flags); + Ok(Box::new(isa)) + }, + } } +/// x64 ISA. pub(crate) struct X64 { + /// The target triple. triple: Triple, + /// ISA specific flags. + isa_flags: x64_settings::Flags, + /// Shared flags. + shared_flags: Flags, } impl X64 { - pub fn new(triple: Triple) -> Self { - Self { triple } + /// Create a x64 ISA. + pub fn new(triple: Triple, shared_flags: Flags, isa_flags: x64_settings::Flags) -> Self { + Self { + isa_flags, + shared_flags, + triple, + } } } @@ -43,24 +72,25 @@ impl TargetIsa for X64 { &self.triple } - // Temporarily returns a Vec fn compile_function( &self, sig: &FuncType, body: &FunctionBody, mut validator: FuncValidator, - ) -> Result> { + ) -> Result> { let mut body = body.get_binary_reader(); - let masm = MacroAssembler::new(); + let mut masm = X64Masm::new(self.shared_flags.clone(), self.isa_flags.clone()); 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 codegen_context = CodeGenContext::new(&mut masm, stack, &frame); let mut codegen = CodeGen::new::(codegen_context, abi_sig, regalloc); - codegen.emit(&mut body, validator) + codegen.emit(&mut body, validator)?; + + Ok(masm.finalize()) } } diff --git a/winch/codegen/src/lib.rs b/winch/codegen/src/lib.rs index dc10f90105..efee4a7114 100644 --- a/winch/codegen/src/lib.rs +++ b/winch/codegen/src/lib.rs @@ -10,6 +10,7 @@ mod abi; mod codegen; mod frame; pub mod isa; +pub use isa::*; mod masm; mod regalloc; mod regset; diff --git a/winch/codegen/src/masm.rs b/winch/codegen/src/masm.rs index 17559f6475..4e12358353 100644 --- a/winch/codegen/src/masm.rs +++ b/winch/codegen/src/masm.rs @@ -1,11 +1,12 @@ use crate::abi::align_to; -use crate::abi::{addressing_mode::Address, local::LocalSlot}; +use crate::abi::{Address, LocalSlot}; use crate::isa::reg::Reg; use crate::regalloc::RegAlloc; +use cranelift_codegen::{Final, MachBufferFinalized}; use std::ops::Range; /// Operand size, in bits. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Eq, PartialEq)] pub(crate) enum OperandSize { /// 32 bits. S32, @@ -89,8 +90,7 @@ pub(crate) trait MacroAssembler { fn push(&mut self, src: Reg) -> u32; /// Finalize the assembly and return the result. - // NOTE Interim, debug approach - fn finalize(&mut self) -> &[String]; + fn finalize(self) -> MachBufferFinalized; /// Zero a particular register. fn zero(&mut self, reg: Reg); diff --git a/winch/codegen/src/regalloc.rs b/winch/codegen/src/regalloc.rs index 5ccdf585a8..8d29f77a50 100644 --- a/winch/codegen/src/regalloc.rs +++ b/winch/codegen/src/regalloc.rs @@ -43,7 +43,7 @@ impl RegAlloc { 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); + Self::move_val_to_reg(val, dst, context.masm, context.frame, size); dst } @@ -63,7 +63,7 @@ impl RegAlloc { 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); + Self::move_val_to_reg(val, dst, context.masm, context.frame, size); dst } diff --git a/winch/src/disasm.rs b/winch/src/disasm.rs new file mode 100644 index 0000000000..bcb9924b41 --- /dev/null +++ b/winch/src/disasm.rs @@ -0,0 +1,61 @@ +//! Disassembly utilities. + +use anyhow::{bail, Result}; +use capstone::prelude::*; +use std::fmt::Write; +use target_lexicon::Architecture; +use winch_codegen::TargetIsa; + +/// Disassemble and print a machine code buffer. +pub fn print(bytes: &[u8], isa: &dyn TargetIsa) -> Result<()> { + let dis = disassembler_for(isa)?; + let insts = dis.disasm_all(bytes, 0x0).unwrap(); + + for i in insts.iter() { + let mut line = String::new(); + + write!(&mut line, "{:4x}:\t", i.address()).unwrap(); + + let mut bytes_str = String::new(); + let mut len = 0; + let mut first = true; + for b in i.bytes() { + if !first { + write!(&mut bytes_str, " ").unwrap(); + } + write!(&mut bytes_str, "{:02x}", b).unwrap(); + len += 1; + first = false; + } + write!(&mut line, "{:21}\t", bytes_str).unwrap(); + if len > 8 { + write!(&mut line, "\n\t\t\t\t").unwrap(); + } + + if let Some(s) = i.mnemonic() { + write!(&mut line, "{}\t", s).unwrap(); + } + + if let Some(s) = i.op_str() { + write!(&mut line, "{}", s).unwrap(); + } + + println!("{}", line); + } + + Ok(()) +} + +fn disassembler_for(isa: &dyn TargetIsa) -> Result { + let disasm = match isa.triple().architecture { + Architecture::X86_64 => Capstone::new() + .x86() + .mode(arch::x86::ArchMode::Mode64) + .build() + .map_err(|e| anyhow::format_err!("{}", e))?, + + _ => bail!("Unsupported ISA"), + }; + + Ok(disasm) +} diff --git a/winch/src/main.rs b/winch/src/main.rs index 914bf8e6d7..3e88bbe7e3 100644 --- a/winch/src/main.rs +++ b/winch/src/main.rs @@ -5,13 +5,16 @@ use anyhow::{Context, Result}; use clap::Parser; +use cranelift_codegen::settings; 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}; +use winch_codegen::{lookup, TargetIsa}; + +mod disasm; #[derive(Parser, Debug)] struct Options { @@ -29,7 +32,9 @@ fn main() -> Result<()> { .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 shared_flags = settings::Flags::new(settings::builder()); + let isa_builder = lookup(triple)?; + let isa = isa_builder.build(shared_flags)?; let mut validator = Validator::new(); let parser = WasmParser::new(0); let mut types = Default::default(); @@ -65,9 +70,8 @@ fn compile( let buffer = isa .compile_function(&sig, &body, validator) .expect("Couldn't compile function"); - for i in buffer { - println!("{}", i); - } + + disasm::print(buffer.data(), isa)?; Ok(()) }