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:
@@ -19,8 +19,10 @@ use crate::flowgraph::ControlFlowGraph;
|
||||
use crate::ir::Function;
|
||||
use crate::isa::TargetIsa;
|
||||
use crate::legalize_function;
|
||||
use crate::legalizer::simple_legalize;
|
||||
use crate::licm::do_licm;
|
||||
use crate::loop_analysis::LoopAnalysis;
|
||||
use crate::machinst::MachCompileResult;
|
||||
use crate::nan_canonicalization::do_nan_canonicalization;
|
||||
use crate::postopt::do_postopt;
|
||||
use crate::redundant_reload_remover::RedundantReloadRemover;
|
||||
@@ -55,6 +57,12 @@ pub struct Context {
|
||||
|
||||
/// Redundant-reload remover context.
|
||||
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 {
|
||||
@@ -78,6 +86,8 @@ impl Context {
|
||||
regalloc: regalloc::Context::new(),
|
||||
loop_analysis: LoopAnalysis::new(),
|
||||
redundant_reload_remover: RedundantReloadRemover::new(),
|
||||
mach_compile_result: None,
|
||||
want_disasm: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +99,14 @@ impl Context {
|
||||
self.regalloc.clear();
|
||||
self.loop_analysis.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>`.
|
||||
@@ -130,9 +148,13 @@ impl Context {
|
||||
pub fn compile(&mut self, isa: &dyn TargetIsa) -> CodegenResult<CodeInfo> {
|
||||
let _tt = timing::compile();
|
||||
self.verify_if(isa)?;
|
||||
debug!("Compiling:\n{}", self.func.display(isa));
|
||||
|
||||
let opt_level = isa.flags().opt_level();
|
||||
debug!(
|
||||
"Compiling (opt level {:?}):\n{}",
|
||||
opt_level,
|
||||
self.func.display(isa)
|
||||
);
|
||||
|
||||
self.compute_cfg();
|
||||
if opt_level != OptLevel::None {
|
||||
@@ -141,6 +163,7 @@ impl Context {
|
||||
if isa.flags().enable_nan_canonicalization() {
|
||||
self.canonicalize_nans(isa)?;
|
||||
}
|
||||
|
||||
self.legalize(isa)?;
|
||||
if opt_level != OptLevel::None {
|
||||
self.postopt(isa)?;
|
||||
@@ -149,23 +172,33 @@ impl Context {
|
||||
self.licm(isa)?;
|
||||
self.simple_gvn(isa)?;
|
||||
}
|
||||
|
||||
self.compute_domtree();
|
||||
self.eliminate_unreachable_code(isa)?;
|
||||
if opt_level != OptLevel::None {
|
||||
self.dce(isa)?;
|
||||
}
|
||||
self.regalloc(isa)?;
|
||||
self.prologue_epilogue(isa)?;
|
||||
if opt_level == OptLevel::Speed || opt_level == OptLevel::SpeedAndSize {
|
||||
self.redundant_reload_remover(isa)?;
|
||||
}
|
||||
if opt_level == OptLevel::SpeedAndSize {
|
||||
self.shrink_instructions(isa)?;
|
||||
}
|
||||
let result = self.relax_branches(isa);
|
||||
|
||||
debug!("Compiled:\n{}", self.func.display(isa));
|
||||
result
|
||||
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.prologue_epilogue(isa)?;
|
||||
if opt_level == OptLevel::Speed || opt_level == OptLevel::SpeedAndSize {
|
||||
self.redundant_reload_remover(isa)?;
|
||||
}
|
||||
if opt_level == OptLevel::SpeedAndSize {
|
||||
self.shrink_instructions(isa)?;
|
||||
}
|
||||
let result = self.relax_branches(isa);
|
||||
|
||||
debug!("Compiled:\n{}", self.func.display(isa));
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit machine code directly into raw memory.
|
||||
@@ -191,7 +224,11 @@ impl Context {
|
||||
) -> CodeInfo {
|
||||
let _tt = timing::binemit();
|
||||
let mut sink = MemoryCodeSink::new(mem, relocs, traps, stackmaps);
|
||||
isa.emit_function_to_memory(&self.func, &mut sink);
|
||||
if let Some(ref result) = &self.mach_compile_result {
|
||||
result.sections.emit(&mut sink);
|
||||
} else {
|
||||
isa.emit_function_to_memory(&self.func, &mut sink);
|
||||
}
|
||||
sink.info
|
||||
}
|
||||
|
||||
@@ -275,13 +312,19 @@ impl Context {
|
||||
|
||||
/// Run the legalizer for `isa` on the function.
|
||||
pub fn legalize(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> {
|
||||
// Legalization invalidates the domtree and loop_analysis by mutating the CFG.
|
||||
// TODO: Avoid doing this when legalization doesn't actually mutate the CFG.
|
||||
self.domtree.clear();
|
||||
self.loop_analysis.clear();
|
||||
legalize_function(&mut self.func, &mut self.cfg, isa);
|
||||
debug!("Legalized:\n{}", self.func.display(isa));
|
||||
self.verify_if(isa)
|
||||
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.
|
||||
// TODO: Avoid doing this when legalization doesn't actually mutate the CFG.
|
||||
self.domtree.clear();
|
||||
self.loop_analysis.clear();
|
||||
legalize_function(&mut self.func, &mut self.cfg, isa);
|
||||
debug!("Legalized:\n{}", self.func.display(isa));
|
||||
self.verify_if(isa)
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform post-legalization rewrites on the function.
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
//! The `Function` struct defined in this module owns all of its basic blocks and
|
||||
//! instructions.
|
||||
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use crate::binemit::CodeOffset;
|
||||
use crate::entity::{PrimaryMap, SecondaryMap};
|
||||
use crate::ir;
|
||||
@@ -17,6 +19,7 @@ use crate::isa::{CallConv, EncInfo, Encoding, Legalize, TargetIsa};
|
||||
use crate::regalloc::{EntryRegDiversions, RegDiversions};
|
||||
use crate::value_label::ValueLabelsRanges;
|
||||
use crate::write::write_function;
|
||||
use alloc::boxed::Box;
|
||||
use core::fmt;
|
||||
|
||||
/// A function.
|
||||
@@ -238,13 +241,21 @@ impl Function {
|
||||
|
||||
/// Wrapper around `encode` which assigns `inst` the resulting encoding.
|
||||
pub fn update_encoding(&mut self, inst: ir::Inst, isa: &dyn TargetIsa) -> Result<(), Legalize> {
|
||||
self.encode(inst, isa).map(|e| self.encodings[inst] = e)
|
||||
if isa.get_mach_backend().is_some() {
|
||||
Ok(())
|
||||
} else {
|
||||
self.encode(inst, isa).map(|e| self.encodings[inst] = e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around `TargetIsa::encode` for encoding an existing instruction
|
||||
/// in the `Function`.
|
||||
pub fn encode(&self, inst: ir::Inst, isa: &dyn TargetIsa) -> Result<Encoding, Legalize> {
|
||||
isa.encode(&self, &self.dfg[inst], self.dfg.ctrl_typevar(inst))
|
||||
if isa.get_mach_backend().is_some() {
|
||||
Ok(Encoding::new(0, 0))
|
||||
} else {
|
||||
isa.encode(&self, &self.dfg[inst], self.dfg.ctrl_typevar(inst))
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts collection of debug information.
|
||||
|
||||
@@ -48,6 +48,7 @@ pub use crate::isa::call_conv::CallConv;
|
||||
pub use crate::isa::constraints::{
|
||||
BranchRange, ConstraintKind, OperandConstraint, RecipeConstraints,
|
||||
};
|
||||
pub use crate::isa::enc_tables::Encodings;
|
||||
pub use crate::isa::encoding::{base_size, EncInfo, Encoding};
|
||||
pub use crate::isa::registers::{regs_overlap, RegClass, RegClassIndex, RegInfo, RegUnit};
|
||||
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::flowgraph;
|
||||
use crate::ir;
|
||||
pub use crate::isa::enc_tables::Encodings;
|
||||
#[cfg(feature = "unwind")]
|
||||
use crate::isa::fde::RegisterMappingError;
|
||||
#[cfg(feature = "unwind")]
|
||||
use crate::machinst::MachBackend;
|
||||
use crate::regalloc;
|
||||
use crate::result::CodegenResult;
|
||||
@@ -117,6 +117,7 @@ pub fn lookup(triple: Triple) -> Result<Builder, LookupError> {
|
||||
isa_builder!(x86, "x86", triple)
|
||||
}
|
||||
Architecture::Arm { .. } => isa_builder!(arm32, "arm32", triple),
|
||||
Architecture::Aarch64 { .. } => isa_builder!(arm64, "arm64", triple),
|
||||
_ => Err(LookupError::Unsupported),
|
||||
}
|
||||
}
|
||||
|
||||
83
cranelift/codegen/src/isa/test_utils.rs
Normal file
83
cranelift/codegen/src/isa/test_utils.rs
Normal 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) {}
|
||||
}
|
||||
@@ -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) {
|
||||
let _tt = timing::postopt();
|
||||
let mut pos = EncCursor::new(func, isa);
|
||||
let is_mach_backend = isa.get_mach_backend().is_some();
|
||||
while let Some(_block) = pos.next_block() {
|
||||
let mut last_flags_clobber = None;
|
||||
while let Some(inst) = pos.next_inst() {
|
||||
@@ -367,13 +368,15 @@ pub fn do_postopt(func: &mut Function, isa: &dyn TargetIsa) {
|
||||
// Optimize instructions to make use of flags.
|
||||
optimize_cpu_flags(&mut pos, inst, last_flags_clobber, isa);
|
||||
|
||||
// Track the most recent seen instruction that clobbers the flags.
|
||||
if let Some(constraints) = isa
|
||||
.encoding_info()
|
||||
.operand_constraints(pos.func.encodings[inst])
|
||||
{
|
||||
if constraints.clobbers_flags {
|
||||
last_flags_clobber = Some(inst)
|
||||
if !is_mach_backend {
|
||||
// Track the most recent seen instruction that clobbers the flags.
|
||||
if let Some(constraints) = isa
|
||||
.encoding_info()
|
||||
.operand_constraints(pos.func.encodings[inst])
|
||||
{
|
||||
if constraints.clobbers_flags {
|
||||
last_flags_clobber = Some(inst)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,17 @@ pub fn verify_flags(
|
||||
errors: &mut VerifierErrors,
|
||||
) -> VerifierStepResult<()> {
|
||||
let _tt = timing::verify_flags();
|
||||
let mut verifier = FlagsVerifier {
|
||||
func,
|
||||
cfg,
|
||||
encinfo: isa.map(|isa| isa.encoding_info()),
|
||||
livein: SecondaryMap::new(),
|
||||
};
|
||||
verifier.check(errors)
|
||||
if isa.is_none() || isa.unwrap().get_mach_backend().is_none() {
|
||||
let mut verifier = FlagsVerifier {
|
||||
func,
|
||||
cfg,
|
||||
encinfo: isa.map(|isa| isa.encoding_info()),
|
||||
livein: SecondaryMap::new(),
|
||||
};
|
||||
verifier.check(errors)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct FlagsVerifier<'a> {
|
||||
|
||||
@@ -49,42 +49,42 @@ fn handle_module(
|
||||
|
||||
// If we have an isa from the command-line, use that. Otherwise if the
|
||||
// file contains a unique isa, use that.
|
||||
let isa = if let Some(isa) = fisa.isa {
|
||||
isa
|
||||
} else if let Some(isa) = test_file.isa_spec.unique_isa() {
|
||||
isa
|
||||
} else {
|
||||
let isa = fisa.isa.or(test_file.isa_spec.unique_isa());
|
||||
|
||||
if isa.is_none() {
|
||||
return Err(String::from("compilation requires a target isa"));
|
||||
};
|
||||
|
||||
for (func, _) in test_file.functions {
|
||||
let mut context = Context::new();
|
||||
context.func = func;
|
||||
|
||||
let mut relocs = PrintRelocs::new(flag_print);
|
||||
let mut traps = PrintTraps::new(flag_print);
|
||||
let mut stackmaps = PrintStackmaps::new(flag_print);
|
||||
let mut mem = vec![];
|
||||
|
||||
// Compile and encode the result to machine code.
|
||||
let code_info = context
|
||||
.compile_and_emit(isa, &mut mem, &mut relocs, &mut traps, &mut stackmaps)
|
||||
.map_err(|err| pretty_error(&context.func, Some(isa), err))?;
|
||||
if let Some(isa) = isa {
|
||||
let mut context = Context::new();
|
||||
context.func = func;
|
||||
let mut mem = vec![];
|
||||
|
||||
if flag_print {
|
||||
println!("{}", context.func.display(isa));
|
||||
}
|
||||
// Compile and encode the result to machine code.
|
||||
let code_info = context
|
||||
.compile_and_emit(isa, &mut mem, &mut relocs, &mut traps, &mut stackmaps)
|
||||
.map_err(|err| pretty_error(&context.func, Some(isa), err))?;
|
||||
|
||||
if flag_disasm {
|
||||
print_all(
|
||||
isa,
|
||||
&mem,
|
||||
code_info.code_size,
|
||||
code_info.jumptables_size + code_info.rodata_size,
|
||||
&relocs,
|
||||
&traps,
|
||||
&stackmaps,
|
||||
)?;
|
||||
if flag_print {
|
||||
println!("{}", context.func.display(isa));
|
||||
}
|
||||
|
||||
if flag_disasm {
|
||||
print_all(
|
||||
isa,
|
||||
&mem,
|
||||
code_info.code_size,
|
||||
code_info.jumptables_size + code_info.rodata_size,
|
||||
&relocs,
|
||||
&traps,
|
||||
&stackmaps,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user