Skip to content

Commit

Permalink
feat: 0x0A-SIGNEXTEND
Browse files Browse the repository at this point in the history
  • Loading branch information
enitrat committed Aug 17, 2023
1 parent 47264c2 commit 95416fa
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 23 deletions.
37 changes: 36 additions & 1 deletion src/instructions/stop_and_arithmetic_operations.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,43 @@ impl StopAndArithmeticOperations of StopAndArithmeticOperationsTrait {
}

/// 0x0B - SIGNEXTEND
/// SIGNEXTEND takes two inputs `b` and `x` where x: integer value to sign extend
/// and b: size in byte - 1 of the integer to sign extend and extends the length of
/// x as a two’s complement signed integer.
/// The first `i` bits of the output (numbered from the /!\LEFT/!\ counting from zero)
/// are equal to the `t`-th bit of `x`, where `t` is equal to
/// `256 - 8(b + 1)`. The remaining bits of the output are equal to the corresponding bits of `x`.
/// If b >= 32, then the output is x because t<=0.
/// To efficiently implement this algorithm we can implement it using a mask, which is all zeroes until the t-th bit included,
/// and all ones afterwards. The index of `t` when numbered from the RIGHT is s = `255 - t` = `8b + 7`; so the integer value
/// of the mask used is 2^s - 1.
/// Let v be the t-th bit of x. If v == 1, then the output should be all 1s until the t-th bit included,
/// followed by the remaining bits of x; which is corresponds to (x | !mask).
/// If v == 0, then the output should be all 0s until the t-th bit included, followed by the remaining bits of x;
/// which corresponds to (x & mask).
/// # Specification: https://www.evm.codes/#0b?fork=shanghai
/// Complex opcode, check: https://ethereum.github.io/yellowpaper/paper.pdf
fn exec_signextend(ref self: ExecutionContext) { // TODO signed integer extension algorithm
fn exec_signextend(ref self: ExecutionContext) {
let popped = self.stack.pop_n(2);
let b = *popped[0];
let x = *popped[1];

let result = if b < 32 {
let s = 8 * b + 7;
let two_pow_s = 2.pow(s);
// Get v, the t-th bit of x. To do this we bitshift x by s bits to the right and apply a mask to get the last bit.
let v = (x / two_pow_s) & 1;
// Compute the mask with 8b+7 bits set to one
let mask = two_pow_s - 1;
if v == 0 {
x & mask
} else {
x | ~mask
}
} else {
x
};

self.stack.push(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,43 @@ fn test_exec_exp_overflow() {
ctx.stack.peek().unwrap() == 0, 'stack top should be 0'
); // (2^128)^2 = 2^256 = 0 % 2^256
}

#[test]
#[available_gas(20000000)]
fn test_exec_signextend() {
// Given
let mut ctx = setup_execution_context();
ctx.stack.push(0xFF);
ctx.stack.push(0x00);

// When
ctx.exec_signextend();

// Then
assert(ctx.stack.len() == 1, 'stack should have one element');
assert(
ctx
.stack
.peek()
.unwrap() == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
'stack top should be MAX_u256 -1'
);
}

#[test]
#[available_gas(20000000)]
fn test_exec_signextend_no_effect() {
// Given
let mut ctx = setup_execution_context();
ctx.stack.push(0x7F);
ctx.stack.push(0x00);

// When
ctx.exec_signextend();

// Then
assert(ctx.stack.len() == 1, 'stack should have one element');
assert(
ctx.stack.peek().unwrap() == 0x7F, 'stack top should be 0x7F'
); // The 248-th bit of x is 0, so the output is not changed.
}
18 changes: 1 addition & 17 deletions src/tests/test_utils/test_u256_signed_math.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use kakarot::utils::u256_signed_math::{u256_not, u256_neg, u256_signed_div_rem, ALL_ONES, MAX_U256};
use kakarot::utils::u256_signed_math::{u256_neg, u256_signed_div_rem, ALL_ONES, MAX_U256};
use integer::u256_safe_div_rem;
use debug::PrintTrait;
use traits::{Into, TryInto};
Expand All @@ -7,22 +7,6 @@ use option::OptionTrait;
const MAX_SIGNED_VALUE: u256 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
const MIN_SIGNED_VALUE: u256 = 0x8000000000000000000000000000000000000000000000000000000000000000;

#[test]
#[available_gas(20000000)]
fn test_u256_not() {
let x = u256_not(1);
/// 0000_0001 turns into 1111_1110
assert(x == u256 { low: ALL_ONES - 1, high: ALL_ONES }, 'u256_not failed.');

let x = u256_not(0);
/// 0000_0000 turns into 1111_1111
assert(x == MAX_U256, 'u256_not with zero failed.');

let x = MAX_U256;
/// 1111_1111 turns into 0000_0000
assert(u256_not(x) == 0, 'u256_not with MAX_U256 failed.');
}


#[test]
#[available_gas(20000000)]
Expand Down
6 changes: 1 addition & 5 deletions src/utils/u256_signed_math.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ const ALL_ONES: u128 = 0xffffffffffffffffffffffffffffffff;
const TWO_POW_127: u128 = 0x80000000000000000000000000000000;
const MAX_U256: u256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;

// Returns the bitwise NOT of an integer.
fn u256_not(a: u256) -> u256 {
u256 { low: ALL_ONES - a.low, high: ALL_ONES - a.high }
}


// Returns the negation of an integer.
Expand All @@ -19,7 +15,7 @@ fn u256_neg(a: u256) -> u256 {
if a == 0 {
return 0;
}
u256_not(a) + 1
~a + 1
}

/// Signed integer division between two integers. Returns the quotient and the remainder.
Expand Down

0 comments on commit 95416fa

Please sign in to comment.