Skip to content

Commit 2247814

Browse files
authored
fix: right shift is not a regular division (#6400)
1 parent 13856a1 commit 2247814

7 files changed

Lines changed: 175 additions & 115 deletions

File tree

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

Lines changed: 61 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::brillig::brillig_ir::artifact::Label;
22
use crate::brillig::brillig_ir::brillig_variable::{
33
type_to_heap_value_type, BrilligArray, BrilligVariable, SingleAddrVariable,
44
};
5+
56
use crate::brillig::brillig_ir::registers::Stack;
67
use crate::brillig::brillig_ir::{
78
BrilligBinaryOp, BrilligContext, ReservedRegisters, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE,
@@ -1202,7 +1203,7 @@ impl<'block> BrilligBlock<'block> {
12021203
let brillig_binary_op = match binary.operator {
12031204
BinaryOp::Div => {
12041205
if is_signed {
1205-
self.convert_signed_division(left, right, result_variable);
1206+
self.brillig_context.convert_signed_division(left, right, result_variable);
12061207
return;
12071208
} else if is_field {
12081209
BrilligBinaryOp::FieldDiv
@@ -1234,7 +1235,14 @@ impl<'block> BrilligBlock<'block> {
12341235
BinaryOp::Or => BrilligBinaryOp::Or,
12351236
BinaryOp::Xor => BrilligBinaryOp::Xor,
12361237
BinaryOp::Shl => BrilligBinaryOp::Shl,
1237-
BinaryOp::Shr => BrilligBinaryOp::Shr,
1238+
BinaryOp::Shr => {
1239+
if is_signed {
1240+
self.convert_signed_shr(left, right, result_variable);
1241+
return;
1242+
} else {
1243+
BrilligBinaryOp::Shr
1244+
}
1245+
}
12381246
};
12391247

12401248
self.brillig_context.binary_instruction(left, right, result_variable, brillig_binary_op);
@@ -1250,98 +1258,6 @@ impl<'block> BrilligBlock<'block> {
12501258
);
12511259
}
12521260

1253-
/// Splits a two's complement signed integer in the sign bit and the absolute value.
1254-
/// For example, -6 i8 (11111010) is split to 00000110 (6, absolute value) and 1 (is_negative).
1255-
fn absolute_value(
1256-
&mut self,
1257-
num: SingleAddrVariable,
1258-
absolute_value: SingleAddrVariable,
1259-
result_is_negative: SingleAddrVariable,
1260-
) {
1261-
let max_positive = self
1262-
.brillig_context
1263-
.make_constant_instruction(((1_u128 << (num.bit_size - 1)) - 1).into(), num.bit_size);
1264-
1265-
// Compute if num is negative
1266-
self.brillig_context.binary_instruction(
1267-
max_positive,
1268-
num,
1269-
result_is_negative,
1270-
BrilligBinaryOp::LessThan,
1271-
);
1272-
1273-
// Two's complement of num
1274-
let zero = self.brillig_context.make_constant_instruction(0_usize.into(), num.bit_size);
1275-
let twos_complement =
1276-
SingleAddrVariable::new(self.brillig_context.allocate_register(), num.bit_size);
1277-
self.brillig_context.binary_instruction(zero, num, twos_complement, BrilligBinaryOp::Sub);
1278-
1279-
// absolute_value = result_is_negative ? twos_complement : num
1280-
self.brillig_context.codegen_branch(result_is_negative.address, |ctx, is_negative| {
1281-
if is_negative {
1282-
ctx.mov_instruction(absolute_value.address, twos_complement.address);
1283-
} else {
1284-
ctx.mov_instruction(absolute_value.address, num.address);
1285-
}
1286-
});
1287-
1288-
self.brillig_context.deallocate_single_addr(zero);
1289-
self.brillig_context.deallocate_single_addr(max_positive);
1290-
self.brillig_context.deallocate_single_addr(twos_complement);
1291-
}
1292-
1293-
fn convert_signed_division(
1294-
&mut self,
1295-
left: SingleAddrVariable,
1296-
right: SingleAddrVariable,
1297-
result: SingleAddrVariable,
1298-
) {
1299-
let left_is_negative = SingleAddrVariable::new(self.brillig_context.allocate_register(), 1);
1300-
let left_abs_value =
1301-
SingleAddrVariable::new(self.brillig_context.allocate_register(), left.bit_size);
1302-
1303-
let right_is_negative =
1304-
SingleAddrVariable::new(self.brillig_context.allocate_register(), 1);
1305-
let right_abs_value =
1306-
SingleAddrVariable::new(self.brillig_context.allocate_register(), right.bit_size);
1307-
1308-
let result_is_negative =
1309-
SingleAddrVariable::new(self.brillig_context.allocate_register(), 1);
1310-
1311-
// Compute both absolute values
1312-
self.absolute_value(left, left_abs_value, left_is_negative);
1313-
self.absolute_value(right, right_abs_value, right_is_negative);
1314-
1315-
// Perform the division on the absolute values
1316-
self.brillig_context.binary_instruction(
1317-
left_abs_value,
1318-
right_abs_value,
1319-
result,
1320-
BrilligBinaryOp::UnsignedDiv,
1321-
);
1322-
1323-
// Compute result sign
1324-
self.brillig_context.binary_instruction(
1325-
left_is_negative,
1326-
right_is_negative,
1327-
result_is_negative,
1328-
BrilligBinaryOp::Xor,
1329-
);
1330-
1331-
// If result has to be negative, perform two's complement
1332-
self.brillig_context.codegen_if(result_is_negative.address, |ctx| {
1333-
let zero = ctx.make_constant_instruction(0_usize.into(), result.bit_size);
1334-
ctx.binary_instruction(zero, result, result, BrilligBinaryOp::Sub);
1335-
ctx.deallocate_single_addr(zero);
1336-
});
1337-
1338-
self.brillig_context.deallocate_single_addr(left_is_negative);
1339-
self.brillig_context.deallocate_single_addr(left_abs_value);
1340-
self.brillig_context.deallocate_single_addr(right_is_negative);
1341-
self.brillig_context.deallocate_single_addr(right_abs_value);
1342-
self.brillig_context.deallocate_single_addr(result_is_negative);
1343-
}
1344-
13451261
fn convert_signed_modulo(
13461262
&mut self,
13471263
left: SingleAddrVariable,
@@ -1354,7 +1270,7 @@ impl<'block> BrilligBlock<'block> {
13541270
SingleAddrVariable::new(self.brillig_context.allocate_register(), left.bit_size);
13551271

13561272
// i = left / right
1357-
self.convert_signed_division(left, right, scratch_var_i);
1273+
self.brillig_context.convert_signed_division(left, right, scratch_var_i);
13581274

13591275
// j = i * right
13601276
self.brillig_context.binary_instruction(
@@ -1401,6 +1317,56 @@ impl<'block> BrilligBlock<'block> {
14011317
self.brillig_context.deallocate_single_addr(bias);
14021318
}
14031319

1320+
fn convert_signed_shr(
1321+
&mut self,
1322+
left: SingleAddrVariable,
1323+
right: SingleAddrVariable,
1324+
result: SingleAddrVariable,
1325+
) {
1326+
// Check if left is negative
1327+
let left_is_negative = SingleAddrVariable::new(self.brillig_context.allocate_register(), 1);
1328+
let max_positive = self
1329+
.brillig_context
1330+
.make_constant_instruction(((1_u128 << (left.bit_size - 1)) - 1).into(), left.bit_size);
1331+
self.brillig_context.binary_instruction(
1332+
max_positive,
1333+
left,
1334+
left_is_negative,
1335+
BrilligBinaryOp::LessThan,
1336+
);
1337+
1338+
self.brillig_context.codegen_branch(left_is_negative.address, |ctx, is_negative| {
1339+
if is_negative {
1340+
let one = ctx.make_constant_instruction(1_u128.into(), left.bit_size);
1341+
1342+
// computes 2^right
1343+
let two = ctx.make_constant_instruction(2_u128.into(), left.bit_size);
1344+
let two_pow = ctx.make_constant_instruction(1_u128.into(), left.bit_size);
1345+
let right_u32 = SingleAddrVariable::new(ctx.allocate_register(), 32);
1346+
ctx.cast(right_u32, right);
1347+
let pow_body = |ctx: &mut BrilligContext<_, _>, _: SingleAddrVariable| {
1348+
ctx.binary_instruction(two_pow, two, two_pow, BrilligBinaryOp::Mul);
1349+
};
1350+
ctx.codegen_for_loop(None, right_u32.address, None, pow_body);
1351+
1352+
// Right shift using division on 1-complement
1353+
ctx.binary_instruction(left, one, result, BrilligBinaryOp::Add);
1354+
ctx.convert_signed_division(result, two_pow, result);
1355+
ctx.binary_instruction(result, one, result, BrilligBinaryOp::Sub);
1356+
1357+
// Clean-up
1358+
ctx.deallocate_single_addr(one);
1359+
ctx.deallocate_single_addr(two);
1360+
ctx.deallocate_single_addr(two_pow);
1361+
ctx.deallocate_single_addr(right_u32);
1362+
} else {
1363+
ctx.binary_instruction(left, right, result, BrilligBinaryOp::Shr);
1364+
}
1365+
});
1366+
1367+
self.brillig_context.deallocate_single_addr(left_is_negative);
1368+
}
1369+
14041370
#[allow(clippy::too_many_arguments)]
14051371
fn add_overflow_check(
14061372
&mut self,

compiler/noirc_evaluator/src/brillig/brillig_ir.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod entry_point;
2525
mod instructions;
2626

2727
use artifact::Label;
28+
use brillig_variable::SingleAddrVariable;
2829
pub(crate) use instructions::BrilligBinaryOp;
2930
use registers::{RegisterAllocator, ScratchSpace};
3031

@@ -109,6 +110,87 @@ impl<F: AcirField + DebugToString> BrilligContext<F, Stack> {
109110
can_call_procedures: true,
110111
}
111112
}
113+
114+
/// Splits a two's complement signed integer in the sign bit and the absolute value.
115+
/// For example, -6 i8 (11111010) is split to 00000110 (6, absolute value) and 1 (is_negative).
116+
pub(crate) fn absolute_value(
117+
&mut self,
118+
num: SingleAddrVariable,
119+
absolute_value: SingleAddrVariable,
120+
result_is_negative: SingleAddrVariable,
121+
) {
122+
let max_positive = self
123+
.make_constant_instruction(((1_u128 << (num.bit_size - 1)) - 1).into(), num.bit_size);
124+
125+
// Compute if num is negative
126+
self.binary_instruction(max_positive, num, result_is_negative, BrilligBinaryOp::LessThan);
127+
128+
// Two's complement of num
129+
let zero = self.make_constant_instruction(0_usize.into(), num.bit_size);
130+
let twos_complement = SingleAddrVariable::new(self.allocate_register(), num.bit_size);
131+
self.binary_instruction(zero, num, twos_complement, BrilligBinaryOp::Sub);
132+
133+
// absolute_value = result_is_negative ? twos_complement : num
134+
self.codegen_branch(result_is_negative.address, |ctx, is_negative| {
135+
if is_negative {
136+
ctx.mov_instruction(absolute_value.address, twos_complement.address);
137+
} else {
138+
ctx.mov_instruction(absolute_value.address, num.address);
139+
}
140+
});
141+
142+
self.deallocate_single_addr(zero);
143+
self.deallocate_single_addr(max_positive);
144+
self.deallocate_single_addr(twos_complement);
145+
}
146+
147+
pub(crate) fn convert_signed_division(
148+
&mut self,
149+
left: SingleAddrVariable,
150+
right: SingleAddrVariable,
151+
result: SingleAddrVariable,
152+
) {
153+
let left_is_negative = SingleAddrVariable::new(self.allocate_register(), 1);
154+
let left_abs_value = SingleAddrVariable::new(self.allocate_register(), left.bit_size);
155+
156+
let right_is_negative = SingleAddrVariable::new(self.allocate_register(), 1);
157+
let right_abs_value = SingleAddrVariable::new(self.allocate_register(), right.bit_size);
158+
159+
let result_is_negative = SingleAddrVariable::new(self.allocate_register(), 1);
160+
161+
// Compute both absolute values
162+
self.absolute_value(left, left_abs_value, left_is_negative);
163+
self.absolute_value(right, right_abs_value, right_is_negative);
164+
165+
// Perform the division on the absolute values
166+
self.binary_instruction(
167+
left_abs_value,
168+
right_abs_value,
169+
result,
170+
BrilligBinaryOp::UnsignedDiv,
171+
);
172+
173+
// Compute result sign
174+
self.binary_instruction(
175+
left_is_negative,
176+
right_is_negative,
177+
result_is_negative,
178+
BrilligBinaryOp::Xor,
179+
);
180+
181+
// If result has to be negative, perform two's complement
182+
self.codegen_if(result_is_negative.address, |ctx| {
183+
let zero = ctx.make_constant_instruction(0_usize.into(), result.bit_size);
184+
ctx.binary_instruction(zero, result, result, BrilligBinaryOp::Sub);
185+
ctx.deallocate_single_addr(zero);
186+
});
187+
188+
self.deallocate_single_addr(left_is_negative);
189+
self.deallocate_single_addr(left_abs_value);
190+
self.deallocate_single_addr(right_is_negative);
191+
self.deallocate_single_addr(right_abs_value);
192+
self.deallocate_single_addr(result_is_negative);
193+
}
112194
}
113195

114196
/// Special brillig context to codegen compiler intrinsic shared procedures

compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -281,15 +281,7 @@ impl Binary {
281281
let zero = dfg.make_constant(FieldElement::zero(), operand_type);
282282
return SimplifyResult::SimplifiedTo(zero);
283283
}
284-
285-
// `two_pow_rhs` is limited to be at most `2 ^ {operand_bitsize - 1}` so it fits in `operand_type`.
286-
let two_pow_rhs = FieldElement::from(2u128).pow(&rhs_const);
287-
let two_pow_rhs = dfg.make_constant(two_pow_rhs, operand_type);
288-
return SimplifyResult::SimplifiedToInstruction(Instruction::binary(
289-
BinaryOp::Div,
290-
self.lhs,
291-
two_pow_rhs,
292-
));
284+
return SimplifyResult::None;
293285
}
294286
}
295287
};

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ impl Context<'_> {
145145
}
146146

147147
/// Insert ssa instructions which computes lhs >> rhs by doing lhs/2^rhs
148+
/// For negative signed integers, we do the division on the 1-complement representation of lhs,
149+
/// before converting back the result to the 2-complement representation.
148150
pub(crate) fn insert_shift_right(
149151
&mut self,
150152
lhs: ValueId,
@@ -153,16 +155,27 @@ impl Context<'_> {
153155
) -> ValueId {
154156
let lhs_typ = self.function.dfg.type_of_value(lhs);
155157
let base = self.field_constant(FieldElement::from(2_u128));
156-
// we can safely cast to unsigned because overflow_checks prevent bit-shift with a negative value
157-
let rhs_unsigned = self.insert_cast(rhs, Type::unsigned(bit_size));
158-
let pow = self.pow(base, rhs_unsigned);
159-
// We need at least one more bit for the case where rhs == bit_size
160-
let div_type = Type::unsigned(bit_size + 1);
161-
let casted_lhs = self.insert_cast(lhs, div_type.clone());
162-
let casted_pow = self.insert_cast(pow, div_type);
163-
let div_result = self.insert_binary(casted_lhs, BinaryOp::Div, casted_pow);
164-
// We have to cast back to the original type
165-
self.insert_cast(div_result, lhs_typ)
158+
let pow = self.pow(base, rhs);
159+
if lhs_typ.is_unsigned() {
160+
// unsigned right bit shift is just a normal division
161+
self.insert_binary(lhs, BinaryOp::Div, pow)
162+
} else {
163+
// Get the sign of the operand; positive signed operand will just do a division as well
164+
let zero = self.numeric_constant(FieldElement::zero(), Type::signed(bit_size));
165+
let lhs_sign = self.insert_binary(lhs, BinaryOp::Lt, zero);
166+
let lhs_sign_as_field = self.insert_cast(lhs_sign, Type::field());
167+
let lhs_as_field = self.insert_cast(lhs, Type::field());
168+
// For negative numbers, convert to 1-complement using wrapping addition of a + 1
169+
let one_complement = self.insert_binary(lhs_sign_as_field, BinaryOp::Add, lhs_as_field);
170+
let one_complement = self.insert_truncate(one_complement, bit_size, bit_size + 1);
171+
let one_complement = self.insert_cast(one_complement, Type::signed(bit_size));
172+
// Performs the division on the 1-complement (or the operand if positive)
173+
let shifted_complement = self.insert_binary(one_complement, BinaryOp::Div, pow);
174+
// Convert back to 2-complement representation if operand is negative
175+
let lhs_sign_as_int = self.insert_cast(lhs_sign, lhs_typ);
176+
let shifted = self.insert_binary(shifted_complement, BinaryOp::Sub, lhs_sign_as_int);
177+
self.insert_truncate(shifted, bit_size, bit_size + 1)
178+
}
166179
}
167180

168181
/// Computes lhs^rhs via square&multiply, using the bits decomposition of rhs

test_programs/execution_success/bit_shifts_comptime/src/main.nr

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ fn main(x: u64) {
1515
assert(x << 63 == 0);
1616

1717
assert_eq((1 as u64) << 32, 0x0100000000);
18+
19+
//regression for 6201
20+
let a: i16 = -769;
21+
assert_eq(a >> 3, -97);
1822
}
1923

2024
fn regression_2250() {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
x = 64
22
y = 1
3+
z = "-769"

test_programs/execution_success/bit_shifts_runtime/src/main.nr

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
fn main(x: u64, y: u8) {
1+
fn main(x: u64, y: u8, z: i16) {
22
// runtime shifts on compile-time known values
33
assert(64 << y == 128);
44
assert(64 >> y == 32);
@@ -17,4 +17,6 @@ fn main(x: u64, y: u8) {
1717
assert(a << y == -2);
1818

1919
assert(x >> (x as u8) == 0);
20+
21+
assert_eq(z >> 3, -97);
2022
}

0 commit comments

Comments
 (0)