1use std::fmt::{Display, Formatter};
2
3use crate::value::LocalValueId;
4
5use super::mnemonic::{Args, MnemonicKind};
6use smallvec::smallvec;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
9pub struct Binary {
10 pub op: Binop,
11 pub lhs: LocalValueId,
12 pub rhs: LocalValueId,
13}
14
15impl MnemonicKind for Binary {
16 fn opcode(&self) -> &'static str {
17 "binop"
18 }
19
20 fn args(&self) -> Args {
21 smallvec![self.lhs, self.rhs]
22 }
23}
24
25#[non_exhaustive]
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
27pub enum Binop {
28 Int(IntBinop),
29 Float(FloatBinop),
30}
31
32impl Display for Binop {
33 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34 match self {
35 Binop::Int(op) => write!(f, "{}", op),
36 Binop::Float(op) => write!(f, "{}", op),
37 }
38 }
39}
40
41impl Binop {
42 pub fn is_comparison(self) -> bool {
43 match self {
44 Binop::Int(op) => op.is_comparison(),
45 Binop::Float(op) => op.is_comparison(),
46 }
47 }
48
49 pub fn is_shift(self) -> bool {
50 matches!(
51 self,
52 Binop::Int(IntBinop::ShiftLeft | IntBinop::ShiftRight | IntBinop::SShiftRight)
53 )
54 }
55}
56
57#[non_exhaustive]
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
59pub enum IntBinop {
60 Equal,
61 NotEqual,
62 Less,
63 SLess,
64 LessEqual,
65 SLessEqual,
66 Add,
67 Sub,
68 Xor,
69 And,
70 Or,
71 ShiftLeft,
72 ShiftRight,
73 SShiftRight,
74 Mul,
75 Div,
76 Rem,
77 Sdiv,
78 Srem,
79}
80
81impl IntBinop {
82 pub fn is_comparison(self) -> bool {
83 matches!(
84 self,
85 IntBinop::Equal
86 | IntBinop::NotEqual
87 | IntBinop::Less
88 | IntBinop::SLess
89 | IntBinop::LessEqual
90 | IntBinop::SLessEqual
91 )
92 }
93
94 pub fn eval(&self, lhs: u128, rhs: u128, size: usize) -> u128 {
99 use super::bits::{mask_for_size, signed_value};
100 let mask = mask_for_size(size);
101 let a = lhs & mask;
102 let b = rhs & mask;
103 match self {
104 IntBinop::Equal => u128::from(a == b),
105 IntBinop::NotEqual => u128::from(a != b),
106 IntBinop::Less => u128::from(a < b),
107 IntBinop::SLess => u128::from(signed_value(a, size) < signed_value(b, size)),
108 IntBinop::LessEqual => u128::from(a <= b),
109 IntBinop::SLessEqual => u128::from(signed_value(a, size) <= signed_value(b, size)),
110 IntBinop::Add => a.wrapping_add(b) & mask,
111 IntBinop::Sub => a.wrapping_sub(b) & mask,
112 IntBinop::Mul => a.wrapping_mul(b) & mask,
113 IntBinop::Div => a.checked_div(b).unwrap_or(0),
114 IntBinop::Rem => a.checked_rem(b).unwrap_or(0),
115 IntBinop::Sdiv => {
116 let l = signed_value(a, size);
117 let r = signed_value(b, size);
118 if r == 0 {
119 0
120 } else {
121 l.overflowing_div(r).0 as u128 & mask
122 }
123 }
124 IntBinop::Srem => {
125 let l = signed_value(a, size);
126 let r = signed_value(b, size);
127 if r == 0 {
128 0
129 } else {
130 l.overflowing_rem(r).0 as u128 & mask
131 }
132 }
133 IntBinop::And => a & b,
134 IntBinop::Or => a | b,
135 IntBinop::Xor => a ^ b,
136 IntBinop::ShiftLeft => shift_amount(rhs, size).map_or(0, |n| (a << n) & mask),
141 IntBinop::ShiftRight => shift_amount(rhs, size).map_or(0, |n| (a >> n) & mask),
142 IntBinop::SShiftRight => {
143 let signed = signed_value(a, size);
144 match shift_amount(rhs, size) {
145 Some(n) => (signed >> n) as u128 & mask,
146 None if signed < 0 => mask,
148 None => 0,
149 }
150 }
151 }
152 }
153}
154
155fn shift_amount(rhs: u128, size: usize) -> Option<u32> {
158 let bits = u128::from(u32::try_from(size).ok()?.saturating_mul(8));
159 (rhs < bits).then_some(rhs as u32)
160}
161
162impl Display for IntBinop {
163 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
164 let s = match self {
165 IntBinop::Equal => "==",
166 IntBinop::NotEqual => "!=",
167 IntBinop::Less => "<",
168 IntBinop::SLess => "s<",
169 IntBinop::LessEqual => "<=",
170 IntBinop::SLessEqual => "s<=",
171 IntBinop::Add => "+",
172 IntBinop::Sub => "-",
173 IntBinop::Xor => "^",
174 IntBinop::And => "&",
175 IntBinop::Or => "|",
176 IntBinop::ShiftLeft => "<<",
177 IntBinop::ShiftRight => ">>",
178 IntBinop::SShiftRight => "s>>",
179 IntBinop::Mul => "*",
180 IntBinop::Div => "/",
181 IntBinop::Rem => "%",
182 IntBinop::Sdiv => "s/",
183 IntBinop::Srem => "s%",
184 };
185
186 write!(f, "{}", s)
187 }
188}
189
190#[non_exhaustive]
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
192pub enum FloatBinop {
193 Equal,
194 NotEqual,
195 Less,
196 LessEqual,
197 Add,
198 Sub,
199 Mul,
200 Div,
201}
202
203impl FloatBinop {
204 pub fn is_comparison(self) -> bool {
205 matches!(
206 self,
207 FloatBinop::Equal | FloatBinop::NotEqual | FloatBinop::Less | FloatBinop::LessEqual
208 )
209 }
210}
211
212impl Display for FloatBinop {
213 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
214 let s = match self {
215 FloatBinop::Equal => "f==",
216 FloatBinop::NotEqual => "f!=",
217 FloatBinop::Less => "f<",
218 FloatBinop::LessEqual => "f<=",
219 FloatBinop::Add => "f+",
220 FloatBinop::Sub => "f-",
221 FloatBinop::Mul => "f*",
222 FloatBinop::Div => "f/",
223 };
224
225 write!(f, "{}", s)
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use wazabin_qcode_macro::qcode;
232
233 use crate::context::Context;
234 use crate::value::insn::{Instruction, Mnemonic};
235
236 use super::*;
237
238 #[test]
239 fn test_add_display() {
240 let mut ctx = Context::new();
241
242 qcode!(
243 ctx,
244 "
245 <block>
246 local i32 V0;
247 %v0 = load(V0:4, V0);
248 %v = i32 %v0 + i32 0x2;
249 goto <0x1001>;
250 "
251 );
252
253 let v = Instruction::from_id(&ctx, v);
254
255 match v.mnemonic() {
256 Mnemonic::Binop(Binary {
257 op: Binop::Int(op), ..
258 }) => assert_eq!(*op, IntBinop::Add),
259 _ => panic!("expected i32 integer binop instruction, found {}", v),
260 }
261
262 assert_eq!(v.size(), 4);
263 assert_eq!(v.as_statement().to_string(), "i32 %v = i32 %v0 + i32 0x2;");
264 }
265
266 #[test]
267 fn test_sub_display() {
268 let mut ctx = Context::new();
269
270 qcode!(
271 ctx,
272 "
273 <block>
274 local i32 V0;
275 %v0 = load(V0:4, V0);
276 %v = i32 %v0 - i32 0x2;
277 goto <0x1001>;
278 "
279 );
280
281 let v = Instruction::from_id(&ctx, v);
282
283 match v.mnemonic() {
284 Mnemonic::Binop(Binary {
285 op: Binop::Int(op), ..
286 }) => assert_eq!(*op, IntBinop::Sub),
287 _ => panic!("expected i32 integer binop instruction, found {}", v),
288 }
289
290 assert_eq!(v.size(), 4);
291 assert_eq!(v.as_statement().to_string(), "i32 %v = i32 %v0 - i32 0x2;");
292 }
293
294 #[test]
295 fn test_mul_display() {
296 let mut ctx = Context::new();
297
298 qcode!(
299 ctx,
300 "
301 <block>
302 local i32 V0;
303 %v0 = load(V0:4, V0);
304 %v = i32 %v0 * i32 0x2;
305 goto <0x1001>;
306 "
307 );
308
309 let v = Instruction::from_id(&ctx, v);
310
311 match v.mnemonic() {
312 Mnemonic::Binop(Binary {
313 op: Binop::Int(op), ..
314 }) => assert_eq!(*op, IntBinop::Mul),
315 _ => panic!("expected i32 integer binop instruction, found {}", v),
316 }
317
318 assert_eq!(v.size(), 4);
319 assert_eq!(v.as_statement().to_string(), "i32 %v = i32 %v0 * i32 0x2;");
320 }
321
322 #[test]
323 fn test_div_display() {
324 let mut ctx = Context::new();
325
326 qcode!(
327 ctx,
328 "
329 <block>
330 local i32 V0;
331 %v0 = load(V0:4, V0);
332 %v = i32 %v0 / i32 0x2;
333 goto <0x1001>;
334 "
335 );
336
337 let v = Instruction::from_id(&ctx, v);
338
339 match v.mnemonic() {
340 Mnemonic::Binop(Binary {
341 op: Binop::Int(op), ..
342 }) => assert_eq!(*op, IntBinop::Div),
343 _ => panic!("expected i32 integer binop instruction, found {}", v),
344 }
345
346 assert_eq!(v.size(), 4);
347 assert_eq!(v.as_statement().to_string(), "i32 %v = i32 %v0 / i32 0x2;");
348 }
349
350 #[test]
351 fn test_bit_and_display() {
352 let mut ctx = Context::new();
353
354 qcode!(
355 ctx,
356 "
357 <block>
358 local i32 V0;
359 %v0 = load(V0:4, V0);
360 %v = i32 %v0 & i32 0x2;
361 goto <0x1001>;
362 "
363 );
364
365 let v = Instruction::from_id(&ctx, v);
366
367 match v.mnemonic() {
368 Mnemonic::Binop(Binary {
369 op: Binop::Int(op), ..
370 }) => assert_eq!(*op, IntBinop::And),
371 _ => panic!("expected i32 integer binop instruction, found {}", v),
372 }
373
374 assert_eq!(v.size(), 4);
375 assert_eq!(v.as_statement().to_string(), "i32 %v = i32 %v0 & i32 0x2;");
376 }
377
378 #[test]
379 fn test_bit_or_display() {
380 let mut ctx = Context::new();
381
382 qcode!(
383 ctx,
384 "
385 <block>
386 local i32 V0;
387 %v0 = load(V0:4, V0);
388 %v = i32 %v0 | i32 0x2;
389 goto <0x1001>;
390 "
391 );
392
393 let v = Instruction::from_id(&ctx, v);
394
395 match v.mnemonic() {
396 Mnemonic::Binop(Binary {
397 op: Binop::Int(op), ..
398 }) => assert_eq!(*op, IntBinop::Or),
399 _ => panic!("expected i32 integer binop instruction, found {}", v),
400 }
401
402 assert_eq!(v.size(), 4);
403 assert_eq!(v.as_statement().to_string(), "i32 %v = i32 %v0 | i32 0x2;");
404 }
405
406 #[test]
407 fn test_bit_xor_display() {
408 let mut ctx = Context::new();
409
410 qcode!(
411 ctx,
412 "
413 <block>
414 local i32 V0;
415 %v0 = load(V0:4, V0);
416 %v = i32 %v0 ^ i32 0x2;
417 goto <0x1001>;
418 "
419 );
420
421 let v = Instruction::from_id(&ctx, v);
422
423 match v.mnemonic() {
424 Mnemonic::Binop(Binary {
425 op: Binop::Int(op), ..
426 }) => assert_eq!(*op, IntBinop::Xor),
427 _ => panic!("expected i32 integer binop instruction, found {}", v),
428 }
429
430 assert_eq!(v.size(), 4);
431 assert_eq!(v.as_statement().to_string(), "i32 %v = i32 %v0 ^ i32 0x2;");
432 }
433
434 #[test]
439 fn shift_amount_is_not_masked_to_the_operand_width() {
440 let wide = 0xc000_0000_0000_0001u128;
445 assert_eq!(IntBinop::ShiftRight.eval(0x8000, wide, 2), 0);
446 assert_eq!(IntBinop::ShiftLeft.eval(0x8000, wide, 2), 0);
447 assert_eq!(IntBinop::SShiftRight.eval(0x8000, wide, 2), 0xffff);
448 assert_eq!(IntBinop::SShiftRight.eval(0x7fff, wide, 2), 0);
449
450 assert_eq!(IntBinop::ShiftRight.eval(0xffff, 16, 2), 0);
452 assert_eq!(IntBinop::ShiftLeft.eval(0xffff, 16, 2), 0);
453 assert_eq!(IntBinop::SShiftRight.eval(0x8000, 16, 2), 0xffff);
454
455 assert_eq!(IntBinop::ShiftRight.eval(0x8000, 1, 2), 0x4000);
457 assert_eq!(IntBinop::ShiftLeft.eval(0x0001, 15, 2), 0x8000);
458 assert_eq!(IntBinop::SShiftRight.eval(0x8000, 1, 2), 0xc000);
459 assert_eq!(IntBinop::ShiftRight.eval(0x8000, 0, 2), 0x8000);
460
461 assert_eq!(IntBinop::ShiftLeft.eval(1, 127, 16), 1u128 << 127);
463 assert_eq!(IntBinop::ShiftLeft.eval(1, 128, 16), 0);
464 }
465
466 #[test]
467 fn test_shl_display() {
468 let mut ctx = Context::new();
469
470 qcode!(
471 ctx,
472 "
473 <block>
474 local i32 V0;
475 %v0 = load(V0:4, V0);
476 %v = i32 %v0 << i32 0x2;
477 goto <0x1001>;
478 "
479 );
480
481 let v = Instruction::from_id(&ctx, v);
482
483 match v.mnemonic() {
484 Mnemonic::Binop(Binary {
485 op: Binop::Int(op), ..
486 }) => assert_eq!(*op, IntBinop::ShiftLeft),
487 _ => panic!("expected i32 integer binop instruction, found {}", v),
488 }
489
490 assert_eq!(v.size(), 4);
491 assert_eq!(v.as_statement().to_string(), "i32 %v = i32 %v0 << i32 0x2;");
492 }
493
494 #[test]
495 fn test_shr_display() {
496 let mut ctx = Context::new();
497
498 qcode!(
499 ctx,
500 "
501 <block>
502 local i32 V0;
503 %v0 = load(V0:4, V0);
504 %v = i32 %v0 >> i32 0x2;
505 goto <0x1001>;
506 "
507 );
508
509 let v = Instruction::from_id(&ctx, v);
510
511 match v.mnemonic() {
512 Mnemonic::Binop(Binary {
513 op: Binop::Int(op), ..
514 }) => assert_eq!(*op, IntBinop::ShiftRight),
515 _ => panic!("expected i32 integer binop instruction, found {}", v),
516 }
517
518 assert_eq!(v.size(), 4);
519 assert_eq!(v.as_statement().to_string(), "i32 %v = i32 %v0 >> i32 0x2;");
520 }
521
522 #[test]
523 fn test_eq_display() {
524 let mut ctx = Context::new();
525
526 qcode!(
527 ctx,
528 "
529 <block>
530 local i32 V0;
531 %v0 = load(V0:4, V0);
532 %v = i32 %v0 == i32 0x2;
533 goto <0x1001>;
534 "
535 );
536
537 let v = Instruction::from_id(&ctx, v);
538
539 match v.mnemonic() {
540 Mnemonic::Binop(Binary {
541 op: Binop::Int(op), ..
542 }) => assert_eq!(*op, IntBinop::Equal),
543 _ => panic!("expected i32 integer binop instruction, found {}", v),
544 }
545
546 assert_eq!(v.size(), 1);
547 assert_eq!(
548 v.as_statement().to_string(),
549 "bool %v = i32 %v0 == i32 0x2;"
550 );
551 }
552
553 #[test]
554 fn test_ne_display() {
555 let mut ctx = Context::new();
556
557 qcode!(
558 ctx,
559 "
560 <block>
561 local i32 V0;
562 %v0 = load(V0:4, V0);
563 %v = i32 %v0 != i32 0x2;
564 goto <0x1001>;
565 "
566 );
567
568 let v = Instruction::from_id(&ctx, v);
569
570 match v.mnemonic() {
571 Mnemonic::Binop(Binary {
572 op: Binop::Int(op), ..
573 }) => assert_eq!(*op, IntBinop::NotEqual),
574 _ => panic!("expected i32 integer binop instruction, found {}", v),
575 }
576
577 assert_eq!(v.size(), 1);
578 assert_eq!(
579 v.as_statement().to_string(),
580 "bool %v = i32 %v0 != i32 0x2;"
581 );
582 }
583
584 #[test]
585 fn test_lt_display() {
586 let mut ctx = Context::new();
587
588 qcode!(
589 ctx,
590 "
591 <block>
592 local i32 V0;
593 %v0 = load(V0:4, V0);
594 %v = i32 %v0 < i32 0x2;
595 goto <0x1001>;
596 "
597 );
598
599 let v = Instruction::from_id(&ctx, v);
600
601 match v.mnemonic() {
602 Mnemonic::Binop(Binary {
603 op: Binop::Int(op), ..
604 }) => assert_eq!(*op, IntBinop::Less),
605 _ => panic!("expected i32 integer binop instruction, found {}", v),
606 }
607
608 assert_eq!(v.size(), 1);
609 assert_eq!(v.as_statement().to_string(), "bool %v = i32 %v0 < i32 0x2;");
610 }
611
612 #[test]
613 fn test_le_display() {
614 let mut ctx = Context::new();
615
616 qcode!(
617 ctx,
618 "
619 <block>
620 local i32 V0;
621 %v0 = load(V0:4, V0);
622 %v = i32 %v0 <= i32 0x2;
623 goto <0x1001>;
624 "
625 );
626
627 let v = Instruction::from_id(&ctx, v);
628
629 match v.mnemonic() {
630 Mnemonic::Binop(Binary {
631 op: Binop::Int(op), ..
632 }) => assert_eq!(*op, IntBinop::LessEqual),
633 _ => panic!("expected i32 integer binop instruction, found {}", v),
634 }
635
636 assert_eq!(v.size(), 1);
637 assert_eq!(
638 v.as_statement().to_string(),
639 "bool %v = i32 %v0 <= i32 0x2;"
640 );
641 }
642
643 #[test]
644 fn test_gt_display() {
645 let mut ctx = Context::new();
646
647 qcode!(
648 ctx,
649 "
650 <block>
651 local i32 V0;
652 %v0 = load(V0:4, V0);
653 %v = i32 %v0 > i32 0x2;
654 goto <0x1001>;
655 "
656 );
657
658 let v = Instruction::from_id(&ctx, v);
659
660 match v.mnemonic() {
661 Mnemonic::Binop(Binary {
662 op: Binop::Int(op), ..
663 }) => assert_eq!(*op, IntBinop::Less),
664 _ => panic!("expected i32 integer binop instruction, found {}", v),
665 }
666
667 assert_eq!(v.size(), 1);
668 assert_eq!(v.as_statement().to_string(), "bool %v = i32 0x2 < i32 %v0;");
669 }
670
671 #[test]
672 fn test_ge_display() {
673 let mut ctx = Context::new();
674
675 qcode!(
676 ctx,
677 "
678 <block>
679 local i32 V0;
680 %v0 = load(V0:4, V0);
681 %v = i32 %v0 >= i32 0x2;
682 goto <0x1001>;
683 "
684 );
685
686 let v = Instruction::from_id(&ctx, v);
687
688 match v.mnemonic() {
689 Mnemonic::Binop(Binary {
690 op: Binop::Int(op), ..
691 }) => assert_eq!(*op, IntBinop::LessEqual),
692 _ => panic!("expected i32 integer binop instruction, found {}", v),
693 }
694
695 assert_eq!(v.size(), 1);
696 assert_eq!(
697 v.as_statement().to_string(),
698 "bool %v = i32 0x2 <= i32 %v0;"
699 );
700 }
701
702 #[test]
703 #[should_panic(expected = "qcode size mismatch")]
704 fn test_explicit_capture_size_mismatch_panics() {
705 let mut ctx = Context::new();
706
707 qcode!(
708 ctx,
709 "
710 <block>
711 local i32 V0;
712 %v0 = load(V0:4, V0);
713 %v = i16 %v0 + i16 0x2;
714 goto <0x1001>;
715 "
716 );
717 }
718}