// -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*-

/*
 * (c) 2012-2016 Petr Ročkai <code@fixp.eu>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <divine/vm/eval.hpp>
#include <divine/vm/program.hpp>
#include <llvm/IR/GlobalVariable.h>
#include <llvm/IR/Module.h>

using namespace divine::vm;
using llvm::dyn_cast;
using llvm::isa;

using PEval = Eval< Program, typename Program::Context, value::Void >;

HeapPointer Program::s2hptr( Program::Slot v, int offset )
{
    PEval eval( *this, _ccontext );
    return eval.s2ptr( v, offset );
}

void Program::initConstant( Program::Slot v, llvm::Value *V )
{
    bool done = true;

    ASSERT_EQ( v.location, Slot::Constant );

    auto &heap = _ccontext.heap();
    PEval eval( *this, _ccontext );
    auto ptr = value::Pointer( eval.s2ptr( v ) );
    auto C = dyn_cast< llvm::Constant >( V );

    if ( auto GA = dyn_cast< llvm::GlobalAlias >( V ) ) 
        C = GA->getBaseObject();

    if ( !valuemap.count( V ) )
    {
        V->dump();
        UNREACHABLE( "value not allocated during initialisation" );
    }

    if ( _doneinit.count( V ) )
        return;

    // skip functions since they only have a 'personality' as their operand and
    // it does not need to be initialized before the function pointer itself;
    // additionally, some transformations might add a 'personality' attribute
    // pointing to the function itself, turning this into an infinite loop

    if ( C && !isa< llvm::Function >( C ) )
        for ( int i = 0; i < int( C->getNumOperands() ); ++i )
        {
            auto op = C->getOperand( i );
            if ( !_doneinit.count( op ) )
            {
                done = false;
            }
            _toinit.emplace_back( [=]{
                ASSERT( valuemap.find(op) != valuemap.end() );
                initConstant( valuemap[ op ].slot, op );
            } );
        }

    if ( auto GV = dyn_cast< llvm::GlobalVariable >( V ) )
    {
        ASSERT( valuemap.count( GV->getInitializer() ) );
        SlotRef location;
        if ( GV->isConstant() )
            location = valuemap[ GV->getInitializer() ];
        else {
            location = globalmap[ GV ];
        }
        heap.write_shift( ptr, value::Pointer( s2ptr( location ) ) );
        _doneinit.insert( GV );
    }

    if ( !done )
    {
        _toinit.emplace_back( [=]{ initConstant( v, V ); } );
        return;
    }

    if ( auto CE = dyn_cast< llvm::ConstantExpr >( V ) )
    {
        Instruction comp;
        comp.op = CE;
        comp.opcode = CE->getOpcode();
        comp.subcode = initSubcode( CE );
        comp.values.push_back( v ); /* the result comes first */
        for ( int i = 0; i < int( C->getNumOperands() ); ++i ) // now the operands
        {
            if ( !valuemap.count( C->getOperand( i ) ) )
            {
                C->dump();
                C->getOperand( i )->dump();
                UNREACHABLE( "oops" );
            }
            comp.values.push_back( valuemap[ C->getOperand( i ) ].slot );
        }
        eval._instruction = &comp;
        eval.dispatch(); /* compute and write out the value */
    }
    else if ( isa< llvm::UndefValue >( V ) )
    {
        /* nothing to do (for now; we don't track uninitialised values yet) */
    }
    else if ( auto I = dyn_cast< llvm::ConstantInt >( V ) )
    {
        const uint8_t *mem = reinterpret_cast< const uint8_t * >( I->getValue().getRawData() );
        std::for_each( mem, mem + v.size(), [&]( char c )
                       {
                           heap.write_shift( ptr, value::Int< 8 >( c ) );
                       } );
    }
    else if ( auto FP = dyn_cast< llvm::ConstantFP >( V ) )
    {
        switch ( v.size() ) {
            case sizeof( float ):
                heap.write_shift( ptr, value::Float< float >( FP->getValueAPF().convertToFloat() ) );
                break;
            case sizeof( double ):
                heap.write_shift( ptr, value::Float< double >( FP->getValueAPF().convertToDouble() ) );
                break;
            case sizeof( long double ): {
                bool lossy;
                llvm::APFloat x = FP->getValueAPF();
                x.convert( llvm::APFloat::IEEEdouble, llvm::APFloat::rmNearestTiesToEven, &lossy );
                /* FIXME? */
                heap.write_shift( ptr, value::Float< long double >( x.convertToDouble() ) );
                break;
            }
            default: UNREACHABLE( "non-double, non-float FP constant" );
        }
    }
    else if ( isa< llvm::ConstantPointerNull >( V ) )
    {
        heap.write_shift( ptr, value::Pointer( nullPointer() ) );
    }
    else if ( isa< llvm::ConstantAggregateZero >( V ) )
    {
        for ( int i = 0; i < v.size(); ++i )
            heap.write_shift( ptr, value::Int< 8 >( 0 ) );
    }
    else if ( C && isCodePointer( C ) )
    {
        heap.write_shift( ptr, value::Pointer( getCodePointer( C ) ) );
    }
    else if ( isCodePointer( V ) )
    {
        heap.write_shift( ptr, value::Pointer( getCodePointer( V ) ) );
    }
    else if ( isa< llvm::ConstantArray >( V ) || isa< llvm::ConstantStruct >( V ) )
    {
        int offset = 0;
        for ( int i = 0; i < int( C->getNumOperands() ); ++i )
        {
            if ( auto CS = dyn_cast< llvm::ConstantStruct >( C ) )
            {
                const llvm::StructLayout *SLO = TD.getStructLayout(CS->getType());
                offset = SLO->getElementOffset( i );
            }

            ASSERT( valuemap.count( C->getOperand( i ) ) );
            auto sub = valuemap[ C->getOperand( i ) ].slot;
            heap.copy( eval.s2ptr( sub ), eval.s2ptr( v, offset ), sub.size() );
            offset += sub.size();
            ASSERT_LEQ( offset, v.size() );
        }
        /* and padding at the end ... */
    }
    else if ( auto CDS = dyn_cast< llvm::ConstantDataSequential >( V ) )
    {
        ASSERT_EQ( v.size(), CDS->getNumElements() * CDS->getElementByteSize() );
        const char *raw = CDS->getRawDataValues().data();
        std::for_each( raw, raw + v.size(), [&]( char c )
                       {
                           heap.write_shift( ptr, value::Int< 8 >( c ) );
                       } );
    }
    else if ( dyn_cast< llvm::ConstantVector >( V ) )
    {
        NOT_IMPLEMENTED();
    }
    else if ( isa< llvm::GlobalVariable >( V ) );
    else
    {
        V->dump();
        if ( V->getType()->isPointerTy() )
            UNREACHABLE( "an unexpected non-zero constant pointer" );
        else
            UNREACHABLE( "unknown constant type" );
    }

    ASSERT( done );
    _doneinit.insert( V );
}