winch: Use cranelift-codegen x64 backend for emission. (#5581)
This change substitutes the string based emission mechanism with
cranelift-codegen's x64 backend.
This change _does not_:
* Introduce new functionality in terms of supported instructions.
* Change the semantics of the assembler/macroassembler in terms of the logic to
emit instructions.
The most notable differences between this change and the previous version are:
* Handling of shared flags and ISA-specific flags, which for now are left with
the default value.
* Simplification of instruction emission per operand size: previously the
assembler defined different methods depending on the operand size (e.g. `mov`
for 64 bits, and `movl` for 32 bits). This change updates such approach so that
each assembler method takes an operand size as a parameter, reducing duplication
and making the code more concise and easier to integrate with the x64's `Inst` enum.
* Introduction of a disassembler for testing purposes.
As of this change, Winch generates the following code for the following test
programs:
```wat
(module
(export "main" (func $main))
(func $main (result i32)
(i32.const 10)
(i32.const 20)
i32.add
))
```
```asm
0: 55 push rbp
1: 48 89 e5 mov rbp, rsp
4: b8 0a 00 00 00 mov eax, 0xa
9: 83 c0 14 add eax, 0x14
c: 5d pop rbp
d: c3 ret
```
```wat
(module
(export "main" (func $main))
(func $main (result i32)
(local $foo i32)
(local $bar i32)
(i32.const 10)
(local.set $foo)
(i32.const 20)
(local.set $bar)
(local.get $foo)
(local.get $bar)
i32.add
))
```
```asm
0: 55 push rbp
1: 48 89 e5 mov rbp, rsp
4: 48 83 ec 08 sub rsp, 8
8: 48 c7 04 24 00 00 00 00 mov qword ptr [rsp], 0
10: b8 0a 00 00 00 mov eax, 0xa
15: 89 44 24 04 mov dword ptr [rsp + 4], eax
19: b8 14 00 00 00 mov eax, 0x14
1e: 89 04 24 mov dword ptr [rsp], eax
21: 8b 04 24 mov eax, dword ptr [rsp]
24: 8b 4c 24 04 mov ecx, dword ptr [rsp + 4]
28: 01 c1 add ecx, eax
2a: 48 89 c8 mov rax, rcx
2d: 48 83 c4 08 add rsp, 8
31: 5d pop rbp
32: c3 ret
```
```wat
(module
(export "main" (func $main))
(func $main (param i32) (param i32) (result i32)
(local.get 0)
(local.get 1)
i32.add
))
```
```asm
0: 55 push rbp
1: 48 89 e5 mov rbp, rsp
4: 48 83 ec 08 sub rsp, 8
8: 89 7c 24 04 mov dword ptr [rsp + 4], edi
c: 89 34 24 mov dword ptr [rsp], esi
f: 8b 04 24 mov eax, dword ptr [rsp]
12: 8b 4c 24 04 mov ecx, dword ptr [rsp + 4]
16: 01 c1 add ecx, eax
18: 48 89 c8 mov rax, rcx
1b: 48 83 c4 08 add rsp, 8
1f: 5d pop rbp
20: c3 ret
```
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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<dyn CompilerBuilder> {
|
||||
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<Box<dyn wasmtime_environ::Compiler>> {
|
||||
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(
|
||||
|
||||
@@ -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<dyn TargetIsa>,
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<ValidatorResources>,
|
||||
) -> Result<Vec<String>> {
|
||||
) -> 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Final> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ValidatorResources>,
|
||||
) -> Result<Vec<String>> {
|
||||
) -> Result<MachBufferFinalized<Final>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Box<dyn TargetIsa>> {
|
||||
/// 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<Box<dyn TargetIsa>>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Create a TargetIsa by combining ISA-specific settings with the provided
|
||||
/// shared flags.
|
||||
pub fn build(self, shared_flags: settings::Flags) -> Result<Box<dyn TargetIsa>> {
|
||||
(self.constructor)(self.triple, shared_flags, self.settings)
|
||||
}
|
||||
}
|
||||
|
||||
/// Look for an ISA builder for the given target triple.
|
||||
pub fn lookup(triple: Triple) -> Result<Builder> {
|
||||
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<ValidatorResources>,
|
||||
) -> Result<Vec<String>>;
|
||||
) -> Result<MachBufferFinalized<Final>>;
|
||||
|
||||
/// Get the default calling convention of the underlying target triple.
|
||||
fn call_conv(&self) -> CallConv {
|
||||
|
||||
@@ -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<Reg> for cranelift_codegen::Reg {
|
||||
fn from(reg: Reg) -> Self {
|
||||
reg.inner().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Reg {
|
||||
|
||||
262
winch/codegen/src/isa/x64/asm.rs
Normal file
262
winch/codegen/src/isa/x64/asm.rs
Normal file
@@ -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<Reg> for RegMemImm {
|
||||
fn from(reg: Reg) -> Self {
|
||||
RegMemImm::reg(reg.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OperandSize> 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<Inst>,
|
||||
/// 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::<Inst>::new(),
|
||||
emit_state: Default::default(),
|
||||
emit_info: EmitInfo::new(shared_flags, isa_flags),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the emitted code.
|
||||
pub fn finalize(self) -> MachBufferFinalized<Final> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<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)
|
||||
}
|
||||
}
|
||||
|
||||
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<Final> {
|
||||
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<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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String>
|
||||
fn compile_function(
|
||||
&self,
|
||||
sig: &FuncType,
|
||||
body: &FunctionBody,
|
||||
mut validator: FuncValidator<ValidatorResources>,
|
||||
) -> Result<Vec<String>> {
|
||||
) -> Result<MachBufferFinalized<Final>> {
|
||||
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::<abi::X64ABI>(codegen_context, abi_sig, regalloc);
|
||||
|
||||
codegen.emit(&mut body, validator)
|
||||
codegen.emit(&mut body, validator)?;
|
||||
|
||||
Ok(masm.finalize())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ mod abi;
|
||||
mod codegen;
|
||||
mod frame;
|
||||
pub mod isa;
|
||||
pub use isa::*;
|
||||
mod masm;
|
||||
mod regalloc;
|
||||
mod regset;
|
||||
|
||||
@@ -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<Final>;
|
||||
|
||||
/// Zero a particular register.
|
||||
fn zero(&mut self, reg: Reg);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
61
winch/src/disasm.rs
Normal file
61
winch/src/disasm.rs
Normal file
@@ -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<Capstone> {
|
||||
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)
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user