1use crate::ir;
28use souper_ir::ast;
29use std::collections::{HashMap, HashSet};
30use std::string::String;
31use std::sync::mpsc;
32use std::vec::Vec;
33
34pub fn do_souper_harvest(func: &ir::Function, out: &mut mpsc::Sender<String>) {
38 let mut allocs = Allocs::default();
39
40 for block in func.layout.blocks() {
43 let mut option_inst = func.layout.first_inst(block);
44 while let Some(inst) = option_inst {
45 let results = func.dfg.inst_results(inst);
46 if results.len() == 1 {
47 let val = results[0];
48 let ty = func.dfg.value_type(val);
49 if ty.is_int() && ty.lane_count() == 1 {
50 harvest_candidate_lhs(&mut allocs, func, val, out);
51 }
52 }
53 option_inst = func.layout.next_inst(inst);
54 }
55 }
56}
57
58#[derive(Default)]
60struct Allocs {
61 ir_to_souper_val: HashMap<ir::Value, ast::ValueId>,
64
65 dfs_stack: Vec<StackEntry>,
67
68 dfs_seen: HashSet<ir::Value>,
70}
71
72impl Allocs {
73 fn reset(&mut self) {
76 self.ir_to_souper_val.clear();
77 self.dfs_stack.clear();
78 self.dfs_seen.clear();
79 }
80}
81
82fn harvest_candidate_lhs(
84 allocs: &mut Allocs,
85 func: &ir::Function,
86 val: ir::Value,
87 out: &mut mpsc::Sender<String>,
88) {
89 allocs.reset();
90 let mut lhs = ast::LeftHandSideBuilder::default();
91 let mut non_var_count = 0;
92
93 let should_trace = |val| match func.dfg.value_def(val) {
96 ir::ValueDef::Result(inst, 0) => match func.dfg.insts[inst].opcode() {
97 ir::Opcode::Iadd
98 | ir::Opcode::IaddImm
99 | ir::Opcode::IrsubImm
100 | ir::Opcode::Imul
101 | ir::Opcode::ImulImm
102 | ir::Opcode::Udiv
103 | ir::Opcode::UdivImm
104 | ir::Opcode::Sdiv
105 | ir::Opcode::SdivImm
106 | ir::Opcode::Urem
107 | ir::Opcode::UremImm
108 | ir::Opcode::Srem
109 | ir::Opcode::SremImm
110 | ir::Opcode::Band
111 | ir::Opcode::BandImm
112 | ir::Opcode::Bor
113 | ir::Opcode::BorImm
114 | ir::Opcode::Bxor
115 | ir::Opcode::BxorImm
116 | ir::Opcode::Ishl
117 | ir::Opcode::IshlImm
118 | ir::Opcode::Sshr
119 | ir::Opcode::SshrImm
120 | ir::Opcode::Ushr
121 | ir::Opcode::UshrImm
122 | ir::Opcode::Select
123 | ir::Opcode::Uextend
124 | ir::Opcode::Sextend
125 | ir::Opcode::Trunc
126 | ir::Opcode::Icmp
127 | ir::Opcode::Popcnt
128 | ir::Opcode::Bitrev
129 | ir::Opcode::Clz
130 | ir::Opcode::Ctz
131 | ir::Opcode::SaddSat
133 | ir::Opcode::SsubSat
134 | ir::Opcode::UsubSat => true,
135 _ => false,
136 },
137 _ => false,
138 };
139
140 post_order_dfs(allocs, &func.dfg, val, should_trace, |allocs, val| {
141 let souper_assignment_rhs = match func.dfg.value_def(val) {
142 ir::ValueDef::Result(inst, 0) => {
143 let args = func.dfg.inst_args(inst);
144
145 let arg = |allocs: &mut Allocs, n| {
147 let arg = args[n];
148 if let Some(a) = allocs.ir_to_souper_val.get(&arg).copied() {
149 a.into()
150 } else {
151 match func.dfg.value_def(arg) {
159 ir::ValueDef::Result(inst, 0) => match func.dfg.insts[inst] {
160 ir::InstructionData::UnaryImm { opcode, imm } => {
161 debug_assert_eq!(opcode, ir::Opcode::Iconst);
162 let imm: i64 = imm.into();
163 ast::Operand::Constant(ast::Constant {
164 value: imm.into(),
165 r#type: souper_type_of(&func.dfg, arg),
166 })
167 }
168 _ => unreachable!(
169 "only iconst instructions \
170 aren't in `ir_to_souper_val`"
171 ),
172 },
173 _ => unreachable!(
174 "only iconst instructions \
175 aren't in `ir_to_souper_val`"
176 ),
177 }
178 }
179 };
180
181 match (func.dfg.insts[inst].opcode(), &func.dfg.insts[inst]) {
182 (ir::Opcode::Iadd, _) => {
183 let a = arg(allocs, 0);
184 let b = arg(allocs, 1);
185 ast::Instruction::Add { a, b }.into()
186 }
187 (ir::Opcode::IaddImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
188 let a = arg(allocs, 0);
189 let value: i64 = (*imm).into();
190 let value: i128 = value.into();
191 let b = ast::Constant {
192 value,
193 r#type: souper_type_of(&func.dfg, val),
194 }
195 .into();
196 ast::Instruction::Add { a, b }.into()
197 }
198 (ir::Opcode::IrsubImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
199 let b = arg(allocs, 0);
200 let value: i64 = (*imm).into();
201 let value: i128 = value.into();
202 let a = ast::Constant {
203 value,
204 r#type: souper_type_of(&func.dfg, val),
205 }
206 .into();
207 ast::Instruction::Sub { a, b }.into()
208 }
209 (ir::Opcode::Imul, _) => {
210 let a = arg(allocs, 0);
211 let b = arg(allocs, 1);
212 ast::Instruction::Mul { a, b }.into()
213 }
214 (ir::Opcode::ImulImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
215 let a = arg(allocs, 0);
216 let value: i64 = (*imm).into();
217 let value: i128 = value.into();
218 let b = ast::Constant {
219 value,
220 r#type: souper_type_of(&func.dfg, val),
221 }
222 .into();
223 ast::Instruction::Mul { a, b }.into()
224 }
225 (ir::Opcode::Udiv, _) => {
226 let a = arg(allocs, 0);
227 let b = arg(allocs, 1);
228 ast::Instruction::Udiv { a, b }.into()
229 }
230 (ir::Opcode::UdivImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
231 let a = arg(allocs, 0);
232 let value: i64 = (*imm).into();
233 let value: i128 = value.into();
234 let b = ast::Constant {
235 value,
236 r#type: souper_type_of(&func.dfg, val),
237 }
238 .into();
239 ast::Instruction::Udiv { a, b }.into()
240 }
241 (ir::Opcode::Sdiv, _) => {
242 let a = arg(allocs, 0);
243 let b = arg(allocs, 1);
244 ast::Instruction::Sdiv { a, b }.into()
245 }
246 (ir::Opcode::SdivImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
247 let a = arg(allocs, 0);
248 let value: i64 = (*imm).into();
249 let value: i128 = value.into();
250 let b = ast::Constant {
251 value,
252 r#type: souper_type_of(&func.dfg, val),
253 }
254 .into();
255 ast::Instruction::Sdiv { a, b }.into()
256 }
257 (ir::Opcode::Urem, _) => {
258 let a = arg(allocs, 0);
259 let b = arg(allocs, 1);
260 ast::Instruction::Urem { a, b }.into()
261 }
262 (ir::Opcode::UremImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
263 let a = arg(allocs, 0);
264 let value: i64 = (*imm).into();
265 let value: i128 = value.into();
266 let b = ast::Constant {
267 value,
268 r#type: souper_type_of(&func.dfg, val),
269 }
270 .into();
271 ast::Instruction::Urem { a, b }.into()
272 }
273 (ir::Opcode::Srem, _) => {
274 let a = arg(allocs, 0);
275 let b = arg(allocs, 1);
276 ast::Instruction::Srem { a, b }.into()
277 }
278 (ir::Opcode::SremImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
279 let a = arg(allocs, 0);
280 let value: i64 = (*imm).into();
281 let value: i128 = value.into();
282 let b = ast::Constant {
283 value,
284 r#type: souper_type_of(&func.dfg, val),
285 }
286 .into();
287 ast::Instruction::Srem { a, b }.into()
288 }
289 (ir::Opcode::Band, _) => {
290 let a = arg(allocs, 0);
291 let b = arg(allocs, 1);
292 ast::Instruction::And { a, b }.into()
293 }
294 (ir::Opcode::BandImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
295 let a = arg(allocs, 0);
296 let value: i64 = (*imm).into();
297 let value: i128 = value.into();
298 let b = ast::Constant {
299 value,
300 r#type: souper_type_of(&func.dfg, val),
301 }
302 .into();
303 ast::Instruction::And { a, b }.into()
304 }
305 (ir::Opcode::Bor, _) => {
306 let a = arg(allocs, 0);
307 let b = arg(allocs, 1);
308 ast::Instruction::Or { a, b }.into()
309 }
310 (ir::Opcode::BorImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
311 let a = arg(allocs, 0);
312 let value: i64 = (*imm).into();
313 let value: i128 = value.into();
314 let b = ast::Constant {
315 value,
316 r#type: souper_type_of(&func.dfg, val),
317 }
318 .into();
319 ast::Instruction::Or { a, b }.into()
320 }
321 (ir::Opcode::Bxor, _) => {
322 let a = arg(allocs, 0);
323 let b = arg(allocs, 1);
324 ast::Instruction::Xor { a, b }.into()
325 }
326 (ir::Opcode::BxorImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
327 let a = arg(allocs, 0);
328 let value: i64 = (*imm).into();
329 let value: i128 = value.into();
330 let b = ast::Constant {
331 value,
332 r#type: souper_type_of(&func.dfg, val),
333 }
334 .into();
335 ast::Instruction::Xor { a, b }.into()
336 }
337 (ir::Opcode::Ishl, _) => {
338 let a = arg(allocs, 0);
339 let b = arg(allocs, 1);
340 ast::Instruction::Shl { a, b }.into()
341 }
342 (ir::Opcode::IshlImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
343 let a = arg(allocs, 0);
344 let value: i64 = (*imm).into();
345 let value: i128 = value.into();
346 let b = ast::Constant {
347 value,
348 r#type: souper_type_of(&func.dfg, val),
349 }
350 .into();
351 ast::Instruction::Shl { a, b }.into()
352 }
353 (ir::Opcode::Sshr, _) => {
354 let a = arg(allocs, 0);
355 let b = arg(allocs, 1);
356 ast::Instruction::Ashr { a, b }.into()
357 }
358 (ir::Opcode::SshrImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
359 let a = arg(allocs, 0);
360 let value: i64 = (*imm).into();
361 let value: i128 = value.into();
362 let b = ast::Constant {
363 value,
364 r#type: souper_type_of(&func.dfg, val),
365 }
366 .into();
367 ast::Instruction::Ashr { a, b }.into()
368 }
369 (ir::Opcode::Ushr, _) => {
370 let a = arg(allocs, 0);
371 let b = arg(allocs, 1);
372 ast::Instruction::Lshr { a, b }.into()
373 }
374 (ir::Opcode::UshrImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {
375 let a = arg(allocs, 0);
376 let value: i64 = (*imm).into();
377 let value: i128 = value.into();
378 let b = ast::Constant {
379 value,
380 r#type: souper_type_of(&func.dfg, val),
381 }
382 .into();
383 ast::Instruction::Lshr { a, b }.into()
384 }
385 (ir::Opcode::Select, _) => {
386 let a = arg(allocs, 0);
387
388 let a = match a {
392 ast::Operand::Value(id) => match lhs.get_value(id).r#type {
393 Some(ast::Type { width: 1 }) => a,
394 _ => lhs
395 .assignment(
396 None,
397 Some(ast::Type { width: 1 }),
398 ast::Instruction::Ne {
399 a,
400 b: ast::Constant {
401 value: 0,
402 r#type: None,
403 }
404 .into(),
405 },
406 vec![],
407 )
408 .into(),
409 },
410 ast::Operand::Constant(ast::Constant { value, .. }) => ast::Constant {
411 value: (value != 0) as _,
412 r#type: Some(ast::Type { width: 1 }),
413 }
414 .into(),
415 };
416
417 let b = arg(allocs, 1);
418 let c = arg(allocs, 2);
419 ast::Instruction::Select { a, b, c }.into()
420 }
421 (ir::Opcode::Uextend, _) => {
422 let a = arg(allocs, 0);
423 ast::Instruction::Zext { a }.into()
424 }
425 (ir::Opcode::Sextend, _) => {
426 let a = arg(allocs, 0);
427 ast::Instruction::Sext { a }.into()
428 }
429 (ir::Opcode::Trunc, _) => {
430 let a = arg(allocs, 0);
431 ast::Instruction::Trunc { a }.into()
432 }
433 (ir::Opcode::Icmp, ir::InstructionData::IntCompare { cond, .. })
434 | (ir::Opcode::IcmpImm, ir::InstructionData::IntCompare { cond, .. }) => {
435 let a = arg(allocs, 0);
436 let b = arg(allocs, 1);
437 match cond {
438 ir::condcodes::IntCC::Equal => ast::Instruction::Eq { a, b }.into(),
439 ir::condcodes::IntCC::NotEqual => ast::Instruction::Ne { a, b }.into(),
440 ir::condcodes::IntCC::UnsignedLessThan => {
441 ast::Instruction::Ult { a, b }.into()
442 }
443 ir::condcodes::IntCC::SignedLessThan => {
444 ast::Instruction::Slt { a, b }.into()
445 }
446 ir::condcodes::IntCC::UnsignedLessThanOrEqual => {
447 ast::Instruction::Sle { a, b }.into()
448 }
449 ir::condcodes::IntCC::SignedLessThanOrEqual => {
450 ast::Instruction::Sle { a, b }.into()
451 }
452 _ => ast::AssignmentRhs::Var,
453 }
454 }
455 (ir::Opcode::Popcnt, _) => {
456 let a = arg(allocs, 0);
457 ast::Instruction::Ctpop { a }.into()
458 }
459 (ir::Opcode::Bitrev, _) => {
460 let a = arg(allocs, 0);
461 ast::Instruction::BitReverse { a }.into()
462 }
463 (ir::Opcode::Clz, _) => {
464 let a = arg(allocs, 0);
465 ast::Instruction::Ctlz { a }.into()
466 }
467 (ir::Opcode::Ctz, _) => {
468 let a = arg(allocs, 0);
469 ast::Instruction::Cttz { a }.into()
470 }
471 (ir::Opcode::SaddSat, _) => {
473 let a = arg(allocs, 0);
474 let b = arg(allocs, 1);
475 ast::Instruction::SaddSat { a, b }.into()
476 }
477 (ir::Opcode::SsubSat, _) => {
478 let a = arg(allocs, 0);
479 let b = arg(allocs, 1);
480 ast::Instruction::SsubSat { a, b }.into()
481 }
482 (ir::Opcode::UsubSat, _) => {
483 let a = arg(allocs, 0);
484 let b = arg(allocs, 1);
485 ast::Instruction::UsubSat { a, b }.into()
486 }
487 (ir::Opcode::Iconst, _) => return,
494 _ => ast::AssignmentRhs::Var,
495 }
496 }
497 _ => ast::AssignmentRhs::Var,
498 };
499
500 non_var_count += match souper_assignment_rhs {
501 ast::AssignmentRhs::Var => 0,
502 _ => 1,
503 };
504 let souper_ty = souper_type_of(&func.dfg, val);
505 let souper_val = lhs.assignment(None, souper_ty, souper_assignment_rhs, vec![]);
506 let old_value = allocs.ir_to_souper_val.insert(val, souper_val);
507 assert!(old_value.is_none());
508 });
509
510 if non_var_count >= 2 {
524 let lhs = lhs.finish(allocs.ir_to_souper_val[&val], None);
525 out.send(format!(
526 ";; Harvested from `{}` in `{}`\n{}\n",
527 val, func.name, lhs
528 ))
529 .unwrap();
530 }
531}
532
533fn souper_type_of(dfg: &ir::DataFlowGraph, val: ir::Value) -> Option<ast::Type> {
534 let ty = dfg.value_type(val);
535 assert!(ty.is_int());
536 assert_eq!(ty.lane_count(), 1);
537 let width = match dfg.value_def(val).inst() {
538 Some(inst)
539 if dfg.insts[inst].opcode() == ir::Opcode::IcmpImm
540 || dfg.insts[inst].opcode() == ir::Opcode::Icmp =>
541 {
542 1
543 }
544 _ => ty.bits().try_into().unwrap(),
545 };
546 Some(ast::Type { width })
547}
548
549#[derive(Debug)]
550enum StackEntry {
551 Visit(ir::Value),
552 Trace(ir::Value),
553}
554
555fn post_order_dfs(
556 allocs: &mut Allocs,
557 dfg: &ir::DataFlowGraph,
558 val: ir::Value,
559 should_trace: impl Fn(ir::Value) -> bool,
560 mut visit: impl FnMut(&mut Allocs, ir::Value),
561) {
562 allocs.dfs_stack.push(StackEntry::Trace(val));
563
564 while let Some(entry) = allocs.dfs_stack.pop() {
565 match entry {
566 StackEntry::Visit(val) => {
567 let is_new = allocs.dfs_seen.insert(val);
568 if is_new {
569 visit(allocs, val);
570 }
571 }
572 StackEntry::Trace(val) => {
573 if allocs.dfs_seen.contains(&val) {
574 continue;
575 }
576
577 allocs.dfs_stack.push(StackEntry::Visit(val));
578 if should_trace(val) {
579 if let ir::ValueDef::Result(inst, 0) = dfg.value_def(val) {
580 let args = dfg.inst_args(inst);
581 for v in args.iter().rev().copied() {
582 allocs.dfs_stack.push(StackEntry::Trace(v));
583 }
584 }
585 }
586 }
587 }
588 }
589}