1 //! Test command for testing the binary machine code emission.
2 //!
3 //! The `binemit` test command generates binary machine code for every instruction in the input
4 //! functions and compares the results to the expected output.
5
6 use crate::match_directive::match_directive;
7 use crate::subtest::{Context, SubTest, SubtestResult};
8 use cranelift_codegen::binemit::{self, CodeInfo, CodeSink, RegDiversions};
9 use cranelift_codegen::dbg::DisplayList;
10 use cranelift_codegen::dominator_tree::DominatorTree;
11 use cranelift_codegen::flowgraph::ControlFlowGraph;
12 use cranelift_codegen::ir;
13 use cranelift_codegen::ir::entities::AnyEntity;
14 use cranelift_codegen::isa;
15 use cranelift_codegen::print_errors::pretty_error;
16 use cranelift_codegen::settings::OptLevel;
17 use cranelift_reader::TestCommand;
18 use std::borrow::Cow;
19 use std::collections::HashMap;
20 use std::fmt::Write;
21
22 struct TestBinEmit;
23
subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>>24 pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> {
25 assert_eq!(parsed.command, "binemit");
26 if !parsed.options.is_empty() {
27 Err(format!("No options allowed on {}", parsed))
28 } else {
29 Ok(Box::new(TestBinEmit))
30 }
31 }
32
33 /// Code sink that generates text.
34 struct TextSink {
35 code_size: binemit::CodeOffset,
36 offset: binemit::CodeOffset,
37 text: String,
38 }
39
40 impl TextSink {
41 /// Create a new empty TextSink.
new() -> Self42 pub fn new() -> Self {
43 Self {
44 code_size: 0,
45 offset: 0,
46 text: String::new(),
47 }
48 }
49 }
50
51 impl binemit::CodeSink for TextSink {
offset(&self) -> binemit::CodeOffset52 fn offset(&self) -> binemit::CodeOffset {
53 self.offset
54 }
55
put1(&mut self, x: u8)56 fn put1(&mut self, x: u8) {
57 write!(self.text, "{:02x} ", x).unwrap();
58 self.offset += 1;
59 }
60
put2(&mut self, x: u16)61 fn put2(&mut self, x: u16) {
62 write!(self.text, "{:04x} ", x).unwrap();
63 self.offset += 2;
64 }
65
put4(&mut self, x: u32)66 fn put4(&mut self, x: u32) {
67 write!(self.text, "{:08x} ", x).unwrap();
68 self.offset += 4;
69 }
70
put8(&mut self, x: u64)71 fn put8(&mut self, x: u64) {
72 write!(self.text, "{:016x} ", x).unwrap();
73 self.offset += 8;
74 }
75
reloc_block(&mut self, reloc: binemit::Reloc, block_offset: binemit::CodeOffset)76 fn reloc_block(&mut self, reloc: binemit::Reloc, block_offset: binemit::CodeOffset) {
77 write!(self.text, "{}({}) ", reloc, block_offset).unwrap();
78 }
79
reloc_external( &mut self, _srcloc: ir::SourceLoc, reloc: binemit::Reloc, name: &ir::ExternalName, addend: binemit::Addend, )80 fn reloc_external(
81 &mut self,
82 _srcloc: ir::SourceLoc,
83 reloc: binemit::Reloc,
84 name: &ir::ExternalName,
85 addend: binemit::Addend,
86 ) {
87 write!(self.text, "{}({}", reloc, name).unwrap();
88 if addend != 0 {
89 write!(self.text, "{:+}", addend).unwrap();
90 }
91 write!(self.text, ") ").unwrap();
92 }
93
reloc_constant(&mut self, reloc: binemit::Reloc, constant: ir::ConstantOffset)94 fn reloc_constant(&mut self, reloc: binemit::Reloc, constant: ir::ConstantOffset) {
95 write!(self.text, "{}({}) ", reloc, constant).unwrap();
96 }
97
reloc_jt(&mut self, reloc: binemit::Reloc, jt: ir::JumpTable)98 fn reloc_jt(&mut self, reloc: binemit::Reloc, jt: ir::JumpTable) {
99 write!(self.text, "{}({}) ", reloc, jt).unwrap();
100 }
101
trap(&mut self, code: ir::TrapCode, _srcloc: ir::SourceLoc)102 fn trap(&mut self, code: ir::TrapCode, _srcloc: ir::SourceLoc) {
103 write!(self.text, "{} ", code).unwrap();
104 }
105
begin_jumptables(&mut self)106 fn begin_jumptables(&mut self) {
107 self.code_size = self.offset
108 }
begin_rodata(&mut self)109 fn begin_rodata(&mut self) {}
end_codegen(&mut self)110 fn end_codegen(&mut self) {}
add_stackmap( &mut self, _: &[ir::entities::Value], _: &ir::Function, _: &dyn isa::TargetIsa, )111 fn add_stackmap(
112 &mut self,
113 _: &[ir::entities::Value],
114 _: &ir::Function,
115 _: &dyn isa::TargetIsa,
116 ) {
117 }
118 }
119
120 impl SubTest for TestBinEmit {
name(&self) -> &'static str121 fn name(&self) -> &'static str {
122 "binemit"
123 }
124
is_mutating(&self) -> bool125 fn is_mutating(&self) -> bool {
126 true
127 }
128
needs_isa(&self) -> bool129 fn needs_isa(&self) -> bool {
130 true
131 }
132
run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()>133 fn run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()> {
134 let isa = context.isa.expect("binemit needs an ISA");
135 let encinfo = isa.encoding_info();
136 // TODO: Run a verifier pass over the code first to detect any bad encodings or missing/bad
137 // value locations. The current error reporting is just crashing...
138 let mut func = func.into_owned();
139
140 // Fix the stack frame layout so we can test spill/fill encodings.
141 let min_offset = func
142 .stack_slots
143 .values()
144 .map(|slot| slot.offset.unwrap())
145 .min();
146 func.stack_slots.layout_info = min_offset.map(|off| ir::StackLayoutInfo {
147 frame_size: (-off) as u32,
148 inbound_args_size: 0,
149 });
150
151 let opt_level = isa.flags().opt_level();
152
153 // Give an encoding to any instruction that doesn't already have one.
154 let mut divert = RegDiversions::new();
155 for block in func.layout.blocks() {
156 divert.clear();
157 for inst in func.layout.block_insts(block) {
158 if !func.encodings[inst].is_legal() {
159 // Find an encoding that satisfies both immediate field and register
160 // constraints.
161 if let Some(enc) = {
162 let mut legal_encodings = isa
163 .legal_encodings(&func, &func.dfg[inst], func.dfg.ctrl_typevar(inst))
164 .filter(|e| {
165 let recipe_constraints = &encinfo.constraints[e.recipe()];
166 recipe_constraints.satisfied(inst, &divert, &func)
167 });
168
169 if opt_level == OptLevel::SpeedAndSize {
170 // Get the smallest legal encoding
171 legal_encodings
172 .min_by_key(|&e| encinfo.byte_size(e, inst, &divert, &func))
173 } else {
174 // If not optimizing, just use the first encoding.
175 legal_encodings.next()
176 }
177 } {
178 func.encodings[inst] = enc;
179 }
180 }
181 divert.apply(&func.dfg[inst]);
182 }
183 }
184
185 // Relax branches and compute block offsets based on the encodings.
186 let mut cfg = ControlFlowGraph::with_function(&func);
187 let mut domtree = DominatorTree::with_function(&func, &cfg);
188 let CodeInfo { total_size, .. } =
189 binemit::relax_branches(&mut func, &mut cfg, &mut domtree, isa)
190 .map_err(|e| pretty_error(&func, context.isa, e))?;
191
192 // Collect all of the 'bin:' directives on instructions.
193 let mut bins = HashMap::new();
194 for comment in &context.details.comments {
195 if let Some(want) = match_directive(comment.text, "bin:") {
196 match comment.entity {
197 AnyEntity::Inst(inst) => {
198 if let Some(prev) = bins.insert(inst, want) {
199 return Err(format!(
200 "multiple 'bin:' directives on {}: '{}' and '{}'",
201 func.dfg.display_inst(inst, isa),
202 prev,
203 want
204 ));
205 }
206 }
207 _ => {
208 return Err(format!(
209 "'bin:' directive on non-inst {}: {}",
210 comment.entity, comment.text
211 ));
212 }
213 }
214 }
215 }
216 if bins.is_empty() {
217 return Err("No 'bin:' directives found".to_string());
218 }
219
220 // Now emit all instructions.
221 let mut sink = TextSink::new();
222 for block in func.layout.blocks() {
223 divert.clear();
224 // Correct header offsets should have been computed by `relax_branches()`.
225 assert_eq!(
226 sink.offset, func.offsets[block],
227 "Inconsistent {} header offset",
228 block
229 );
230 for (offset, inst, enc_bytes) in func.inst_offsets(block, &encinfo) {
231 assert_eq!(sink.offset, offset);
232 sink.text.clear();
233 let enc = func.encodings[inst];
234
235 // Send legal encodings into the emitter.
236 if enc.is_legal() {
237 // Generate a better error message if output locations are not specified.
238 validate_location_annotations(&func, inst, isa, false)?;
239
240 let before = sink.offset;
241 isa.emit_inst(&func, inst, &mut divert, &mut sink);
242 let emitted = sink.offset - before;
243 // Verify the encoding recipe sizes against the ISAs emit_inst implementation.
244 assert_eq!(
245 emitted,
246 enc_bytes,
247 "Inconsistent size for [{}] {}",
248 encinfo.display(enc),
249 func.dfg.display_inst(inst, isa)
250 );
251 }
252
253 // Check against bin: directives.
254 if let Some(want) = bins.remove(&inst) {
255 if !enc.is_legal() {
256 // A possible cause of an unencoded instruction is a missing location for
257 // one of the input/output operands.
258 validate_location_annotations(&func, inst, isa, true)?;
259 validate_location_annotations(&func, inst, isa, false)?;
260
261 // Do any encodings exist?
262 let encodings = isa
263 .legal_encodings(&func, &func.dfg[inst], func.dfg.ctrl_typevar(inst))
264 .map(|e| encinfo.display(e))
265 .collect::<Vec<_>>();
266
267 if encodings.is_empty() {
268 return Err(format!(
269 "No encodings found for: {}",
270 func.dfg.display_inst(inst, isa)
271 ));
272 }
273 return Err(format!(
274 "No matching encodings for {} in {}",
275 func.dfg.display_inst(inst, isa),
276 DisplayList(&encodings),
277 ));
278 }
279 let have = sink.text.trim();
280 if have != want {
281 return Err(format!(
282 "Bad machine code for {}: {}\nWant: {}\nGot: {}",
283 inst,
284 func.dfg.display_inst(inst, isa),
285 want,
286 have
287 ));
288 }
289 }
290 }
291 }
292
293 sink.begin_jumptables();
294
295 for (jt, jt_data) in func.jump_tables.iter() {
296 let jt_offset = func.jt_offsets[jt];
297 for block in jt_data.iter() {
298 let rel_offset: i32 = func.offsets[*block] as i32 - jt_offset as i32;
299 sink.put4(rel_offset as u32)
300 }
301 }
302
303 sink.begin_rodata();
304
305 // output constants
306 for (_, constant_data) in func.dfg.constants.iter() {
307 for byte in constant_data.iter() {
308 sink.put1(*byte)
309 }
310 }
311
312 sink.end_codegen();
313
314 if sink.offset != total_size {
315 return Err(format!(
316 "Expected code size {}, got {}",
317 total_size, sink.offset
318 ));
319 }
320
321 Ok(())
322 }
323 }
324
325 /// Validate registers/stack slots are correctly annotated.
validate_location_annotations( func: &ir::Function, inst: ir::Inst, isa: &dyn isa::TargetIsa, validate_inputs: bool, ) -> SubtestResult<()>326 fn validate_location_annotations(
327 func: &ir::Function,
328 inst: ir::Inst,
329 isa: &dyn isa::TargetIsa,
330 validate_inputs: bool,
331 ) -> SubtestResult<()> {
332 let values = if validate_inputs {
333 func.dfg.inst_args(inst)
334 } else {
335 func.dfg.inst_results(inst)
336 };
337
338 if let Some(&v) = values.iter().find(|&&v| !func.locations[v].is_assigned()) {
339 Err(format!(
340 "Need register/stack slot annotation for {} in {}",
341 v,
342 func.dfg.display_inst(inst, isa)
343 ))
344 } else {
345 Ok(())
346 }
347 }
348