Remove ancient register allocation (#3401)
This commit is contained in:
@@ -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()?;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user