// -*- C++ -*- (c) 2012-2014 Petr Rockai #include #include #include #include #include #include #include #include #include #if ( LLVM_VERSION_MAJOR == 3 && LLVM_VERSION_MINOR < 2 ) #include #else #include #define TargetData DataLayout #endif #include #include #include #ifdef _WIN32 #include #define isnan _isnan #endif #ifndef DIVINE_LLVM_EXECUTION_H #define DIVINE_LLVM_EXECUTION_H namespace llvm { template class generic_gep_type_iterator; typedef generic_gep_type_iterator gep_type_iterator; } namespace divine { namespace llvm { using ::llvm::dyn_cast; using ::llvm::cast; using ::llvm::isa; using ::llvm::ICmpInst; using ::llvm::FCmpInst; using ::llvm::AtomicRMWInst; namespace Intrinsic = ::llvm::Intrinsic; using ::llvm::CallSite; using ::llvm::Type; using brick::types::Unit; template< int, int > struct Eq; template< int i > struct Eq< i, i > { typedef int Yes; }; template< typename As, typename A, typename B > list::Cons< As *, B > consPtr( A *a, B b ) { return list::Cons< As *, B >( reinterpret_cast< As * >( a ), b ); } template< typename X > struct UnPtr {}; template< typename X > struct UnPtr< X * > { typedef X T; }; template< int I, typename Cons > auto deconsptr( Cons c ) -> typename UnPtr< typename list::ConsAt< I >::template T< Cons > >::T & { return *c.template get< I >(); } #define MATCH(l, ...) template< typename F, typename X, \ typename = typename Eq< l, X::length >::Yes > \ auto match( F &f, X x ) -> decltype( f( __VA_ARGS__ ) ) \ { static_cast< void >( x ); return f( __VA_ARGS__ ); } MATCH( 0, /**/ ) MATCH( 1, deconsptr< 0 >( x ) ) MATCH( 2, deconsptr< 1 >( x ), deconsptr< 0 >( x ) ) MATCH( 3, deconsptr< 2 >( x ), deconsptr< 1 >( x ), deconsptr< 0 >( x ) ) MATCH( 4, deconsptr< 3 >( x ), deconsptr< 2 >( x ), deconsptr< 1 >( x ), deconsptr< 0 >( x ) ) #undef MATCH template< typename T > auto rem( brick::types::Preferred, T a, T b ) -> decltype( a % b ) { return a % b; } template< typename T > auto rem( brick::types::NotPreferred, T a, T b ) -> decltype( std::fmod( a, b ) ) { return std::fmod( a, b ); } /* Dummy implementation of a ControlContext, useful for Evaluator for * control-flow-free snippets (like ConstantExpr). */ struct ControlContext { bool jumped; Choice choice; void enter( int ) { ASSERT_UNREACHABLE( "" ); } void leave() { ASSERT_UNREACHABLE( "" ); } machine::Frame &frame( int /*depth*/ = 0 ) { ASSERT_UNREACHABLE( "" ); } machine::Flags &flags() { ASSERT_UNREACHABLE( "" ); } void problem( Problem::What, Pointer = Pointer() ) { ASSERT_UNREACHABLE( "" ); } PC &pc() { ASSERT_UNREACHABLE( "" ); } int new_thread( PC, Maybe< Pointer >, MemoryFlag ) { ASSERT_UNREACHABLE( "" ); } int stackDepth() { ASSERT_UNREACHABLE( "" ); } int threadId() { ASSERT_UNREACHABLE( "" ); } int threadCount() { ASSERT_UNREACHABLE( "" ); } void switch_thread( int ) { ASSERT_UNREACHABLE( "" ); } void dump() {} }; template< typename X > struct Dummy { static X &v() { ASSERT_UNREACHABLE( "" ); } }; /* * A relatively efficient evaluator for the LLVM instruction set. Current * semantics are derived from C++ semantics, which is not always entirely * correct. TBD. * * EvalContext provides access to the register file and memory. It needs to * provide at least TargetData, dereference( Value ), dereference( Pointer ) * and malloc( int ). * * BEWARE. The non-const references in argument lists of Implementations are * necessary to restrict matching to exact same types, since const references * and lvalues allow conversions through temporaries to happen. This is * undesirable. Please watch out to not modify right-hand values accidentally. */ template < typename EvalContext, typename ControlContext > struct Evaluator { ProgramInfo &info; EvalContext &econtext; ControlContext &ccontext; Evaluator( ProgramInfo &i, EvalContext &e, ControlContext &c ) : info( i ), econtext( e ), ccontext( c ) {} bool is_signed; typedef ::llvm::Instruction LLVMInst; ProgramInfo::Instruction *_instruction; ProgramInfo::Instruction &instruction() { return *_instruction; } using ValueRefs = brick::data::SmallVector< ValueRef, 4 >; using MemoryFlags = brick::data::SmallVector< MemoryFlag, 4 >; ValueRefs values, result; brick::data::SmallVector< MemoryFlags, 4 > flags; struct Implementation { typedef Unit T; Evaluator< EvalContext, ControlContext > *_evaluator; Evaluator< EvalContext, ControlContext > &evaluator() { return *_evaluator; } ProgramInfo::Instruction i() { return _evaluator->instruction(); } EvalContext &econtext() { return _evaluator->econtext; } ControlContext &ccontext() { return _evaluator->ccontext; } ::llvm::TargetData &TD() { return econtext().TD; } MemoryFlag resultFlag( MemoryFlags ) { return MemoryFlag::Data; } }; template< typename X > char *dereference( X x ) { return econtext.dereference( x ); } template< typename... X > static Unit declcheck( X... ) { return Unit(); } std::vector< int > pointerId( bool nonempty = false ) { auto r = econtext.pointerId( cast< ::llvm::Instruction >( instruction().op ) ); if ( nonempty && !r.size() ) return std::vector< int >{ 0 }; return r; } void checkPointsTo() { // TODO: check aa_use as well auto &r = instruction().result(); if ( instruction().result().type == ProgramInfo::Value::Void ) return; auto mflag = econtext.memoryflag( r ); if ( !mflag.valid() || mflag.get() != MemoryFlag::HeapPointer ) return; /* nothing to do */ auto p = *reinterpret_cast< Pointer * >( econtext.dereference( r ) ); if ( p.null() ) return; int actual = econtext.pointerId( p ); if ( !actual ) return; auto expected = pointerId(); for ( auto i: expected ) if ( i == actual ) return; ccontext.problem( Problem::PointsToViolated, p ); } /******** Arithmetic & comparisons *******/ struct BinaryOperator : Implementation { MemoryFlag resultFlag( MemoryFlags x ) { if ( x[1] == MemoryFlag::Uninitialised || x[2] == MemoryFlag::Uninitialised ) return MemoryFlag::Uninitialised; if ( x[1] == MemoryFlag::HeapPointer || x[2] == MemoryFlag::HeapPointer ) return MemoryFlag::HeapPointer; return MemoryFlag::Data; } }; struct Arithmetic : BinaryOperator { static const int arity = 3; template< typename X = int > auto operator()( X &r = Dummy< X >::v(), X &a = Dummy< X >::v(), X &b = Dummy< X >::v() ) -> decltype( declcheck( a + b, rem( brick::types::Preferred(), a, b ) ) ) { switch( this->i().opcode ) { case LLVMInst::FAdd: case LLVMInst::Add: r = a + b; return Unit(); case LLVMInst::FSub: case LLVMInst::Sub: r = a - b; return Unit(); case LLVMInst::FMul: case LLVMInst::Mul: r = a * b; return Unit(); case LLVMInst::FDiv: case LLVMInst::SDiv: case LLVMInst::UDiv: if ( !b ) this->ccontext().problem( Problem::DivisionByZero ); r = b ? (a / b) : 0; return Unit(); case LLVMInst::FRem: case LLVMInst::URem: case LLVMInst::SRem: if ( !b ) this->ccontext().problem( Problem::DivisionByZero ); r = b ? rem( brick::types::Preferred(), a, b ) : 0; return Unit(); default: ASSERT_UNREACHABLE_F( "invalid arithmetic opcode %d", this->i().opcode ); } } }; struct ArithmeticWithOverflow : BinaryOperator { static const int arity = 4; int operation; ArithmeticWithOverflow( int op ) : operation( op ) {} template< typename X = int, typename Y = uint8_t > auto operator()( X &r = Dummy< X >::v(), Y &overflow = Dummy< Y >::v(), X &a = Dummy< X >::v(), X &b = Dummy< X >::v() ) -> decltype( declcheck( a + b, overflow = true ) ) { overflow = false; switch ( operation ) { case Intrinsic::umul_with_overflow: ASSERT( !this->evaluator().is_signed ); r = a * b; if ( a > 0 && b > 0 && ( r < a || r < b ) ) overflow = true; return Unit(); case Intrinsic::uadd_with_overflow: ASSERT( !this->evaluator().is_signed ); r = a + b; if ( r < a || r < b ) overflow = true; return Unit(); case Intrinsic::usub_with_overflow: if ( a < b ) overflow = true; r = a - b; return Unit(); case Intrinsic::smul_with_overflow: case Intrinsic::sadd_with_overflow: case Intrinsic::ssub_with_overflow: ASSERT( this->evaluator().is_signed ); default: ASSERT_UNREACHABLE_F( "invalid .with.overflow operation %d", operation ); } } }; struct Bitwise : BinaryOperator { static const int arity = 3; template< typename X = int > auto operator()( X &r = Dummy< X >::v(), X &a = Dummy< X >::v(), X &b = Dummy< X >::v() ) -> decltype( declcheck( a | b ) ) { switch( this->i().opcode ) { case LLVMInst::And: r = a & b; return Unit(); case LLVMInst::Or: r = a | b; return Unit(); case LLVMInst::Xor: r = a ^ b; return Unit(); case LLVMInst::Shl: r = a << b; return Unit(); case LLVMInst::AShr: // XXX? case LLVMInst::LShr: r = a >> b; return Unit(); default: ASSERT_UNREACHABLE_F( "invalid bitwise opcode %d", this->i().opcode ); } } }; struct Select : Implementation { int _selected; static const int arity = 4; template< typename R = int, typename C = int > auto operator()( R &r = Dummy< R >::v(), C &a = Dummy< C >::v(), R &b = Dummy< R >::v(), R &c = Dummy< R >::v() ) -> decltype( declcheck( r = a ? b : c ) ) { _selected = a ? 1 : 2; r = a ? b : c; return Unit(); } MemoryFlag resulFlag( MemoryFlags x ) { return x[ _selected ]; } }; struct ICmp : Implementation { static const int arity = 3; template< typename R = uint8_t, typename X = int > auto operator()( R &r = Dummy< R >::v(), X &a = Dummy< X >::v(), X &b = Dummy< X >::v() ) -> typename std::enable_if< sizeof( R ) == 1, decltype( declcheck( r = a < b ) ) >::type { auto p = dyn_cast< ICmpInst >( this->i().op )->getPredicate(); switch ( p ) { case ICmpInst::ICMP_EQ: r = a == b; return Unit(); case ICmpInst::ICMP_NE: r = a != b; return Unit(); case ICmpInst::ICMP_ULT: case ICmpInst::ICMP_SLT: r = a < b; return Unit(); case ICmpInst::ICMP_UGT: case ICmpInst::ICMP_SGT: r = a > b; return Unit(); case ICmpInst::ICMP_ULE: case ICmpInst::ICMP_SLE: r = a <= b; return Unit(); case ICmpInst::ICMP_UGE: case ICmpInst::ICMP_SGE: r = a >= b; return Unit(); default: ASSERT_UNREACHABLE_F( "unexpected icmp op %d", p ); } } }; struct FCmp : Implementation { static const int arity = 3; template< typename R = uint8_t, typename X = int > auto operator()( R &r = Dummy< R >::v(), X &a = Dummy< X >::v(), X &b = Dummy< X >::v() ) -> typename std::enable_if< sizeof( R ) == 1, decltype( declcheck( r = isnan( a ) && isnan( b ) ) ) >::type { auto p = dyn_cast< FCmpInst >( this->i().op )->getPredicate(); switch ( p ) { case FCmpInst::FCMP_FALSE: r = false; return Unit(); case FCmpInst::FCMP_TRUE: r = true; return Unit(); case FCmpInst::FCMP_ORD: r = !isnan( a ) && !isnan( b ); return Unit(); case FCmpInst::FCMP_UNO: r = isnan( a ) || isnan( b ); return Unit(); case FCmpInst::FCMP_UEQ: case FCmpInst::FCMP_UNE: case FCmpInst::FCMP_UGE: case FCmpInst::FCMP_ULE: case FCmpInst::FCMP_ULT: case FCmpInst::FCMP_UGT: if ( isnan( a ) || isnan( b ) ) { r = true; return Unit(); } break; case FCmpInst::FCMP_OEQ: case FCmpInst::FCMP_ONE: case FCmpInst::FCMP_OGE: case FCmpInst::FCMP_OLE: case FCmpInst::FCMP_OLT: case FCmpInst::FCMP_OGT: if ( isnan( a ) || isnan( b ) ) { r = false; return Unit(); } break; default: ASSERT_UNREACHABLE_F( "unexpected fcmp op %d", p ); } switch ( p ) { case FCmpInst::FCMP_OEQ: case FCmpInst::FCMP_UEQ: r = a == b; return Unit(); case FCmpInst::FCMP_ONE: case FCmpInst::FCMP_UNE: r = a != b; return Unit(); case FCmpInst::FCMP_OLT: case FCmpInst::FCMP_ULT: r = a < b; return Unit(); case FCmpInst::FCMP_OGT: case FCmpInst::FCMP_UGT: r = a > b; return Unit(); case FCmpInst::FCMP_OLE: case FCmpInst::FCMP_ULE: r = a <= b; return Unit(); case FCmpInst::FCMP_OGE: case FCmpInst::FCMP_UGE: r = a >= b; return Unit(); default: ASSERT_UNREACHABLE_F( "unexpected fcmp op %d", p ); } } }; /******** Register access & conversion *******/ struct Convert : Implementation { static const int arity = 2; template< typename L = int, typename R = L > auto operator()( L &l = Dummy< L >::v(), R &r = Dummy< R >::v() ) -> decltype( declcheck( l = L( r ) ) ) { l = L( r ); return Unit(); } MemoryFlag resultFlag( MemoryFlags x ) { return x[1]; } }; void implement_bitcast() { auto r = memcopy( instruction().operand( 0 ), instruction().result(), instruction().result().width ); ASSERT_EQ( r, Problem::NoProblem ); static_cast< void >( r ); } template< typename _T > struct Get : Implementation { static const int arity = 1; typedef _T T; template< typename X = T > auto operator()( X &l = Dummy< X >::v() ) -> decltype( static_cast< T >( l ) ) { return static_cast< T >( l ); } MemoryFlag resultFlag( MemoryFlags x ) { return x[0]; } }; template< typename _T > struct Set : Implementation { typedef _T Arg; Arg v; MemoryFlag _flag; static const int arity = 1; template< typename X = Arg > auto operator()( X &r = Dummy< X >::v() ) -> decltype( declcheck( static_cast< X >( v ) ) ) { r = static_cast< X >( v ); return Unit(); } MemoryFlag resultFlag( MemoryFlags ) { return _flag; } Set( Arg v, MemoryFlag f = MemoryFlag::Data ) : v( v ), _flag( f ) {} }; typedef Get< bool > IsTrue; typedef Get< int > GetInt; typedef Set< int > SetInt; /******** Memory access & conversion ********/ using AtOffset = std::pair< int, Type * >; AtOffset compositeOffset( Type *t ) { return std::make_pair( 0, t ); } template< typename... Args > AtOffset compositeOffset( Type *t, int index, Args... indices ) { int offset; if (::llvm::StructType *STy = dyn_cast< ::llvm::StructType >( t )) { const ::llvm::StructLayout *SLO = econtext.TD.getStructLayout(STy); offset = SLO->getElementOffset( index ); } else { const ::llvm::SequentialType *ST = cast< ::llvm::SequentialType >( t ); offset = index * econtext.TD.getTypeAllocSize( ST->getElementType() ); } auto r = compositeOffset( cast< ::llvm::CompositeType >( t )->getTypeAtIndex( index ), indices... ); return std::make_pair( r.first + offset, r.second ); } int compositeOffsetFromInsn( Type *t, int current, int end ) { if ( current == end ) return 0; int index = withValues( GetInt(), instruction().operand( current ) ); auto r = compositeOffset( t, index ); return r.first + compositeOffsetFromInsn( r.second, current + 1, end ); } struct GetElement : Implementation { static const int arity = 2; Unit operator()( Pointer &r = Dummy< Pointer >::v(), Pointer &p = Dummy< Pointer >::v() ) { r = p + this->evaluator().compositeOffsetFromInsn( this->i().op->getOperand(0)->getType(), 1, this->i().values.size() - 1 ); return Unit(); } MemoryFlag resultFlag( MemoryFlags x ) { ASSERT_LEQ( 2U, x.size() ); return x[1]; } }; void implement_store() { Pointer to = withValues( Get< Pointer >(), instruction().operand( 1 ) ); auto mf = econtext.memoryflag( instruction().operand( 1 ) ); if ( mf.valid() ) ASSERT( econtext.validate( to, mf.get() == MemoryFlag::HeapPointer ) ); if ( auto r = memcopy( instruction().operand( 0 ), to, instruction().operand( 0 ).width ) ) ccontext.problem( r, to ); } void implement_load() { Pointer from = withValues( Get< Pointer >(), instruction().operand( 0 ) ); auto mf = econtext.memoryflag( instruction().operand( 0 ) ); if ( mf.valid() ) ASSERT( econtext.validate( from, mf.get() == MemoryFlag::HeapPointer ) ); if ( auto r = memcopy( from, instruction().result(), instruction().result().width ) ) ccontext.problem( r, from ); } template < typename From, typename To, typename FromC, typename ToC > Problem::What memcopy( From f, To t, int bytes, FromC &fromc, ToC &toc ) { return llvm::memcopy( f, t, bytes, fromc, toc ); } template < typename From, typename To > Problem::What memcopy( From f, To t, int bytes ) { return memcopy( f, t, bytes, econtext, econtext ); } struct Memcpy : Implementation { static const int arity = 4; template< typename I = int > auto operator()( Pointer &ret = Dummy< Pointer >::v(), Pointer &dest = Dummy< Pointer >::v(), Pointer &src = Dummy< Pointer >::v(), I &nmemb = Dummy< I >::v() ) -> decltype( declcheck( memcpy( Dummy< void * >::v(), Dummy< void * >::v(), nmemb ) ) ) { if ( auto problem = this->evaluator().memcopy( src, dest, nmemb ) ) this->ccontext().problem( problem ); ret = dest; return Unit(); } /* copy status from dest */ MemoryFlag resultFlag( MemoryFlags x ) { return x[1]; } }; struct Switch : Implementation { static const int arity = 1; template< typename X = int > Unit operator()( X &condition = Dummy< X >::v() ) { for ( int o = 2; o < int( this->i().values.size() ) - 1; o += 2 ) { X &v = *reinterpret_cast< X * >( this->evaluator().dereference( this->i().operand( o ) ) ); if ( v == condition ) { this->evaluator().jumpTo( this->i().operand( o + 1 ) ); return Unit(); } } this->evaluator().jumpTo( this->i().operand( 1 ) ); return Unit(); } MemoryFlag resultFlag( MemoryFlags x ) { return x[0]; } }; struct CmpXchg : Implementation { static const int arity = 4; template< typename X = int > auto operator()( X &result = Dummy< X >::v(), Pointer &p = Dummy< Pointer >::v(), X &expected = Dummy< X >::v(), X &changed = Dummy< X >::v() ) -> decltype( declcheck( expected == changed ) ) { X ¤t = *reinterpret_cast< X * >( this->evaluator().dereference( p ) ); result = current; if ( current == expected ) current = changed; return Unit(); } MemoryFlag resultFlag( MemoryFlags ) { return MemoryFlag::Data; /* nothing */ } }; struct AtomicRMW : Implementation { static const int arity = 3; template< typename X = int > auto operator()( X &result = Dummy< X >::v(), Pointer &p = Dummy< Pointer >::v(), X &x = Dummy< X >::v() ) -> decltype( declcheck( x + x, x ^ x ) ) { X &v = *reinterpret_cast< X * >( this->evaluator().dereference( p ) ); result = v; switch (dyn_cast< AtomicRMWInst >( this->i().op )->getOperation()) { case AtomicRMWInst::Xchg: v = x; return Unit(); case AtomicRMWInst::Add: v = v + x; return Unit(); case AtomicRMWInst::Sub: v = v - x; return Unit(); case AtomicRMWInst::And: v = v & x; return Unit(); case AtomicRMWInst::Nand: v = ~v & x; return Unit(); case AtomicRMWInst::Or: v = v | x; return Unit(); case AtomicRMWInst::Xor: v = v ^ x; return Unit(); case AtomicRMWInst::Max: case AtomicRMWInst::UMax: v = std::max( v, x ); return Unit(); case AtomicRMWInst::UMin: case AtomicRMWInst::Min: v = std::min( v, x ); return Unit(); case AtomicRMWInst::BAD_BINOP: ASSERT_UNREACHABLE_F( "bad binop in atomicrmw" ); } return Unit(); } MemoryFlag resultFlag( MemoryFlags ) { return MemoryFlag::Data; /* nothing */ } }; void implement_alloca() { ::llvm::AllocaInst *I = cast< ::llvm::AllocaInst >( instruction().op ); Type *ty = I->getAllocatedType(); int count = withValues( GetInt(), instruction().operand( 0 ) ); int size = econtext.TD.getTypeAllocSize(ty); /* possibly aggregate */ unsigned alloc = std::max( 1, count * size ); Pointer &p = *reinterpret_cast< Pointer * >( dereference( instruction().result() ) ); p = econtext.malloc( alloc, pointerId( true )[0] ); econtext.memoryflag( instruction().result() ).set( MemoryFlag::HeapPointer ); } void implement_extractvalue() { auto r = memcopy( ValueRef( instruction().operand( 0 ), 0, -1, compositeOffsetFromInsn( instruction().op->getOperand(0)->getType(), 1, instruction().values.size() - 1 ) ), instruction().result(), instruction().result().width ); ASSERT_EQ( r, Problem::NoProblem ); static_cast< void >( r ); } void implement_insertvalue() { /* NB. Implemented against spec, UNTESTED! */ /* first copy the original */ auto r = memcopy( instruction().operand( 0 ), instruction().result(), instruction().result().width ); ASSERT_EQ( r, Problem::NoProblem ); /* write the new value over the selected field */ r = memcopy( instruction().operand( 1 ), ValueRef( instruction().result(), 0, -1, compositeOffsetFromInsn( instruction().op->getOperand(0)->getType(), 2, instruction().values.size() - 1 ) ), instruction().operand( 1 ).width ); ASSERT_EQ( r, Problem::NoProblem ); static_cast< void >( r ); } /******** Control flow ********/ void jumpTo( ProgramInfo::Value v ) { PC to = *reinterpret_cast< PC * >( dereference( v ) ); jumpTo( to ); } void jumpTo( PC to ) { if ( ccontext.pc().function != to.function ) ASSERT_UNREACHABLE_F( "Can't deal with cross-function jumps." ); switchBB( to ); } void maybe_die() { /* kill off everything if the main thread died */ if ( ccontext.threadId() == 0 && ccontext.stackDepth() == 0 ) { for ( int i = 1; i < ccontext.threadCount(); ++i ) { ccontext.switch_thread( i ); while ( ccontext.stackDepth() ) ccontext.leave(); } } } void implement_ret() { if ( ccontext.stackDepth() == 1 ) { ccontext.leave(); return maybe_die(); } auto caller = info.instruction( ccontext.frame( 1 ).pc ); if ( instruction().values.size() > 1 ) /* return value */ memcopy( instruction().operand( 0 ), ValueRef( caller.result(), 1 ), caller.result().width ); ccontext.leave(); if ( isa< ::llvm::InvokeInst >( caller.op ) ) jumpTo( caller.operand( -2 ) ); } void implement_br() { if ( instruction().values.size() == 2 ) jumpTo( instruction().operand( 0 ) ); else { auto mflag = econtext.memoryflag( instruction().operand( 0 ) ); if ( mflag.valid() && mflag.get() == MemoryFlag::Uninitialised ) ccontext.problem( Problem::Uninitialised ); if ( withValues( IsTrue(), instruction().operand( 0 ) ) ) jumpTo( instruction().operand( 2 ) ); else jumpTo( instruction().operand( 1 ) ); } } void implement_indirectBr() { Pointer target = withValues( Get< Pointer >(), instruction().operand( 0 ) ); jumpTo( *reinterpret_cast< PC * >( dereference( target ) ) ); } /* * Two-phase PHI handler. We do this because all of the PHI nodes must be * executed atomically, reading their inputs before any of the results are * updated. Not doing this can cause problems if the PHI nodes depend on * other PHI nodes for their inputs. If the input PHI node is updated * before it is read, incorrect results can happen. */ void switchBB( PC target ) { PC origin = ccontext.pc(); target.masked = origin.masked; ccontext.pc() = target; ccontext.jumped = true; _instruction = &info.instruction( ccontext.pc() ); ASSERT( instruction().op ); if ( !isa< ::llvm::PHINode >( instruction().op ) ) return; // Nothing fancy to do machine::Frame &original = ccontext.frame(); int framesize = original.framesize( info ); std::vector tmp; tmp.resize( sizeof( machine::Frame ) + framesize ); machine::Frame © = *reinterpret_cast< machine::Frame* >( &tmp[0] ); copy = ccontext.frame(); std::copy( original.memory(), original.memory() + framesize, copy.memory() ); while ( ::llvm::PHINode *PN = dyn_cast< ::llvm::PHINode >( instruction().op ) ) { /* TODO use operands directly, avoiding valuemap lookup */ auto v = info.valuemap[ PN->getIncomingValueForBlock( cast< ::llvm::Instruction >( info.instruction( origin ).op )->getParent() ) ]; FrameContext copyctx( info, copy ); memcopy( v, instruction().result(), v.width, econtext, copyctx ); ccontext.pc().instruction ++; _instruction = &info.instruction( ccontext.pc() ); } std::copy( copy.memory(), copy.memory() + framesize, original.memory() ); } /* * NB. Internally, we only deal with positive frameid's and they are always * relative to the *topmost* frame. The userspace doesn't know the stack * depth though, so we need to transform the frameid. */ bool transform_frameid( int &frameid ) { if ( frameid > 0 ) // L - (L - id) - 1 = L - L + id - 1 = id - 1 frameid = ccontext.stackDepth() - frameid; else frameid = std::min( frameid == INT_MIN ? INT_MAX : -frameid, ccontext.stackDepth() ); if ( frameid > ccontext.stackDepth() ) { ccontext.problem( Problem::InvalidArgument ); return false; } return true; } typedef std::vector< ProgramInfo::Value > Values; int find_invoke() { for ( int fid = 1; fid < ccontext.stackDepth(); ++fid ) { auto target = info.instruction( ccontext.frame( fid ).pc ); if ( isa< ::llvm::InvokeInst >( target.op ) ) return fid; } return ccontext.stackDepth(); /* past the end */ } void unwind( int frameid, const Values &args ) { if ( !transform_frameid( frameid ) ) return; PC target_pc; /* we have a place to return to */ if ( frameid < ccontext.stackDepth() ) { auto target = &info.instruction( ccontext.frame( frameid ).pc ); if ( isa< ::llvm::InvokeInst >( target->op ) ) { target_pc = withValues( Get< PC >(), ValueRef( target->operand( -1 ), frameid ) ); target = &info.instruction( target_pc ); } int copied = 0; for ( auto arg : args ) { memcopy( arg, ValueRef( target->result(), frameid, -1, copied ), arg.width ); copied += arg.width; } } while ( frameid ) { /* jump out */ ccontext.leave(); -- frameid; } if ( target_pc.function ) jumpTo( target_pc ); maybe_die(); } struct LPInfo { bool cleanup; Pointer person; std::vector< std::pair< int, Pointer > > items; LPInfo() : cleanup( false ) {} }; LPInfo getLPInfo( PC pc, int frameid = 0 ) { LPInfo r; auto lpi = info.instruction( pc ); if ( auto LPI = dyn_cast< ::llvm::LandingPadInst >( lpi.op ) ) { r.cleanup = LPI->isCleanup(); r.person = withValues( Get< Pointer >(), ValueRef( lpi.operand( 0 ) ) ); for ( int i = 0; i < int( LPI->getNumClauses() ); ++i ) { if ( LPI->isFilter( i ) ) { r.items.push_back( std::make_pair( -1, Pointer() ) ); } else { auto tag = withValues( Get< Pointer >(), ValueRef( lpi.operand( i + 1 ), frameid ) ); auto type_id = info.function( pc ).typeID( tag ); r.items.push_back( std::make_pair( type_id, tag ) ); } } } return r; } Pointer make_lpinfo( int frameid ) { if ( !transform_frameid( frameid ) ) return Pointer(); if ( frameid == ccontext.stackDepth() ) return Pointer(); LPInfo lp; PC pc = ccontext.frame( frameid ).pc; auto insn = info.instruction( pc ); if ( isa< ::llvm::InvokeInst >( insn.op ) ) { pc = withValues( Get< PC >(), ValueRef( insn.operand( -1 ), frameid ) ); lp = getLPInfo( pc, frameid ); } /* Well, this is not very pretty. */ int psize = info.TD.getPointerSize(); Pointer r = econtext.malloc( 8 + psize + (4 + psize) * lp.items.size(), 0 ); auto c = econtext.dereference( r ); *reinterpret_cast< int32_t * >( c ) = lp.cleanup; c += 4; *reinterpret_cast< int32_t * >( c ) = lp.items.size(); c += 4; *reinterpret_cast< Pointer * >( c ) = lp.person; c+= psize; for ( auto p : lp.items ) { *reinterpret_cast< int32_t * >( c ) = p.first; c += 4; *reinterpret_cast< Pointer * >( c ) = p.second; c+= psize; } return r; } struct PointerBlock { int count; Pointer ptr[0]; }; void implement_intrinsic( int id ) { switch ( id ) { case Intrinsic::vastart: case Intrinsic::trap: case Intrinsic::vaend: case Intrinsic::vacopy: ASSERT_UNIMPLEMENTED(); /* TODO */ case Intrinsic::eh_typeid_for: { auto tag = withValues( Get< Pointer >(), instruction().operand( 0 ) ); int type_id = info.function( ccontext.pc() ).typeID( tag ); withValues( Set< int >( type_id ), instruction().result() ); return; } case Intrinsic::smul_with_overflow: case Intrinsic::sadd_with_overflow: case Intrinsic::ssub_with_overflow: is_signed = true; case Intrinsic::umul_with_overflow: case Intrinsic::uadd_with_overflow: case Intrinsic::usub_with_overflow: { auto res = instruction().result(); res.width = instruction().operand( 0 ).width; res.type = ProgramInfo::Value::Integer; auto over = instruction().result(); over.width = 1; // hmmm over.type = ProgramInfo::Value::Integer; over.offset += compositeOffset( instruction().op->getType(), 1 ).first; withValues( ArithmeticWithOverflow( id ), res, over, instruction().operand( 0 ), instruction().operand( 1 ) ); return; } case Intrinsic::stacksave: case Intrinsic::stackrestore: { std::vector< ProgramInfo::Instruction > allocas; std::vector< Pointer > ptrs; auto &f = info.functions[ ccontext.pc().function ]; for ( auto &i : f.instructions ) if ( i.opcode == LLVMInst::Alloca ) { allocas.push_back( i ); auto ptr = withValues( Get< Pointer >(), i.result() ); if ( !ptr.null() ) ptrs.push_back( ptr ); } if ( id == Intrinsic::stacksave ) { Pointer r = econtext.malloc( sizeof( PointerBlock ) + ptrs.size() * sizeof( Pointer ), 0 ); auto &c = *reinterpret_cast< PointerBlock * >( econtext.dereference( r ) ); c.count = ptrs.size(); int i = 0; for ( auto ptr : ptrs ) { econtext.memoryflag( r + sizeof( int ) + sizeof( Pointer ) * i ).set( MemoryFlag::HeapPointer ); c.ptr[ i++ ] = ptr; } withValues( Set< Pointer >( r, MemoryFlag::HeapPointer ), instruction().result() ); } else { // stackrestore Pointer r = withValues( Get< Pointer >(), instruction().operand( 0 ) ); auto &c = *reinterpret_cast< PointerBlock * >( econtext.dereference( r ) ); for ( auto ptr : ptrs ) { bool retain = false; for ( int i = 0; i < c.count; ++i ) if ( ptr == c.ptr[i] ) { retain = true; break; } if ( !retain ) econtext.free( ptr ); } } return; } default: /* We lowered everything else in buildInfo. */ instruction().op->dump(); ASSERT_UNREACHABLE_F( "unexpected intrinsic %d", id ); } } void implement_builtin() { switch( instruction().builtin ) { case BuiltinChoice: { auto &c = ccontext.choice; c.options = withValues( GetInt(), instruction().operand( 0 ) ); for ( int i = 1; i < int( instruction().values.size() ) - 2; ++i ) c.p.push_back( withValues( GetInt(), instruction().operand( i ) ) ); if ( !c.p.empty() && int( c.p.size() ) != c.options ) { ccontext.problem( Problem::InvalidArgument ); c.p.clear(); } return; } case BuiltinAssert: if ( !withValues( GetInt(), instruction().operand( 0 ) ) ) ccontext.problem( Problem::Assert ); return; case BuiltinProblem: ccontext.problem( Problem::What( withValues( GetInt(), instruction().operand( 0 ) ) ), withValues( Get< Pointer >(), instruction().operand( 1 ) ) ); return; case BuiltinAp: ccontext.flags().ap |= (1 << withValues( GetInt(), instruction().operand( 0 ) )); return; case BuiltinMask: ccontext.pc().masked = true; return; case BuiltinUnmask: ccontext.pc().masked = false; return; case BuiltinInterrupt: return; /* an observable noop, see interpreter.h */ case BuiltinGetTID: withValues( SetInt( ccontext.threadId() ), instruction().result() ); return; case BuiltinNewThread: { PC entry = withValues( Get< PC >(), instruction().operand( 0 ) ); Pointer arg = withValues( Get< Pointer >(), instruction().operand( 1 ) ); int tid = ccontext.new_thread( entry, Maybe< Pointer >::Just( arg ), econtext.memoryflag( instruction().operand( 1 ) ).get() ); withValues( SetInt( tid ), instruction().result() ); return; } case BuiltinMalloc: { int size = withValues( Get< int >(), instruction().operand( 0 ) ); if ( size >= ( 2 << Pointer::offsetSize ) ) { ccontext.problem( Problem::InvalidArgument, Pointer() ); size = 0; } Pointer result = size ? econtext.malloc( size, pointerId( true )[0] ) : Pointer(); withValues( Set< Pointer >( result, MemoryFlag::HeapPointer ), instruction().result() ); return; } case BuiltinFree: { Pointer v = withValues( Get< Pointer >(), instruction().operand( 0 ) ); if ( !econtext.free( v ) ) ccontext.problem( Problem::InvalidArgument, v ); return; } case BuiltinHeapObjectSize: { Pointer v = withValues( Get< Pointer >(), instruction().operand( 0 ) ); if ( !econtext.dereference( v ) ) ccontext.problem( Problem::InvalidArgument, v ); else withValues( Set< int >( econtext.pointerSize( v ) ), instruction().result() ); return; } case BuiltinMemcpy: implement( Memcpy(), 4 ); return; case BuiltinVaStart: { auto f = info.functions[ ccontext.pc().function ]; memcopy( f.values[ f.argcount ], instruction().result(), instruction().result().width ); return; } case BuiltinUnwind: return unwind( withValues( GetInt(), instruction().operand( 0 ) ), Values( instruction().values.begin() + 2, instruction().values.end() - 1 ) ); case BuiltinLandingPad: withValues( Set< Pointer >( make_lpinfo( withValues( GetInt(), instruction().operand( 0 ) ) ), MemoryFlag::HeapPointer ), instruction().result() ); return; default: ASSERT_UNREACHABLE_F( "unknown builtin %d", instruction().builtin ); } } void implement_call() { CallSite CS( cast< ::llvm::Instruction >( instruction().op ) ); ::llvm::Function *F = CS.getCalledFunction(); if ( F && F->isDeclaration() ) { auto id = F->getIntrinsicID(); if ( id != Intrinsic::not_intrinsic ) return implement_intrinsic( id ); if ( instruction().builtin ) return implement_builtin(); } bool invoke = isa< ::llvm::InvokeInst >( instruction().op ); auto pc = withValues( Get< PC >(), instruction().operand( invoke ? -3 : -1 ) ); if ( !pc.function ) { ccontext.problem( Problem::InvalidArgument ); /* function 0 does not exist */ return; } const auto &function = info.function( pc ); /* report problems with the call before pushing the new stackframe */ if ( !function.vararg && int( CS.arg_size() ) > function.argcount ) ccontext.problem( Problem::InvalidArgument ); /* too many actual arguments */ ccontext.enter( pc.function ); /* push a new frame */ ccontext.jumped = true; /* Copy arguments to the new frame. */ for ( int i = 0; i < int( CS.arg_size() ) && i < int( function.argcount ); ++i ) memcopy( ValueRef( instruction().operand( i ), 1 ), function.values[ i ], function.values[ i ].width ); if ( function.vararg ) { int size = 0; for ( int i = function.argcount; i < int( CS.arg_size() ); ++i ) size += instruction().operand( i ).width; Pointer vaptr = size ? econtext.malloc( size, 0 ) : Pointer(); withValues( Set< Pointer >( vaptr, MemoryFlag::HeapPointer ), function.values[ function.argcount ] ); for ( int i = function.argcount; i < int( CS.arg_size() ); ++i ) { auto op = instruction().operand( i ); memcopy( ValueRef( op, 1 ), vaptr, op.width ); vaptr = vaptr + int( op.width ); } } ASSERT( !isa< ::llvm::PHINode >( instruction().op ) ); } /******** Dispatch ********/ void run() { is_signed = false; switch ( instruction().opcode ) { case LLVMInst::GetElementPtr: implement( GetElement(), 2 ); break; case LLVMInst::Select: implement< Select >(); break; case LLVMInst::ICmp: switch ( dyn_cast< ICmpInst >( instruction().op )->getPredicate() ) { case ICmpInst::ICMP_SLT: case ICmpInst::ICMP_SGT: case ICmpInst::ICMP_SLE: case ICmpInst::ICMP_SGE: is_signed = true; default: ; } implement< ICmp >(); break; case LLVMInst::FCmp: implement< FCmp >(); break; case LLVMInst::ZExt: case LLVMInst::FPExt: case LLVMInst::UIToFP: case LLVMInst::FPToUI: case LLVMInst::PtrToInt: case LLVMInst::IntToPtr: case LLVMInst::FPTrunc: case LLVMInst::Trunc: implement< Convert >(); break; case LLVMInst::Br: implement_br(); break; case LLVMInst::IndirectBr: implement_indirectBr(); break; case LLVMInst::Switch: implement( Switch(), 2 ); break; case LLVMInst::Call: case LLVMInst::Invoke: implement_call(); break; case LLVMInst::Ret: implement_ret(); break; case LLVMInst::SExt: case LLVMInst::SIToFP: case LLVMInst::FPToSI: is_signed = true; implement< Convert >(); break; case LLVMInst::BitCast: implement_bitcast(); break; case LLVMInst::Load: implement_load(); break; case LLVMInst::Store: implement_store(); break; case LLVMInst::Alloca: implement_alloca(); break; case LLVMInst::AtomicCmpXchg: implement< CmpXchg >(); break; case LLVMInst::AtomicRMW: implement< AtomicRMW >(); break; case LLVMInst::ExtractValue: implement_extractvalue(); break; case LLVMInst::InsertValue: implement_insertvalue(); break; case LLVMInst::SDiv: case LLVMInst::SRem: is_signed = true; case LLVMInst::FAdd: case LLVMInst::Add: case LLVMInst::FSub: case LLVMInst::Sub: case LLVMInst::FMul: case LLVMInst::Mul: case LLVMInst::FDiv: case LLVMInst::UDiv: case LLVMInst::FRem: case LLVMInst::URem: implement< Arithmetic >(); break; case LLVMInst::AShr: is_signed = true; case LLVMInst::And: case LLVMInst::Or: case LLVMInst::Xor: case LLVMInst::Shl: case LLVMInst::LShr: implement< Bitwise >(); break; case LLVMInst::Unreachable: ccontext.problem( Problem::UnreachableExecuted ); break; case LLVMInst::Resume: /* unwind down to the next invoke instruction in the stack */ unwind( -find_invoke(), Values( { instruction().operand( 0 ) } ) ); break; case LLVMInst::LandingPad: break; /* nothing to do, handled by the unwinder */ case LLVMInst::Fence: /* noop until we have reordering simulation */ break; default: instruction().op->dump(); ASSERT_UNREACHABLE_F( "unknown opcode %d", instruction().opcode ); } /* invoke and call are checked when ret executes */ if ( instruction().opcode != LLVMInst::Call && instruction().opcode != LLVMInst::Invoke ) checkPointsTo(); } template< typename Fun > typename Fun::T implement( brick::types::NotPreferred, Fun = Fun(), ... ) { instruction().op->dump(); ASSERT_UNREACHABLE_F( "bad parameters for opcode %d", instruction().opcode ); } template< typename Fun, typename I, typename Cons, typename = decltype( match( std::declval< Fun & >(), std::declval< Cons & >() ) ) > auto implement( brick::types::Preferred, Fun fun, I i, I e, Cons list ) -> typename std::enable_if< Fun::arity == Cons::length, typename Fun::T >::type { ASSERT( i == e ); brick::_assert::unused( i, e ); fun._evaluator = this; auto retval = match( fun, list ); auto mflag = econtext.memoryflag( result.back() ); if ( mflag.valid() ) mflag.set( fun.resultFlag( flags.back() ) ); flags.pop_back(); result.pop_back(); return retval; } template< typename Fun, typename I, typename Cons, typename = decltype( match( std::declval< Fun & >(), std::declval< Cons & >() ) ) > auto implement( brick::types::Preferred, Fun fun, I i, I e, Cons list ) -> typename std::enable_if< (Fun::arity > Cons::length), typename Fun::T >::type { typedef ProgramInfo::Value Value; brick::types::Preferred p; ASSERT( i != e ); ValueRef v = *i++; char *mem = dereference( v ); auto mflag = econtext.memoryflag( v ); flags.back().push_back( mflag.valid() ? mflag.get() : MemoryFlag::Data ); switch ( v.v.type ) { case Value::Integer: if ( is_signed ) switch ( v.v.width ) { case 1: return implement( p, fun, i, e, consPtr< int8_t >( mem, list ) ); case 4: return implement( p, fun, i, e, consPtr< int32_t >( mem, list ) ); case 2: return implement( p, fun, i, e, consPtr< int16_t >( mem, list ) ); case 8: return implement( p, fun, i, e, consPtr< int64_t >( mem, list ) ); } else switch ( v.v.width ) { case 1: return implement( p, fun, i, e, consPtr< uint8_t >( mem, list ) ); case 4: return implement( p, fun, i, e, consPtr< uint32_t >( mem, list ) ); case 2: return implement( p, fun, i, e, consPtr< uint16_t >( mem, list ) ); case 8: return implement( p, fun, i, e, consPtr< uint64_t >( mem, list ) ); } ASSERT_UNREACHABLE_F( "Wrong integer width %d", v.v.width ); case Value::Pointer: case Value::Alloca: return implement( p, fun, i, e, consPtr< Pointer >( mem, list ) ); case Value::CodePointer: return implement( p, fun, i, e, consPtr< PC >( mem, list ) ); case Value::Float: switch ( v.v.width ) { case sizeof(float): return implement( p, fun, i, e, consPtr< float >( mem, list ) ); case sizeof(double): return implement( p, fun, i, e, consPtr< double >( mem, list ) ); } case Value::Aggregate: return implement( p, fun, i, e, consPtr< void >( mem, list ) ); case Value::Void: return implement( p, fun, i, e, list ); /* ignore void items */ } ASSERT_UNREACHABLE_F( "unexpected value type %d", v.v.type ); } template< typename Fun, int Limit = 3 > typename Fun::T implement( Fun fun = Fun(), int limit = 0 ) { flags.push_back( MemoryFlags() ); auto i = instruction().values.begin(), e = limit ? i + limit : instruction().values.end(); result.push_back( instruction().result() ); return implement( brick::types::Preferred(), fun, i, e, list::Nil() ); } template< typename Fun > typename Fun::T _withValues( Fun fun ) { flags.push_back( MemoryFlags() ); return implement< Fun >( brick::types::Preferred(), fun, values.begin(), values.end(), list::Nil() ); } template< typename Fun, typename... Values > typename Fun::T _withValues( Fun fun, ValueRef v, Values... vs ) { if ( values.empty() ) result.push_back( v ); values.push_back( v ); return _withValues< Fun >( fun, vs... ); } template< typename Fun, typename... Values > typename Fun::T withValues( Fun fun, Values... vs ) { values.clear(); return _withValues< Fun >( fun, vs... ); } }; } } #endif