Remove ancient register allocation (#3401)

This commit is contained in:
Benjamin Bouvier
2021-09-30 21:27:23 +02:00
committed by GitHub
parent 80336f4535
commit bae4ec6427
66 changed files with 112 additions and 15380 deletions

View File

@@ -248,7 +248,7 @@ fn compile(function: Function, isa: &dyn TargetIsa) -> Result<Mmap, CompilationE
let mut code_page = MmapMut::map_anon(code_info.total_size as usize)?;
unsafe {
context.emit_to_memory(isa, code_page.as_mut_ptr(), relocs, traps, stack_maps);
context.emit_to_memory(code_page.as_mut_ptr(), relocs, traps, stack_maps);
};
let code_page = code_page.make_exec()?;

View File

@@ -37,7 +37,6 @@ mod runone;
mod runtest_environment;
mod subtest;
mod test_binemit;
mod test_cat;
mod test_compile;
mod test_dce;
@@ -46,14 +45,10 @@ mod test_interpret;
mod test_legalizer;
mod test_licm;
mod test_peepmatic;
mod test_postopt;
mod test_preopt;
mod test_print_cfg;
mod test_regalloc;
mod test_rodata;
mod test_run;
mod test_safepoint;
mod test_shrink;
mod test_simple_gvn;
mod test_simple_preopt;
mod test_stack_maps;
@@ -118,7 +113,6 @@ pub fn run_passes(
/// a `.clif` test file.
fn new_subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn subtest::SubTest>> {
match parsed.command {
"binemit" => test_binemit::subtest(parsed),
"cat" => test_cat::subtest(parsed),
"compile" => test_compile::subtest(parsed),
"dce" => test_dce::subtest(parsed),
@@ -127,14 +121,10 @@ fn new_subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn subtest::SubTest>
"legalizer" => test_legalizer::subtest(parsed),
"licm" => test_licm::subtest(parsed),
"peepmatic" => test_peepmatic::subtest(parsed),
"postopt" => test_postopt::subtest(parsed),
"preopt" => test_preopt::subtest(parsed),
"print-cfg" => test_print_cfg::subtest(parsed),
"regalloc" => test_regalloc::subtest(parsed),
"rodata" => test_rodata::subtest(parsed),
"run" => test_run::subtest(parsed),
"safepoint" => test_safepoint::subtest(parsed),
"shrink" => test_shrink::subtest(parsed),
"simple-gvn" => test_simple_gvn::subtest(parsed),
"simple_preopt" => test_simple_preopt::subtest(parsed),
"stack_maps" => test_stack_maps::subtest(parsed),

View File

@@ -1,335 +0,0 @@
//! Test command for testing the binary machine code emission.
//!
//! The `binemit` test command generates binary machine code for every instruction in the input
//! functions and compares the results to the expected output.
use crate::match_directive::match_directive;
use crate::subtest::{Context, SubTest};
use cranelift_codegen::binemit::{self, CodeInfo, CodeSink, RegDiversions};
use cranelift_codegen::dbg::DisplayList;
use cranelift_codegen::dominator_tree::DominatorTree;
use cranelift_codegen::flowgraph::ControlFlowGraph;
use cranelift_codegen::ir;
use cranelift_codegen::ir::entities::AnyEntity;
use cranelift_codegen::isa;
use cranelift_codegen::settings::OptLevel;
use cranelift_reader::TestCommand;
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::Write;
struct TestBinEmit;
pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "binemit");
if !parsed.options.is_empty() {
anyhow::bail!("No options allowed on {}", parsed)
} else {
Ok(Box::new(TestBinEmit))
}
}
/// Code sink that generates text.
struct TextSink {
offset: binemit::CodeOffset,
text: String,
}
impl TextSink {
/// Create a new empty TextSink.
pub fn new() -> Self {
Self {
offset: 0,
text: String::new(),
}
}
}
impl binemit::CodeSink for TextSink {
fn offset(&self) -> binemit::CodeOffset {
self.offset
}
fn put1(&mut self, x: u8) {
write!(self.text, "{:02x} ", x).unwrap();
self.offset += 1;
}
fn put2(&mut self, x: u16) {
write!(self.text, "{:04x} ", x).unwrap();
self.offset += 2;
}
fn put4(&mut self, x: u32) {
write!(self.text, "{:08x} ", x).unwrap();
self.offset += 4;
}
fn put8(&mut self, x: u64) {
write!(self.text, "{:016x} ", x).unwrap();
self.offset += 8;
}
fn reloc_external(
&mut self,
_srcloc: ir::SourceLoc,
reloc: binemit::Reloc,
name: &ir::ExternalName,
addend: binemit::Addend,
) {
write!(self.text, "{}({}", reloc, name).unwrap();
if addend != 0 {
write!(self.text, "{:+}", addend).unwrap();
}
write!(self.text, ") ").unwrap();
}
fn reloc_constant(&mut self, reloc: binemit::Reloc, constant: ir::ConstantOffset) {
write!(self.text, "{}({}) ", reloc, constant).unwrap();
}
fn reloc_jt(&mut self, reloc: binemit::Reloc, jt: ir::JumpTable) {
write!(self.text, "{}({}) ", reloc, jt).unwrap();
}
fn trap(&mut self, code: ir::TrapCode, _srcloc: ir::SourceLoc) {
write!(self.text, "{} ", code).unwrap();
}
fn begin_jumptables(&mut self) {}
fn begin_rodata(&mut self) {}
fn end_codegen(&mut self) {}
fn add_stack_map(
&mut self,
_: &[ir::entities::Value],
_: &ir::Function,
_: &dyn isa::TargetIsa,
) {
}
}
impl SubTest for TestBinEmit {
fn name(&self) -> &'static str {
"binemit"
}
fn is_mutating(&self) -> bool {
true
}
fn needs_isa(&self) -> bool {
true
}
fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("binemit needs an ISA");
let encinfo = isa.encoding_info();
// TODO: Run a verifier pass over the code first to detect any bad encodings or missing/bad
// value locations. The current error reporting is just crashing...
let mut func = func.into_owned();
// Fix the stack frame layout so we can test spill/fill encodings.
let min_offset = func
.stack_slots
.values()
.map(|slot| slot.offset.unwrap())
.min();
func.stack_slots.layout_info = min_offset.map(|off| ir::StackLayoutInfo {
frame_size: (-off) as u32,
inbound_args_size: 0,
});
let opt_level = isa.flags().opt_level();
// Give an encoding to any instruction that doesn't already have one.
let mut divert = RegDiversions::new();
for block in func.layout.blocks() {
divert.clear();
for inst in func.layout.block_insts(block) {
if !func.encodings[inst].is_legal() {
// Find an encoding that satisfies both immediate field and register
// constraints.
if let Some(enc) = {
let mut legal_encodings = isa
.legal_encodings(&func, &func.dfg[inst], func.dfg.ctrl_typevar(inst))
.filter(|e| {
let recipe_constraints = &encinfo.constraints[e.recipe()];
recipe_constraints.satisfied(inst, &divert, &func)
});
if opt_level == OptLevel::SpeedAndSize {
// Get the smallest legal encoding
legal_encodings
.min_by_key(|&e| encinfo.byte_size(e, inst, &divert, &func))
} else {
// If not optimizing, just use the first encoding.
legal_encodings.next()
}
} {
func.encodings[inst] = enc;
}
}
divert.apply(&func.dfg[inst]);
}
}
// Relax branches and compute block offsets based on the encodings.
let mut cfg = ControlFlowGraph::with_function(&func);
let mut domtree = DominatorTree::with_function(&func, &cfg);
let CodeInfo { total_size, .. } =
binemit::relax_branches(&mut func, &mut cfg, &mut domtree, isa)
.map_err(|e| crate::pretty_anyhow_error(&func, context.isa, e))?;
// Collect all of the 'bin:' directives on instructions.
let mut bins = HashMap::new();
for comment in &context.details.comments {
if let Some(want) = match_directive(comment.text, "bin:") {
match comment.entity {
AnyEntity::Inst(inst) => {
if let Some(prev) = bins.insert(inst, want) {
anyhow::bail!(
"multiple 'bin:' directives on {}: '{}' and '{}'",
func.dfg.display_inst(inst, isa),
prev,
want
);
}
}
_ => {
anyhow::bail!(
"'bin:' directive on non-inst {}: {}",
comment.entity,
comment.text
);
}
}
}
}
if bins.is_empty() {
anyhow::bail!("No 'bin:' directives found");
}
// Now emit all instructions.
let mut sink = TextSink::new();
for block in func.layout.blocks() {
divert.clear();
// Correct header offsets should have been computed by `relax_branches()`.
assert_eq!(
sink.offset, func.offsets[block],
"Inconsistent {} header offset",
block
);
for (offset, inst, enc_bytes) in func.inst_offsets(block, &encinfo) {
assert_eq!(sink.offset, offset);
sink.text.clear();
let enc = func.encodings[inst];
// Send legal encodings into the emitter.
if enc.is_legal() {
// Generate a better error message if output locations are not specified.
validate_location_annotations(&func, inst, isa, false)?;
let before = sink.offset;
isa.emit_inst(&func, inst, &mut divert, &mut sink);
let emitted = sink.offset - before;
// Verify the encoding recipe sizes against the ISAs emit_inst implementation.
assert_eq!(
emitted,
enc_bytes,
"Inconsistent size for [{}] {}",
encinfo.display(enc),
func.dfg.display_inst(inst, isa)
);
}
// Check against bin: directives.
if let Some(want) = bins.remove(&inst) {
if !enc.is_legal() {
// A possible cause of an unencoded instruction is a missing location for
// one of the input/output operands.
validate_location_annotations(&func, inst, isa, true)?;
validate_location_annotations(&func, inst, isa, false)?;
// Do any encodings exist?
let encodings = isa
.legal_encodings(&func, &func.dfg[inst], func.dfg.ctrl_typevar(inst))
.map(|e| encinfo.display(e))
.collect::<Vec<_>>();
if encodings.is_empty() {
anyhow::bail!(
"No encodings found for: {}",
func.dfg.display_inst(inst, isa)
);
}
anyhow::bail!(
"No matching encodings for {} in {}",
func.dfg.display_inst(inst, isa),
DisplayList(&encodings),
);
}
let have = sink.text.trim();
if have != want {
anyhow::bail!(
"Bad machine code for {}: {}\nWant: {}\nGot: {}",
inst,
func.dfg.display_inst(inst, isa),
want,
have
);
}
}
}
}
sink.begin_jumptables();
for (jt, jt_data) in func.jump_tables.iter() {
let jt_offset = func.jt_offsets[jt];
for block in jt_data.iter() {
let rel_offset: i32 = func.offsets[*block] as i32 - jt_offset as i32;
sink.put4(rel_offset as u32)
}
}
sink.begin_rodata();
// output constants
for (_, constant_data) in func.dfg.constants.iter() {
for byte in constant_data.iter() {
sink.put1(*byte)
}
}
sink.end_codegen();
if sink.offset != total_size {
anyhow::bail!("Expected code size {}, got {}", total_size, sink.offset);
}
Ok(())
}
}
/// Validate registers/stack slots are correctly annotated.
fn validate_location_annotations(
func: &ir::Function,
inst: ir::Inst,
isa: &dyn isa::TargetIsa,
validate_inputs: bool,
) -> anyhow::Result<()> {
let values = if validate_inputs {
func.dfg.inst_args(inst)
} else {
func.dfg.inst_results(inst)
};
if let Some(&v) = values.iter().find(|&&v| !func.locations[v].is_assigned()) {
anyhow::bail!(
"Need register/stack slot annotation for {} in {}",
v,
func.dfg.display_inst(inst, isa)
);
}
Ok(())
}

View File

@@ -38,10 +38,8 @@ impl SubTest for TestCompile {
let isa = context.isa.expect("compile needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
if isa.get_mach_backend().is_some() {
// With `MachBackend`s, we need to explicitly request dissassembly results.
comp_ctx.set_disasm(true);
}
// With `MachBackend`s, we need to explicitly request dissassembly results.
comp_ctx.set_disasm(true);
let CodeInfo { total_size, .. } = comp_ctx
.compile(isa)
@@ -53,33 +51,14 @@ impl SubTest for TestCompile {
comp_ctx.func.display(isa)
);
if !isa.get_mach_backend().is_some() {
// Verify that the returned code size matches the emitted bytes.
let mut sink = SizeSink { offset: 0 };
binemit::emit_function(
&comp_ctx.func,
|func, inst, div, sink, isa| isa.emit_inst(func, inst, div, sink),
&mut sink,
isa,
);
if sink.offset != total_size {
anyhow::bail!("Expected code size {}, got {}", total_size, sink.offset);
}
// Run final code through filecheck.
let text = comp_ctx.func.display(Some(isa)).to_string();
run_filecheck(&text, context)
} else {
let disasm = comp_ctx
.mach_compile_result
.as_ref()
.unwrap()
.disasm
.as_ref()
.unwrap();
run_filecheck(&disasm, context)
}
let disasm = comp_ctx
.mach_compile_result
.as_ref()
.unwrap()
.disasm
.as_ref()
.unwrap();
run_filecheck(&disasm, context)
}
}

View File

@@ -1,46 +0,0 @@
//! Test command for testing the postopt pass.
//!
//! The resulting function is sent to `filecheck`.
use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen;
use cranelift_codegen::ir::Function;
use cranelift_reader::TestCommand;
use std::borrow::Cow;
struct TestPostopt;
pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "postopt");
if !parsed.options.is_empty() {
anyhow::bail!("No options allowed on {}", parsed);
}
Ok(Box::new(TestPostopt))
}
impl SubTest for TestPostopt {
fn name(&self) -> &'static str {
"postopt"
}
fn needs_isa(&self) -> bool {
true
}
fn is_mutating(&self) -> bool {
true
}
fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
let isa = context.isa.expect("postopt needs an ISA");
comp_ctx.flowgraph();
comp_ctx
.postopt(isa)
.map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, Into::into(e)))?;
let text = comp_ctx.func.display(isa).to_string();
run_filecheck(&text, context)
}
}

View File

@@ -1,54 +0,0 @@
//! Test command for testing the register allocator.
//!
//! The `regalloc` test command runs each function through the register allocator after ensuring
//! that all instructions are legal for the target.
//!
//! The resulting function is sent to `filecheck`.
use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen;
use cranelift_codegen::ir::Function;
use cranelift_reader::TestCommand;
use std::borrow::Cow;
struct TestRegalloc;
pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "regalloc");
if !parsed.options.is_empty() {
anyhow::bail!("No options allowed on {}", parsed);
}
Ok(Box::new(TestRegalloc))
}
impl SubTest for TestRegalloc {
fn name(&self) -> &'static str {
"regalloc"
}
fn is_mutating(&self) -> bool {
true
}
fn needs_isa(&self) -> bool {
true
}
fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("register allocator needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
comp_ctx.compute_cfg();
// TODO: Should we have an option to skip legalization?
comp_ctx
.legalize(isa)
.map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
comp_ctx.compute_domtree();
comp_ctx
.regalloc(isa)
.map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
let text = comp_ctx.func.display(Some(isa)).to_string();
run_filecheck(&text, context)
}
}

View File

@@ -1,133 +0,0 @@
//! Test command for verifying the rodata emitted after each function
//!
//! The `rodata` test command runs each function through the full code generator pipeline
use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen;
use cranelift_codegen::binemit::{self, CodeInfo};
use cranelift_codegen::ir;
use cranelift_codegen::ir::{Function, Value};
use cranelift_codegen::isa::TargetIsa;
use cranelift_reader::TestCommand;
use log::info;
use std::borrow::Cow;
struct TestRodata;
pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "rodata");
if !parsed.options.is_empty() {
anyhow::bail!("No options allowed on {}", parsed);
}
Ok(Box::new(TestRodata))
}
impl SubTest for TestRodata {
fn name(&self) -> &'static str {
"rodata"
}
fn is_mutating(&self) -> bool {
true
}
fn needs_isa(&self) -> bool {
true
}
fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("rodata needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
let CodeInfo { total_size, .. } = comp_ctx
.compile(isa)
.map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
info!(
"Generated {} bytes of code:\n{}",
total_size,
comp_ctx.func.display(isa)
);
// Verify that the returned code size matches the emitted bytes.
let mut sink = RodataSink::default();
binemit::emit_function(
&comp_ctx.func,
|func, inst, div, sink, isa| isa.emit_inst(func, inst, div, sink),
&mut sink,
isa,
);
// Run final code through filecheck.
let text = format!("{:X?}", sink.rodata);
info!("Found rodata: {}", text);
run_filecheck(&text, context)
}
}
/// Code sink that only captures emitted rodata
#[derive(Default)]
struct RodataSink {
offset: usize,
rodata: Vec<u8>,
in_rodata: bool,
}
impl binemit::CodeSink for RodataSink {
fn offset(&self) -> binemit::CodeOffset {
self.offset as u32
}
fn put1(&mut self, byte: u8) {
self.offset += 1;
if self.in_rodata {
self.rodata.push(byte);
}
}
fn put2(&mut self, bytes: u16) {
self.offset += 2;
if self.in_rodata {
self.rodata.extend_from_slice(&bytes.to_be_bytes());
}
}
fn put4(&mut self, bytes: u32) {
self.offset += 4;
if self.in_rodata {
self.rodata.extend_from_slice(&bytes.to_be_bytes());
}
}
fn put8(&mut self, bytes: u64) {
self.offset += 8;
if self.in_rodata {
self.rodata.extend_from_slice(&bytes.to_be_bytes());
}
}
fn reloc_external(
&mut self,
_: ir::SourceLoc,
_: binemit::Reloc,
_: &ir::ExternalName,
_: binemit::Addend,
) {
}
fn reloc_constant(&mut self, _: binemit::Reloc, _: ir::ConstantOffset) {}
fn reloc_jt(&mut self, _reloc: binemit::Reloc, _jt: ir::JumpTable) {}
fn trap(&mut self, _code: ir::TrapCode, _srcloc: ir::SourceLoc) {}
fn begin_jumptables(&mut self) {
assert!(!self.in_rodata, "Jump tables must be emitted before rodata");
}
fn begin_rodata(&mut self) {
self.in_rodata = true;
}
fn end_codegen(&mut self) {
assert!(
self.in_rodata,
"Expected rodata to be emitted before the end of codegen"
);
}
fn add_stack_map(&mut self, _: &[Value], _: &Function, _: &dyn TargetIsa) {}
}

View File

@@ -27,9 +27,6 @@ impl SubTest for TestSafepoint {
.legalize(isa)
.map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
comp_ctx.compute_domtree();
comp_ctx
.regalloc(isa)
.map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
let text = comp_ctx.func.display(context.isa).to_string();
run_filecheck(&text, context)

View File

@@ -1,44 +0,0 @@
//! Test command for testing the Shrink pass.
//!
//! The `shrink` test command runs each function through the Shrink pass after ensuring
//! that all instructions are legal for the target.
//!
//! The resulting function is sent to `filecheck`.
use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen;
use cranelift_codegen::ir::Function;
use cranelift_reader::TestCommand;
use std::borrow::Cow;
struct TestShrink;
pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "shrink");
if !parsed.options.is_empty() {
anyhow::bail!("No options allowed on {}", parsed);
}
Ok(Box::new(TestShrink))
}
impl SubTest for TestShrink {
fn name(&self) -> &'static str {
"shrink"
}
fn is_mutating(&self) -> bool {
true
}
fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("shrink needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
comp_ctx
.shrink_instructions(isa)
.map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, Into::into(e)))?;
let text = comp_ctx.func.display(isa).to_string();
run_filecheck(&text, context)
}
}

View File

@@ -29,13 +29,13 @@ impl SubTest for TestStackMaps {
.map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
let mut sink = TestStackMapsSink::default();
// TODO remove entirely? seems a bit meaningless now
binemit::emit_function(
&comp_ctx.func,
|func, inst, div, sink, isa| {
|func, inst, sink, isa| {
if func.dfg[inst].opcode() == Opcode::Safepoint {
writeln!(&mut sink.text, "{}", func.dfg.display_inst(inst, isa)).unwrap();
}
isa.emit_inst(func, inst, div, sink)
},
&mut sink,
context.isa.expect("`test stack_maps` requires an isa"),