diff --git a/compiler/noirc_evaluator/src/ssa/interpreter/mod.rs b/compiler/noirc_evaluator/src/ssa/interpreter/mod.rs index 862d26c59c0..33fb1fe0e67 100644 --- a/compiler/noirc_evaluator/src/ssa/interpreter/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/interpreter/mod.rs @@ -1095,6 +1095,17 @@ impl<'ssa, W: Write> Interpreter<'ssa, W> { }); self.define(result, array) } + + fn interpret_binary(&self, binary: &Binary, side_effects_enabled: bool) -> IResult { + let lhs_id = binary.lhs; + let rhs_id = binary.rhs; + let lhs = self.lookup_numeric(lhs_id, "binary op lhs")?; + let rhs = self.lookup_numeric(rhs_id, "binary op rhs")?; + evaluate_binary(binary, lhs, rhs, side_effects_enabled, |binary| { + display_binary(binary, self.dfg()) + }) + .map(Value::Numeric) + } } /// Applies an infallible integer binary operation to two `NumericValue`s. @@ -1162,7 +1173,7 @@ macro_rules! apply_int_binop { /// # Returns /// A `NumericValue` containing the result of the operation, or an `Err` with the appropriate error. macro_rules! apply_int_binop_opt { - ($dfg:expr, $lhs:expr, $rhs:expr, $binary:expr, $f:expr) => {{ + ($lhs:expr, $rhs:expr, $binary:expr, $f:expr, $display_binary:expr) => {{ use value::NumericValue::*; let lhs = $lhs; @@ -1179,7 +1190,7 @@ macro_rules! apply_int_binop_opt { InterpreterError::DivisionByZero { lhs_id, lhs, rhs_id, rhs } } else { let instruction = - format!("`{}` ({operator} {lhs}, {rhs})", display_binary(binary, $dfg)); + format!("`{}` ({operator} {lhs}, {rhs})", $display_binary(binary)); InterpreterError::Overflow { operator, instruction } } }; @@ -1242,324 +1253,350 @@ macro_rules! apply_int_comparison_op { }}; } -impl Interpreter<'_, W> { - fn interpret_binary(&mut self, binary: &Binary, side_effects_enabled: bool) -> IResult { - let lhs_id = binary.lhs; - let rhs_id = binary.rhs; - let lhs = self.lookup_numeric(lhs_id, "binary op lhs")?; - let rhs = self.lookup_numeric(rhs_id, "binary op rhs")?; +fn evaluate_binary( + binary: &Binary, + lhs: NumericValue, + rhs: NumericValue, + side_effects_enabled: bool, + display_binary: impl Fn(&Binary) -> String, +) -> IResult { + let lhs_id = binary.lhs; + let rhs_id = binary.rhs; + + if lhs.get_type() != rhs.get_type() { + return Err(internal(InternalError::MismatchedTypesInBinaryOperator { + lhs_id, + lhs: lhs.to_string(), + operator: binary.operator, + rhs_id, + rhs: rhs.to_string(), + })); + } - if lhs.get_type() != rhs.get_type() - && !matches!(binary.operator, BinaryOp::Shl | BinaryOp::Shr) - { - return Err(internal(InternalError::MismatchedTypesInBinaryOperator { - lhs_id, - lhs: lhs.to_string(), - operator: binary.operator, - rhs_id, - rhs: rhs.to_string(), - })); - } + // Disable this instruction if it is side-effectual and side effects are disabled. + if !side_effects_enabled { + return Ok(NumericValue::zero(lhs.get_type())); + } - // Disable this instruction if it is side-effectual and side effects are disabled. - if !side_effects_enabled { - let zero = NumericValue::zero(lhs.get_type()); - return Ok(Value::Numeric(zero)); - } + if let (Some(lhs), Some(rhs)) = (lhs.as_field(), rhs.as_field()) { + return interpret_field_binary_op(lhs, binary.operator, rhs, lhs_id, rhs_id); + } - if let (Some(lhs), Some(rhs)) = (lhs.as_field(), rhs.as_field()) { - return self.interpret_field_binary_op(lhs, binary.operator, rhs, lhs_id, rhs_id); - } + if let (Some(lhs), Some(rhs)) = (lhs.as_bool(), rhs.as_bool()) { + return interpret_u1_binary_op(lhs, rhs, binary, &display_binary); + } - if let (Some(lhs), Some(rhs)) = (lhs.as_bool(), rhs.as_bool()) { - return self.interpret_u1_binary_op(lhs, rhs, binary); + let result = match binary.operator { + BinaryOp::Add { unchecked: false } => { + apply_int_binop_opt!( + lhs, + rhs, + binary, + num_traits::CheckedAdd::checked_add, + display_binary + ) } - - let dfg = self.dfg(); - let result = match binary.operator { - BinaryOp::Add { unchecked: false } => { - apply_int_binop_opt!(dfg, lhs, rhs, binary, num_traits::CheckedAdd::checked_add) - } - BinaryOp::Add { unchecked: true } => { - apply_int_binop!(lhs, rhs, binary, num_traits::WrappingAdd::wrapping_add) - } - BinaryOp::Sub { unchecked: false } => { - apply_int_binop_opt!(dfg, lhs, rhs, binary, num_traits::CheckedSub::checked_sub) - } - BinaryOp::Sub { unchecked: true } => { - apply_int_binop!(lhs, rhs, binary, num_traits::WrappingSub::wrapping_sub) - } - BinaryOp::Mul { unchecked: false } => { - // Only unsigned multiplication has side effects - apply_int_binop_opt!(dfg, lhs, rhs, binary, num_traits::CheckedMul::checked_mul) - } - BinaryOp::Mul { unchecked: true } => { - apply_int_binop!(lhs, rhs, binary, num_traits::WrappingMul::wrapping_mul) - } - BinaryOp::Div => { - apply_int_binop_opt!(dfg, lhs, rhs, binary, num_traits::CheckedDiv::checked_div) + BinaryOp::Add { unchecked: true } => { + apply_int_binop!(lhs, rhs, binary, num_traits::WrappingAdd::wrapping_add) + } + BinaryOp::Sub { unchecked: false } => { + apply_int_binop_opt!( + lhs, + rhs, + binary, + num_traits::CheckedSub::checked_sub, + display_binary + ) + } + BinaryOp::Sub { unchecked: true } => { + apply_int_binop!(lhs, rhs, binary, num_traits::WrappingSub::wrapping_sub) + } + BinaryOp::Mul { unchecked: false } => { + // Only unsigned multiplication has side effects + apply_int_binop_opt!( + lhs, + rhs, + binary, + num_traits::CheckedMul::checked_mul, + display_binary + ) + } + BinaryOp::Mul { unchecked: true } => { + apply_int_binop!(lhs, rhs, binary, num_traits::WrappingMul::wrapping_mul) + } + BinaryOp::Div => { + apply_int_binop_opt!( + lhs, + rhs, + binary, + num_traits::CheckedDiv::checked_div, + display_binary + ) + } + BinaryOp::Mod => match (lhs, rhs) { + (NumericValue::I8(i8::MIN), NumericValue::I8(-1)) => NumericValue::I8(0), + (NumericValue::I16(i16::MIN), NumericValue::I16(-1)) => NumericValue::I16(0), + (NumericValue::I32(i32::MIN), NumericValue::I32(-1)) => NumericValue::I32(0), + (NumericValue::I64(i64::MIN), NumericValue::I64(-1)) => NumericValue::I64(0), + _ => { + apply_int_binop_opt!( + lhs, + rhs, + binary, + num_traits::CheckedRem::checked_rem, + display_binary + ) } - BinaryOp::Mod => match (lhs, rhs) { - (NumericValue::I8(i8::MIN), NumericValue::I8(-1)) => NumericValue::I8(0), - (NumericValue::I16(i16::MIN), NumericValue::I16(-1)) => NumericValue::I16(0), - (NumericValue::I32(i32::MIN), NumericValue::I32(-1)) => NumericValue::I32(0), - (NumericValue::I64(i64::MIN), NumericValue::I64(-1)) => NumericValue::I64(0), + }, + BinaryOp::Eq => apply_int_comparison_op!(lhs, rhs, binary, |a, b| a == b), + BinaryOp::Lt => apply_int_comparison_op!(lhs, rhs, binary, |a, b| a < b), + BinaryOp::And => { + apply_int_binop!(lhs, rhs, binary, std::ops::BitAnd::bitand) + } + BinaryOp::Or => { + apply_int_binop!(lhs, rhs, binary, std::ops::BitOr::bitor) + } + BinaryOp::Xor => { + apply_int_binop!(lhs, rhs, binary, std::ops::BitXor::bitxor) + } + BinaryOp::Shl => { + use NumericValue::*; + let instruction = format!("`{}` ({lhs} << {rhs})", display_binary(binary)); + let overflow = InterpreterError::Overflow { operator: BinaryOp::Shl, instruction }; + match (lhs, rhs) { + (Field(_), _) | (_, Field(_)) => { + return Err(internal(InternalError::UnsupportedOperatorForType { + operator: "<<", + typ: "Field", + })); + } + (U1(lhs_value), U1(rhs_value)) => U1(if !rhs_value { lhs_value } else { false }), + (U8(lhs_value), U8(rhs_value)) => { + lhs_value.checked_shl(rhs_value.into()).map(U8).ok_or(overflow)? + } + (U16(lhs_value), U16(rhs_value)) => { + lhs_value.checked_shl(rhs_value.into()).map(U16).ok_or(overflow)? + } + (U32(lhs_value), U32(rhs_value)) => { + lhs_value.checked_shl(rhs_value).map(U32).ok_or(overflow)? + } + (U64(lhs_value), U64(rhs_value)) => { + let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; + lhs_value.checked_shl(rhs_value).map(U64).ok_or(overflow)? + } + (U128(lhs_value), U128(rhs_value)) => { + let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; + lhs_value.checked_shl(rhs_value).map(U128).ok_or(overflow)? + } + (I8(lhs_value), I8(rhs_value)) => { + let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; + lhs_value.checked_shl(rhs_value).map(I8).ok_or(overflow)? + } + (I16(lhs_value), I16(rhs_value)) => { + let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; + lhs_value.checked_shl(rhs_value).map(I16).ok_or(overflow)? + } + (I32(lhs_value), I32(rhs_value)) => { + let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; + lhs_value.checked_shl(rhs_value).map(I32).ok_or(overflow)? + } + (I64(lhs_value), I64(rhs_value)) => { + let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; + lhs_value.checked_shl(rhs_value).map(I64).ok_or(overflow)? + } _ => { - apply_int_binop_opt!(dfg, lhs, rhs, binary, num_traits::CheckedRem::checked_rem) + return Err(internal(InternalError::MismatchedTypesInBinaryOperator { + lhs: lhs.to_string(), + rhs: rhs.to_string(), + operator: binary.operator, + lhs_id: binary.lhs, + rhs_id: binary.rhs, + })); } - }, - BinaryOp::Eq => apply_int_comparison_op!(lhs, rhs, binary, |a, b| a == b), - BinaryOp::Lt => apply_int_comparison_op!(lhs, rhs, binary, |a, b| a < b), - BinaryOp::And => { - apply_int_binop!(lhs, rhs, binary, std::ops::BitAnd::bitand) - } - BinaryOp::Or => { - apply_int_binop!(lhs, rhs, binary, std::ops::BitOr::bitor) } - BinaryOp::Xor => { - apply_int_binop!(lhs, rhs, binary, std::ops::BitXor::bitxor) - } - BinaryOp::Shl => { - use NumericValue::*; - let instruction = - format!("`{}` ({lhs} << {rhs})", display_binary(binary, self.dfg())); - let overflow = InterpreterError::Overflow { operator: BinaryOp::Shl, instruction }; - match (lhs, rhs) { - (Field(_), _) | (_, Field(_)) => { - return Err(internal(InternalError::UnsupportedOperatorForType { - operator: "<<", - typ: "Field", - })); - } - (U1(lhs_value), U1(rhs_value)) => { - U1(if !rhs_value { lhs_value } else { false }) - } - (U8(lhs_value), U8(rhs_value)) => { - lhs_value.checked_shl(rhs_value.into()).map(U8).ok_or(overflow)? - } - (U16(lhs_value), U16(rhs_value)) => { - lhs_value.checked_shl(rhs_value.into()).map(U16).ok_or(overflow)? - } - (U32(lhs_value), U32(rhs_value)) => { - lhs_value.checked_shl(rhs_value).map(U32).ok_or(overflow)? - } - (U64(lhs_value), U64(rhs_value)) => { - let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; - lhs_value.checked_shl(rhs_value).map(U64).ok_or(overflow)? - } - (U128(lhs_value), U128(rhs_value)) => { - let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; - lhs_value.checked_shl(rhs_value).map(U128).ok_or(overflow)? - } - (I8(lhs_value), I8(rhs_value)) => { - let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; - lhs_value.checked_shl(rhs_value).map(I8).ok_or(overflow)? - } - (I16(lhs_value), I16(rhs_value)) => { - let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; - lhs_value.checked_shl(rhs_value).map(I16).ok_or(overflow)? - } - (I32(lhs_value), I32(rhs_value)) => { - let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; - lhs_value.checked_shl(rhs_value).map(I32).ok_or(overflow)? - } - (I64(lhs_value), I64(rhs_value)) => { - let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; - lhs_value.checked_shl(rhs_value).map(I64).ok_or(overflow)? - } - _ => { - return Err(internal(InternalError::MismatchedTypesInBinaryOperator { - lhs: lhs.to_string(), - rhs: rhs.to_string(), - operator: binary.operator, - lhs_id: binary.lhs, - rhs_id: binary.rhs, - })); - } + } + BinaryOp::Shr => { + let instruction = format!("`{}` ({lhs} >> {rhs})", display_binary(binary)); + let overflow = InterpreterError::Overflow { operator: BinaryOp::Shr, instruction }; + + use NumericValue::*; + match (lhs, rhs) { + (Field(_), _) | (_, Field(_)) => { + return Err(internal(InternalError::UnsupportedOperatorForType { + operator: "<<", + typ: "Field", + })); } - } - BinaryOp::Shr => { - let instruction = - format!("`{}` ({lhs} >> {rhs})", display_binary(binary, self.dfg())); - let overflow = InterpreterError::Overflow { operator: BinaryOp::Shr, instruction }; - - use NumericValue::*; - match (lhs, rhs) { - (Field(_), _) | (_, Field(_)) => { - return Err(internal(InternalError::UnsupportedOperatorForType { - operator: "<<", - typ: "Field", - })); - } - (U1(lhs_value), U1(rhs_value)) => { - U1(if !rhs_value { lhs_value } else { false }) - } - (U8(lhs_value), U8(rhs_value)) => { - lhs_value.checked_shr(rhs_value.into()).map(U8).ok_or(overflow)? - } - (U16(lhs_value), U16(rhs_value)) => { - lhs_value.checked_shr(rhs_value.into()).map(U16).ok_or(overflow)? - } - (U32(lhs_value), U32(rhs_value)) => { - lhs_value.checked_shr(rhs_value).map(U32).ok_or(overflow)? - } - (U64(lhs_value), U64(rhs_value)) => { - let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; - lhs_value.checked_shr(rhs_value).map(U64).ok_or(overflow)? - } - (U128(lhs_value), U128(rhs_value)) => { - let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; - lhs_value.checked_shr(rhs_value).map(U128).ok_or(overflow)? - } - (I8(lhs_value), I8(rhs_value)) => { - let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; - lhs_value.checked_shr(rhs_value).map(I8).ok_or(overflow)? - } - (I16(lhs_value), I16(rhs_value)) => { - let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; - lhs_value.checked_shr(rhs_value).map(I16).ok_or(overflow)? - } - (I32(lhs_value), I32(rhs_value)) => { - let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; - lhs_value.checked_shr(rhs_value).map(I32).ok_or(overflow)? - } - (I64(lhs_value), I64(rhs_value)) => { - let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; - lhs_value.checked_shr(rhs_value).map(I64).ok_or(overflow)? - } - _ => { - return Err(internal(InternalError::MismatchedTypesInBinaryOperator { - lhs: lhs.to_string(), - rhs: rhs.to_string(), - operator: binary.operator, - lhs_id: binary.lhs, - rhs_id: binary.rhs, - })); - } + (U1(lhs_value), U1(rhs_value)) => U1(if !rhs_value { lhs_value } else { false }), + (U8(lhs_value), U8(rhs_value)) => { + lhs_value.checked_shr(rhs_value.into()).map(U8).ok_or(overflow)? + } + (U16(lhs_value), U16(rhs_value)) => { + lhs_value.checked_shr(rhs_value.into()).map(U16).ok_or(overflow)? + } + (U32(lhs_value), U32(rhs_value)) => { + lhs_value.checked_shr(rhs_value).map(U32).ok_or(overflow)? + } + (U64(lhs_value), U64(rhs_value)) => { + let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; + lhs_value.checked_shr(rhs_value).map(U64).ok_or(overflow)? + } + (U128(lhs_value), U128(rhs_value)) => { + let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; + lhs_value.checked_shr(rhs_value).map(U128).ok_or(overflow)? + } + (I8(lhs_value), I8(rhs_value)) => { + let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; + lhs_value.checked_shr(rhs_value).map(I8).ok_or(overflow)? + } + (I16(lhs_value), I16(rhs_value)) => { + let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; + lhs_value.checked_shr(rhs_value).map(I16).ok_or(overflow)? + } + (I32(lhs_value), I32(rhs_value)) => { + let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; + lhs_value.checked_shr(rhs_value).map(I32).ok_or(overflow)? + } + (I64(lhs_value), I64(rhs_value)) => { + let rhs_value: u32 = rhs_value.try_into().map_err(|_| overflow.clone())?; + lhs_value.checked_shr(rhs_value).map(I64).ok_or(overflow)? + } + _ => { + return Err(internal(InternalError::MismatchedTypesInBinaryOperator { + lhs: lhs.to_string(), + rhs: rhs.to_string(), + operator: binary.operator, + lhs_id: binary.lhs, + rhs_id: binary.rhs, + })); } } - }; - Ok(Value::Numeric(result)) - } + } + }; + Ok(result) +} - fn interpret_field_binary_op( - &mut self, - lhs: FieldElement, - operator: BinaryOp, - rhs: FieldElement, - lhs_id: ValueId, - rhs_id: ValueId, - ) -> IResult { - let unsupported_operator = |operator| -> IResult { - let typ = "Field"; - Err(internal(InternalError::UnsupportedOperatorForType { operator, typ })) - }; +fn interpret_field_binary_op( + lhs: FieldElement, + operator: BinaryOp, + rhs: FieldElement, + lhs_id: ValueId, + rhs_id: ValueId, +) -> IResult { + let unsupported_operator = |operator| -> IResult { + let typ = "Field"; + Err(internal(InternalError::UnsupportedOperatorForType { operator, typ })) + }; - let result = match operator { - BinaryOp::Add { unchecked: _ } => NumericValue::Field(lhs + rhs), - BinaryOp::Sub { unchecked: _ } => NumericValue::Field(lhs - rhs), - BinaryOp::Mul { unchecked: _ } => NumericValue::Field(lhs * rhs), - BinaryOp::Div => { - if rhs.is_zero() { - let lhs = lhs.to_string(); - let rhs = rhs.to_string(); - return Err(InterpreterError::DivisionByZero { lhs_id, lhs, rhs_id, rhs }); - } - NumericValue::Field(lhs / rhs) + let result = match operator { + BinaryOp::Add { unchecked: _ } => NumericValue::Field(lhs + rhs), + BinaryOp::Sub { unchecked: _ } => NumericValue::Field(lhs - rhs), + BinaryOp::Mul { unchecked: _ } => NumericValue::Field(lhs * rhs), + BinaryOp::Div => { + if rhs.is_zero() { + let lhs = lhs.to_string(); + let rhs = rhs.to_string(); + return Err(InterpreterError::DivisionByZero { lhs_id, lhs, rhs_id, rhs }); } - BinaryOp::Mod => return unsupported_operator("%"), - BinaryOp::Eq => NumericValue::U1(lhs == rhs), - BinaryOp::Lt => NumericValue::U1(lhs < rhs), - BinaryOp::And => return unsupported_operator("&"), - BinaryOp::Or => return unsupported_operator("|"), - BinaryOp::Xor => return unsupported_operator("^"), - BinaryOp::Shl => return unsupported_operator("<<"), - BinaryOp::Shr => return unsupported_operator(">>"), - }; - Ok(Value::Numeric(result)) - } + NumericValue::Field(lhs / rhs) + } + BinaryOp::Mod => return unsupported_operator("%"), + BinaryOp::Eq => NumericValue::U1(lhs == rhs), + BinaryOp::Lt => NumericValue::U1(lhs < rhs), + BinaryOp::And => return unsupported_operator("&"), + BinaryOp::Or => return unsupported_operator("|"), + BinaryOp::Xor => return unsupported_operator("^"), + BinaryOp::Shl => return unsupported_operator("<<"), + BinaryOp::Shr => return unsupported_operator(">>"), + }; + Ok(result) +} - fn interpret_u1_binary_op(&mut self, lhs: bool, rhs: bool, binary: &Binary) -> IResult { - let overflow = || { - let instruction = format!("`{}` ({lhs} << {rhs})", display_binary(binary, self.dfg())); - let operator = binary.operator; - InterpreterError::Overflow { operator, instruction } - }; +fn interpret_u1_binary_op( + lhs: bool, + rhs: bool, + binary: &Binary, + display_binary: &impl Fn(&Binary) -> String, +) -> IResult { + let overflow = || { + let instruction = format!("`{}` ({lhs} << {rhs})", display_binary(binary)); + let operator = binary.operator; + InterpreterError::Overflow { operator, instruction } + }; - let lhs_id = binary.lhs; - let rhs_id = binary.rhs; + let lhs_id = binary.lhs; + let rhs_id = binary.rhs; - let result = match binary.operator { - BinaryOp::Add { unchecked: true } => lhs ^ rhs, - BinaryOp::Add { unchecked: false } => { - if lhs && rhs { - return Err(overflow()); - } else { - lhs ^ rhs - } - } - BinaryOp::Sub { unchecked: true } => { - // (0, 0) -> 0 - // (0, 1) -> 1 (underflow) - // (1, 0) -> 1 - // (1, 1) -> 0 + let result = match binary.operator { + BinaryOp::Add { unchecked: true } => lhs ^ rhs, + BinaryOp::Add { unchecked: false } => { + if lhs && rhs { + return Err(overflow()); + } else { lhs ^ rhs } - BinaryOp::Sub { unchecked: false } => { - if !lhs && rhs { - return Err(overflow()); - } else { - lhs ^ rhs - } + } + BinaryOp::Sub { unchecked: true } => { + // (0, 0) -> 0 + // (0, 1) -> 1 (underflow) + // (1, 0) -> 1 + // (1, 1) -> 0 + lhs ^ rhs + } + BinaryOp::Sub { unchecked: false } => { + if !lhs && rhs { + return Err(overflow()); + } else { + lhs ^ rhs } - BinaryOp::Mul { unchecked: _ } => lhs & rhs, // (*) = (&) for u1 - BinaryOp::Div => { - // (0, 0) -> (division by 0) - // (0, 1) -> 0 - // (1, 0) -> (division by 0) - // (1, 1) -> 1 - if !rhs { - let lhs = (lhs as u8).to_string(); - let rhs = (rhs as u8).to_string(); - return Err(InterpreterError::DivisionByZero { lhs_id, lhs, rhs_id, rhs }); - } + } + BinaryOp::Mul { unchecked: _ } => lhs & rhs, // (*) = (&) for u1 + BinaryOp::Div => { + // (0, 0) -> (division by 0) + // (0, 1) -> 0 + // (1, 0) -> (division by 0) + // (1, 1) -> 1 + if !rhs { + let lhs = (lhs as u8).to_string(); + let rhs = (rhs as u8).to_string(); + return Err(InterpreterError::DivisionByZero { lhs_id, lhs, rhs_id, rhs }); + } + lhs + } + BinaryOp::Mod => { + // (0, 0) -> (division by 0) + // (0, 1) -> 0 + // (1, 0) -> (division by 0) + // (1, 1) -> 0 + if !rhs { + let lhs = format!("u1 {}", lhs as u8); + let rhs = format!("u1 {}", rhs as u8); + return Err(InterpreterError::DivisionByZero { lhs_id, lhs, rhs_id, rhs }); + } + false + } + BinaryOp::Eq => lhs == rhs, + // clippy complains when you do `lhs < rhs` and recommends this instead + BinaryOp::Lt => !lhs & rhs, + BinaryOp::And => lhs & rhs, + BinaryOp::Or => lhs | rhs, + BinaryOp::Xor => lhs ^ rhs, + BinaryOp::Shl => { + if rhs { + false + } else { lhs } - BinaryOp::Mod => { - // (0, 0) -> (division by 0) - // (0, 1) -> 0 - // (1, 0) -> (division by 0) - // (1, 1) -> 0 - if !rhs { - let lhs = format!("u1 {}", lhs as u8); - let rhs = format!("u1 {}", rhs as u8); - return Err(InterpreterError::DivisionByZero { lhs_id, lhs, rhs_id, rhs }); - } + } + BinaryOp::Shr => { + if rhs { false + } else { + lhs } - BinaryOp::Eq => lhs == rhs, - // clippy complains when you do `lhs < rhs` and recommends this instead - BinaryOp::Lt => !lhs & rhs, - BinaryOp::And => lhs & rhs, - BinaryOp::Or => lhs | rhs, - BinaryOp::Xor => lhs ^ rhs, - BinaryOp::Shl => { - if rhs { - false - } else { - lhs - } - } - BinaryOp::Shr => { - if rhs { - false - } else { - lhs - } - } - }; - Ok(Value::Numeric(NumericValue::U1(result))) - } + } + }; + Ok(NumericValue::U1(result)) } fn truncate_unsigned(value: T, bit_size: u32) -> IResult @@ -1589,6 +1626,14 @@ fn internal(error: InternalError) -> InterpreterError { #[cfg(test)] mod test { + use crate::ssa::{ + interpreter::value::NumericValue, + ir::{ + instruction::{Binary, BinaryOp}, + value::ValueId, + }, + }; + #[test] fn test_truncate_unsigned() { assert_eq!(super::truncate_unsigned(57_u32, 8).unwrap(), 57); @@ -1617,4 +1662,19 @@ mod test { // underflow to i16::MAX assert_eq!(super::truncate_unsigned(i16::MIN as u32 - 1, 16).unwrap() as i16, 32767); } + + #[test] + fn test_shl() { + assert_eq!( + super::evaluate_binary( + &Binary { lhs: ValueId::new(0), rhs: ValueId::new(1), operator: BinaryOp::Shl }, + NumericValue::I8(1), + NumericValue::I8(7), + true, + |_| { String::new() } + ) + .unwrap(), + NumericValue::I8(-128) + ); + } }