Skip to content

Commit 13158f3

Browse files
authored
fix: returns 0 for right shift overflow (#8189)
1 parent f4910ca commit 13158f3

16 files changed

Lines changed: 152 additions & 103 deletions

compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,28 +1616,47 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> {
16161616

16171617
self.brillig_context.codegen_branch(left_is_negative.address, |ctx, is_negative| {
16181618
if is_negative {
1619-
let one = ctx.make_constant_instruction(1_u128.into(), left.bit_size);
1620-
1621-
// computes 2^right
1622-
let two = ctx.make_constant_instruction(2_u128.into(), left.bit_size);
1623-
let two_pow = ctx.make_constant_instruction(1_u128.into(), left.bit_size);
1624-
let right_u32 = SingleAddrVariable::new(ctx.allocate_register(), 32);
1625-
ctx.cast(right_u32, right);
1626-
let pow_body = |ctx: &mut BrilligContext<_, _>, _: SingleAddrVariable| {
1627-
ctx.binary_instruction(two_pow, two, two_pow, BrilligBinaryOp::Mul);
1628-
};
1629-
ctx.codegen_for_loop(None, right_u32.address, None, pow_body);
1630-
1631-
// Right shift using division on 1-complement
1632-
ctx.binary_instruction(left, one, result, BrilligBinaryOp::Add);
1633-
ctx.convert_signed_division(result, two_pow, result);
1634-
ctx.binary_instruction(result, one, result, BrilligBinaryOp::Sub);
1635-
1636-
// Clean-up
1637-
ctx.deallocate_single_addr(one);
1638-
ctx.deallocate_single_addr(two);
1639-
ctx.deallocate_single_addr(two_pow);
1640-
ctx.deallocate_single_addr(right_u32);
1619+
// If right value is greater than the left bit size, return 0
1620+
let rhs_does_not_overflow = SingleAddrVariable::new(ctx.allocate_register(), 1);
1621+
let lhs_bit_size =
1622+
ctx.make_constant_instruction(left.bit_size.into(), right.bit_size);
1623+
ctx.binary_instruction(
1624+
right,
1625+
lhs_bit_size,
1626+
rhs_does_not_overflow,
1627+
BrilligBinaryOp::LessThan,
1628+
);
1629+
1630+
ctx.codegen_branch(rhs_does_not_overflow.address, |ctx, no_overflow| {
1631+
if no_overflow {
1632+
let one = ctx.make_constant_instruction(1_u128.into(), left.bit_size);
1633+
1634+
// computes 2^right
1635+
let two = ctx.make_constant_instruction(2_u128.into(), left.bit_size);
1636+
let two_pow = ctx.make_constant_instruction(1_u128.into(), left.bit_size);
1637+
let right_u32 = SingleAddrVariable::new(ctx.allocate_register(), 32);
1638+
ctx.cast(right_u32, right);
1639+
let pow_body = |ctx: &mut BrilligContext<_, _>, _: SingleAddrVariable| {
1640+
ctx.binary_instruction(two_pow, two, two_pow, BrilligBinaryOp::Mul);
1641+
};
1642+
ctx.codegen_for_loop(None, right_u32.address, None, pow_body);
1643+
1644+
// Right shift using division on 1-complement
1645+
ctx.binary_instruction(left, one, result, BrilligBinaryOp::Add);
1646+
ctx.convert_signed_division(result, two_pow, result);
1647+
ctx.binary_instruction(result, one, result, BrilligBinaryOp::Sub);
1648+
1649+
// Clean-up
1650+
ctx.deallocate_single_addr(one);
1651+
ctx.deallocate_single_addr(two);
1652+
ctx.deallocate_single_addr(two_pow);
1653+
ctx.deallocate_single_addr(right_u32);
1654+
} else {
1655+
ctx.const_instruction(result, 0_u128.into());
1656+
}
1657+
});
1658+
1659+
ctx.deallocate_single_addr(rhs_does_not_overflow);
16411660
} else {
16421661
ctx.binary_instruction(left, right, result, BrilligBinaryOp::Shr);
16431662
}

compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs

Lines changed: 19 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,21 @@ impl Context<'_, '_, '_> {
161161
) -> ValueId {
162162
let lhs_typ = self.context.dfg.type_of_value(lhs).unwrap_numeric();
163163
let base = self.field_constant(FieldElement::from(2_u128));
164+
let rhs_typ = self.context.dfg.type_of_value(rhs).unwrap_numeric();
165+
//Check whether rhs is less than the bit_size: if it's not then it will overflow and we will return 0 instead.
166+
let bit_size_value = self.numeric_constant(bit_size as u128, rhs_typ);
167+
let rhs_is_less_than_bit_size = self.insert_binary(rhs, BinaryOp::Lt, bit_size_value);
168+
let rhs_is_less_than_bit_size_with_rhs_typ =
169+
self.insert_cast(rhs_is_less_than_bit_size, rhs_typ);
170+
// Nullify rhs in case of overflow, to ensure that pow returns a value compatible with lhs
171+
let rhs = self.insert_binary(
172+
rhs_is_less_than_bit_size_with_rhs_typ,
173+
BinaryOp::Mul { unchecked: true },
174+
rhs,
175+
);
164176
let pow = self.pow(base, rhs);
165-
let pow = self.pow_or_max_for_bit_size(pow, rhs, bit_size, lhs_typ);
166177
let pow = self.insert_cast(pow, lhs_typ);
167-
if lhs_typ.is_unsigned() {
178+
let result = if lhs_typ.is_unsigned() {
168179
// unsigned right bit shift is just a normal division
169180
self.insert_binary(lhs, BinaryOp::Div, pow)
170181
} else {
@@ -198,53 +209,14 @@ impl Context<'_, '_, '_> {
198209
lhs_sign_as_int,
199210
);
200211
self.insert_truncate(shifted, bit_size, bit_size + 1)
201-
}
202-
}
203-
204-
/// Returns `pow` or the maximum value allowed for `typ` if 2^rhs is guaranteed to exceed that maximum.
205-
fn pow_or_max_for_bit_size(
206-
&mut self,
207-
pow: ValueId,
208-
rhs: ValueId,
209-
bit_size: u32,
210-
typ: NumericType,
211-
) -> ValueId {
212-
let max = if typ.is_unsigned() {
213-
if bit_size == 128 { u128::MAX } else { (1_u128 << bit_size) - 1 }
214-
} else {
215-
1_u128 << (bit_size - 1)
216212
};
217-
let max = self.field_constant(FieldElement::from(max));
218-
219-
// Here we check whether rhs is less than the bit_size: if it's not then it will overflow.
220-
// Then we do:
221-
//
222-
// rhs_is_less_than_bit_size = lt rhs, bit_size
223-
// rhs_is_not_less_than_bit_size = not rhs_is_less_than_bit_size
224-
// pow_when_is_less_than_bit_size = rhs_is_less_than_bit_size * pow
225-
// pow_when_is_not_less_than_bit_size = rhs_is_not_less_than_bit_size * max
226-
// pow = add pow_when_is_less_than_bit_size, pow_when_is_not_less_than_bit_size
227-
//
228-
// All operations here are unchecked because they work on field types.
229-
let rhs_typ = self.context.dfg.type_of_value(rhs).unwrap_numeric();
230-
let bit_size = self.numeric_constant(bit_size as u128, rhs_typ);
231-
let rhs_is_less_than_bit_size = self.insert_binary(rhs, BinaryOp::Lt, bit_size);
232-
let rhs_is_not_less_than_bit_size = self.insert_not(rhs_is_less_than_bit_size);
233-
let rhs_is_less_than_bit_size =
234-
self.insert_cast(rhs_is_less_than_bit_size, NumericType::NativeField);
235-
let rhs_is_not_less_than_bit_size =
236-
self.insert_cast(rhs_is_not_less_than_bit_size, NumericType::NativeField);
237-
let pow_when_is_less_than_bit_size =
238-
self.insert_binary(rhs_is_less_than_bit_size, BinaryOp::Mul { unchecked: true }, pow);
239-
let pow_when_is_not_less_than_bit_size = self.insert_binary(
240-
rhs_is_not_less_than_bit_size,
241-
BinaryOp::Mul { unchecked: true },
242-
max,
243-
);
213+
// Returns 0 in case of overflow
214+
let rhs_is_less_than_bit_size_with_lhs_typ =
215+
self.insert_cast(rhs_is_less_than_bit_size, lhs_typ);
244216
self.insert_binary(
245-
pow_when_is_less_than_bit_size,
246-
BinaryOp::Add { unchecked: true },
247-
pow_when_is_not_less_than_bit_size,
217+
rhs_is_less_than_bit_size_with_lhs_typ,
218+
BinaryOp::Mul { unchecked: true },
219+
result,
248220
)
249221
}
250222

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
x = 64
22
y = 1
33
z = "-769"
4+
u = -1

test_programs/execution_success/bit_shifts_runtime/src/main.nr

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
fn main(x: u64, y: u8, z: i16) {
1+
fn main(x: u64, y: u8, z: i16, u: i64) {
22
// runtime shifts on compile-time known values
33
assert(64 << y == 128);
44
assert(64 >> y == 32);
55
// runtime shifts on runtime values
66
assert(x << y == 128);
77
assert(x >> y == 32);
8+
// regression tests for issue #8176
9+
assert(u >> (x as u8) == 0);
10+
assert(z >> (x as u8) == 0);
811

912
// Bit-shift with signed integers
1013
let mut a: i8 = y as i8;

0 commit comments

Comments
 (0)