1 //! Test command for verifying the rodata emitted after each function
2 //!
3 //! The `rodata` test command runs each function through the full code generator pipeline
4 
5 use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult};
6 use cranelift_codegen;
7 use cranelift_codegen::binemit::{self, CodeInfo};
8 use cranelift_codegen::ir;
9 use cranelift_codegen::ir::{Function, Value};
10 use cranelift_codegen::isa::TargetIsa;
11 use cranelift_codegen::print_errors::pretty_error;
12 use cranelift_reader::TestCommand;
13 use log::info;
14 use std::borrow::Cow;
15 
16 struct TestRodata;
17 
subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>>18 pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> {
19     assert_eq!(parsed.command, "rodata");
20     if !parsed.options.is_empty() {
21         Err(format!("No options allowed on {}", parsed))
22     } else {
23         Ok(Box::new(TestRodata))
24     }
25 }
26 
27 impl SubTest for TestRodata {
name(&self) -> &'static str28     fn name(&self) -> &'static str {
29         "rodata"
30     }
31 
is_mutating(&self) -> bool32     fn is_mutating(&self) -> bool {
33         true
34     }
35 
needs_isa(&self) -> bool36     fn needs_isa(&self) -> bool {
37         true
38     }
39 
run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()>40     fn run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()> {
41         let isa = context.isa.expect("rodata needs an ISA");
42         let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
43 
44         let CodeInfo { total_size, .. } = comp_ctx
45             .compile(isa)
46             .map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?;
47 
48         info!(
49             "Generated {} bytes of code:\n{}",
50             total_size,
51             comp_ctx.func.display(isa)
52         );
53 
54         // Verify that the returned code size matches the emitted bytes.
55         let mut sink = RodataSink::default();
56         binemit::emit_function(
57             &comp_ctx.func,
58             |func, inst, div, sink, isa| isa.emit_inst(func, inst, div, sink),
59             &mut sink,
60             isa,
61         );
62 
63         // Run final code through filecheck.
64         let text = format!("{:X?}", sink.rodata);
65         info!("Found rodata: {}", text);
66         run_filecheck(&text, context)
67     }
68 }
69 
70 /// Code sink that only captures emitted rodata
71 #[derive(Default)]
72 struct RodataSink {
73     offset: usize,
74     rodata: Vec<u8>,
75     in_rodata: bool,
76 }
77 
78 impl binemit::CodeSink for RodataSink {
offset(&self) -> binemit::CodeOffset79     fn offset(&self) -> binemit::CodeOffset {
80         self.offset as u32
81     }
82 
put1(&mut self, byte: u8)83     fn put1(&mut self, byte: u8) {
84         self.offset += 1;
85         if self.in_rodata {
86             self.rodata.push(byte);
87         }
88     }
89 
put2(&mut self, bytes: u16)90     fn put2(&mut self, bytes: u16) {
91         self.offset += 2;
92         if self.in_rodata {
93             self.rodata.extend_from_slice(&bytes.to_be_bytes());
94         }
95     }
96 
put4(&mut self, bytes: u32)97     fn put4(&mut self, bytes: u32) {
98         self.offset += 4;
99         if self.in_rodata {
100             self.rodata.extend_from_slice(&bytes.to_be_bytes());
101         }
102     }
103 
put8(&mut self, bytes: u64)104     fn put8(&mut self, bytes: u64) {
105         self.offset += 8;
106         if self.in_rodata {
107             self.rodata.extend_from_slice(&bytes.to_be_bytes());
108         }
109     }
110 
reloc_block(&mut self, _reloc: binemit::Reloc, _block_offset: binemit::CodeOffset)111     fn reloc_block(&mut self, _reloc: binemit::Reloc, _block_offset: binemit::CodeOffset) {}
reloc_external( &mut self, _: ir::SourceLoc, _: binemit::Reloc, _: &ir::ExternalName, _: binemit::Addend, )112     fn reloc_external(
113         &mut self,
114         _: ir::SourceLoc,
115         _: binemit::Reloc,
116         _: &ir::ExternalName,
117         _: binemit::Addend,
118     ) {
119     }
reloc_constant(&mut self, _: binemit::Reloc, _: ir::ConstantOffset)120     fn reloc_constant(&mut self, _: binemit::Reloc, _: ir::ConstantOffset) {}
reloc_jt(&mut self, _reloc: binemit::Reloc, _jt: ir::JumpTable)121     fn reloc_jt(&mut self, _reloc: binemit::Reloc, _jt: ir::JumpTable) {}
trap(&mut self, _code: ir::TrapCode, _srcloc: ir::SourceLoc)122     fn trap(&mut self, _code: ir::TrapCode, _srcloc: ir::SourceLoc) {}
begin_jumptables(&mut self)123     fn begin_jumptables(&mut self) {
124         assert!(!self.in_rodata, "Jump tables must be emitted before rodata");
125     }
begin_rodata(&mut self)126     fn begin_rodata(&mut self) {
127         self.in_rodata = true;
128     }
end_codegen(&mut self)129     fn end_codegen(&mut self) {
130         assert!(
131             self.in_rodata,
132             "Expected rodata to be emitted before the end of codegen"
133         );
134     }
add_stackmap(&mut self, _: &[Value], _: &Function, _: &dyn TargetIsa)135     fn add_stackmap(&mut self, _: &[Value], _: &Function, _: &dyn TargetIsa) {}
136 }
137