ARM64 backend, part 8 / 11: integration.

This patch ties together the new backend infrastructure with the
existing Cranelift codegen APIs.

With all patches in this series up to this patch applied, the ARM64
compiler is now functional and can be used. Two uses of this
functionality -- filecheck-based tests and integration into wasmtime --
will come in subsequent patches.
This commit is contained in:
Chris Fallin
2020-04-09 13:38:58 -07:00
parent a0e629ecfb
commit 60990aeaae
7 changed files with 209 additions and 64 deletions

View File

@@ -19,8 +19,10 @@ use crate::flowgraph::ControlFlowGraph;
use crate::ir::Function; use crate::ir::Function;
use crate::isa::TargetIsa; use crate::isa::TargetIsa;
use crate::legalize_function; use crate::legalize_function;
use crate::legalizer::simple_legalize;
use crate::licm::do_licm; use crate::licm::do_licm;
use crate::loop_analysis::LoopAnalysis; use crate::loop_analysis::LoopAnalysis;
use crate::machinst::MachCompileResult;
use crate::nan_canonicalization::do_nan_canonicalization; use crate::nan_canonicalization::do_nan_canonicalization;
use crate::postopt::do_postopt; use crate::postopt::do_postopt;
use crate::redundant_reload_remover::RedundantReloadRemover; use crate::redundant_reload_remover::RedundantReloadRemover;
@@ -55,6 +57,12 @@ pub struct Context {
/// Redundant-reload remover context. /// Redundant-reload remover context.
pub redundant_reload_remover: RedundantReloadRemover, pub redundant_reload_remover: RedundantReloadRemover,
/// Result of MachBackend compilation, if computed.
pub mach_compile_result: Option<MachCompileResult>,
/// Flag: do we want a disassembly with the MachCompileResult?
pub want_disasm: bool,
} }
impl Context { impl Context {
@@ -78,6 +86,8 @@ impl Context {
regalloc: regalloc::Context::new(), regalloc: regalloc::Context::new(),
loop_analysis: LoopAnalysis::new(), loop_analysis: LoopAnalysis::new(),
redundant_reload_remover: RedundantReloadRemover::new(), redundant_reload_remover: RedundantReloadRemover::new(),
mach_compile_result: None,
want_disasm: false,
} }
} }
@@ -89,6 +99,14 @@ impl Context {
self.regalloc.clear(); self.regalloc.clear();
self.loop_analysis.clear(); self.loop_analysis.clear();
self.redundant_reload_remover.clear(); self.redundant_reload_remover.clear();
self.mach_compile_result = None;
self.want_disasm = false;
}
/// Set the flag to request a disassembly when compiling with a
/// `MachBackend` backend.
pub fn set_disasm(&mut self, val: bool) {
self.want_disasm = val;
} }
/// Compile the function, and emit machine code into a `Vec<u8>`. /// Compile the function, and emit machine code into a `Vec<u8>`.
@@ -130,9 +148,13 @@ impl Context {
pub fn compile(&mut self, isa: &dyn TargetIsa) -> CodegenResult<CodeInfo> { pub fn compile(&mut self, isa: &dyn TargetIsa) -> CodegenResult<CodeInfo> {
let _tt = timing::compile(); let _tt = timing::compile();
self.verify_if(isa)?; self.verify_if(isa)?;
debug!("Compiling:\n{}", self.func.display(isa));
let opt_level = isa.flags().opt_level(); let opt_level = isa.flags().opt_level();
debug!(
"Compiling (opt level {:?}):\n{}",
opt_level,
self.func.display(isa)
);
self.compute_cfg(); self.compute_cfg();
if opt_level != OptLevel::None { if opt_level != OptLevel::None {
@@ -141,6 +163,7 @@ impl Context {
if isa.flags().enable_nan_canonicalization() { if isa.flags().enable_nan_canonicalization() {
self.canonicalize_nans(isa)?; self.canonicalize_nans(isa)?;
} }
self.legalize(isa)?; self.legalize(isa)?;
if opt_level != OptLevel::None { if opt_level != OptLevel::None {
self.postopt(isa)?; self.postopt(isa)?;
@@ -149,11 +172,20 @@ impl Context {
self.licm(isa)?; self.licm(isa)?;
self.simple_gvn(isa)?; self.simple_gvn(isa)?;
} }
self.compute_domtree(); self.compute_domtree();
self.eliminate_unreachable_code(isa)?; self.eliminate_unreachable_code(isa)?;
if opt_level != OptLevel::None { if opt_level != OptLevel::None {
self.dce(isa)?; self.dce(isa)?;
} }
if let Some(backend) = isa.get_mach_backend() {
let func = std::mem::replace(&mut self.func, Function::new());
let result = backend.compile_function(func, self.want_disasm)?;
let info = result.code_info();
self.mach_compile_result = Some(result);
Ok(info)
} else {
self.regalloc(isa)?; self.regalloc(isa)?;
self.prologue_epilogue(isa)?; self.prologue_epilogue(isa)?;
if opt_level == OptLevel::Speed || opt_level == OptLevel::SpeedAndSize { if opt_level == OptLevel::Speed || opt_level == OptLevel::SpeedAndSize {
@@ -167,6 +199,7 @@ impl Context {
debug!("Compiled:\n{}", self.func.display(isa)); debug!("Compiled:\n{}", self.func.display(isa));
result result
} }
}
/// Emit machine code directly into raw memory. /// Emit machine code directly into raw memory.
/// ///
@@ -191,7 +224,11 @@ impl Context {
) -> CodeInfo { ) -> CodeInfo {
let _tt = timing::binemit(); let _tt = timing::binemit();
let mut sink = MemoryCodeSink::new(mem, relocs, traps, stackmaps); let mut sink = MemoryCodeSink::new(mem, relocs, traps, stackmaps);
if let Some(ref result) = &self.mach_compile_result {
result.sections.emit(&mut sink);
} else {
isa.emit_function_to_memory(&self.func, &mut sink); isa.emit_function_to_memory(&self.func, &mut sink);
}
sink.info sink.info
} }
@@ -275,6 +312,11 @@ impl Context {
/// Run the legalizer for `isa` on the function. /// Run the legalizer for `isa` on the function.
pub fn legalize(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> { pub fn legalize(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> {
if isa.get_mach_backend().is_some() {
// Run some specific legalizations only.
simple_legalize(&mut self.func, &mut self.cfg, isa);
Ok(())
} else {
// Legalization invalidates the domtree and loop_analysis by mutating the CFG. // Legalization invalidates the domtree and loop_analysis by mutating the CFG.
// TODO: Avoid doing this when legalization doesn't actually mutate the CFG. // TODO: Avoid doing this when legalization doesn't actually mutate the CFG.
self.domtree.clear(); self.domtree.clear();
@@ -283,6 +325,7 @@ impl Context {
debug!("Legalized:\n{}", self.func.display(isa)); debug!("Legalized:\n{}", self.func.display(isa));
self.verify_if(isa) self.verify_if(isa)
} }
}
/// Perform post-legalization rewrites on the function. /// Perform post-legalization rewrites on the function.
pub fn postopt(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> { pub fn postopt(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> {

View File

@@ -3,6 +3,8 @@
//! The `Function` struct defined in this module owns all of its basic blocks and //! The `Function` struct defined in this module owns all of its basic blocks and
//! instructions. //! instructions.
#![allow(unused_imports)]
use crate::binemit::CodeOffset; use crate::binemit::CodeOffset;
use crate::entity::{PrimaryMap, SecondaryMap}; use crate::entity::{PrimaryMap, SecondaryMap};
use crate::ir; use crate::ir;
@@ -17,6 +19,7 @@ use crate::isa::{CallConv, EncInfo, Encoding, Legalize, TargetIsa};
use crate::regalloc::{EntryRegDiversions, RegDiversions}; use crate::regalloc::{EntryRegDiversions, RegDiversions};
use crate::value_label::ValueLabelsRanges; use crate::value_label::ValueLabelsRanges;
use crate::write::write_function; use crate::write::write_function;
use alloc::boxed::Box;
use core::fmt; use core::fmt;
/// A function. /// A function.
@@ -238,14 +241,22 @@ impl Function {
/// Wrapper around `encode` which assigns `inst` the resulting encoding. /// Wrapper around `encode` which assigns `inst` the resulting encoding.
pub fn update_encoding(&mut self, inst: ir::Inst, isa: &dyn TargetIsa) -> Result<(), Legalize> { pub fn update_encoding(&mut self, inst: ir::Inst, isa: &dyn TargetIsa) -> Result<(), Legalize> {
if isa.get_mach_backend().is_some() {
Ok(())
} else {
self.encode(inst, isa).map(|e| self.encodings[inst] = e) self.encode(inst, isa).map(|e| self.encodings[inst] = e)
} }
}
/// Wrapper around `TargetIsa::encode` for encoding an existing instruction /// Wrapper around `TargetIsa::encode` for encoding an existing instruction
/// in the `Function`. /// in the `Function`.
pub fn encode(&self, inst: ir::Inst, isa: &dyn TargetIsa) -> Result<Encoding, Legalize> { pub fn encode(&self, inst: ir::Inst, isa: &dyn TargetIsa) -> Result<Encoding, Legalize> {
if isa.get_mach_backend().is_some() {
Ok(Encoding::new(0, 0))
} else {
isa.encode(&self, &self.dfg[inst], self.dfg.ctrl_typevar(inst)) isa.encode(&self, &self.dfg[inst], self.dfg.ctrl_typevar(inst))
} }
}
/// Starts collection of debug information. /// Starts collection of debug information.
pub fn collect_debug_info(&mut self) { pub fn collect_debug_info(&mut self) {

View File

@@ -48,6 +48,7 @@ pub use crate::isa::call_conv::CallConv;
pub use crate::isa::constraints::{ pub use crate::isa::constraints::{
BranchRange, ConstraintKind, OperandConstraint, RecipeConstraints, BranchRange, ConstraintKind, OperandConstraint, RecipeConstraints,
}; };
pub use crate::isa::enc_tables::Encodings;
pub use crate::isa::encoding::{base_size, EncInfo, Encoding}; pub use crate::isa::encoding::{base_size, EncInfo, Encoding};
pub use crate::isa::registers::{regs_overlap, RegClass, RegClassIndex, RegInfo, RegUnit}; pub use crate::isa::registers::{regs_overlap, RegClass, RegClassIndex, RegInfo, RegUnit};
pub use crate::isa::stack::{StackBase, StackBaseMask, StackRef}; pub use crate::isa::stack::{StackBase, StackBaseMask, StackRef};
@@ -55,9 +56,8 @@ pub use crate::isa::stack::{StackBase, StackBaseMask, StackRef};
use crate::binemit; use crate::binemit;
use crate::flowgraph; use crate::flowgraph;
use crate::ir; use crate::ir;
pub use crate::isa::enc_tables::Encodings;
#[cfg(feature = "unwind")]
use crate::isa::fde::RegisterMappingError; use crate::isa::fde::RegisterMappingError;
#[cfg(feature = "unwind")]
use crate::machinst::MachBackend; use crate::machinst::MachBackend;
use crate::regalloc; use crate::regalloc;
use crate::result::CodegenResult; use crate::result::CodegenResult;
@@ -117,6 +117,7 @@ pub fn lookup(triple: Triple) -> Result<Builder, LookupError> {
isa_builder!(x86, "x86", triple) isa_builder!(x86, "x86", triple)
} }
Architecture::Arm { .. } => isa_builder!(arm32, "arm32", triple), Architecture::Arm { .. } => isa_builder!(arm32, "arm32", triple),
Architecture::Aarch64 { .. } => isa_builder!(arm64, "arm64", triple),
_ => Err(LookupError::Unsupported), _ => Err(LookupError::Unsupported),
} }
} }

View File

@@ -0,0 +1,83 @@
use crate::binemit::{Addend, CodeOffset, CodeSink, Reloc};
use crate::ir::Value;
use crate::ir::{ConstantOffset, ExternalName, Function, JumpTable, Opcode, SourceLoc, TrapCode};
use crate::isa::TargetIsa;
use alloc::vec::Vec;
use std::string::{String, ToString};
pub struct TestCodeSink {
bytes: Vec<u8>,
}
impl TestCodeSink {
/// Create a new TestCodeSink.
pub fn new() -> TestCodeSink {
TestCodeSink { bytes: vec![] }
}
/// This is pretty lame, but whatever ..
pub fn stringify(&self) -> String {
let mut s = "".to_string();
for b in &self.bytes {
s = s + &format!("{:02X}", b).to_string();
}
s
}
}
impl CodeSink for TestCodeSink {
fn offset(&self) -> CodeOffset {
self.bytes.len() as CodeOffset
}
fn put1(&mut self, x: u8) {
self.bytes.push(x);
}
fn put2(&mut self, x: u16) {
self.bytes.push((x >> 0) as u8);
self.bytes.push((x >> 8) as u8);
}
fn put4(&mut self, mut x: u32) {
for _ in 0..4 {
self.bytes.push(x as u8);
x >>= 8;
}
}
fn put8(&mut self, mut x: u64) {
for _ in 0..8 {
self.bytes.push(x as u8);
x >>= 8;
}
}
fn reloc_block(&mut self, _rel: Reloc, _block_offset: CodeOffset) {}
fn reloc_external(
&mut self,
_srcloc: SourceLoc,
_rel: Reloc,
_name: &ExternalName,
_addend: Addend,
) {
}
fn reloc_constant(&mut self, _rel: Reloc, _constant_offset: ConstantOffset) {}
fn reloc_jt(&mut self, _rel: Reloc, _jt: JumpTable) {}
fn trap(&mut self, _code: TrapCode, _srcloc: SourceLoc) {}
fn begin_jumptables(&mut self) {}
fn begin_rodata(&mut self) {}
fn end_codegen(&mut self) {}
fn add_stackmap(&mut self, _val_list: &[Value], _func: &Function, _isa: &dyn TargetIsa) {}
fn add_call_site(&mut self, _opcode: Opcode, _srcloc: SourceLoc) {}
}

View File

@@ -360,6 +360,7 @@ fn optimize_complex_addresses(pos: &mut EncCursor, inst: Inst, isa: &dyn TargetI
pub fn do_postopt(func: &mut Function, isa: &dyn TargetIsa) { pub fn do_postopt(func: &mut Function, isa: &dyn TargetIsa) {
let _tt = timing::postopt(); let _tt = timing::postopt();
let mut pos = EncCursor::new(func, isa); let mut pos = EncCursor::new(func, isa);
let is_mach_backend = isa.get_mach_backend().is_some();
while let Some(_block) = pos.next_block() { while let Some(_block) = pos.next_block() {
let mut last_flags_clobber = None; let mut last_flags_clobber = None;
while let Some(inst) = pos.next_inst() { while let Some(inst) = pos.next_inst() {
@@ -367,6 +368,7 @@ pub fn do_postopt(func: &mut Function, isa: &dyn TargetIsa) {
// Optimize instructions to make use of flags. // Optimize instructions to make use of flags.
optimize_cpu_flags(&mut pos, inst, last_flags_clobber, isa); optimize_cpu_flags(&mut pos, inst, last_flags_clobber, isa);
if !is_mach_backend {
// Track the most recent seen instruction that clobbers the flags. // Track the most recent seen instruction that clobbers the flags.
if let Some(constraints) = isa if let Some(constraints) = isa
.encoding_info() .encoding_info()
@@ -377,6 +379,7 @@ pub fn do_postopt(func: &mut Function, isa: &dyn TargetIsa) {
} }
} }
} }
}
if isa.uses_complex_addresses() { if isa.uses_complex_addresses() {
optimize_complex_addresses(&mut pos, inst, isa); optimize_complex_addresses(&mut pos, inst, isa);

View File

@@ -28,6 +28,7 @@ pub fn verify_flags(
errors: &mut VerifierErrors, errors: &mut VerifierErrors,
) -> VerifierStepResult<()> { ) -> VerifierStepResult<()> {
let _tt = timing::verify_flags(); let _tt = timing::verify_flags();
if isa.is_none() || isa.unwrap().get_mach_backend().is_none() {
let mut verifier = FlagsVerifier { let mut verifier = FlagsVerifier {
func, func,
cfg, cfg,
@@ -35,6 +36,9 @@ pub fn verify_flags(
livein: SecondaryMap::new(), livein: SecondaryMap::new(),
}; };
verifier.check(errors) verifier.check(errors)
} else {
Ok(())
}
} }
struct FlagsVerifier<'a> { struct FlagsVerifier<'a> {

View File

@@ -49,21 +49,20 @@ fn handle_module(
// If we have an isa from the command-line, use that. Otherwise if the // If we have an isa from the command-line, use that. Otherwise if the
// file contains a unique isa, use that. // file contains a unique isa, use that.
let isa = if let Some(isa) = fisa.isa { let isa = fisa.isa.or(test_file.isa_spec.unique_isa());
isa
} else if let Some(isa) = test_file.isa_spec.unique_isa() { if isa.is_none() {
isa
} else {
return Err(String::from("compilation requires a target isa")); return Err(String::from("compilation requires a target isa"));
}; };
for (func, _) in test_file.functions { for (func, _) in test_file.functions {
let mut context = Context::new();
context.func = func;
let mut relocs = PrintRelocs::new(flag_print); let mut relocs = PrintRelocs::new(flag_print);
let mut traps = PrintTraps::new(flag_print); let mut traps = PrintTraps::new(flag_print);
let mut stackmaps = PrintStackmaps::new(flag_print); let mut stackmaps = PrintStackmaps::new(flag_print);
if let Some(isa) = isa {
let mut context = Context::new();
context.func = func;
let mut mem = vec![]; let mut mem = vec![];
// Compile and encode the result to machine code. // Compile and encode the result to machine code.
@@ -87,6 +86,7 @@ fn handle_module(
)?; )?;
} }
} }
}
if flag_report_times { if flag_report_times {
print!("{}", timing::take_current()); print!("{}", timing::take_current());