From 0c2e3e122e924510433e344b2f2694ae828d9cab Mon Sep 17 00:00:00 2001
From: dword64 <74870446+dword64@users.noreply.github.com>
Date: Sun, 5 Apr 2026 00:02:58 +0200
Subject: [PATCH] updated pe parsing, split operators, update docs
---
README.md | 6 +-
VTIL-Architecture/arch/register_desc.hpp | 20 +-
VTIL-Common/formats/winpe.cpp | 66 +--
VTIL-Common/math/operators.hpp | 562 +---------------------
VTIL-Common/math/operators_partial.hpp | 566 +++++++++++++++++++++++
5 files changed, 622 insertions(+), 598 deletions(-)
create mode 100644 VTIL-Common/math/operators_partial.hpp
diff --git a/README.md b/README.md
index 1c2fee3..2c60220 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-
-
+
+
VTIL
@@ -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)
diff --git a/VTIL-Architecture/arch/register_desc.hpp b/VTIL-Architecture/arch/register_desc.hpp
index 7872b6a..b19f5ef 100644
--- a/VTIL-Architecture/arch/register_desc.hpp
+++ b/VTIL-Architecture/arch/register_desc.hpp
@@ -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, and register_cast returns register_desc
+ //
+ struct register_desc;
+
+ template
+ 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
struct register_cast
@@ -352,7 +361,10 @@ namespace vtil
return {};
}
};
- template<>
+
+ // Specializations for known register types.
+ //
+ template<>
struct register_cast
{
constexpr register_desc operator()( register_desc v ) { return v; }
diff --git a/VTIL-Common/formats/winpe.cpp b/VTIL-Common/formats/winpe.cpp
index d24ac8a..14a27a3 100644
--- a/VTIL-Common/formats/winpe.cpp
+++ b/VTIL-Common/formats/winpe.cpp
@@ -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( id ) ] ); }
bool has_directory( const data_directory_t* dir ) const { return &data_directories.entries[ num_data_directories ] < dir && dir->present(); }
};
template
@@ -435,8 +435,8 @@ namespace vtil
file_header_t file_header;
optional_header_t 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( reinterpret_cast( &optional_header ) + file_header.size_optional_header ); }
+ auto get_sections() const { return reinterpret_cast( reinterpret_cast( &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 auto get_nt_headers() { return ( nt_headers_t* ) ( ( char* ) this + e_lfanew ); }
- template auto get_nt_headers() const { return ( const nt_headers_t* ) ( ( char* ) this + e_lfanew ); }
+ template auto get_nt_headers() { return reinterpret_cast*>( reinterpret_cast( this ) + e_lfanew ); }
+ template auto get_nt_headers() const { return reinterpret_cast*>( reinterpret_cast( 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( reinterpret_cast( this ) + this->size_block ); }
+ auto get_next() const { return reinterpret_cast( reinterpret_cast( this ) + this->size_block ); }
+ size_t num_entries() const { return reinterpret_cast( get_next() ) - &entries[ 0 ]; }
};
struct reloc_directory_t
@@ -511,18 +511,18 @@ namespace vtil
template
static decltype( auto ) visit_nt( S* self, T&& fn )
{
- auto dos_header = ( dos_header_t* ) self->cdata();
+ auto dos_header = reinterpret_cast( const_cast( self->cdata() ) );
auto* nt_hdrs = dos_header->get_nt_headers();
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_hdrs ) ) );
else
- return fn( carry_const( self, ( nt_headers_x86_t* ) nt_hdrs ) );
+ return fn( carry_const( self, reinterpret_cast( nt_hdrs ) ) );
}
bool pe_image::is_pe64() const
{
- auto dos_header = ( const dos_header_t* ) cdata();
+ auto dos_header = reinterpret_cast( cdata() );
return dos_header->get_nt_headers()->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( cdata() );
return dos_header->get_nt_headers()->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( cdata() );
auto nt_headers = dos_header->get_nt_headers();
// 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( data() );
auto nt_headers = dos_header->get_nt_headers();
// 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( cdata() ) + size();
+ auto dos_header = reinterpret_cast( 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( 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();
+ auto nt_hdrs = reinterpret_cast( this->data() )->get_nt_headers();
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_dir.rva )->first_block;
- const auto* block_end = ( const reloc_block_t* ) ( ( char* ) block_begin + reloc_dir.size );
+ const auto* block_end = reinterpret_cast( reinterpret_cast( 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( 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( 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( delta ); };
+ entry.relocator = [ ] ( void* data, int64_t delta ) { *static_cast( data ) += math::narrow_cast( 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( data ) += static_cast( static_cast( 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( data ) += static_cast( static_cast( 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;
diff --git a/VTIL-Common/math/operators.hpp b/VTIL-Common/math/operators.hpp
index c87b84f..7736106 100644
--- a/VTIL-Common/math/operators.hpp
+++ b/VTIL-Common/math/operators.hpp
@@ -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( *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( *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();
- }
-};
\ No newline at end of file
+// evaluate_partial is split into operators_partial.hpp for build-time reasons.
+//
+#include "operators_partial.hpp"
diff --git a/VTIL-Common/math/operators_partial.hpp b/VTIL-Common/math/operators_partial.hpp
new file mode 100644
index 0000000..668dd61
--- /dev/null
+++ b/VTIL-Common/math/operators_partial.hpp
@@ -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( *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( *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();
+ }
+};