/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Author: pgess <v.melnychenko@xreate.org>
 *
 * compilepass.cpp
 */

/**
 * \file    compilepass.h
 * \brief   Compilation pass
 */

#include "compilepass.h"
#include "clasplayer.h"
#include <ast.h>
#include "llvmlayer.h"

#include "query/containers.h"
#include "query/context.h"
#include "compilation/containers.h"
#include "compilation/latecontextcompiler2.h"
#include "ExternLayer.h"
#include "pass/adhocpass.h"
#include "compilation/targetinterpretation.h"
#include "pass/versionspass.h"
#include "compilation/scopedecorators.h"
#include "compilation/adhocfunctiondecorator.h"
#include "compilation/operators.h"
#include "analysis/typeinference.h"

#include <boost/optional.hpp>
#include <memory>
#include <iostream>

using namespace std;
using namespace llvm;

//TODO use Scope<TargetLlvm>

    //SECTIONTAG late-context FunctionDecorator

namespace xreate{namespace context{

/** \brief Late Context enabled decorator for IFunctionUnit
 * \extends IFunctionUnit
 */
template<class Parent>
class LateContextFunctionDecorator : public Parent {
public:
    LateContextFunctionDecorator(ManagedFnPtr f, CompilePass* p)
    : Parent(f, p), contextCompiler(this, p) {
    }

protected:
    std::vector<llvm::Type*> prepareArguments() {
        std::vector<llvm::Type*>&& arguments = Parent::prepareArguments();

        size_t sizeLateContextDemand = contextCompiler.getFunctionDemandSize();
        if (sizeLateContextDemand) {
            llvm::Type* ty32 = llvm::Type::getInt32Ty(llvm::getGlobalContext());
            llvm::Type* tyDemand = llvm::ArrayType::get(ty32, sizeLateContextDemand);

            arguments.push_back(tyDemand);
        }

        return arguments;
    }

    llvm::Function::arg_iterator prepareBindings() {
        llvm::Function::arg_iterator fargsI = Parent::prepareBindings();

        size_t sizeLateContextDemand = contextCompiler.getFunctionDemandSize();
        if (sizeLateContextDemand) {
            fargsI->setName("latecontext");
            contextCompiler.rawContextArgument = &*fargsI;
            ++fargsI;
        }

        return fargsI;
    }

public:
    context::LateContextCompiler2 contextCompiler;
};

}} //end of namespace xreate::context

namespace xreate { namespace compilation{

std::string
BasicFunctionUnit::prepareName(){
    AST* ast = IFunctionUnit::pass->man->root;

    string name = ast->getFunctionSpecializations(IFunctionUnit::function->__name).size() > 1 ?
            IFunctionUnit::function->__name + std::to_string(IFunctionUnit::function.id()) :
            IFunctionUnit::function->__name;

    return name;
}

std::vector<llvm::Type*>
BasicFunctionUnit::prepareArguments() {
    LLVMLayer* llvm = IFunctionUnit::pass->man->llvm;
    AST* ast = IFunctionUnit::pass->man->root;
    CodeScope* entry = IFunctionUnit::function->__entry;
    std::vector<llvm::Type*> signature;

    std::transform(entry->__bindings.begin(), entry->__bindings.end(), std::inserter(signature, signature.end()),
            [llvm, ast, entry](const std::string & arg)->llvm::Type* {
                assert(entry->__identifiers.count(arg));

                ScopedSymbol argid{entry->__identifiers.at(arg), versions::VERSION_NONE};
                return llvm->toLLVMType(ast->expandType(entry->__declarations.at(argid).type));
            });

    return signature;
}

llvm::Type*
BasicFunctionUnit::prepareResult() {
    LLVMLayer* llvm = IFunctionUnit::pass->man->llvm;
    AST* ast = IFunctionUnit::pass->man->root;
    CodeScope* entry = IFunctionUnit::function->__entry;

    return llvm->toLLVMType(ast->expandType(entry->__declarations.at(ScopedSymbol::RetSymbol).type));
}

llvm::Function::arg_iterator
BasicFunctionUnit::prepareBindings() {
    CodeScope* entry = IFunctionUnit::function->__entry;
    ICodeScopeUnit* entryCompilation = IFunctionUnit::getScopeUnit(entry);
    llvm::Function::arg_iterator fargsI = IFunctionUnit::raw->arg_begin();

    for (std::string &arg : entry->__bindings) {
        ScopedSymbol argid{entry->__identifiers[arg], versions::VERSION_NONE};

        entryCompilation->bindArg(&*fargsI, argid);
        fargsI->setName(arg);
        ++fargsI;
    }

    return fargsI;
}

    //DEBT compiler rigidly depends on exact definition of DefaultFunctionUnit
typedef context::LateContextFunctionDecorator<
        adhoc::AdhocFunctionDecorator<
        BasicFunctionUnit>> DefaultFunctionUnit;

ICodeScopeUnit::ICodeScopeUnit(const CodeScope* const codeScope, IFunctionUnit* f, CompilePass* compilePass)
: pass(compilePass), function(f), scope(codeScope), currentBlockRaw(nullptr) {
}

llvm::Value*
CallStatementRaw::operator()(std::vector<llvm::Value *>&& args, const std::string& hintDecl) {
    llvm::Function* calleeInfo = dyn_cast<llvm::Function>(__callee);

    if (calleeInfo) {
        auto argsFormal = calleeInfo->args();

        int pos = 0;
        //SECTIONTAG types/convert function ret value
        for (auto argFormal = argsFormal.begin(); argFormal != argsFormal.end(); ++argFormal, ++pos) {
            args[pos] = typeinference::doAutomaticTypeConversion(args[pos], argFormal->getType(), llvm->builder);
        }
    }

    return llvm->builder.CreateCall(__calleeTy, __callee, args, hintDecl);
}

    //DESABLEDFEATURE implement inlining
class CallStatementInline : public ICallStatement {
public:
    CallStatementInline(IFunctionUnit* caller, IFunctionUnit* callee, LLVMLayer* l)
    : __caller(caller), __callee(callee), llvm(l) {
    }

    llvm::Value* operator()(std::vector<llvm::Value *>&& args, const std::string& hintDecl) {
        //TOTEST inlining
        //            CodeScopeUnit* entryCompilation = outer->getScopeUnit(function->__entry);
        //            for(int i=0, size = args.size(); i<size; ++i) {
        //                entryCompilation->bindArg(args.at(i), string(entryCompilation->scope->__bindings.at(i)));
        //            }
        //
        //
        //            return entryCompilation->compile();

        return nullptr;
    }

private:
    IFunctionUnit* __caller;
    IFunctionUnit* __callee;
    LLVMLayer* llvm;

    bool isInline() {
        // Symbol ret = Symbol{0, function->__entry};
        // bool flagOnTheFly = SymbolAttachments::get<IsImplementationOnTheFly>(ret, false);
        //TODO consider inlining
        return false;
    }
};

BasicCodeScopeUnit::BasicCodeScopeUnit(const CodeScope* const codeScope, IFunctionUnit* f, CompilePass* compilePass)
: ICodeScopeUnit(codeScope, f, compilePass) {
}

llvm::Value*
BasicCodeScopeUnit::processSymbol(const Symbol& s, std::string hintRetVar) {
    Expression declaration = CodeScope::getDefinition(s);
    const CodeScope* scope = s.scope;
    ICodeScopeUnit* scopeExternal = ICodeScopeUnit::function->getScopeUnit(scope);

    llvm::Value* resultRaw;
    if (scopeExternal == this){
        resultRaw = process(declaration, hintRetVar);
        currentBlockRaw = pass->man->llvm->builder.GetInsertBlock();

    } else {
        assert(scopeExternal->currentBlockRaw);

        llvm::BasicBlock* blockOwn = pass->man->llvm->builder.GetInsertBlock();
        pass->man->llvm->builder.SetInsertPoint(scopeExternal->currentBlockRaw);
        resultRaw = scopeExternal->processSymbol(s, hintRetVar);
        pass->man->llvm->builder.SetInsertPoint(blockOwn);
    }

    return resultRaw;
}

//TASK Isolate out context functionalty in decorator
//TOTEST static late context decisions
//TOTEST dynamic late context decisions
ICallStatement*
BasicCodeScopeUnit::findFunction(const Expression& opCall) {
    const std::string& calleeName = opCall.getValueString();
    LLVMLayer* llvm = pass->man->llvm;
    ClaspLayer* clasp = pass->man->clasp;
    DefaultFunctionUnit* function = dynamic_cast<DefaultFunctionUnit*> (this->function);
    context::ContextQuery* queryContext = pass->queryContext;

    const std::list<ManagedFnPtr>& specializations = pass->man->root->getFunctionSpecializations(calleeName);

    //if no specializations registered - check external function
    if (specializations.size() == 0) {
        llvm::Function* external = llvm->layerExtern->lookupFunction(calleeName);

        llvm::outs() << "Debug/External function: " << calleeName;
        external->getType()->print(llvm::outs(), true);
        llvm::outs() << "\n";

        return new CallStatementRaw(external, llvm);
    }

    //no decisions required
    if (specializations.size() == 1) {
        if (!specializations.front()->guardContext.isValid()) {
            return new CallStatementRaw(pass->getFunctionUnit(specializations.front())->compile(), llvm);
        }
    }

    //TODO move dictSpecialization over to a separate function in order to perform cache, etc.
    //prepare specializations dictionary
    std::map<Expression, ManagedFnPtr> dictSpecializations;

    boost::optional<ManagedFnPtr> variantDefault;
    boost::optional<ManagedFnPtr> variant;

    for (const ManagedFnPtr& f : specializations) {
        const Expression& guard = f->guardContext;

        //default case:
        if (!guard.isValid()) {
            variantDefault = f;
            continue;
        }

        assert(dictSpecializations.emplace(guard, f).second && "Found several identical specializations");
    }

    //check static context
    ScopePacked scopeCaller = clasp->pack(this->scope);
    const string atomSpecialization = "specialization";
    const Expression topicSpecialization(Operator::CALL,{(Atom<Identifier_t>(string(atomSpecialization))),
        Expression(Operator::CALL,
        {Atom<Identifier_t>(string(calleeName))}),
        Atom<Number_t>(scopeCaller)});

    const context::Decisions& decisions = queryContext->getFinalDecisions(scopeCaller);
    if (decisions.count(topicSpecialization)) {
        variant = dictSpecializations.at(decisions.at(topicSpecialization));
    }

    //TODO check only demand for this particular topic.
    size_t sizeDemand = function->contextCompiler.getFunctionDemandSize();

    //decision made if static context found or no late context exists(and there is default variant)
    bool flagHasStaticDecision = variant || (variantDefault && !sizeDemand);

    //if no late context exists
    if (flagHasStaticDecision) {
        IFunctionUnit* calleeUnit = pass->getFunctionUnit(variant ? *variant : *variantDefault);

        //inlining possible based on static decision only
        //        if (calleeUnit->isInline()) {
        //            return new CallStatementInline(function, calleeUnit);
        //        }

        return new CallStatementRaw(calleeUnit->compile(), llvm);
    }

    //require default variant if no static decision made
    assert(variantDefault);

    llvm::Function* functionVariantDefault = this->pass->getFunctionUnit(*variantDefault)->compile();
    llvm::Value* resultFn = function->contextCompiler.findFunction(calleeName, functionVariantDefault, scopeCaller);
    llvm::PointerType *resultPTy = cast<llvm::PointerType>(resultFn->getType());
    llvm::FunctionType *resultFTy = cast<llvm::FunctionType>(resultPTy->getElementType());
    return new CallStatementRaw(resultFn, resultFTy, llvm);
}



//DISABLEDFEATURE transformations
//    if (pass->transformations->isAcceptable(expr)){
//        return pass->transformations->transform(expr, result, ctx);
//    }

llvm::Value*
BasicCodeScopeUnit::process(const Expression& expr, const std::string& hintVarDecl) {
#define DEFAULT(x) (hintVarDecl.empty()? x: hintVarDecl)
    llvm::Value *left;
    llvm::Value *right;
    LLVMLayer& l = *pass->man->llvm;
    xreate::compilation::AdvancedInstructions instructions = xreate::compilation::AdvancedInstructions({this, function, pass});

    switch (expr.op) {
        case Operator::SUB: case Operator::MUL:
        case Operator::DIV: case Operator::EQU: case Operator::LSS:
        case Operator::GTR: case Operator::NE: case Operator::LSE:
        case Operator::GTE:

            assert(expr.__state == Expression::COMPOUND);
            assert(expr.operands.size() == 2);

            left = process(expr.operands[0]);
            right = process(expr.operands[1]);

            //SECTIONTAG types/convert binary operation
            right = typeinference::doAutomaticTypeConversion(right, left->getType(), l.builder);
            break;

        default:;
    }

    switch (expr.op) {
        case Operator::ADD:
        {
            left = process(expr.operands[0]);
            Context context{this, function, pass};

            llvm::Value* resultSU = StructUpdate::add(expr.operands[0], left, expr.operands[1], context, DEFAULT("tmp_add"));
            if (resultSU) return resultSU;

            right = process(expr.operands[1]);

            llvm::Value* resultAddPA = pointerarithmetic::PointerArithmetic::add(left, right, context, DEFAULT("tmp_add"));
            if (resultAddPA) {
                return resultAddPA;
            }

            return l.builder.CreateAdd(left, right, DEFAULT("tmp_add"));
            break;
        }

        case Operator::SUB:
            return l.builder.CreateSub(left, right, DEFAULT("tmp_sub"));
            break;

        case Operator::MUL:
            return l.builder.CreateMul(left, right, DEFAULT("tmp_mul"));
            break;

        case Operator::DIV:
            return l.builder.CreateSDiv(left, right, DEFAULT("tmp_div"));
            break;

        case Operator::EQU:
            if (left->getType()->isIntegerTy()) return l.builder.CreateICmpEQ(left, right, DEFAULT("tmp_equ"));
            if (left->getType()->isFloatingPointTy()) return l.builder.CreateFCmpOEQ(left, right, DEFAULT("tmp_equ"));
            break;

        case Operator::NE:
            return l.builder.CreateICmpNE(left, right, DEFAULT("tmp_ne"));
            break;

        case Operator::LSS:
            return l.builder.CreateICmpSLT(left, right, DEFAULT("tmp_lss"));
            break;

        case Operator::LSE:
            return l.builder.CreateICmpSLE(left, right, DEFAULT("tmp_lse"));
            break;

        case Operator::GTR:
            return l.builder.CreateICmpSGT(left, right, DEFAULT("tmp_gtr"));
            break;

        case Operator::GTE:
            return l.builder.CreateICmpSGE(left, right, DEFAULT("tmp_gte"));
            break;

        case Operator::NEG:
            left = process(expr.operands[0]);
            return l.builder.CreateNeg(left, DEFAULT("tmp_neg"));
            break;

        case Operator::CALL:
        {
            assert(expr.__state == Expression::COMPOUND);
            shared_ptr<ICallStatement> callee(findFunction(expr));
            const std::string& nameCallee = expr.getValueString();

            //prepare arguments
            std::vector<llvm::Value *> args;
            args.reserve(expr.operands.size());

            std::transform(expr.operands.begin(), expr.operands.end(), std::inserter(args, args.end()),
                [this](const Expression & operand) {
                    return process(operand);
                }
            );

            ScopePacked outerScopeId = pass->man->clasp->pack(this->scope);

            //TASK a) refactor CALL/ADHOC/find function
            //SECTIONTAG late-context propagation arg
            size_t calleeDemandSize = pass->queryContext->getFunctionDemand(nameCallee).size();
            if (calleeDemandSize) {
                DefaultFunctionUnit* function = dynamic_cast<DefaultFunctionUnit*> (this->function);
                llvm::Value* argLateContext = function->contextCompiler.compileContextArgument(nameCallee, outerScopeId);
                args.push_back(argLateContext);
            }

            return (*callee)(move(args), DEFAULT("res_" + nameCallee));
        }

        case Operator::IF:
        {
            return instructions.compileIf(expr, DEFAULT("tmp_if"));
        }

        case Operator::SWITCH:
        {
            return instructions.compileSwitch(expr, DEFAULT("tmp_switch"));
        }

        case Operator::LOOP_CONTEXT:
        {
            assert(false);
            return nullptr;
            //return instructions.compileLoopContext(expr, DEFAULT("tmp_loop"));
        }

        case Operator::LOGIC_AND:
        {
            assert(expr.operands.size() == 1);
            return process(expr.operands[0]);
        }

        case Operator::LIST:
        {
            return instructions.compileListAsSolidArray(expr, DEFAULT("tmp_list"));
        };

        case Operator::LIST_RANGE:
        {
            assert(false); //no compilation phase for a range list
            //  return InstructionList(this).compileConstantArray(expr, l, hintRetVar);
        };

        case Operator::LIST_NAMED:
        {
            typedef Expanded<TypeAnnotation> ExpandedType;

            ExpandedType tyStructLiteral = l.ast->getType(expr);

            const std::vector<string> fieldsFormal = (tyStructLiteral.get().__operator == TypeOperator::CUSTOM) ?
                    l.layerExtern->getStructFields(l.layerExtern->lookupType(tyStructLiteral.get().__valueCustom))
                    : tyStructLiteral.get().fields;

            std::map<std::string, size_t> indexFields;
            for (size_t i = 0, size = fieldsFormal.size(); i < size; ++i) {
                indexFields.emplace(fieldsFormal[i], i);
            }

            llvm::StructType* tyLiteralRaw = llvm::cast<llvm::StructType>(l.toLLVMType(tyStructLiteral));
            llvm::Value* record = llvm::UndefValue::get(tyLiteralRaw);

            for (size_t i = 0; i < expr.operands.size(); ++i) {
                const Expression& operand = expr.operands.at(i);
                unsigned int fieldId = indexFields.at(expr.bindings.at(i));
                llvm::Value* result = process(operand);
                assert(result);
                record = l.builder.CreateInsertValue(record, result, llvm::ArrayRef<unsigned>({fieldId}));
            }

            return record;
        };

        case Operator::MAP:
        {
            assert(expr.blocks.size());
            return instructions.compileMapSolidOutput(expr, DEFAULT("map"));
        };

        case Operator::FOLD:
        {
            return instructions.compileFold(expr, DEFAULT("fold"));
        };

        case Operator::FOLD_INF:
        {
            return instructions.compileFoldInf(expr, DEFAULT("fold"));
        };

        case Operator::INDEX:
        {
            //TASK allow multiindex compilation
            assert(expr.operands.size() == 2);
            assert(expr.operands[0].__state == Expression::IDENT);

            const std::string& hintIdent = expr.operands[0].getValueString();
            Symbol s = Attachments::get<IdentifierSymbol>(expr.operands[0]);
            const ExpandedType& t2 = pass->man->root->getType(expr.operands[0]);

            llvm::Value* aggr = processSymbol(s, hintIdent);

            switch (t2.get().__operator) {
                case TypeOperator::LIST_NAMED: case TypeOperator::CUSTOM:
                {
                    std::string idxField;
                    const Expression& idx = expr.operands.at(1);
                    switch (idx.__state) {

                            //named struct field
                        case Expression::STRING:
                            idxField = idx.getValueString();
                            break;

                            //anonymous struct field
                        case Expression::NUMBER:
                            idxField = to_string((int) idx.getValueDouble());
                            break;

                        default:
                            assert(false && "Wrong index for a struct");
                    }

                    return instructions.compileStructIndex(aggr, t2, idxField);
                };

                case TypeOperator::LIST:
                {
                    std::vector<llvm::Value*> indexes;
                    std::transform(++expr.operands.begin(), expr.operands.end(), std::inserter(indexes, indexes.end()),
                            [this] (const Expression & op) {
                                return process(op);
                            }
                    );

                    return instructions.compileArrayIndex(aggr, indexes, DEFAULT(string("el_") + hintIdent));
                };

                default:
                    assert(false);
            }
        };

            //SECTIONTAG adhoc actual compilation
            //TODO a) make sure that it's correct: function->adhocImplementation built for Entry scope and used in another scope
        case Operator::ADHOC:
        {
            DefaultFunctionUnit* function = dynamic_cast<DefaultFunctionUnit*> (this->function);
            assert(function->adhocImplementation && "Adhoc implementation not found");
            const Expression& comm = adhoc::AdhocExpression(expr).getCommand();

            CodeScope* scope = function->adhocImplementation->getCommandImplementation(comm);
            ICodeScopeUnit* unitScope = function->getScopeUnit(scope);

            //SECTIONTAG types/convert ADHOC ret convertation
            llvm::Type* resultTy = l.toLLVMType(pass->man->root->expandType(function->adhocImplementation->getResultType()));
            return typeinference::doAutomaticTypeConversion(unitScope->compile(), resultTy, l.builder);
        };

        case Operator::CALL_INTRINSIC:
        {
            const std::string op = expr.getValueString();

            if (op == "copy") {
                llvm::Value* result = process(expr.getOperands().at(0));

                auto decoratorVersions = Decorators<VersionsScopeDecoratorTag>::getInterface(this);
                llvm::Value* storage = decoratorVersions->processIntrinsicInit(result->getType());
                decoratorVersions->processIntrinsicCopy(result, storage);

                return l.builder.CreateLoad(storage, hintVarDecl);
            }

            assert(false && "undefined intrinsic");
        }

        case Operator::VARIANT:
        {
            const ExpandedType& typVariant = pass->man->root->getType(expr);
            llvm::Type* typVariantRaw = l.toLLVMType(typVariant);
            llvm::Type* typIdRaw = llvm::cast<llvm::StructType>(typVariantRaw)->getElementType(0);

            uint64_t id = expr.getValueDouble();
            llvm::Value* variantRaw = llvm::UndefValue::get(typVariantRaw);
            variantRaw = l.builder.CreateInsertValue(variantRaw, llvm::ConstantInt::get(typIdRaw, id), llvm::ArrayRef<unsigned>({0}));

            const bool flagDoReference =  expr.operands.size();
            if (flagDoReference){
                const ExpandedType& subtyp = ExpandedType(typVariant->__operands.at(id));
                llvm::Type* subtypRaw = l.toLLVMType(subtyp);
                Attachments::put<TypeInferred>(expr.operands.at(0), subtyp);
                llvm::Value* subtypValue = process(expr.operands.at(0));

                llvm::Type* typStorageRaw = llvm::cast<llvm::StructType>(typVariantRaw)->getElementType(1);
                llvm::Value* addrAsStorage = l.builder.CreateAlloca(typStorageRaw);
                llvm::Value* addrAsSubtyp =  l.builder.CreateBitOrPointerCast(addrAsStorage, subtypRaw->getPointerTo());

                l.builder.CreateStore(subtypValue, addrAsSubtyp);
                llvm::Value* storageRaw = l.builder.CreateLoad(typStorageRaw, addrAsStorage);
                variantRaw = l.builder.CreateInsertValue(variantRaw, storageRaw, llvm::ArrayRef<unsigned>({1}));
            }

            return variantRaw;
        }

        case Operator::SWITCH_VARIANT:
        {
            return instructions.compileSwitchVariant(expr, DEFAULT("tmpswitch"));
        }

        case Operator::NONE:
            assert(expr.__state != Expression::COMPOUND);

            switch (expr.__state) {
                case Expression::IDENT:
                {
                    Symbol s = Attachments::get<IdentifierSymbol>(expr);
                    return processSymbol(s, expr.getValueString());
                }

                case Expression::NUMBER:
                {
                    llvm::Type* typConst;

                    if (expr.type.isValid()) {
                        typConst = l.toLLVMType(pass->man->root->getType(expr));

                    } else {
                        typConst = llvm::Type::getInt32Ty(llvm::getGlobalContext());
                    }

                    int literal = expr.getValueDouble();
                    return llvm::ConstantInt::get(typConst, literal);
                }

                case Expression::STRING:
                {
                    return instructions.compileConstantStringAsPChar(expr.getValueString(), DEFAULT("tmp_str"));
                };

                default:
                {
                    break;
                }
            };

            break;

        default: break;

    }

    assert(false && "Can't compile expression");
    return 0;
}

llvm::Value*
BasicCodeScopeUnit::compile(const std::string& hintBlockDecl) {
    if (!hintBlockDecl.empty()) {
        llvm::BasicBlock *block = llvm::BasicBlock::Create(llvm::getGlobalContext(), hintBlockDecl, function->raw);
        pass->man->llvm->builder.SetInsertPoint(block);
    }

    currentBlockRaw = pass->man->llvm->builder.GetInsertBlock();
    Symbol symbScope = Symbol{ScopedSymbol::RetSymbol, scope};
    return processSymbol(symbScope);
}

ICodeScopeUnit::~ICodeScopeUnit() {
}

IFunctionUnit::~IFunctionUnit() {
}

llvm::Function*
IFunctionUnit::compile() {
    if (raw != nullptr) return raw;

    LLVMLayer* llvm = pass->man->llvm;
    llvm::IRBuilder<>& builder = llvm->builder;

    string&& functionName = prepareName();
    std::vector<llvm::Type*>&& types = prepareArguments();
    llvm::Type* expectedResultType = prepareResult();

    llvm::FunctionType *ft = llvm::FunctionType::get(expectedResultType, types, false);
    raw = llvm::cast<llvm::Function>(llvm->module->getOrInsertFunction(functionName, ft));
    prepareBindings();

    const std::string&blockName = "entry";
    llvm::BasicBlock* blockCurrent = builder.GetInsertBlock();

    llvm::Value* result = getScopeUnit(function->__entry)->compile(blockName);
    assert(result);

    //SECTIONTAG types/convert function ret value
    builder.CreateRet(typeinference::doAutomaticTypeConversion(result, expectedResultType, llvm->builder));

    if (blockCurrent) {
        builder.SetInsertPoint(blockCurrent);
    }

    llvm->moveToGarbage(ft);
    return raw;
}

ICodeScopeUnit*
IFunctionUnit::getScopeUnit(const CodeScope * const scope) {
    if (__scopes.count(scope)) {
        auto result = __scopes.at(scope).lock();

        if (result) {
            return result.get();
        }
    }

    std::shared_ptr<ICodeScopeUnit> unit(pass->buildCodeScopeUnit(scope, this));

    if (scope->__parent != nullptr) {
        auto parentUnit = Decorators<CachedScopeDecoratorTag>::getInterface(getScopeUnit(scope->__parent));
        parentUnit->registerChildScope(unit);

    } else {
        __orphanedScopes.push_back(unit);
    }

    if (!__scopes.emplace(scope, unit).second) {
        __scopes[scope] = unit;
    }

    return unit.get();
}

ICodeScopeUnit*
IFunctionUnit::getScopeUnit(ManagedScpPtr scope) {
    return getScopeUnit(&*scope);
}

ICodeScopeUnit*
IFunctionUnit::getEntry() {
    return getScopeUnit(function->getEntryScope());
}

template<>
compilation::IFunctionUnit*
CompilePassCustomDecorators<void, void>::buildFunctionUnit(const ManagedFnPtr& function){
    return new DefaultFunctionUnit(function, this);
}

template<>
compilation::ICodeScopeUnit*
CompilePassCustomDecorators<void, void>::buildCodeScopeUnit(const CodeScope* const scope, IFunctionUnit* function){
    return new DefaultCodeScopeUnit(scope, function, this);
}

} // emf of compilation

IFunctionUnit*
CompilePass::getFunctionUnit(const ManagedFnPtr& function) {
    unsigned int id = function.id();

    if (!functions.count(id)) {
        IFunctionUnit* unit = buildFunctionUnit(function);
        functions.emplace(id, unit);
        return unit;
    }

    return functions.at(id);
}

void
CompilePass::run() {
    managerTransformations = new TransformationsManager();
    targetInterpretation = new interpretation::TargetInterpretation(this->man->root, this);
    queryContext = reinterpret_cast<context::ContextQuery*> (man->clasp->getQuery(QueryId::ContextQuery));

    //Find out main function;
    ClaspLayer::ModelFragment model = man->clasp->query(Config::get("function-entry"));
    assert(model && "Error: No entry function found");
    assert(model->first != model->second && "Error: Ambiguous entry function");

    string nameMain = std::get<0>(ClaspLayer::parse<std::string>(model->first->second));
    IFunctionUnit* unitMain = getFunctionUnit(man->root->findFunction(nameMain));
    entry = unitMain->compile();
}

llvm::Function*
CompilePass::getEntryFunction() {
    assert(entry);
    return entry;
}

void
CompilePass::prepareQueries(ClaspLayer* clasp) {
    clasp->registerQuery(new containers::Query(), QueryId::ContainersQuery);
    clasp->registerQuery(new context::ContextQuery(), QueryId::ContextQuery);

    Attachments::init<PolymorphGuard>();
    clasp->registerQuery(new polymorph::PolymorphQuery(), QueryId::PolymorphQuery);
}
} //end of namespace xreate

/**
 * \class xreate::CompilePass
 * \brief Encapsulates all compilation activities
 *
 * xreate::CompilePass iterates over xreate::AST tree and produces executable code fed by data(via xreate::Attachments) gathered by previous passes  as well as data via queries(xreate::IQuery) from xreate:ClaspLayer reasoner.
 * Compilation's done using xreate::LLVMLayer(wrapper over LLVM toolchain) and based on following aspects:
 *   - Containers support. See \ref compilation/containers.h
 *   - Late Conext compilation. See xreate::context::LateContextCompiler2
 *   - Interpretation support. See xreate::interpretation::TargetInterpretation
 *   - Loop saturation support. See xreate::compilation::TransformerSaturation
 *   - External Code access. See xreate::ExternLayer(wrapper over Clang library)
 *
 * \section adaptability_sect Adaptability
 * xreate::CompilePass's architecture provides adaptability by employing:
 *   - %Function Decorators to alter function-level compilation. See xreate::compilation::IFunctionUnit
 *   - Code Block Decorators to alter code block level compilation. See xreate::compilation::ICodeScopeUnit
 *     Default functionality defined by \ref xreate::compilation::DefaultCodeScopeUnit
 *   - Targets to allow more versitile extensions.
 *     Currently only xreate::interpretation::TargetInterpretation  use Targets infrastructure. See xreate::compilation::Target
 *   - %Altering Function invocation. xreate::compilation::ICallStatement
 *
 * Client able to construct compiler with desired decorators using xreate::compilation::CompilePassCustomDecorators.
 * As a handy alias, `CompilePassCustomDecorators<void, void>` constructs default compiler
 *
 */
