From 2d0a0e2e817aa9101fd0cf2b8c8b7515f1f8b38c Mon Sep 17 00:00:00 2001 From: xermicus Date: Tue, 4 Jun 2024 18:45:06 +0200 Subject: [PATCH] implement BYTE Signed-off-by: xermicus --- crates/integration/contracts/Bitwise.sol | 11 +++ crates/integration/src/cases.rs | 18 +++++ crates/integration/src/tests.rs | 24 ++++++ .../llvm-context/src/polkavm/evm/bitwise.rs | 76 ++++++++++++++++--- 4 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 crates/integration/contracts/Bitwise.sol diff --git a/crates/integration/contracts/Bitwise.sol b/crates/integration/contracts/Bitwise.sol new file mode 100644 index 0000000..7cf581a --- /dev/null +++ b/crates/integration/contracts/Bitwise.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8; + +contract Bitwise { + function opByte(uint i, uint x) public payable returns (uint ret) { + assembly { + ret := byte(i, x) + } + } +} diff --git a/crates/integration/src/cases.rs b/crates/integration/src/cases.rs index 282d83e..b636767 100644 --- a/crates/integration/src/cases.rs +++ b/crates/integration/src/cases.rs @@ -154,6 +154,12 @@ sol!( } ); +sol!( + contract Bitwise { + function opByte(uint i, uint x) public payable returns (uint ret); + } +); + impl Contract { /// Execute the contract. /// @@ -508,6 +514,18 @@ impl Contract { calldata: Value::balance_ofCall::new((address,)).abi_encode(), } } + + pub fn bitwise_byte(index: U256, value: U256) -> Self { + let code = include_str!("../contracts/Bitwise.sol"); + let name = "Bitwise"; + + Self { + name, + evm_runtime: crate::compile_evm_bin_runtime(name, code), + pvm_runtime: crate::compile_blob(name, code), + calldata: Bitwise::opByteCall::new((index, value)).abi_encode(), + } + } } #[cfg(test)] diff --git a/crates/integration/src/tests.rs b/crates/integration/src/tests.rs index 0e359db..271c3cd 100644 --- a/crates/integration/src/tests.rs +++ b/crates/integration/src/tests.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use alloy_primitives::{keccak256, Address, FixedBytes, B256, I256, U256}; use alloy_sol_types::{sol, SolCall, SolValue}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; @@ -590,3 +592,25 @@ fn balance() { let received = U256::from_be_slice(&output.data); assert_eq!(expected, received) } + +#[test] +fn bitwise_byte() { + assert_success(&Contract::bitwise_byte(U256::ZERO, U256::ZERO), true); + assert_success(&Contract::bitwise_byte(U256::ZERO, U256::MAX), true); + assert_success(&Contract::bitwise_byte(U256::MAX, U256::ZERO), true); + assert_success( + &Contract::bitwise_byte(U256::from_str("18446744073709551619").unwrap(), U256::MAX), + true, + ); + + let de_bruijn_sequence = + hex::decode("4060503824160d0784426150b864361d0f88c4a27148ac5a2f198d46e391d8f4").unwrap(); + let value = U256::from_be_bytes::<32>(de_bruijn_sequence.clone().try_into().unwrap()); + + for (index, byte) in de_bruijn_sequence.iter().enumerate() { + let (_, output) = assert_success(&Contract::bitwise_byte(U256::from(index), value), true); + let expected = U256::from(*byte as i32); + let received = U256::abi_decode(&output.data, true).unwrap(); + assert_eq!(expected, received) + } +} diff --git a/crates/llvm-context/src/polkavm/evm/bitwise.rs b/crates/llvm-context/src/polkavm/evm/bitwise.rs index 3bd4243..786ae79 100644 --- a/crates/llvm-context/src/polkavm/evm/bitwise.rs +++ b/crates/llvm-context/src/polkavm/evm/bitwise.rs @@ -195,7 +195,14 @@ where context.build_load(result_pointer, "shift_right_arithmetic_result") } -/// Translates the `byte` instruction. +/// Translates the `byte` instruction, extracting the byte of `operand_2` +/// found at index `operand_1`, starting from the most significant bit. +/// +/// Builds a logical `and` with a corresponding bit mask. +/// +/// Because this opcode returns zero on overflows, the index `operand_1` +/// is checked for overflow. On overflow, the mask will be all zeros, +/// resulting in a branchless implementation. pub fn byte<'ctx, D>( context: &mut Context<'ctx, D>, operand_1: inkwell::values::IntValue<'ctx>, @@ -204,14 +211,61 @@ pub fn byte<'ctx, D>( where D: Dependency + Clone, { - Ok(context - .build_call( - context.llvm_runtime().byte, - &[ - operand_1.as_basic_value_enum(), - operand_2.as_basic_value_enum(), - ], - "byte_call", - ) - .expect("Always exists")) + const MAX_INDEX_BYTES: u64 = 31; + + let is_overflow_bit = context.builder().build_int_compare( + inkwell::IntPredicate::ULE, + operand_1, + context.word_const(MAX_INDEX_BYTES), + "is_overflow_bit", + )?; + let is_overflow_byte = context.builder().build_int_z_extend( + is_overflow_bit, + context.byte_type(), + "is_overflow_byte", + )?; + let mask_byte = context.builder().build_int_mul( + context.byte_type().const_all_ones(), + is_overflow_byte, + "mask_byte", + )?; + let mask_byte_word = + context + .builder() + .build_int_z_extend(mask_byte, context.word_type(), "mask_byte_word")?; + + let index_truncated = + context + .builder() + .build_int_truncate(operand_1, context.byte_type(), "index_truncated")?; + let index_in_bits = context.builder().build_int_mul( + index_truncated, + context + .byte_type() + .const_int(revive_common::BIT_LENGTH_BYTE as u64, false), + "index_in_bits", + )?; + let index_from_most_significant_bit = context.builder().build_int_sub( + context.byte_type().const_int( + MAX_INDEX_BYTES * revive_common::BIT_LENGTH_BYTE as u64, + false, + ), + index_in_bits, + "index_from_msb", + )?; + let index_extended = context.builder().build_int_z_extend( + index_from_most_significant_bit, + context.word_type(), + "index", + )?; + + let mask = context + .builder() + .build_left_shift(mask_byte_word, index_extended, "mask")?; + let masked_value = context.builder().build_and(operand_2, mask, "masked")?; + let byte = context + .builder() + .build_right_shift(masked_value, index_extended, false, "byte")?; + + Ok(byte.as_basic_value_enum()) }