mirror of
https://github.com/vtil-project/VTIL-Core
synced 2026-08-17 08:23:03 -04:00
updated pe parsing, split operators, update docs
This commit is contained in:
parent
81144e520a
commit
0c2e3e122e
5 changed files with 622 additions and 598 deletions
|
|
@ -1,7 +1,7 @@
|
|||
<p align="center">
|
||||
|
||||
<a href="https://www.vtil.org/">
|
||||
<img width="256" heigth="256" src="https://vtil.org/logo.png">
|
||||
<a href="https://www.vtil.cc/">
|
||||
<img width="256" heigth="256" src="https://vtil.cc/assets/media/logo.png">
|
||||
</a>
|
||||
|
||||
<h1 align="center">VTIL</h1>
|
||||
|
|
@ -39,7 +39,7 @@ This repository contains the core components of the VTIL Project used across the
|
|||
|
||||
It is currently incomplete as the initial release is not done yet, and documentation and FAQ will be within this repository and the organization website once they're done.
|
||||
|
||||
Until the initial release, you can keep up to date with the VTIL project by checking my [personal twitter account](https://twitter.com/_can1357) or the VTIL website [vtil.org](https://vtil.org/).
|
||||
Until the initial release, you can keep up to date with the VTIL project by checking my [personal twitter account](https://twitter.com/_can1357) or the VTIL website [vtil.cc](https://vtil.cc/).
|
||||
|
||||
## Building (Windows)
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,15 @@
|
|||
|
||||
namespace vtil
|
||||
{
|
||||
// Forward declarations: register_desc and register_cast are mutually
|
||||
// referencing the converting constructor in register_desc calls
|
||||
// register_cast<T>, and register_cast returns register_desc
|
||||
//
|
||||
struct register_desc;
|
||||
|
||||
template<typename T>
|
||||
struct register_cast;
|
||||
|
||||
// Flags that describe the properties of the register.
|
||||
//
|
||||
enum register_flag : uint32_t
|
||||
|
|
@ -339,9 +348,9 @@ namespace vtil
|
|||
REDUCE_TO( bit_count, ( uint64_t(architecture) << 56 ) | local_id, flags, bit_offset );
|
||||
};
|
||||
|
||||
// Should be overriden by the user to describe conversion of the
|
||||
// register type they use (e.g. x86_reg for Capstone/Keystone) into
|
||||
// VTIL register descriptors for seamless casting into vtil::operand type.
|
||||
// Primary template of register_cast triggers a static_assert for unsupported types.
|
||||
// Specialize this for your register enum (e.g. x86_reg, arm64_reg) to enable
|
||||
// seamless casting into vtil::operand.
|
||||
//
|
||||
template<typename T>
|
||||
struct register_cast
|
||||
|
|
@ -352,7 +361,10 @@ namespace vtil
|
|||
return {};
|
||||
}
|
||||
};
|
||||
template<>
|
||||
|
||||
// Specializations for known register types.
|
||||
//
|
||||
template<>
|
||||
struct register_cast<register_desc>
|
||||
{
|
||||
constexpr register_desc operator()( register_desc v ) { return v; }
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ namespace vtil
|
|||
|
||||
// Directory indices
|
||||
//
|
||||
enum directory_id
|
||||
enum class directory_id
|
||||
{
|
||||
directory_entry_export = 0, // Export Directory
|
||||
directory_entry_import = 1, // Import Directory
|
||||
|
|
@ -396,7 +396,7 @@ namespace vtil
|
|||
uint32_t num_data_directories;
|
||||
data_directories_x86_t data_directories;
|
||||
|
||||
bool has_directory( directory_id id ) const { return has_directory( &data_directories.entries[ id ] ); }
|
||||
bool has_directory( directory_id id ) const { return has_directory( &data_directories.entries[ static_cast<size_t>( id ) ] ); }
|
||||
bool has_directory( const data_directory_t* dir ) const { return &data_directories.entries[ num_data_directories ] < dir && dir->present(); }
|
||||
};
|
||||
template<bool x64>
|
||||
|
|
@ -435,8 +435,8 @@ namespace vtil
|
|||
file_header_t file_header;
|
||||
optional_header_t<x64> optional_header;
|
||||
|
||||
auto get_sections() { return ( section_header_t* ) ( ( char* ) &optional_header + file_header.size_optional_header ); }
|
||||
auto get_sections() const { return ( const section_header_t* ) ( ( char* ) &optional_header + file_header.size_optional_header ); }
|
||||
auto get_sections() { return reinterpret_cast<section_header_t*>( reinterpret_cast<char*>( &optional_header ) + file_header.size_optional_header ); }
|
||||
auto get_sections() const { return reinterpret_cast<const section_header_t*>( reinterpret_cast<const char*>( &optional_header ) + file_header.size_optional_header ); }
|
||||
auto get_section( size_t n ) { return get_sections() + n; }
|
||||
auto get_section( size_t n ) const { return get_sections() + n; }
|
||||
};
|
||||
|
|
@ -467,11 +467,11 @@ namespace vtil
|
|||
uint16_t e_res2[ 10 ];
|
||||
uint32_t e_lfanew;
|
||||
|
||||
template<bool x64> auto get_nt_headers() { return ( nt_headers_t<x64>* ) ( ( char* ) this + e_lfanew ); }
|
||||
template<bool x64> auto get_nt_headers() const { return ( const nt_headers_t<x64>* ) ( ( char* ) this + e_lfanew ); }
|
||||
template<bool x64> auto get_nt_headers() { return reinterpret_cast<nt_headers_t<x64>*>( reinterpret_cast<char*>( this ) + e_lfanew ); }
|
||||
template<bool x64> auto get_nt_headers() const { return reinterpret_cast<const nt_headers_t<x64>*>( reinterpret_cast<const char*>( this ) + e_lfanew ); }
|
||||
};
|
||||
|
||||
enum reloc_type_id
|
||||
enum class reloc_type_id : uint16_t
|
||||
{
|
||||
rel_based_absolute = 0,
|
||||
rel_based_high = 1,
|
||||
|
|
@ -494,9 +494,9 @@ namespace vtil
|
|||
uint32_t size_block;
|
||||
reloc_entry_t entries[ 1 ]; // Variable length array
|
||||
|
||||
auto get_next() { return ( reloc_block_t* ) ( ( char* ) this + this->size_block ); }
|
||||
auto get_next() const { return ( const reloc_block_t* ) ( ( char* ) this + this->size_block ); }
|
||||
size_t num_entries() const { return ( reloc_entry_t* ) get_next() - &entries[ 0 ]; }
|
||||
auto get_next() { return reinterpret_cast<reloc_block_t*>( reinterpret_cast<char*>( this ) + this->size_block ); }
|
||||
auto get_next() const { return reinterpret_cast<const reloc_block_t*>( reinterpret_cast<const char*>( this ) + this->size_block ); }
|
||||
size_t num_entries() const { return reinterpret_cast<const reloc_entry_t*>( get_next() ) - &entries[ 0 ]; }
|
||||
};
|
||||
|
||||
struct reloc_directory_t
|
||||
|
|
@ -511,18 +511,18 @@ namespace vtil
|
|||
template<typename S, typename T>
|
||||
static decltype( auto ) visit_nt( S* self, T&& fn )
|
||||
{
|
||||
auto dos_header = ( dos_header_t* ) self->cdata();
|
||||
auto dos_header = reinterpret_cast<dos_header_t*>( const_cast<void*>( self->cdata() ) );
|
||||
auto* nt_hdrs = dos_header->get_nt_headers<true>();
|
||||
|
||||
if( nt_hdrs->optional_header.magic == OPT_HDR64_MAGIC )
|
||||
return fn( carry_const( self, ( nt_headers_x64_t* ) nt_hdrs ) );
|
||||
return fn( carry_const( self, reinterpret_cast<nt_headers_x64_t*>( nt_hdrs ) ) );
|
||||
else
|
||||
return fn( carry_const( self, ( nt_headers_x86_t* ) nt_hdrs ) );
|
||||
return fn( carry_const( self, reinterpret_cast<nt_headers_x86_t*>( nt_hdrs ) ) );
|
||||
}
|
||||
|
||||
bool pe_image::is_pe64() const
|
||||
{
|
||||
auto dos_header = ( const dos_header_t* ) cdata();
|
||||
auto dos_header = reinterpret_cast<const dos_header_t*>( cdata() );
|
||||
return dos_header->get_nt_headers<true>()->optional_header.magic == OPT_HDR64_MAGIC;
|
||||
}
|
||||
uintptr_t pe_image::get_alignment_mask() const
|
||||
|
|
@ -541,7 +541,7 @@ namespace vtil
|
|||
{
|
||||
// Get the section count from file header.
|
||||
//
|
||||
auto dos_header = ( const dos_header_t* ) cdata();
|
||||
auto dos_header = reinterpret_cast<const dos_header_t*>( cdata() );
|
||||
return dos_header->get_nt_headers<true>()->file_header.num_sections;
|
||||
}
|
||||
|
||||
|
|
@ -549,7 +549,7 @@ namespace vtil
|
|||
{
|
||||
// Get the NT headers.
|
||||
//
|
||||
auto dos_header = ( const dos_header_t* ) cdata();
|
||||
auto dos_header = reinterpret_cast<const dos_header_t*>( cdata() );
|
||||
auto nt_headers = dos_header->get_nt_headers<true>();
|
||||
|
||||
// Return invalid descriptor if out-of-boundaries.
|
||||
|
|
@ -577,7 +577,7 @@ namespace vtil
|
|||
{
|
||||
// Get the NT headers.
|
||||
//
|
||||
auto dos_header = ( dos_header_t* ) cdata();
|
||||
auto dos_header = reinterpret_cast<dos_header_t*>( data() );
|
||||
auto nt_headers = dos_header->get_nt_headers<true>();
|
||||
|
||||
// Fill section descriptor and return.
|
||||
|
|
@ -659,8 +659,8 @@ namespace vtil
|
|||
// Get image boundaries and the dos header.
|
||||
//
|
||||
const void* data = cdata();
|
||||
const void* data_limit = ( char* ) cdata() + size();
|
||||
auto dos_header = ( const dos_header_t* ) cdata();
|
||||
const void* data_limit = reinterpret_cast<const char*>( cdata() ) + size();
|
||||
auto dos_header = reinterpret_cast<const dos_header_t*>( cdata() );
|
||||
|
||||
// Validate DOS header.
|
||||
//
|
||||
|
|
@ -669,7 +669,7 @@ namespace vtil
|
|||
|
||||
// Validate image size.
|
||||
//
|
||||
if ( ( ( const char* ) data + dos_header->e_lfanew + std::min( sizeof( nt_headers_x64_t ), sizeof( nt_headers_x86_t ) ) ) > data_limit )
|
||||
if ( ( reinterpret_cast<const char*>( data ) + dos_header->e_lfanew + std::min( sizeof( nt_headers_x64_t ), sizeof( nt_headers_x86_t ) ) ) > data_limit )
|
||||
return false;
|
||||
|
||||
// Validate NT Magic.
|
||||
|
|
@ -717,7 +717,7 @@ namespace vtil
|
|||
|
||||
// Append a section and write the characteristics.
|
||||
//
|
||||
auto nt_hdrs = ( ( dos_header_t* ) this->data() )->get_nt_headers<true>();
|
||||
auto nt_hdrs = reinterpret_cast<dos_header_t*>( this->data() )->get_nt_headers<true>();
|
||||
size_t index = nt_hdrs->file_header.num_sections++;
|
||||
auto scn = nt_hdrs->get_section( index );
|
||||
memset( scn, 0, sizeof( section_header_t ) );
|
||||
|
|
@ -740,7 +740,7 @@ namespace vtil
|
|||
// Get block boundaries
|
||||
//
|
||||
const auto* block_begin = &rva_to_ptr<reloc_directory_t>( reloc_dir.rva )->first_block;
|
||||
const auto* block_end = ( const reloc_block_t* ) ( ( char* ) block_begin + reloc_dir.size );
|
||||
const auto* block_end = reinterpret_cast<const reloc_block_t*>( reinterpret_cast<const char*>( block_begin ) + reloc_dir.size );
|
||||
|
||||
// For each block:
|
||||
//
|
||||
|
|
@ -756,25 +756,25 @@ namespace vtil
|
|||
.rva = uint64_t( block->base_rva ) + block->entries[ i ].offset
|
||||
};
|
||||
|
||||
switch ( block->entries[ i ].type )
|
||||
switch ( static_cast<reloc_type_id>( block->entries[ i ].type ) )
|
||||
{
|
||||
case rel_based_dir64:
|
||||
entry.length = 8;
|
||||
entry.relocator = [ ] ( void* data, int64_t delta ) { *( ( uint64_t* ) data ) += delta; };
|
||||
case reloc_type_id::rel_based_dir64:
|
||||
entry.length = 8;
|
||||
entry.relocator = [ ] ( void* data, int64_t delta ) { *static_cast<uint64_t*>( data ) += delta; };
|
||||
break;
|
||||
case rel_based_high_low:
|
||||
case reloc_type_id::rel_based_high_low:
|
||||
entry.length = 4;
|
||||
entry.relocator = [ ] ( void* data, int64_t delta ) { *( ( int32_t* ) data ) += math::narrow_cast<int32_t>( delta ); };
|
||||
entry.relocator = [ ] ( void* data, int64_t delta ) { *static_cast<int32_t*>( data ) += math::narrow_cast<int32_t>( delta ); };
|
||||
break;
|
||||
case rel_based_low:
|
||||
case reloc_type_id::rel_based_low:
|
||||
entry.length = 2;
|
||||
entry.relocator = [ ] ( void* data, int64_t delta ) { *( ( int16_t* ) data ) += ( int16_t ) ( ( uint16_t ) delta ); };
|
||||
entry.relocator = [ ] ( void* data, int64_t delta ) { *static_cast<int16_t*>( data ) += static_cast<int16_t>( static_cast<uint16_t>( delta ) ); };
|
||||
break;
|
||||
case rel_based_high:
|
||||
case reloc_type_id::rel_based_high:
|
||||
entry.length = 2;
|
||||
entry.relocator = [ ] ( void* data, int64_t delta ) { *( ( int16_t* ) data ) += ( int16_t ) ( ( ( uint32_t ) delta ) >> 16 ); };
|
||||
entry.relocator = [ ] ( void* data, int64_t delta ) { *static_cast<int16_t*>( data ) += static_cast<int16_t>( static_cast<uint32_t>( delta ) >> 16 ); };
|
||||
break;
|
||||
case rel_based_absolute:
|
||||
case reloc_type_id::rel_based_absolute:
|
||||
entry.length = 0;
|
||||
entry.relocator = [ ] ( void* data, int64_t delta ) { /*nop*/ };
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -392,562 +392,8 @@ namespace vtil::math
|
|||
return { result & fill( bcnt_res ), bcnt_res };
|
||||
}
|
||||
|
||||
// Applies the specified operator [op] on left hand side [lhs] and right hand side [rhs] where
|
||||
// input and output values are expressed in the format of bit-vectors with optional unknowns,
|
||||
// and no size constraints.
|
||||
//
|
||||
static constexpr bit_vector evaluate_partial( operator_id op, const bit_vector& lhs, const bit_vector& rhs )
|
||||
{
|
||||
// If invalid operation, return invalid.
|
||||
//
|
||||
auto& desc = descriptor_of( op );
|
||||
|
||||
bool known = false;
|
||||
switch ( desc.operand_count )
|
||||
{
|
||||
case 1:
|
||||
if ( rhs.is_valid() )
|
||||
{
|
||||
known = rhs.is_known();
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
if ( rhs.is_valid() && lhs.is_valid() )
|
||||
{
|
||||
known = lhs.is_known() && rhs.is_known();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
// If no unknown bits, redirect to more efficient evaluate().
|
||||
//
|
||||
if ( known )
|
||||
{
|
||||
auto [val, size] = evaluate( op, lhs.size(), lhs.known_one(), rhs.size(), rhs.known_one() );
|
||||
return { val, size };
|
||||
}
|
||||
|
||||
switch ( op )
|
||||
{
|
||||
//
|
||||
// Basic bitwise operators.
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::bitwise_not:
|
||||
// Unknown mask does not change, known bits are flipped.
|
||||
//
|
||||
return bit_vector{ ~rhs.known_one(), rhs.unknown_mask(), rhs.size() };
|
||||
|
||||
case operator_id::bitwise_and:
|
||||
// Bitwise AND known bits, unknown mask is unset if one side had a known zero.
|
||||
//
|
||||
return bit_vector
|
||||
{
|
||||
lhs.known_one() & rhs.known_one(),
|
||||
( lhs.unknown_mask() | rhs.unknown_mask() ) & ~( lhs.known_zero() | rhs.known_zero() ),
|
||||
std::min( lhs.size(), rhs.size() )
|
||||
}.resize( std::max( lhs.size(), rhs.size() ) );
|
||||
|
||||
case operator_id::bitwise_or:
|
||||
// Bitwise OR known bits, unknown mask is unset if one side had a known one.
|
||||
//
|
||||
return bit_vector
|
||||
{
|
||||
lhs.known_one() | rhs.known_one(),
|
||||
( lhs.unknown_mask() | rhs.unknown_mask() ) & ~( lhs.known_one() | rhs.known_one() ),
|
||||
std::max( lhs.size(), rhs.size() )
|
||||
};
|
||||
|
||||
case operator_id::bitwise_xor:
|
||||
// Bitwise XOR known bits, unknown mask is merged.
|
||||
//
|
||||
return bit_vector
|
||||
{
|
||||
lhs.known_one() ^ rhs.known_one(),
|
||||
lhs.unknown_mask() | rhs.unknown_mask(),
|
||||
std::max( lhs.size(), rhs.size() )
|
||||
};
|
||||
|
||||
//
|
||||
// Rotations and shifts.
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::shift_right:
|
||||
// If shift count is known:
|
||||
//
|
||||
if ( auto n = rhs.get() )
|
||||
{
|
||||
// If shifting more bits than we have, return 0.
|
||||
//
|
||||
uint64_t shr_count = n.value();
|
||||
if ( shr_count >= lhs.size() ) return bit_vector( 0, lhs.size() );
|
||||
|
||||
// Return shifted masks, vector will normalize rest.
|
||||
//
|
||||
return { lhs.known_one() >> shr_count, lhs.unknown_mask() >> shr_count, lhs.size() };
|
||||
}
|
||||
// If shift count is unknown, return unknown bit-vector or 0 if input was only consisting of zeros.
|
||||
//
|
||||
return lhs.all_zero() ? lhs : bit_vector( lhs.size() );
|
||||
|
||||
case operator_id::shift_left:
|
||||
// If shift count is known:
|
||||
//
|
||||
if ( auto n = rhs.get() )
|
||||
{
|
||||
// If shifting more bits than we have, return 0.
|
||||
//
|
||||
uint64_t shl_count = n.value();
|
||||
if ( shl_count >= lhs.size() ) return bit_vector( 0, lhs.size() );
|
||||
|
||||
// Return shifted masks, vector will normalize rest.
|
||||
//
|
||||
return { lhs.known_one() << shl_count, lhs.unknown_mask() << shl_count, lhs.size() };
|
||||
}
|
||||
// If shift count is unknown, return unknown bit-vector or 0 if input was only consisting of zeros.
|
||||
//
|
||||
return lhs.all_zero() ? lhs : bit_vector( lhs.size() );
|
||||
|
||||
case operator_id::rotate_right:
|
||||
// If rotation count is known, return rotated masks, vector will normalize rest.
|
||||
//
|
||||
if ( auto n = rhs.get() )
|
||||
{
|
||||
uint64_t shr_count = n.value() % lhs.size();
|
||||
uint64_t shl_count = lhs.size() - shr_count;
|
||||
return
|
||||
{
|
||||
( lhs.known_one() >> shr_count ) | ( lhs.known_one() << shl_count ),
|
||||
( lhs.unknown_mask() >> shr_count ) | ( lhs.unknown_mask() << shl_count ),
|
||||
lhs.size()
|
||||
};
|
||||
}
|
||||
// If rotation count is unknown, return unknown bit-vector or 0/1 if input was only consisting of the same bit state.
|
||||
//
|
||||
return ( lhs.all_one() || lhs.all_zero() ) ? lhs : bit_vector( lhs.size() );
|
||||
|
||||
case operator_id::rotate_left:
|
||||
// If rotation count is known, return rotated masks, vector will normalize rest.
|
||||
//
|
||||
if ( auto n = rhs.get() )
|
||||
{
|
||||
uint64_t shl_count = n.value() % lhs.size();
|
||||
uint64_t shr_count = lhs.size() - shl_count;
|
||||
return
|
||||
{
|
||||
( lhs.known_one() >> shr_count ) | ( lhs.known_one() << shl_count ),
|
||||
( lhs.unknown_mask() >> shr_count ) | ( lhs.unknown_mask() << shl_count ),
|
||||
lhs.size()
|
||||
};
|
||||
}
|
||||
// If rotation count is unknown, return unknown bit-vector or 0/1 if input was only consisting of the same bit state.
|
||||
//
|
||||
return ( lhs.all_one() || lhs.all_zero() ) ? lhs : bit_vector( lhs.size() );
|
||||
|
||||
//
|
||||
// Arithmetic operators:
|
||||
// - TODO: Re-implement *fixed* O(1) solution for ADD SUB and NEG.
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::add:
|
||||
{
|
||||
bitcnt_t out_size = std::max( lhs.size(), rhs.size() );
|
||||
|
||||
// Return unknown if no bits are known from one side.
|
||||
//
|
||||
if( lhs.unknown_mask() == lhs.value_mask() ||
|
||||
rhs.unknown_mask() == rhs.value_mask() )
|
||||
return bit_vector( out_size );
|
||||
|
||||
// Create the temp holding the new bit vector.
|
||||
//
|
||||
uint64_t known_mask = 0;
|
||||
uint64_t unknown_mask = 0;
|
||||
|
||||
// For each bit in the output size:
|
||||
//
|
||||
bit_vector lhs_sx = bit_vector{ lhs }.resize( out_size, true );
|
||||
bit_vector rhs_sx = bit_vector{ rhs }.resize( out_size, true );
|
||||
bit_state carry = bit_state::zero;
|
||||
for ( int i = 0; i < out_size; i++ )
|
||||
{
|
||||
// Get current bits and choose the branch depending on the type:
|
||||
//
|
||||
bit_state a = lhs_sx[ i ];
|
||||
bit_state b = rhs_sx[ i ];
|
||||
if ( const int unk_count = ( a == bit_state::unknown ) + ( b == bit_state::unknown ) + ( carry == bit_state::unknown ) )
|
||||
{
|
||||
const int one_count = ( a == bit_state::one ) + ( b == bit_state::one ) + ( carry == bit_state::one );
|
||||
const int zero_count = 3 - one_count - unk_count;
|
||||
|
||||
// Carry is one if 2 elements are 1, zero if 2 elements are zero
|
||||
// and unknown otherise.
|
||||
//
|
||||
if ( one_count == 2 ) carry = bit_state::one;
|
||||
else if ( zero_count == 2 ) carry = bit_state::zero;
|
||||
else carry = bit_state::unknown;
|
||||
|
||||
// Output is always unknown.
|
||||
//
|
||||
unknown_mask |= 1ull << i;
|
||||
}
|
||||
else if ( a == b )
|
||||
{
|
||||
// Duplicated element propagates as carry, output is current carry.
|
||||
//
|
||||
known_mask |= uint64_t( carry == bit_state::one ) << i;
|
||||
carry = a;
|
||||
}
|
||||
else if ( a != b )
|
||||
{
|
||||
// Carry propagates as is, output is inverse of current carry.
|
||||
//
|
||||
known_mask |= uint64_t( carry == bit_state::zero ) << i;
|
||||
}
|
||||
}
|
||||
return bit_vector( known_mask, unknown_mask, out_size );
|
||||
|
||||
/*a = ( lhs.unknown_mask() | lhs.known_one() ) + ( rhs.unknown_mask() | rhs.known_one() );
|
||||
b = ( lhs.known_one() ) + ( rhs.known_one() );
|
||||
|
||||
return
|
||||
{
|
||||
a & b,
|
||||
~( a & b ) & ~( ~a & ~b ),
|
||||
std::max( lhs.size(), rhs.size() )
|
||||
};
|
||||
break;*/
|
||||
}
|
||||
|
||||
case operator_id::negate:
|
||||
// -A = 0-A
|
||||
//
|
||||
return evaluate_partial( operator_id::subtract, { 0, rhs.size() }, rhs );
|
||||
|
||||
/*a = mask( rhs.size() ) & -__sx64( ( rhs.unknown_mask() | rhs.known_one() ), rhs.size() );
|
||||
b = mask( rhs.size() ) & -__sx64( ( rhs.known_one() ), rhs.size() );
|
||||
|
||||
return
|
||||
{
|
||||
a & b,
|
||||
~( a & b ) & ~( ~a & ~b ),
|
||||
rhs.size()
|
||||
};
|
||||
break;*/
|
||||
case operator_id::subtract:
|
||||
// A-B = ~(~A+B)
|
||||
//
|
||||
return evaluate_partial( operator_id::bitwise_not, {},
|
||||
evaluate_partial( operator_id::add,
|
||||
evaluate_partial( operator_id::bitwise_not, {}, lhs ),
|
||||
rhs ) );
|
||||
|
||||
/*a = ( lhs.unknown_mask() | lhs.known_one() ) - ( rhs.known_one() );
|
||||
b = ( lhs.known_one() ) - ( rhs.unknown_mask() | rhs.known_one() );
|
||||
|
||||
return
|
||||
{
|
||||
a & b,
|
||||
~( a & b ) & ~( ~a & ~b ),
|
||||
std::max( lhs.size(), rhs.size() )
|
||||
};
|
||||
break;*/
|
||||
|
||||
//
|
||||
// Bitwise specials.
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::ucast:
|
||||
// Get new size from RHS as constant, and resize LHS to be of size [RHS] with zero extension if relevant.
|
||||
//
|
||||
if ( auto new_size = rhs.get() ) return bit_vector( lhs ).resize( narrow_cast<bitcnt_t>( *new_size ), false );
|
||||
else unreachable();
|
||||
|
||||
case operator_id::cast:
|
||||
// Get new size from RHS as constant, and resize LHS to be of size [RHS] with sign extension if relevant.
|
||||
//
|
||||
if ( auto new_size = rhs.get() ) return bit_vector( lhs ).resize( narrow_cast<bitcnt_t>( *new_size ), true );
|
||||
else unreachable();
|
||||
|
||||
case operator_id::popcnt:
|
||||
// Cannot be calculated with unknown values, return unknown of expected size.
|
||||
//
|
||||
return bit_vector( popcnt( rhs.known_one() | rhs.unknown_mask() ) ).resize( bit_index_size );
|
||||
|
||||
case operator_id::bitscan_fwd:
|
||||
case operator_id::bitscan_rev:
|
||||
// Cannot be calculated with unknown values, return unknown of expected size.
|
||||
//
|
||||
return bit_vector( bit_index_size );
|
||||
|
||||
case operator_id::bit_test:
|
||||
// If we can get the index being tested as constant, try to evaluate.
|
||||
//
|
||||
if ( auto index = rhs.get() )
|
||||
{
|
||||
return
|
||||
{
|
||||
( lhs.known_one() >> rhs.known_one() ) & 1,
|
||||
( lhs.unknown_mask() >> rhs.known_one() ) & 1,
|
||||
1
|
||||
};
|
||||
}
|
||||
// Otherwise, return unknown of one bit.
|
||||
//
|
||||
return bit_vector( 1 );
|
||||
|
||||
case operator_id::mask:
|
||||
// Return the mask of the vector as is.
|
||||
//
|
||||
return bit_vector( rhs.value_mask(), rhs.size() );
|
||||
|
||||
case operator_id::bit_count:
|
||||
// Return the number of bits in the vector as is.
|
||||
//
|
||||
return bit_vector( rhs.size(), bit_index_size );
|
||||
|
||||
case operator_id::value_if:
|
||||
// Try to evaluate the (x&1)?y:0 statement.
|
||||
//
|
||||
if ( lhs.known_one() & 1 ) return rhs;
|
||||
else if ( lhs.unknown_mask() & 1 ) return bit_vector{ rhs.size() };
|
||||
else return bit_vector{ 0, rhs.size() };
|
||||
|
||||
|
||||
//
|
||||
// Complex arithmetic operators.
|
||||
// - TODO: Whole thing :)
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::multiply_high:
|
||||
return bit_vector(std::max(rhs.size(), lhs.size()));
|
||||
case operator_id::multiply:
|
||||
// result of imul and mul are same at low operand size bits.
|
||||
return evaluate_partial(operator_id::umultiply, lhs, rhs);
|
||||
case operator_id::divide:
|
||||
case operator_id::remainder:
|
||||
case operator_id::umultiply_high:
|
||||
return bit_vector(std::max(rhs.size(), lhs.size()));
|
||||
case operator_id::umultiply:
|
||||
{
|
||||
bitcnt_t out_size = std::max(lhs.size(), rhs.size());
|
||||
|
||||
bit_vector lhs_sx = bit_vector{ lhs }.resize(out_size, true);
|
||||
bit_vector rhs_sx = bit_vector{ rhs }.resize(out_size, true);
|
||||
bit_vector result = bit_vector(0, out_size);
|
||||
for (int i = 0; i < rhs.size(); i++)
|
||||
{
|
||||
bit_state b = rhs_sx[i];
|
||||
if (b == bit_state::unknown)
|
||||
{
|
||||
result = evaluate_partial(operator_id::add,
|
||||
evaluate_partial(operator_id::shift_left,
|
||||
bit_vector(out_size),
|
||||
bit_vector(i, out_size))
|
||||
, result);
|
||||
}
|
||||
else if (b == bit_state::one)
|
||||
{
|
||||
result = evaluate_partial(operator_id::add,
|
||||
evaluate_partial(operator_id::shift_left,
|
||||
lhs_sx,
|
||||
bit_vector(i, out_size))
|
||||
, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case operator_id::udivide:
|
||||
case operator_id::uremainder:
|
||||
return bit_vector( std::max( rhs.size(), lhs.size() ) );
|
||||
|
||||
|
||||
//
|
||||
// MinMax operators:
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::min_value:
|
||||
case operator_id::max_value:
|
||||
case operator_id::umin_value:
|
||||
case operator_id::umax_value:
|
||||
{
|
||||
// Map each min-max to a comperator.
|
||||
//
|
||||
operator_id cmp_id;
|
||||
switch ( op )
|
||||
{
|
||||
case operator_id::umin_value: cmp_id = operator_id::uless; break;
|
||||
case operator_id::umax_value: cmp_id = operator_id::ugreater_eq; break;
|
||||
case operator_id::min_value: cmp_id = operator_id::less; break;
|
||||
case operator_id::max_value: cmp_id = operator_id::greater_eq; break;
|
||||
default: unreachable();
|
||||
}
|
||||
|
||||
// cmp<>(A,B) ? A : B
|
||||
bit_state cmp_res = evaluate_partial( cmp_id, lhs, rhs )[ 0 ];
|
||||
bitcnt_t cmp_out_size = std::max( lhs.size(), rhs.size() );
|
||||
switch ( cmp_res )
|
||||
{
|
||||
case bit_state::one: return bit_vector{ lhs }.resize( cmp_out_size );
|
||||
case bit_state::zero: return bit_vector{ rhs }.resize( cmp_out_size );
|
||||
case bit_state::unknown: return bit_vector{ cmp_out_size };
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Signed comparisons:
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::greater:
|
||||
case operator_id::greater_eq:
|
||||
case operator_id::less_eq:
|
||||
case operator_id::less:
|
||||
{
|
||||
// Fail if sign bits are not known
|
||||
//
|
||||
bit_state rhs_sign = rhs[ rhs.size() - 1 ];
|
||||
if ( rhs_sign == bit_state::unknown ) return bit_vector( 1 );
|
||||
bit_state lhs_sign = lhs[ lhs.size() - 1 ];
|
||||
if ( lhs_sign == bit_state::unknown ) return bit_vector( 1 );
|
||||
|
||||
// If LHS is negative and RHS is positive, <, <= wins.
|
||||
//
|
||||
if ( lhs_sign == bit_state::one && rhs_sign == bit_state::zero )
|
||||
return bit_vector( op == operator_id::less || op == operator_id::less_eq, 1 );
|
||||
|
||||
// If RHS is negative and LHS is positive, >, >= wins.
|
||||
//
|
||||
if ( rhs_sign == bit_state::one && lhs_sign == bit_state::zero )
|
||||
return bit_vector( op == operator_id::greater || op == operator_id::greater_eq, 1 );
|
||||
|
||||
// For each bit index we should compare:
|
||||
//
|
||||
bitcnt_t cmp_size = std::max( lhs.size(), rhs.size() );
|
||||
bit_vector lhs_sx = bit_vector{ lhs }.resize( cmp_size, true );
|
||||
bit_vector rhs_sx = bit_vector{ rhs }.resize( cmp_size, true );
|
||||
for ( int i = cmp_size - 1; i >= 0; i-- )
|
||||
{
|
||||
// If any of the bits are unknown, result is unknown.
|
||||
//
|
||||
if ( lhs_sx[ i ] == bit_state::unknown || rhs_sx[ i ] == bit_state::unknown )
|
||||
return bit_vector( 1 );
|
||||
|
||||
// If LHS is one and RHS is zero, >, >= and != wins.
|
||||
//
|
||||
if ( lhs_sx[ i ] == bit_state::one && rhs_sx[ i ] == bit_state::zero )
|
||||
return bit_vector( op == operator_id::greater || op == operator_id::greater_eq, 1 );
|
||||
|
||||
// If RHS is one and LHS is zero, <, <= and != wins.
|
||||
//
|
||||
if ( rhs_sx[ i ] == bit_state::one && lhs_sx[ i ] == bit_state::zero )
|
||||
return bit_vector( op == operator_id::less || op == operator_id::less_eq, 1 );
|
||||
}
|
||||
|
||||
// If completely equivalent (when sign extended), <=, >= wins.
|
||||
//
|
||||
return bit_vector( op == operator_id::less_eq || op == operator_id::greater_eq, 1 );
|
||||
}
|
||||
|
||||
//
|
||||
// Equality checks:
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::equal:
|
||||
case operator_id::not_equal:
|
||||
{
|
||||
// Fail if sign bits are not known
|
||||
//
|
||||
bit_state rhs_sign = rhs[ rhs.size() - 1 ];
|
||||
if ( rhs_sign == bit_state::unknown ) return bit_vector( 1 );
|
||||
bit_state lhs_sign = lhs[ lhs.size() - 1 ];
|
||||
if ( lhs_sign == bit_state::unknown ) return bit_vector( 1 );
|
||||
|
||||
// If signs do not match, != wins.
|
||||
//
|
||||
if ( lhs_sign != rhs_sign )
|
||||
return bit_vector( op == operator_id::not_equal, 1 );
|
||||
|
||||
// Sign extend both.
|
||||
//
|
||||
bitcnt_t cmp_size = std::max( lhs.size(), rhs.size() );
|
||||
bit_vector lhs_sx = bit_vector{ lhs }.resize( cmp_size, true );
|
||||
bit_vector rhs_sx = bit_vector{ rhs }.resize( cmp_size, true );
|
||||
|
||||
// If known zero of one side maps to known one of other and vice versa, != wins.
|
||||
//
|
||||
if ( ( lhs_sx.known_zero() & rhs_sx.known_one() ) || ( lhs_sx.known_one() & rhs_sx.known_zero() ) )
|
||||
return bit_vector( op == operator_id::not_equal, 1 );
|
||||
|
||||
// If any of the bits are unknown, result is unknown.
|
||||
//
|
||||
if ( lhs_sx.unknown_mask() | rhs_sx.unknown_mask() )
|
||||
return bit_vector( 1 );
|
||||
|
||||
// Simply compare all bits and adjust to the operator result.
|
||||
//
|
||||
return bit_vector( ( op == operator_id::not_equal ) ^ ( lhs_sx.known_one() == rhs_sx.known_one() ), 1 );
|
||||
}
|
||||
|
||||
//
|
||||
// Unsigned comparisons:
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::ugreater:
|
||||
case operator_id::ugreater_eq:
|
||||
case operator_id::uless_eq:
|
||||
case operator_id::uless:
|
||||
// For each bit index we should compare:
|
||||
//
|
||||
for ( int i = std::max( lhs.size(), rhs.size() ) - 1; i >= 0; i-- )
|
||||
{
|
||||
// If any of the bits are unknown, result is unknown.
|
||||
//
|
||||
if ( lhs[ i ] == bit_state::unknown || rhs[ i ] == bit_state::unknown )
|
||||
return bit_vector( 1 );
|
||||
|
||||
// If LHS is one and RHS is zero, >, >= wins.
|
||||
//
|
||||
if ( lhs[ i ] == bit_state::one && rhs[ i ] == bit_state::zero )
|
||||
return bit_vector( op == operator_id::ugreater || op == operator_id::ugreater_eq, 1 );
|
||||
|
||||
// If RHS is one and LHS is zero, <, <= wins.
|
||||
//
|
||||
if ( rhs[ i ] == bit_state::one && lhs[ i ] == bit_state::zero )
|
||||
return bit_vector( op == operator_id::uless || op == operator_id::uless_eq, 1 );
|
||||
}
|
||||
|
||||
// If completely equivalent (when zero extended), <=, >= wins.
|
||||
//
|
||||
return bit_vector( op == operator_id::uless_eq || op == operator_id::ugreater_eq, 1 );
|
||||
|
||||
//
|
||||
// Unsigned equality checks:
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::uequal:
|
||||
case operator_id::unot_equal:
|
||||
// If known zero of one side maps to known one of other and vice versa, != wins.
|
||||
//
|
||||
if ( ( lhs.known_zero() & rhs.known_one() ) || ( lhs.known_one() & rhs.known_zero() ) )
|
||||
return bit_vector( op == operator_id::unot_equal, 1 );
|
||||
|
||||
// If any of the bits are unknown, result is unknown.
|
||||
//
|
||||
if ( lhs.unknown_mask() | rhs.unknown_mask() )
|
||||
return bit_vector( 1 );
|
||||
|
||||
// Simply compare all bits and adjust to the operator result.
|
||||
//
|
||||
return bit_vector( ( op == operator_id::unot_equal ) ^ ( lhs.known_one() == rhs.known_one() ), 1 );
|
||||
|
||||
// If unknown, fall through:
|
||||
//
|
||||
default:
|
||||
break;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
};
|
||||
// evaluate_partial is split into operators_partial.hpp for build-time reasons.
|
||||
//
|
||||
#include "operators_partial.hpp"
|
||||
|
|
|
|||
566
VTIL-Common/math/operators_partial.hpp
Normal file
566
VTIL-Common/math/operators_partial.hpp
Normal file
|
|
@ -0,0 +1,566 @@
|
|||
// Partial evaluation of operators on bit-vectors with unknown bits.
|
||||
//
|
||||
#pragma once
|
||||
#include "operators.hpp"
|
||||
|
||||
namespace vtil::math
|
||||
{
|
||||
// Applies the specified operator [op] on left hand side [lhs] and right hand side [rhs] where
|
||||
// input and output values are expressed in the format of bit-vectors with optional unknowns,
|
||||
// and no size constraints.
|
||||
//
|
||||
static constexpr bit_vector evaluate_partial( operator_id op, const bit_vector& lhs, const bit_vector& rhs )
|
||||
{
|
||||
// If invalid operation, return invalid.
|
||||
//
|
||||
auto& desc = descriptor_of( op );
|
||||
|
||||
bool known = false;
|
||||
switch ( desc.operand_count )
|
||||
{
|
||||
case 1:
|
||||
if ( rhs.is_valid() )
|
||||
{
|
||||
known = rhs.is_known();
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
if ( rhs.is_valid() && lhs.is_valid() )
|
||||
{
|
||||
known = lhs.is_known() && rhs.is_known();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
// If no unknown bits, redirect to more efficient evaluate().
|
||||
//
|
||||
if ( known )
|
||||
{
|
||||
auto [val, size] = evaluate( op, lhs.size(), lhs.known_one(), rhs.size(), rhs.known_one() );
|
||||
return { val, size };
|
||||
}
|
||||
|
||||
switch ( op )
|
||||
{
|
||||
//
|
||||
// Basic bitwise operators.
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::bitwise_not:
|
||||
// Unknown mask does not change, known bits are flipped.
|
||||
//
|
||||
return bit_vector{ ~rhs.known_one(), rhs.unknown_mask(), rhs.size() };
|
||||
|
||||
case operator_id::bitwise_and:
|
||||
// Bitwise AND known bits, unknown mask is unset if one side had a known zero.
|
||||
//
|
||||
return bit_vector
|
||||
{
|
||||
lhs.known_one() & rhs.known_one(),
|
||||
( lhs.unknown_mask() | rhs.unknown_mask() ) & ~( lhs.known_zero() | rhs.known_zero() ),
|
||||
std::min( lhs.size(), rhs.size() )
|
||||
}.resize( std::max( lhs.size(), rhs.size() ) );
|
||||
|
||||
case operator_id::bitwise_or:
|
||||
// Bitwise OR known bits, unknown mask is unset if one side had a known one.
|
||||
//
|
||||
return bit_vector
|
||||
{
|
||||
lhs.known_one() | rhs.known_one(),
|
||||
( lhs.unknown_mask() | rhs.unknown_mask() ) & ~( lhs.known_one() | rhs.known_one() ),
|
||||
std::max( lhs.size(), rhs.size() )
|
||||
};
|
||||
|
||||
case operator_id::bitwise_xor:
|
||||
// Bitwise XOR known bits, unknown mask is merged.
|
||||
//
|
||||
return bit_vector
|
||||
{
|
||||
lhs.known_one() ^ rhs.known_one(),
|
||||
lhs.unknown_mask() | rhs.unknown_mask(),
|
||||
std::max( lhs.size(), rhs.size() )
|
||||
};
|
||||
|
||||
//
|
||||
// Rotations and shifts.
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::shift_right:
|
||||
// If shift count is known:
|
||||
//
|
||||
if ( auto n = rhs.get() )
|
||||
{
|
||||
// If shifting more bits than we have, return 0.
|
||||
//
|
||||
uint64_t shr_count = n.value();
|
||||
if ( shr_count >= lhs.size() ) return bit_vector( 0, lhs.size() );
|
||||
|
||||
// Return shifted masks, vector will normalize rest.
|
||||
//
|
||||
return { lhs.known_one() >> shr_count, lhs.unknown_mask() >> shr_count, lhs.size() };
|
||||
}
|
||||
// If shift count is unknown, return unknown bit-vector or 0 if input was only consisting of zeros.
|
||||
//
|
||||
return lhs.all_zero() ? lhs : bit_vector( lhs.size() );
|
||||
|
||||
case operator_id::shift_left:
|
||||
// If shift count is known:
|
||||
//
|
||||
if ( auto n = rhs.get() )
|
||||
{
|
||||
// If shifting more bits than we have, return 0.
|
||||
//
|
||||
uint64_t shl_count = n.value();
|
||||
if ( shl_count >= lhs.size() ) return bit_vector( 0, lhs.size() );
|
||||
|
||||
// Return shifted masks, vector will normalize rest.
|
||||
//
|
||||
return { lhs.known_one() << shl_count, lhs.unknown_mask() << shl_count, lhs.size() };
|
||||
}
|
||||
// If shift count is unknown, return unknown bit-vector or 0 if input was only consisting of zeros.
|
||||
//
|
||||
return lhs.all_zero() ? lhs : bit_vector( lhs.size() );
|
||||
|
||||
case operator_id::rotate_right:
|
||||
// If rotation count is known, return rotated masks, vector will normalize rest.
|
||||
//
|
||||
if ( auto n = rhs.get() )
|
||||
{
|
||||
uint64_t shr_count = n.value() % lhs.size();
|
||||
uint64_t shl_count = lhs.size() - shr_count;
|
||||
return
|
||||
{
|
||||
( lhs.known_one() >> shr_count ) | ( lhs.known_one() << shl_count ),
|
||||
( lhs.unknown_mask() >> shr_count ) | ( lhs.unknown_mask() << shl_count ),
|
||||
lhs.size()
|
||||
};
|
||||
}
|
||||
// If rotation count is unknown, return unknown bit-vector or 0/1 if input was only consisting of the same bit state.
|
||||
//
|
||||
return ( lhs.all_one() || lhs.all_zero() ) ? lhs : bit_vector( lhs.size() );
|
||||
|
||||
case operator_id::rotate_left:
|
||||
// If rotation count is known, return rotated masks, vector will normalize rest.
|
||||
//
|
||||
if ( auto n = rhs.get() )
|
||||
{
|
||||
uint64_t shl_count = n.value() % lhs.size();
|
||||
uint64_t shr_count = lhs.size() - shl_count;
|
||||
return
|
||||
{
|
||||
( lhs.known_one() >> shr_count ) | ( lhs.known_one() << shl_count ),
|
||||
( lhs.unknown_mask() >> shr_count ) | ( lhs.unknown_mask() << shl_count ),
|
||||
lhs.size()
|
||||
};
|
||||
}
|
||||
// If rotation count is unknown, return unknown bit-vector or 0/1 if input was only consisting of the same bit state.
|
||||
//
|
||||
return ( lhs.all_one() || lhs.all_zero() ) ? lhs : bit_vector( lhs.size() );
|
||||
|
||||
//
|
||||
// Arithmetic operators:
|
||||
// - TODO: Re-implement *fixed* O(1) solution for ADD SUB and NEG.
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::add:
|
||||
{
|
||||
bitcnt_t out_size = std::max( lhs.size(), rhs.size() );
|
||||
|
||||
// Return unknown if no bits are known from one side.
|
||||
//
|
||||
if( lhs.unknown_mask() == lhs.value_mask() ||
|
||||
rhs.unknown_mask() == rhs.value_mask() )
|
||||
return bit_vector( out_size );
|
||||
|
||||
// Create the temp holding the new bit vector.
|
||||
//
|
||||
uint64_t known_mask = 0;
|
||||
uint64_t unknown_mask = 0;
|
||||
|
||||
// For each bit in the output size:
|
||||
//
|
||||
bit_vector lhs_sx = bit_vector{ lhs }.resize( out_size, true );
|
||||
bit_vector rhs_sx = bit_vector{ rhs }.resize( out_size, true );
|
||||
bit_state carry = bit_state::zero;
|
||||
for ( int i = 0; i < out_size; i++ )
|
||||
{
|
||||
// Get current bits and choose the branch depending on the type:
|
||||
//
|
||||
bit_state a = lhs_sx[ i ];
|
||||
bit_state b = rhs_sx[ i ];
|
||||
if ( const int unk_count = ( a == bit_state::unknown ) + ( b == bit_state::unknown ) + ( carry == bit_state::unknown ) )
|
||||
{
|
||||
const int one_count = ( a == bit_state::one ) + ( b == bit_state::one ) + ( carry == bit_state::one );
|
||||
const int zero_count = 3 - one_count - unk_count;
|
||||
|
||||
// Carry is one if 2 elements are 1, zero if 2 elements are zero
|
||||
// and unknown otherise.
|
||||
//
|
||||
if ( one_count == 2 ) carry = bit_state::one;
|
||||
else if ( zero_count == 2 ) carry = bit_state::zero;
|
||||
else carry = bit_state::unknown;
|
||||
|
||||
// Output is always unknown.
|
||||
//
|
||||
unknown_mask |= 1ull << i;
|
||||
}
|
||||
else if ( a == b )
|
||||
{
|
||||
// Duplicated element propagates as carry, output is current carry.
|
||||
//
|
||||
known_mask |= uint64_t( carry == bit_state::one ) << i;
|
||||
carry = a;
|
||||
}
|
||||
else if ( a != b )
|
||||
{
|
||||
// Carry propagates as is, output is inverse of current carry.
|
||||
//
|
||||
known_mask |= uint64_t( carry == bit_state::zero ) << i;
|
||||
}
|
||||
}
|
||||
return bit_vector( known_mask, unknown_mask, out_size );
|
||||
|
||||
/*a = ( lhs.unknown_mask() | lhs.known_one() ) + ( rhs.unknown_mask() | rhs.known_one() );
|
||||
b = ( lhs.known_one() ) + ( rhs.known_one() );
|
||||
|
||||
return
|
||||
{
|
||||
a & b,
|
||||
~( a & b ) & ~( ~a & ~b ),
|
||||
std::max( lhs.size(), rhs.size() )
|
||||
};
|
||||
break;*/
|
||||
}
|
||||
|
||||
case operator_id::negate:
|
||||
// -A = 0-A
|
||||
//
|
||||
return evaluate_partial( operator_id::subtract, { 0, rhs.size() }, rhs );
|
||||
|
||||
/*a = mask( rhs.size() ) & -__sx64( ( rhs.unknown_mask() | rhs.known_one() ), rhs.size() );
|
||||
b = mask( rhs.size() ) & -__sx64( ( rhs.known_one() ), rhs.size() );
|
||||
|
||||
return
|
||||
{
|
||||
a & b,
|
||||
~( a & b ) & ~( ~a & ~b ),
|
||||
rhs.size()
|
||||
};
|
||||
break;*/
|
||||
case operator_id::subtract:
|
||||
// A-B = ~(~A+B)
|
||||
//
|
||||
return evaluate_partial( operator_id::bitwise_not, {},
|
||||
evaluate_partial( operator_id::add,
|
||||
evaluate_partial( operator_id::bitwise_not, {}, lhs ),
|
||||
rhs ) );
|
||||
|
||||
/*a = ( lhs.unknown_mask() | lhs.known_one() ) - ( rhs.known_one() );
|
||||
b = ( lhs.known_one() ) - ( rhs.unknown_mask() | rhs.known_one() );
|
||||
|
||||
return
|
||||
{
|
||||
a & b,
|
||||
~( a & b ) & ~( ~a & ~b ),
|
||||
std::max( lhs.size(), rhs.size() )
|
||||
};
|
||||
break;*/
|
||||
|
||||
//
|
||||
// Bitwise specials.
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::ucast:
|
||||
// Get new size from RHS as constant, and resize LHS to be of size [RHS] with zero extension if relevant.
|
||||
//
|
||||
if ( auto new_size = rhs.get() ) return bit_vector( lhs ).resize( narrow_cast<bitcnt_t>( *new_size ), false );
|
||||
else unreachable();
|
||||
|
||||
case operator_id::cast:
|
||||
// Get new size from RHS as constant, and resize LHS to be of size [RHS] with sign extension if relevant.
|
||||
//
|
||||
if ( auto new_size = rhs.get() ) return bit_vector( lhs ).resize( narrow_cast<bitcnt_t>( *new_size ), true );
|
||||
else unreachable();
|
||||
|
||||
case operator_id::popcnt:
|
||||
// Cannot be calculated with unknown values, return unknown of expected size.
|
||||
//
|
||||
return bit_vector( popcnt( rhs.known_one() | rhs.unknown_mask() ) ).resize( bit_index_size );
|
||||
|
||||
case operator_id::bitscan_fwd:
|
||||
case operator_id::bitscan_rev:
|
||||
// Cannot be calculated with unknown values, return unknown of expected size.
|
||||
//
|
||||
return bit_vector( bit_index_size );
|
||||
|
||||
case operator_id::bit_test:
|
||||
// If we can get the index being tested as constant, try to evaluate.
|
||||
//
|
||||
if ( auto index = rhs.get() )
|
||||
{
|
||||
return
|
||||
{
|
||||
( lhs.known_one() >> rhs.known_one() ) & 1,
|
||||
( lhs.unknown_mask() >> rhs.known_one() ) & 1,
|
||||
1
|
||||
};
|
||||
}
|
||||
// Otherwise, return unknown of one bit.
|
||||
//
|
||||
return bit_vector( 1 );
|
||||
|
||||
case operator_id::mask:
|
||||
// Return the mask of the vector as is.
|
||||
//
|
||||
return bit_vector( rhs.value_mask(), rhs.size() );
|
||||
|
||||
case operator_id::bit_count:
|
||||
// Return the number of bits in the vector as is.
|
||||
//
|
||||
return bit_vector( rhs.size(), bit_index_size );
|
||||
|
||||
case operator_id::value_if:
|
||||
// Try to evaluate the (x&1)?y:0 statement.
|
||||
//
|
||||
if ( lhs.known_one() & 1 ) return rhs;
|
||||
else if ( lhs.unknown_mask() & 1 ) return bit_vector{ rhs.size() };
|
||||
else return bit_vector{ 0, rhs.size() };
|
||||
|
||||
|
||||
//
|
||||
// Complex arithmetic operators.
|
||||
// - TODO: Whole thing :)
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::multiply_high:
|
||||
return bit_vector(std::max(rhs.size(), lhs.size()));
|
||||
case operator_id::multiply:
|
||||
// result of imul and mul are same at low operand size bits.
|
||||
return evaluate_partial(operator_id::umultiply, lhs, rhs);
|
||||
case operator_id::divide:
|
||||
case operator_id::remainder:
|
||||
case operator_id::umultiply_high:
|
||||
return bit_vector(std::max(rhs.size(), lhs.size()));
|
||||
case operator_id::umultiply:
|
||||
{
|
||||
bitcnt_t out_size = std::max(lhs.size(), rhs.size());
|
||||
|
||||
bit_vector lhs_sx = bit_vector{ lhs }.resize(out_size, true);
|
||||
bit_vector rhs_sx = bit_vector{ rhs }.resize(out_size, true);
|
||||
bit_vector result = bit_vector(0, out_size);
|
||||
for (int i = 0; i < rhs.size(); i++)
|
||||
{
|
||||
bit_state b = rhs_sx[i];
|
||||
if (b == bit_state::unknown)
|
||||
{
|
||||
result = evaluate_partial(operator_id::add,
|
||||
evaluate_partial(operator_id::shift_left,
|
||||
bit_vector(out_size),
|
||||
bit_vector(i, out_size))
|
||||
, result);
|
||||
}
|
||||
else if (b == bit_state::one)
|
||||
{
|
||||
result = evaluate_partial(operator_id::add,
|
||||
evaluate_partial(operator_id::shift_left,
|
||||
lhs_sx,
|
||||
bit_vector(i, out_size))
|
||||
, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case operator_id::udivide:
|
||||
case operator_id::uremainder:
|
||||
return bit_vector( std::max( rhs.size(), lhs.size() ) );
|
||||
|
||||
|
||||
//
|
||||
// MinMax operators:
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::min_value:
|
||||
case operator_id::max_value:
|
||||
case operator_id::umin_value:
|
||||
case operator_id::umax_value:
|
||||
{
|
||||
// Map each min-max to a comperator.
|
||||
//
|
||||
operator_id cmp_id;
|
||||
switch ( op )
|
||||
{
|
||||
case operator_id::umin_value: cmp_id = operator_id::uless; break;
|
||||
case operator_id::umax_value: cmp_id = operator_id::ugreater_eq; break;
|
||||
case operator_id::min_value: cmp_id = operator_id::less; break;
|
||||
case operator_id::max_value: cmp_id = operator_id::greater_eq; break;
|
||||
default: unreachable();
|
||||
}
|
||||
|
||||
// cmp<>(A,B) ? A : B
|
||||
bit_state cmp_res = evaluate_partial( cmp_id, lhs, rhs )[ 0 ];
|
||||
bitcnt_t cmp_out_size = std::max( lhs.size(), rhs.size() );
|
||||
switch ( cmp_res )
|
||||
{
|
||||
case bit_state::one: return bit_vector{ lhs }.resize( cmp_out_size );
|
||||
case bit_state::zero: return bit_vector{ rhs }.resize( cmp_out_size );
|
||||
case bit_state::unknown: return bit_vector{ cmp_out_size };
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Signed comparisons:
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::greater:
|
||||
case operator_id::greater_eq:
|
||||
case operator_id::less_eq:
|
||||
case operator_id::less:
|
||||
{
|
||||
// Fail if sign bits are not known
|
||||
//
|
||||
bit_state rhs_sign = rhs[ rhs.size() - 1 ];
|
||||
if ( rhs_sign == bit_state::unknown ) return bit_vector( 1 );
|
||||
bit_state lhs_sign = lhs[ lhs.size() - 1 ];
|
||||
if ( lhs_sign == bit_state::unknown ) return bit_vector( 1 );
|
||||
|
||||
// If LHS is negative and RHS is positive, <, <= wins.
|
||||
//
|
||||
if ( lhs_sign == bit_state::one && rhs_sign == bit_state::zero )
|
||||
return bit_vector( op == operator_id::less || op == operator_id::less_eq, 1 );
|
||||
|
||||
// If RHS is negative and LHS is positive, >, >= wins.
|
||||
//
|
||||
if ( rhs_sign == bit_state::one && lhs_sign == bit_state::zero )
|
||||
return bit_vector( op == operator_id::greater || op == operator_id::greater_eq, 1 );
|
||||
|
||||
// For each bit index we should compare:
|
||||
//
|
||||
bitcnt_t cmp_size = std::max( lhs.size(), rhs.size() );
|
||||
bit_vector lhs_sx = bit_vector{ lhs }.resize( cmp_size, true );
|
||||
bit_vector rhs_sx = bit_vector{ rhs }.resize( cmp_size, true );
|
||||
for ( int i = cmp_size - 1; i >= 0; i-- )
|
||||
{
|
||||
// If any of the bits are unknown, result is unknown.
|
||||
//
|
||||
if ( lhs_sx[ i ] == bit_state::unknown || rhs_sx[ i ] == bit_state::unknown )
|
||||
return bit_vector( 1 );
|
||||
|
||||
// If LHS is one and RHS is zero, >, >= and != wins.
|
||||
//
|
||||
if ( lhs_sx[ i ] == bit_state::one && rhs_sx[ i ] == bit_state::zero )
|
||||
return bit_vector( op == operator_id::greater || op == operator_id::greater_eq, 1 );
|
||||
|
||||
// If RHS is one and LHS is zero, <, <= and != wins.
|
||||
//
|
||||
if ( rhs_sx[ i ] == bit_state::one && lhs_sx[ i ] == bit_state::zero )
|
||||
return bit_vector( op == operator_id::less || op == operator_id::less_eq, 1 );
|
||||
}
|
||||
|
||||
// If completely equivalent (when sign extended), <=, >= wins.
|
||||
//
|
||||
return bit_vector( op == operator_id::less_eq || op == operator_id::greater_eq, 1 );
|
||||
}
|
||||
|
||||
//
|
||||
// Equality checks:
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::equal:
|
||||
case operator_id::not_equal:
|
||||
{
|
||||
// Fail if sign bits are not known
|
||||
//
|
||||
bit_state rhs_sign = rhs[ rhs.size() - 1 ];
|
||||
if ( rhs_sign == bit_state::unknown ) return bit_vector( 1 );
|
||||
bit_state lhs_sign = lhs[ lhs.size() - 1 ];
|
||||
if ( lhs_sign == bit_state::unknown ) return bit_vector( 1 );
|
||||
|
||||
// If signs do not match, != wins.
|
||||
//
|
||||
if ( lhs_sign != rhs_sign )
|
||||
return bit_vector( op == operator_id::not_equal, 1 );
|
||||
|
||||
// Sign extend both.
|
||||
//
|
||||
bitcnt_t cmp_size = std::max( lhs.size(), rhs.size() );
|
||||
bit_vector lhs_sx = bit_vector{ lhs }.resize( cmp_size, true );
|
||||
bit_vector rhs_sx = bit_vector{ rhs }.resize( cmp_size, true );
|
||||
|
||||
// If known zero of one side maps to known one of other and vice versa, != wins.
|
||||
//
|
||||
if ( ( lhs_sx.known_zero() & rhs_sx.known_one() ) || ( lhs_sx.known_one() & rhs_sx.known_zero() ) )
|
||||
return bit_vector( op == operator_id::not_equal, 1 );
|
||||
|
||||
// If any of the bits are unknown, result is unknown.
|
||||
//
|
||||
if ( lhs_sx.unknown_mask() | rhs_sx.unknown_mask() )
|
||||
return bit_vector( 1 );
|
||||
|
||||
// Simply compare all bits and adjust to the operator result.
|
||||
//
|
||||
return bit_vector( ( op == operator_id::not_equal ) ^ ( lhs_sx.known_one() == rhs_sx.known_one() ), 1 );
|
||||
}
|
||||
|
||||
//
|
||||
// Unsigned comparisons:
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::ugreater:
|
||||
case operator_id::ugreater_eq:
|
||||
case operator_id::uless_eq:
|
||||
case operator_id::uless:
|
||||
// For each bit index we should compare:
|
||||
//
|
||||
for ( int i = std::max( lhs.size(), rhs.size() ) - 1; i >= 0; i-- )
|
||||
{
|
||||
// If any of the bits are unknown, result is unknown.
|
||||
//
|
||||
if ( lhs[ i ] == bit_state::unknown || rhs[ i ] == bit_state::unknown )
|
||||
return bit_vector( 1 );
|
||||
|
||||
// If LHS is one and RHS is zero, >, >= wins.
|
||||
//
|
||||
if ( lhs[ i ] == bit_state::one && rhs[ i ] == bit_state::zero )
|
||||
return bit_vector( op == operator_id::ugreater || op == operator_id::ugreater_eq, 1 );
|
||||
|
||||
// If RHS is one and LHS is zero, <, <= wins.
|
||||
//
|
||||
if ( rhs[ i ] == bit_state::one && lhs[ i ] == bit_state::zero )
|
||||
return bit_vector( op == operator_id::uless || op == operator_id::uless_eq, 1 );
|
||||
}
|
||||
|
||||
// If completely equivalent (when zero extended), <=, >= wins.
|
||||
//
|
||||
return bit_vector( op == operator_id::uless_eq || op == operator_id::ugreater_eq, 1 );
|
||||
|
||||
//
|
||||
// Unsigned equality checks:
|
||||
//
|
||||
// ####################################################################################################################################
|
||||
case operator_id::uequal:
|
||||
case operator_id::unot_equal:
|
||||
// If known zero of one side maps to known one of other and vice versa, != wins.
|
||||
//
|
||||
if ( ( lhs.known_zero() & rhs.known_one() ) || ( lhs.known_one() & rhs.known_zero() ) )
|
||||
return bit_vector( op == operator_id::unot_equal, 1 );
|
||||
|
||||
// If any of the bits are unknown, result is unknown.
|
||||
//
|
||||
if ( lhs.unknown_mask() | rhs.unknown_mask() )
|
||||
return bit_vector( 1 );
|
||||
|
||||
// Simply compare all bits and adjust to the operator result.
|
||||
//
|
||||
return bit_vector( ( op == operator_id::unot_equal ) ^ ( lhs.known_one() == rhs.known_one() ), 1 );
|
||||
|
||||
// If unknown, fall through:
|
||||
//
|
||||
default:
|
||||
break;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue