Skip to content

Commit 37b0192

Browse files
authored
perf(interpreter): improve i256 instructions (#630)
* perf(interpreter): improve i256 instructions * chore: remove unused code, address review * perf: drop zero check after dividing
1 parent 37027db commit 37b0192

File tree

2 files changed

+66
-103
lines changed

2 files changed

+66
-103
lines changed

crates/interpreter/src/instructions/bitwise.rs

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::i256::{i256_cmp, i256_sign, two_compl, Sign};
1+
use super::i256::{i256_cmp, i256_sign_compl, two_compl, Sign};
22
use crate::{
33
gas,
44
primitives::SpecId::CONSTANTINOPLE,
@@ -31,21 +31,13 @@ pub fn gt(interpreter: &mut Interpreter, _host: &mut dyn Host) {
3131
pub fn slt(interpreter: &mut Interpreter, _host: &mut dyn Host) {
3232
gas!(interpreter, gas::VERYLOW);
3333
pop_top!(interpreter, op1, op2);
34-
*op2 = if i256_cmp(op1, *op2) == Ordering::Less {
35-
U256::from(1)
36-
} else {
37-
U256::ZERO
38-
}
34+
*op2 = U256::from(i256_cmp(&op1, op2) == Ordering::Less);
3935
}
4036

4137
pub fn sgt(interpreter: &mut Interpreter, _host: &mut dyn Host) {
4238
gas!(interpreter, gas::VERYLOW);
4339
pop_top!(interpreter, op1, op2);
44-
*op2 = if i256_cmp(op1, *op2) == Ordering::Greater {
45-
U256::from(1)
46-
} else {
47-
U256::ZERO
48-
};
40+
*op2 = U256::from(i256_cmp(&op1, op2) == Ordering::Greater);
4941
}
5042

5143
pub fn eq(interpreter: &mut Interpreter, _host: &mut dyn Host) {
@@ -125,26 +117,21 @@ pub fn sar<SPEC: Spec>(interpreter: &mut Interpreter, _host: &mut dyn Host) {
125117
gas!(interpreter, gas::VERYLOW);
126118
pop_top!(interpreter, op1, op2);
127119

128-
let value_sign = i256_sign::<true>(op2);
120+
let value_sign = i256_sign_compl(op2);
129121

130122
*op2 = if *op2 == U256::ZERO || op1 >= U256::from(256) {
131123
match value_sign {
132124
// value is 0 or >=1, pushing 0
133125
Sign::Plus | Sign::Zero => U256::ZERO,
134126
// value is <0, pushing -1
135-
Sign::Minus => two_compl(U256::from(1)),
127+
Sign::Minus => U256::MAX,
136128
}
137129
} else {
130+
const ONE: U256 = U256::from_limbs([1, 0, 0, 0]);
138131
let shift = usize::try_from(op1).unwrap();
139-
140132
match value_sign {
141-
Sign::Plus | Sign::Zero => *op2 >> shift,
142-
Sign::Minus => {
143-
let shifted = ((op2.overflowing_sub(U256::from(1)).0) >> shift)
144-
.overflowing_add(U256::from(1))
145-
.0;
146-
two_compl(shifted)
147-
}
133+
Sign::Plus | Sign::Zero => op2.wrapping_shr(shift),
134+
Sign::Minus => two_compl(op2.wrapping_sub(ONE).wrapping_shr(shift).wrapping_add(ONE)),
148135
}
149136
};
150137
}
Lines changed: 58 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,17 @@
11
use crate::primitives::U256;
22
use core::cmp::Ordering;
33

4-
#[cfg(test)]
5-
use proptest_derive::Arbitrary as PropTestArbitrary;
6-
7-
#[cfg(any(test, feature = "arbitrary"))]
8-
use arbitrary::Arbitrary;
9-
10-
#[cfg_attr(test, derive(PropTestArbitrary))]
11-
#[cfg_attr(any(test, feature = "arbitrary"), derive(Arbitrary))]
12-
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
13-
pub enum Sign {
14-
Plus,
15-
Minus,
16-
Zero,
4+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
5+
#[repr(i8)]
6+
pub(super) enum Sign {
7+
// same as `cmp::Ordering`
8+
Minus = -1,
9+
Zero = 0,
10+
#[allow(dead_code)] // "constructed" with `mem::transmute` in `i256_sign` below
11+
Plus = 1,
1712
}
1813

19-
pub const _SIGN_BIT_MASK: U256 = U256::from_limbs([
20-
0xFFFFFFFFFFFFFFFF,
21-
0xFFFFFFFFFFFFFFFF,
22-
0xFFFFFFFFFFFFFFFF,
23-
0x7FFFFFFFFFFFFFFF,
24-
]);
25-
pub const MIN_NEGATIVE_VALUE: U256 = U256::from_limbs([
14+
const MIN_NEGATIVE_VALUE: U256 = U256::from_limbs([
2615
0x0000000000000000,
2716
0x0000000000000000,
2817
0x0000000000000000,
@@ -31,107 +20,99 @@ pub const MIN_NEGATIVE_VALUE: U256 = U256::from_limbs([
3120

3221
const FLIPH_BITMASK_U64: u64 = 0x7FFFFFFFFFFFFFFF;
3322

34-
#[cfg_attr(test, derive(PropTestArbitrary))]
35-
#[cfg_attr(any(test, feature = "arbitrary"), derive(Arbitrary))]
36-
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
37-
pub struct I256(pub Sign, pub U256);
38-
3923
#[inline(always)]
40-
pub fn i256_sign<const DO_TWO_COMPL: bool>(val: &mut U256) -> Sign {
41-
if !val.bit(U256::BITS - 1) {
42-
if *val == U256::ZERO {
43-
Sign::Zero
44-
} else {
45-
Sign::Plus
46-
}
47-
} else {
48-
if DO_TWO_COMPL {
49-
two_compl_mut(val);
50-
}
24+
pub(super) fn i256_sign(val: &U256) -> Sign {
25+
if val.bit(U256::BITS - 1) {
5126
Sign::Minus
27+
} else {
28+
// SAFETY: false == 0 == Zero, true == 1 == Plus
29+
unsafe { core::mem::transmute(*val != U256::ZERO) }
30+
}
31+
}
32+
33+
#[inline(always)]
34+
pub(super) fn i256_sign_compl(val: &mut U256) -> Sign {
35+
let sign = i256_sign(val);
36+
if sign == Sign::Minus {
37+
two_compl_mut(val);
5238
}
39+
sign
5340
}
5441

5542
#[inline(always)]
5643
fn u256_remove_sign(val: &mut U256) {
44+
// SAFETY: U256 does not have any padding bytes
5745
unsafe {
5846
val.as_limbs_mut()[3] &= FLIPH_BITMASK_U64;
5947
}
6048
}
6149

6250
#[inline(always)]
63-
pub fn two_compl_mut(op: &mut U256) {
51+
pub(super) fn two_compl_mut(op: &mut U256) {
6452
*op = two_compl(*op);
6553
}
6654

67-
pub fn two_compl(op: U256) -> U256 {
55+
#[inline(always)]
56+
pub(super) fn two_compl(op: U256) -> U256 {
6857
op.wrapping_neg()
6958
}
7059

7160
#[inline(always)]
72-
pub fn i256_cmp(mut first: U256, mut second: U256) -> Ordering {
73-
let first_sign = i256_sign::<false>(&mut first);
74-
let second_sign = i256_sign::<false>(&mut second);
75-
match (first_sign, second_sign) {
76-
(Sign::Zero, Sign::Zero) => Ordering::Equal,
77-
(Sign::Zero, Sign::Plus) => Ordering::Less,
78-
(Sign::Zero, Sign::Minus) => Ordering::Greater,
79-
(Sign::Minus, Sign::Zero) => Ordering::Less,
80-
(Sign::Minus, Sign::Plus) => Ordering::Less,
81-
(Sign::Minus, Sign::Minus) => first.cmp(&second),
82-
(Sign::Plus, Sign::Minus) => Ordering::Greater,
83-
(Sign::Plus, Sign::Zero) => Ordering::Greater,
84-
(Sign::Plus, Sign::Plus) => first.cmp(&second),
61+
pub(super) fn i256_cmp(first: &U256, second: &U256) -> Ordering {
62+
let first_sign = i256_sign(first);
63+
let second_sign = i256_sign(second);
64+
match first_sign.cmp(&second_sign) {
65+
// note: adding `if first_sign != Sign::Zero` to short circuit zero comparisons performs
66+
// slower on average, as of #582
67+
Ordering::Equal => first.cmp(second),
68+
o => o,
8569
}
8670
}
8771

8872
#[inline(always)]
89-
pub fn i256_div(mut first: U256, mut second: U256) -> U256 {
90-
let second_sign = i256_sign::<true>(&mut second);
73+
pub(super) fn i256_div(mut first: U256, mut second: U256) -> U256 {
74+
let second_sign = i256_sign_compl(&mut second);
9175
if second_sign == Sign::Zero {
9276
return U256::ZERO;
9377
}
94-
let first_sign = i256_sign::<true>(&mut first);
78+
79+
let first_sign = i256_sign_compl(&mut first);
9580
if first_sign == Sign::Minus && first == MIN_NEGATIVE_VALUE && second == U256::from(1) {
9681
return two_compl(MIN_NEGATIVE_VALUE);
9782
}
9883

99-
//let mut d = first / second;
100-
let mut d = first.div_rem(second).0;
84+
// necessary overflow checks are done above, perform the division
85+
let mut d = first / second;
10186

87+
// set sign bit to zero
10288
u256_remove_sign(&mut d);
103-
//set sign bit to zero
10489

105-
if d == U256::ZERO {
106-
return U256::ZERO;
107-
}
108-
109-
match (first_sign, second_sign) {
110-
(Sign::Zero, Sign::Plus)
111-
| (Sign::Plus, Sign::Zero)
112-
| (Sign::Zero, Sign::Zero)
113-
| (Sign::Plus, Sign::Plus)
114-
| (Sign::Minus, Sign::Minus) => d,
115-
(Sign::Zero, Sign::Minus)
116-
| (Sign::Plus, Sign::Minus)
117-
| (Sign::Minus, Sign::Zero)
118-
| (Sign::Minus, Sign::Plus) => two_compl(d),
90+
// two's complement only if the signs are different
91+
// note: this condition has better codegen than an exhaustive match, as of #582
92+
if (first_sign == Sign::Minus && second_sign != Sign::Minus)
93+
|| (second_sign == Sign::Minus && first_sign != Sign::Minus)
94+
{
95+
two_compl(d)
96+
} else {
97+
d
11998
}
12099
}
121100

122101
#[inline(always)]
123-
pub fn i256_mod(mut first: U256, mut second: U256) -> U256 {
124-
let first_sign = i256_sign::<true>(&mut first);
102+
pub(super) fn i256_mod(mut first: U256, mut second: U256) -> U256 {
103+
let first_sign = i256_sign_compl(&mut first);
125104
if first_sign == Sign::Zero {
126105
return U256::ZERO;
127106
}
128107

129-
let _ = i256_sign::<true>(&mut second);
108+
let _ = i256_sign_compl(&mut second);
109+
110+
// necessary overflow checks are done above, perform the operation
130111
let mut r = first % second;
112+
113+
// set sign bit to zero
131114
u256_remove_sign(&mut r);
132-
if r == U256::ZERO {
133-
return U256::ZERO;
134-
}
115+
135116
if first_sign == Sign::Minus {
136117
two_compl(r)
137118
} else {
@@ -171,9 +152,4 @@ mod tests {
171152
assert_eq!(i256_div(one_hundred, minus_one), neg_one_hundred);
172153
assert_eq!(i256_div(one_hundred, two), fifty);
173154
}
174-
175-
#[test]
176-
fn arbitrary() {
177-
proptest::proptest!(|(_value: I256)| { })
178-
}
179155
}

0 commit comments

Comments
 (0)