// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // 3. Neither the name of VTIL Project nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // #include "expression.hpp" #include #include "../simplifier/simplifier.hpp" namespace vtil::symbolic { // Returns the number of constants used in the expression. // size_t expression::count_constants() const { if ( is_constant() ) return 1; return ( lhs ? lhs->count_constants() : 0 ) + ( rhs ? rhs->count_constants() : 0 ); } // Returns the number of variables used in the expression. // size_t expression::count_variables() const { if ( is_variable() ) return 1; return ( lhs ? lhs->count_variables() : 0 ) + ( rhs ? rhs->count_variables() : 0 ); } // Returns the number of unique variables used in the expression. // size_t expression::count_unique_variables( std::set* visited ) const { std::set tmp; if ( !visited ) visited = &tmp; if ( is_variable() && visited->find( uid ) == visited->end() ) { visited->insert( uid ); return 1; } else { return ( lhs ? lhs->count_unique_variables( visited ) : 0 ) + ( rhs ? rhs->count_unique_variables( visited ) : 0 ); } } // Resizes the expression, if not constant, expression::resize will try to propagate // the operation as deep as possible. // expression& expression::resize( bitcnt_t new_size, bool signed_cast, bool no_explicit ) { // If requested size is equal, skip. // if ( value.size() == new_size ) return *this; // Try to convert signed casts into unsigned ones: // if ( signed_cast ) { // If result is a boolean or is smaller than current value, sign is irrelevant: // if ( new_size == 1 || new_size < value.size() ) { signed_cast = false; } // If high bit is known zero: // else if ( value.at( value.size() - 1 ) == math::bit_state::zero ) { signed_cast = false; } } // If expression is lazy, delay it. // if ( is_lazy ) { if ( is_constant() ) { value = value.resize( new_size, signed_cast ); update( false ); } else { if ( no_explicit ) return *this; if ( signed_cast ) *this = __cast( *this, new_size ); else *this = __ucast( *this, new_size ); } return *this; } switch ( op ) { // If constant resize the value, if variable apply the operation as is. // case math::operator_id::invalid: if ( is_constant() ) { value = value.resize( new_size, signed_cast ); update( false ); } else { if ( no_explicit ) return *this; if ( signed_cast ) *this = __cast( *this, new_size ); else *this = __ucast( *this, new_size ); } break; // If rotation, unpack into two shifts if non-zero constant rotation and operation is not signed: // case math::operator_id::rotate_left: if ( rhs->is_constant() && rhs->known_one() != 0 && !signed_cast ) { auto lhs_v = std::move( lhs ); auto rhs_v = std::move( rhs ); *this = ( ( lhs_v << rhs_v ).resize( new_size ) | ( lhs_v >> ( lhs_v->size() - rhs_v ) ).resize( new_size ) ); } else { if ( no_explicit ) return *this; if ( signed_cast ) *this = __cast( *this, new_size ); else *this = __ucast( *this, new_size ); } break; case math::operator_id::rotate_right: if ( rhs->is_constant() && rhs->known_one() != 0 && !signed_cast ) { auto lhs_v = std::move( lhs ); auto rhs_v = std::move( rhs ); *this = ( ( lhs_v >> rhs_v ).resize( new_size ) | ( lhs_v << ( lhs_v->size() - rhs_v ) ).resize( new_size ) ); } else { if ( no_explicit ) return *this; if ( signed_cast ) *this = __cast( *this, new_size ); else *this = __ucast( *this, new_size ); } break; // If bitshift, propagate where possible: // case math::operator_id::shift_left: // If we're shrinking the result: // - Cannot be handled for shift right. // if ( new_size < value.size() ) { // Resize shifted expression and break. // lhs.resize( new_size, false ); update( false ); break; } case math::operator_id::shift_right: // If we're zero-extending the result: // if( !signed_cast && new_size > value.size() ) { lhs = std::move( lhs ).resize( new_size, false ); update( false ); } // Otherwise nothing else to do. // else { if ( no_explicit ) return *this; if ( signed_cast ) *this = __cast( *this, new_size ); else *this = __ucast( *this, new_size ); } break; // If not: // case math::operator_id::bitwise_not: if ( !signed_cast ) { // If shrinking, just resize. // if ( new_size < value.size() ) { ( +rhs )->resize( new_size, false ); update( false ); } // If extending: // else { uint64_t rhs_mask = value.known_one() | value.unknown_mask(); auto rhs_v = std::move( rhs ); *this = ( ~( rhs_v.resize( new_size, false ) ) ) & expression{ rhs_mask, new_size }; } } else { if ( no_explicit ) return *this; *this = __cast( *this, new_size ); } break; // If basic unsigned operation, unsigned cast both sides if requested type is also unsigned. // case math::operator_id::bitwise_and: case math::operator_id::bitwise_or: case math::operator_id::bitwise_xor: case math::operator_id::umultiply: case math::operator_id::udivide: case math::operator_id::uremainder: case math::operator_id::umax_value: case math::operator_id::umin_value: if ( !signed_cast ) { // If shrinking and is division-related: // if ( new_size < value.size() && ( op == math::operator_id::udivide || op == math::operator_id::uremainder )) { if ( no_explicit ) return *this; *this = __ucast( *this, new_size ); } else { if ( lhs ) lhs.resize( new_size, false ); rhs.resize( new_size, false ); update( false ); } } else { if ( no_explicit ) return *this; *this = __cast( *this, new_size ); } break; // If basic signed operation, signed cast both sides if requested type is also signed. // case math::operator_id::multiply: case math::operator_id::divide: case math::operator_id::remainder: case math::operator_id::add: case math::operator_id::negate: case math::operator_id::subtract: case math::operator_id::max_value: case math::operator_id::min_value: if ( signed_cast ) { if ( lhs ) lhs.resize( new_size, true ); rhs.resize( new_size, true ); update( false ); } else { // If shrinking and is not division-related: // if ( new_size < value.size() && op != math::operator_id::divide && op != math::operator_id::remainder ) { if ( lhs ) lhs.resize( new_size, false ); rhs.resize( new_size, false ); update( false ); } else { if ( no_explicit ) return *this; *this = __ucast( *this, new_size ); } } break; // If casting the result of an unsigned cast: // case math::operator_id::ucast: // If it was shrinked: // if ( lhs->size() > (int) rhs->get().value() ) { // If sign extension, double cast. // if ( signed_cast ) { if ( no_explicit ) return *this; *this = __cast( *this, new_size ); break; } // Otherwise mask it and resize. // auto lhs_v = std::move( lhs ); auto rhs_v = std::move( rhs ); *this = ( lhs_v & expression{ math::fill( rhs_v->get().value() ), lhs_v->size() } ).resize( new_size ); } // If sizes match, escape cast operator. // else if ( lhs->size() == new_size ) { *this = *std::move( lhs ); } // Otherwise upgrade the parameter. // else { *+rhs = new_size; return update( false ); } break; // If casting the result of a signed cast: // case math::operator_id::cast: // Signed cast should not be used to shrink. // fassert( lhs->size() <= (int) rhs->get().value() ); // If sizes match, escape cast operator. // if ( lhs->size() == new_size ) { *this = *std::move( lhs ); } // Otherwise, if both are signed upgrade the parameter. // else if ( signed_cast ) { *+rhs = new_size; return update( false ); } // Else, convert to unsigned cast since top bits will be zero. // else { if ( no_explicit ) return *this; *this = __ucast( *this, new_size ); } break; // Redirect to conditional output since zx 0 == sx 0. // case math::operator_id::value_if: if ( rhs.size() != new_size ) { rhs.resize( new_size, false ); update( false ); } break; // If no handler found: // default: if ( no_explicit ) return *this; if ( signed_cast ) *this = __cast( *this, new_size ); else *this = __ucast( *this, new_size ); break; } simplify(); return *this; } // Updates the expression state. // expression& expression::update( bool auto_simplify ) { // Propagate lazyness. // if ( ( lhs && lhs->is_lazy ) || ( rhs && rhs->is_lazy ) ) { auto_simplify = false; is_lazy = true; } // If it's not a full expression tree: // if ( !is_expression() ) { // Reset depth. // depth = 0; // If constant value: // if ( is_constant() ) { // Punish for each set bit in [min_{msb x + popcnt x}(v, |v|)], in an exponentially decreasing rate. // int64_t cval = *value.get(); complexity = sqrt( 1 + std::min( math::msb( cval ) + math::popcnt( cval ), math::msb( abs( cval ) ) + math::popcnt( abs( cval ) ) ) ); // Hash is made up of the bit vector masks and the number of bits. // hash_value = make_hash( value.known_zero(), value.known_one(), ( uint8_t ) value.size() ); } // If symbolic variable: // else { dassert( is_variable() ); // Assign the constant complexity value. // complexity = 128; // Hash is made up of UID's hash and the number of bits. // hash_value = make_hash( uid.hash(), ( uint8_t ) value.size() ); } // Set the signature. // signature = { value }; // Set simplification state. // simplify_hint = true; } else { dassert( is_expression() ); // If unary operator: // const math::operator_desc& desc = get_op_desc(); if ( desc.operand_count == 1 ) { // Partially evaluate the expression. // value = math::evaluate_partial( op, {}, rhs->value ); // Speculative simplification, if value is known replace with a constant, this // is a major performance boost with lazy expressions as child copies and large // destruction chains are completely avoided. Lazy expressions are meant to // delay complex simplification rather than block all simplification so this // step is totally fine. [1] // if ( ( is_lazy || auto_simplify ) && value.is_known() ) { lhs = {}; rhs = {}; op = math::operator_id::invalid; is_lazy = false; return update( false ); } // Calculate base complexity and the depth. // depth = rhs->depth + 1; complexity = rhs->complexity * 2; dassert( complexity != 0 ); // Begin hash as rhs. // hash_value = rhs->hash(); } // If binary operator: // else { dassert( desc.operand_count == 2 ); // If operation is __cast or __ucast, right hand side must always be a constant, propagate // left hand side value and resize as requested. // if ( op == math::operator_id::ucast || op == math::operator_id::cast ) { value = lhs->value; value.resize( rhs->get().value(), op == math::operator_id::cast ); } // Partially evaluate the expression if not resize. // else { value = math::evaluate_partial( op, lhs->value, rhs->value ); } // Speculative simplification, see [1]. // if ( ( is_lazy || auto_simplify ) && value.is_known() ) { lhs = {}; rhs = {}; op = math::operator_id::invalid; is_lazy = false; return update( false ); } // Handle size mismatches. // const auto optimistic_size = [ ] ( symbolic::expression::reference& lhs, symbolic::expression::reference& rhs ) { bitcnt_t op_size = lhs->size(); if ( ( op_size < rhs->size() && math::msb( ~rhs->value.known_zero() ) > op_size ) || ( op_size > rhs->size() && math::msb( ~lhs->value.known_zero() ) < rhs->size() ) ) op_size = rhs->size(); return op_size; }; switch ( op ) { case math::operator_id::bitwise_and: case math::operator_id::bitwise_or: case math::operator_id::bitwise_xor: case math::operator_id::umultiply_high: case math::operator_id::udivide: case math::operator_id::uremainder: case math::operator_id::umax_value: case math::operator_id::umin_value: { lhs.resize( value.size(), false ); rhs.resize( value.size(), false ); break; } case math::operator_id::shift_left: case math::operator_id::shift_right: case math::operator_id::rotate_left: case math::operator_id::rotate_right: { rhs.resize( 32, false ); break; } case math::operator_id::multiply_high: case math::operator_id::multiply: case math::operator_id::divide: case math::operator_id::remainder: case math::operator_id::add: case math::operator_id::subtract: case math::operator_id::max_value: case math::operator_id::min_value: { lhs.resize( value.size(), true ); rhs.resize( value.size(), true ); break; } case math::operator_id::ugreater: case math::operator_id::ugreater_eq: case math::operator_id::uless_eq: case math::operator_id::uless: { bitcnt_t op_size = optimistic_size( lhs, rhs ); lhs.resize( op_size, false ); rhs.resize( op_size, false ); break; } case math::operator_id::greater: case math::operator_id::greater_eq: case math::operator_id::less_eq: case math::operator_id::less: case math::operator_id::equal: case math::operator_id::not_equal: { bitcnt_t op_size = optimistic_size( lhs, rhs ); lhs.resize( op_size, true ); rhs.resize( op_size, true ); break; } // Convert unsigned multiply to signed multiply. // case math::operator_id::umultiply: { lhs.resize( value.size(), true ); rhs.resize( value.size(), true ); op = math::operator_id::multiply; break; } // Convert unsigned compare to signed compare. // case math::operator_id::uequal: case math::operator_id::unot_equal: { bitcnt_t op_size = optimistic_size( lhs, rhs ); lhs.resize( op_size, false ); rhs.resize( op_size, false ); op = op == math::operator_id::uequal ? math::operator_id::equal : math::operator_id::not_equal; break; } default: break; } // Calculate base complexity and the depth. // depth = std::max( lhs->depth, rhs->depth ) + 1; complexity = ( lhs->complexity + rhs->complexity ) * 2; dassert( complexity != 0 ); // Multiply with operator complexity coefficient. // complexity *= desc.complexity_coeff; // Begin hash as combine(op#1, op#2), make it unordered if operator is commutative. // hash_value = desc.is_commutative ? combine_unordered_hash( lhs->hash(), rhs->hash() ) : combine_hash( lhs->hash(), rhs->hash() ); } // Set the signature. // if( lhs ) signature = { lhs->signature, op, rhs->signature }; else signature = { op, rhs->signature }; // Append depth, size, and operator information to the hash. // hash_value = combine_hash( hash_value, make_hash( op, depth, uint8_t( value.size() ) ) ); // Punish for mixing bitwise and arithmetic operators. // for ( auto& operand : { &lhs, &rhs } ) { if ( *operand && operand->get()->is_expression() ) { // Bitwise hint of the descriptor contains +1 or -1 if the operator // is strictly bitwise or arithmetic respectively and 0 otherwise. // This works since mulitplication between them will only be negative // if the hints mismatch. // complexity *= 1 + math::sgn( operand->get()->get_op_desc().hint_bitwise * desc.hint_bitwise ); } } // Reset simplification state since expression was updated. // simplify_hint = false; // If auto simplification is relevant, invoke it. // if ( auto_simplify ) simplify(); } // Clear lazyness from children. // if ( is_lazy ) { if ( lhs && lhs->is_lazy ) ( +lhs )->is_lazy = false; if ( rhs && rhs->is_lazy ) ( +rhs )->is_lazy = false; } return *this; } // Simplifies the expression. // expression& expression::simplify( bool prettify ) { // Reset lazyness. // is_lazy = false; // Skip if no point in simplifying. // if ( !prettify && simplify_hint ) return *this; // By changing the prototype of simplify_expression from f(expression&) to // f(expression::reference&), we gain an important performance benefit that is // a significantly less amount of copies made. Cache will also store references // this way and additionally we avoid copying where an operand is being simplified // as that can be replaced by a simple swap of shared references. // reference ref = ( reference&& ) make_local_reference( this ); simplify_expression( ref, prettify ); // Set the simplifier hint to indicate skipping further calls to simplify_expression. // ref->simplify_hint = true; // If reference is changed, move from it. // if ( ref.get() != this ) { if( ref.get_entry()->second.load() == 1 ) operator=( std::move( *ref ) ); else operator=( *ref ); } return *this; } // Returns whether the given expression is identical to the current instance. // static bool is_identical_impl( const expression& self, const expression& other ) { if ( &self == &other ) return true; auto report_hash_collision = [ & ] () { #ifdef _DEBUG logger::log( "Hash collision detected!\n" ); logger::log( "[0]: %s\n", self ); logger::log( "[1]: %s\n", other ); if ( make_copy( self ).update( false ).hash() != self.hash() ) logger::log( "Invalid hash for A\n" ); else if ( make_copy( other ).update( false ).hash() != other.hash() ) logger::log( "Invalid hash for B\n" ); #endif return false; }; constexpr auto cmp = is_identical_impl; // If hash/size mismatches, return false without checking anything. // if ( self.hash() != other.hash() || self.size() != other.size() ) return false; // If variable, check if the identifiers match. // if ( self.is_variable() ) return ( other.is_variable() && self.uid == other.uid ) || report_hash_collision(); // If constant, check if the constants match. // if ( self.is_constant() ) return ( other.is_constant() && self.value == other.value ) || report_hash_collision(); // If operator is not the same, return false. // if ( self.op != other.op ) return report_hash_collision(); // Resolve operator descriptor, if unary, just compare right hand side. // const math::operator_desc& desc = self.get_op_desc(); if ( desc.operand_count == 1 ) return cmp( *self.rhs, *other.rhs ) || report_hash_collision(); // If both sides match, return true. // if ( cmp( *self.lhs, *other.lhs ) && cmp( *self.rhs, *other.rhs ) ) return true; // If not, check in reverse as well if commutative and return the final result. // return ( desc.is_commutative && cmp( *self.lhs, *other.rhs ) && cmp( *self.rhs, *other.lhs ) ) || report_hash_collision(); } bool expression::is_identical( const expression& other ) const { return is_identical_impl( *this, other ); } // Returns whether the given expression is equivalent to the current instance. // bool expression::equals( const expression& other ) const { // If identical, return true. // if ( is_identical( other ) ) return true; // Filter by known bits. // if ( ( other.known_one() & known_zero() ) || ( other.known_zero() & known_one() )) return false; // Fast path: if x values do not match, expressions cannot be equivalent. // if( xvalues() != other.xvalues() ) return false; // Simplify both expressions. // expression::reference a = make_local_reference( this ); expression::reference b = make_local_reference( &other ); a.simplify(); b.simplify(); // Determine the final bitwise hint. // int8_t a_hint = a->is_expression() ? a->get_op_desc().hint_bitwise : 0; int8_t b_hint = b->is_expression() ? b->get_op_desc().hint_bitwise : 0; // If arithmetic hint, try A-B==0 first and then A^B==0. // if ( ( a_hint + b_hint ) <= -1 ) return ( a - b ).get().value_or( -1 ) == 0 || ( a ^ b ).get().value_or( -1 ) == 0; // If bitwise or null hint, try A^B==0 first and then A-B==0. // else return ( a ^ b ).get().value_or( -1 ) == 0 || ( a - b ).get().value_or( -1 ) == 0; } // Returns whether the given expression is matching the current instance, if so returns // the UID relation table, otherwise returns nullopt. // using fast_uid_relation_table = stack_vector>; static bool match_to_impl( const expression::reference& a, const expression::reference& b, fast_uid_relation_table* tbl, bool same_depth ) { // If identical, try pushing all variables into the table. // if ( a->is_identical( *b ) ) { bool success = true; a.transform( [ & ] ( symbolic::expression_delegate& exp ) { if ( !success || !exp->is_variable() ) return; for ( auto& [src, dst] : *tbl ) { if ( exp->uid == src->uid && exp->uid != dst->uid ) { success = false; return; } } tbl->emplace_back( exp.ref, exp.ref ); }, true, false ); return success; } // Check if properties match. // if ( ( same_depth ? a->signature != b->signature : b->signature.can_match( a->signature ) ) || ( same_depth ? a->depth != b->depth : a->depth > b->depth ) || a->op != b->op || a->size() != b->size() ) return false; // If variable: // if ( a->is_variable() ) { // Skip if compared expression is not a variable if same depth is set. // if ( same_depth && !b->is_variable() ) return false; // Check if this UID is already in the table, if so return the result of // the comparison of the mapping. Otherwise insert into the table. // for ( auto& [src, dst] : *tbl ) if ( src->uid == a->uid ) return dst->is_identical( *b ); tbl->emplace_back( a, b ); return true; } // If constant, check if the constants match. // if ( a->is_constant() ) return b->is_constant() && a->value == b->value; // Resolve operator descriptor, if unary, just compare right hand side. // const math::operator_desc& desc = a->get_op_desc(); if ( desc.operand_count == 1 ) { size_t prev = tbl->size(); if ( match_to_impl( a->rhs, b->rhs, tbl, same_depth ) ) return true; tbl->resize( prev ); return false; } // If both sides match, return true. // size_t prev = tbl->size(); if ( match_to_impl( a->lhs, b->lhs, tbl, same_depth ) && match_to_impl( a->rhs, b->rhs, tbl, same_depth ) ) return true; tbl->resize( prev ); // Fail if not commutative. // if ( !desc.is_commutative ) return false; // Check in reverse as well and return the final result. // prev = tbl->size(); if ( match_to_impl( a->rhs, b->lhs, tbl, same_depth ) && match_to_impl( a->lhs, b->rhs, tbl, same_depth ) ) return true; tbl->resize( prev ); return false; } std::optional expression::match_to( const expression& other, bool same_depth ) const { auto a = make_local_reference( this ); auto b = make_local_reference( &other ); // If variable, fail if other expression is not a variable of same size. // if ( is_variable() ) { if ( b->is_variable() && size() == b->size() ) return a->uid != b->uid ? expression::uid_relation_table{ { a, b } } : expression::uid_relation_table{}; } // Otherwise, create the relation table and call into real implementation. // else { fast_uid_relation_table fast_tbl; if ( match_to_impl( ( expression::reference& )a, ( expression::reference& )b, &fast_tbl, same_depth ) ) return expression::uid_relation_table{ fast_tbl.begin(), fast_tbl.end() }; } return std::nullopt; } // Checks if the expression given is a subexpression of the current one. // bool expression::contains( const expression& o ) const { // Depth based traversal fast-path. // if ( depth < o.depth ) return false; // If same depth, redirect to is_identical. // if ( depth == o.depth ) return is_identical( o ); // Check child-nodes where possible. // return rhs && ( rhs->contains( o ) || ( lhs && lhs->contains( o ) ) ); } // Converts to human-readable format. // std::string expression::to_string() const { // Redirect to operator descriptor. // if ( is_expression() ) return get_op_desc().to_string( lhs ? lhs->to_string() : "", rhs->to_string() ); // Handle constants, invalids and variables. // if ( is_constant() ) return format::hex( value.get().value() ); if ( is_variable() ) return uid.to_string(); return "null"; } // Implement some helpers to conditionally copy. // expression_reference& expression_reference::resize( bitcnt_t new_size, bool signed_cast, bool no_explicit ) { if ( new_size != get()->size() ) own()->resize( new_size, signed_cast, no_explicit ); return *this; } expression_reference expression_reference::resize( bitcnt_t new_size, bool signed_cast, bool no_explicit ) const { return std::move( make_copy( *this ).resize( new_size, signed_cast, no_explicit ) ); } expression_reference& expression_reference::simplify( bool prettify, bool* out ) { bool simplified; if ( is_valid() && ( prettify || !get()->simplify_hint ) ) simplified = simplify_expression( *this, prettify ); else simplified = false; if ( out ) *out = simplified; return *this; } expression_reference expression_reference::simplify( bool prettify, bool* out ) const { return std::move( make_copy( *this ).simplify( prettify, out ) ); } expression_reference& expression_reference::make_lazy() { if ( !get()->is_lazy ) own()->is_lazy = true; return *this; } expression_reference expression_reference::make_lazy() const { return std::move( make_copy( *this ).make_lazy() ); } // Forward declared redirects for internal use cases. // hash_t expression_reference::hash() const { return get()->hash(); } bool expression_reference::is_simple() const { return get()->simplify_hint; } void expression_reference::update( bool auto_simplify ) { own()->update( auto_simplify ); } // Equivalence check. // bool expression_reference::equals( const expression& exp ) const { return !is_valid() ? !exp : get()->equals( exp ); } bool expression_reference::is_identical( const expression& exp ) const { return !is_valid() ? !exp : get()->is_identical( exp ); } // Implemented for sinkhole use. // bitcnt_t expression_reference::size() const { return is_valid() ? get()->size() : 0; } // Implemented for logger use. // std::string expression_reference::to_string() const { return is_valid() ? get()->to_string() : "null"; } };