#include "RD_subst.h"
#include "IR.h"
#include "CFGraph.h"

#include <stdlib.h>
#include <stdio.h>
#include <stdexcept>
#include <unordered_map>
#include <unordered_set>

#include "SgUtils.h"
#include "CommonBlock.h"
#include "graph_calls.h"
#include "expr_transform.h"

#define PRINT_PROF_INFO 0
#define DEBUG_CHECKS 0

using std::string;
using std::vector;
using std::list;
using std::map;
using std::set;
using std::pair;
using std::unordered_map;
using std::unordered_set;
using std::cout;
using std::endl;

using SAPFOR::CFG_VAL;
using SAPFOR::CFG_OP;
using SAPFOR::CFG_OP_S;
using SAPFOR::CFG_ARG_TYPE;
using SAPFOR::CFG_MEM_TYPE;

static bool substitutions_were_built = false;
static bool substitutions_applied = false;

static bool wereSubstitutionsBuilt()
{
    return substitutions_were_built;
}

static void setSubstitutionsWereBuilt(bool new_state)
{
    substitutions_were_built = new_state;
}

static bool wereSubstitutionsApplied()
{
    return substitutions_applied;
}

static void setSubstitutionsApplied(bool new_state)
{
    substitutions_applied = new_state;
}

static map<string, map<SgStatement*, vector<SgExpression*>>> oldExpressionsInFile;
static map<string, map<SgStatement*, vector<SgExpression*>>> replacementsInFiles;
static int substitution_counter = 0;

static const map<string, map<SgStatement*, vector<SgExpression*>>>& getReplacementsInFiles()
{
    return replacementsInFiles; 
}

static const map<string, map<SgStatement*, vector<SgExpression*>>>& getOldExpressionsInFile()
{
    return oldExpressionsInFile;
}

static bool storeExpression(map<string, map<SgStatement*, vector<SgExpression*>>>& place,
                            const string& filename, SgStatement* stmt, int expr_index, SgExpression* expr, bool override_expr = true)
{
    auto& by_curr_file = place[filename];
    auto it = by_curr_file.find(stmt);
    if (it == by_curr_file.end())
        it = by_curr_file.insert(it, make_pair(stmt, vector<SgExpression*>(3, NULL)));

#if DEBUG_CHECKS
    if(override_expr && it->second[expr_index])
        __spf_print(DEBUG_CHECKS, "lost pointer: %s:%d expr %d %s\n", filename.c_str(), stmt->lineNumber(), expr_index, expr->unparse());
#endif

    if (override_expr || !(it->second[expr_index]))
    {
        it->second[expr_index] = expr;
        return true;
    }

    return false;
}

static bool addOldExpression(const string& filename, SgStatement* stmt, int expr_index, SgExpression* old_expr, bool override_expr = true)
{
    return storeExpression(oldExpressionsInFile, filename, stmt, expr_index, old_expr, override_expr);
}

static bool addReplacement(const string& filename, SgStatement* stmt, int expr_index, SgExpression* new_expr, bool override_expr = true)
{
    return storeExpression(replacementsInFiles, filename, stmt, expr_index, new_expr, override_expr);
}

static bool addReplacementAndBackup(const string& filename, SgStatement* stmt, int expr_index, SgExpression* new_expr)
{
    addReplacement(filename, stmt, expr_index, new_expr);
    
    SgExpression* original = stmt->expr(expr_index);

    addOldExpression(filename, stmt, expr_index, original, true);

    return true;
}

static void updateOldExpression()
{
    for (auto& byFile : oldExpressionsInFile)
    {
        SgFile::switchToFile(byFile.first);

        for (auto& byStmt : byFile.second)
        {
            for (int i = 0; i < 3; i++)
            {
                auto& expr = byStmt.second[i];
                if (byStmt.second[i] != NULL)
                    byStmt.second[i] = byStmt.first->expr(i);
            }
        }
    }
}

static void applyReplacement(map<string, map<SgStatement*, vector<SgExpression*>>>& replacements)
{
    for (auto& byFile : replacements)
    {
        SgFile::switchToFile(byFile.first);

        for (auto& byStmt : byFile.second)
        {
            for (int i = 0; i < 3; i++)
            {
                auto& expr = byStmt.second[i];
                if (expr != NULL)
                    byStmt.first->setExpression(i, expr);
            }
        }
    }
}

static void removeInfo(map<string, map<SgStatement*, vector<SgExpression*>>>& replacements, string filename, SgStatement* stmt, int i) {
    auto it_file = replacements.find(filename);

    if (it_file != replacements.end()) {
        auto it_stmt = it_file->second.find(stmt);

        if (it_stmt != it_file->second.end()) {
            it_stmt->second[i] = NULL;

            bool all_null = true;
            for (auto e : it_stmt->second)
            {
                if (e != NULL) {
                    all_null = false;
                    break;
                }
            }

            if (all_null)
                it_file->second.erase(it_stmt);
        }

        if (it_file->second.size() == 0)
            replacements.erase(it_file);
    }
}

static void dump_replacements_for_file(string filename)
{
    cout << "substitutions for file " << filename << ":" << endl;
    for (auto& byStmt : replacementsInFiles[filename])
    {
        for (int i = 0; i < 3; i++)
        {
            auto& expr = byStmt.second[i];
            if (expr != NULL)
                cout << "    stmt:" << byStmt.first->sunparse() << ", index: " << i << ", expr: " << expr->sunparse() << endl;
        }
    }
}

static int getSubstitutionCounter()
{
    return substitution_counter;
}

static void incrementSubstitutionCounter()
{
    ++substitution_counter;
}

static void resetSubstitutionCounter()
{
    substitution_counter = 0;
}

const map<string, SgSymbol*>& getCommonArgsByFunc(FuncInfo* func, map<FuncInfo*, map<string, SgSymbol*>>& cache,
                                                  const vector<pair<const Variable*, CommonBlock*>>& commonVars,
                                                  unordered_map<SgSymbol*, string>& globals_cache, 
                                                  unordered_map<SgSymbol*, string>& locals_cache)
{
    auto it = cache.find(func);
    if (it != cache.end())
        return it->second;

    auto& res = cache[func];

    for (const auto& byVar : commonVars)
    {
        string file = func->fileName;
        SgSymbol* s = NULL;

        for (const auto& varUse : byVar.first->getAllUse())
            if (varUse.getFileName() == file && (s = varUse.getUseS()))
                break;

        if (!s)
            s = byVar.first->getSymbol();

        if (s)
            res[getArgNameBySymbol(commonVars, func, s, globals_cache, locals_cache)] = s;
    }

    return res;
}

static bool getDependencies(SAPFOR::Argument* var, SAPFOR::Instruction* instr, const SAPFOR::BasicBlock* curr_BB,
                            map<SAPFOR::Argument*, set<SAPFOR::Argument*>>& dependencies)
{
    map<SAPFOR::Argument*, SAPFOR::Instruction*> process_instr;
    process_instr[var] = instr;
    dependencies[var].clear();
    while (process_instr.size() != 0)
    {
        map<SAPFOR::Argument*, SAPFOR::Instruction*> reg_decl;
        map<SAPFOR::Argument*, SAPFOR::Instruction*> to_add;
        for (const auto& byVar : process_instr)
        {
            if (byVar.second->getOperation() == SAPFOR::CFG_OP::F_CALL)
                return false;

            auto instr_type = byVar.second->getOperation();
            int instr_num = byVar.second->getNumber();
            set<SAPFOR::Argument*> args;

            try
            {
                if (instr_type == CFG_OP::LOAD)
                {
                    args.insert(byVar.second->getArg1());
                    int num_of_params = stoi(byVar.second->getArg2()->getValue());

                    int offset = instr_num - curr_BB->getInstructions().front()->getNumber() - num_of_params;
                    auto start = curr_BB->getInstructions().begin() + offset;
                    auto end = start + num_of_params;

                    for (; start != end; start++)
                        args.insert((*start)->getInstruction()->getArg1());
                }
                else if (hasStoreStructure(instr_type))
                {
                    return false;
                }
                else
                    args = { byVar.second->getArg1(), byVar.second->getArg2() };
            }
            catch (std::invalid_argument e)
            {
                //cannot convert number of parameters into integer in LOAD or STORE instruction
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }

            for (const auto arg : args)
            {
                if (arg != NULL)
                {
                    if (arg->getType() == CFG_ARG_TYPE::REG)
                    {
                        auto reg_decl_it = reg_decl.find(arg);
                        if (reg_decl_it != reg_decl.end())
                            to_add.insert(*reg_decl_it);
                        else
                        {
                            for (SAPFOR::IR_Block* byIR : curr_BB->getInstructions())
                            {
                                auto instruction = byIR->getInstruction();
                                auto instr_res = instruction->getResult();

                                if (instr_res && instr_res->getType() == CFG_ARG_TYPE::REG && !hasStoreStructure(instruction->getOperation()))
                                    reg_decl[instr_res] = instruction;
                            }

                            reg_decl_it = reg_decl.find(arg);
                            if (reg_decl_it != reg_decl.end())
                                to_add.insert(*reg_decl_it);
                            else
                                //printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                                return false;
                        }
                    }
                    else if (arg->getType() == CFG_ARG_TYPE::VAR || arg->getType() == CFG_ARG_TYPE::ARRAY)
                        dependencies[var].insert(arg);
                }
            }
        }

        process_instr = to_add;
    }

    return true;
}

static const set<SAPFOR::Argument*>& func_affects(const FuncInfo* func, const vector<SAPFOR::BasicBlock*>& blocks)
{
    static map<const FuncInfo*, set<SAPFOR::Argument*>> affects;

    auto func_affects_it = affects.find(func);
    if (func_affects_it == affects.end())
    {
        func_affects_it = affects.insert({ func, { } }).first;

        for (auto block : blocks)
            if (block->getNext().size() == 0)
                for (const auto& out : block->getRD_Out())
                    /*
                    if a function affects variable, than there is basic block, where
                    OUT for that variable contains at least 1 valid index of instruction
                    (index < 0 means that this variable has reaching definition otside of this function)
                    */

                    if (out.second.size() != 1 || *out.second.begin() >= 0)
                        func_affects_it->second.insert(out.first);
    }

    return func_affects_it->second;
}

static SgSymbol* findCommon(SgSymbol* symb, const FuncInfo* processed_function, const map<string, CommonBlock*>& commonBlocks)
{
    for (const auto& cb : processed_function->commonBlocks)
    {
        auto cb_it = commonBlocks.find(cb.first);
        if (cb_it != commonBlocks.end())
        {
            for (auto& var_pos : cb_it->second->getGroupedVars())
            {
                bool this_var = false;
                for (auto& var : var_pos.second)
                {
                    for (auto& varUse : var->getAllUse())
                    {
                        if (varUse.getUseS() == symb)
                        {
                            this_var = true;
                            break;
                        }
                    }
                    if (this_var)
                        break;
                }

                if (this_var)
                    for (auto& var : var_pos.second)
                        for (auto& varUse : var->getAllUse())
                            if (varUse.getFunctionName() == processed_function->funcName)
                                return varUse.getUseS();
            }
        }
    }

    return NULL;
}

static SgExpression* copyExpr(SgExpression* exp_to_copy, const FuncInfo* processed_function,
                              const map<string, CommonBlock*>& commonBlocks, bool only_global = false)
{
    SgExpression* copy = exp_to_copy->copyPtr();

    set<SgSymbol*> locals;
    map<SgSymbol*, SgSymbol*> commons; //common symb from expr -> common symb from processed_func

    set<SgExpression*> exp_to_process = { copy };

    while (exp_to_process.size() != 0)
    {
        set<SgExpression*> to_add;
        for (auto& exp : exp_to_process)
        {
            if (exp->lhs())
                to_add.insert(exp->lhs());

            if (exp->rhs())
                to_add.insert(exp->lhs());

            if (exp->symbol())
            {
                SgSymbol* symb = exp->symbol();

                if (locals.count(symb))
                    continue;

                auto it = commons.find(symb);
                if (it == commons.end())
                {
                    SgSymbol* found = findCommon(symb, processed_function, commonBlocks);
                    if (!found)
                    {
                        if (only_global)
                            return NULL;

                        locals.insert(symb);
                        continue;
                    }

                    it = commons.insert({ symb, found }).first;
                }

                exp->setSymbol(it->second);
            }
        }

        exp_to_process = to_add;
    }

    return copy;
}

static bool isBBlockFromCurrentFile(const map<FuncInfo*, vector<SAPFOR::BasicBlock*>>& CFGraph,
                                   const SAPFOR::BasicBlock* bb)
{
    const string curr = current_file->filename();
    for (auto& byFunc : CFGraph)
    {
        if (count(byFunc.second.begin(), byFunc.second.end(), bb))
            return byFunc.first->fileName == curr;
    }

    return false;
}

static bool isIfOrLoopFromBB(SgStatement* st, SAPFOR::BasicBlock* bb) {
    SAPFOR::Instruction* last = bb->getInstructions().back()->getInstruction();
    int variant = st->variant();

    bool if_or_loop_from_BB = last->getOperator() == st;
    switch (variant)
    {
    case IF_NODE:
    case LOGIF_NODE:
    case ELSEIF_NODE:
        return if_or_loop_from_BB && last->getOperation() == CFG_OP::JUMP_IF;
    case FOR_NODE:
        return if_or_loop_from_BB && (last->getOperation() != CFG_OP::JUMP && last->getOperation() != CFG_OP::JUMP_IF);
    case WHILE_NODE:
        return if_or_loop_from_BB && last->getOperation() == CFG_OP::JUMP_IF;
    default:
        return true;
    }

    return true;
}

static map<SAPFOR::Argument*, SgExpression*> 
    func_makes_available(const FuncInfo* func,
                         SgProject* project,
                         map<const FuncInfo*, map<SAPFOR::Argument*, SgExpression*>>& availableForFunc,
                         map<const FuncInfo*, map<SAPFOR::Argument*, set<SAPFOR::Argument*>>>& dependenciesForFunc,
                         const vector<SAPFOR::BasicBlock*>& blocks,
                         const map<FuncInfo*, vector<SAPFOR::BasicBlock*>>& CFGraph,
                         map<SAPFOR::Argument*, set<SAPFOR::Argument*>>& dependencies,
                         const FuncInfo* processed_function, 
                         const map<string, CommonBlock*>& commonBlocks)
{
    if (string(current_file->filename()) != func->fileName)
        return { };
    
    auto available_after_func_it = availableForFunc.find(func);

    if (available_after_func_it == availableForFunc.end())
    {
        available_after_func_it = availableForFunc.insert({ func, { } }).first;
        auto& availableForCurrentFunc = available_after_func_it->second;
        auto& dependenciesForCurrentFunc = dependenciesForFunc[func];

        bool first_out_block = true;

        vector<pair<SAPFOR::Argument*, int>> makes_available;
        for (auto block : blocks)
        {
            if (block->getNext().size() == 0)
            {
                if (first_out_block)
                {
                    for (auto& out : block->getRD_Out())
                    {
                        if ((out.first->getMemType() == SAPFOR::CFG_MEM_TYPE::COMMON_ /* || out.first->getMemType() == SAPFOR::CFG_MEM_TYPE::MODULE_*/)
                            && out.second.size() == 1 && *(out.second.begin()) >= 0)
                        {
                            makes_available.push_back(std::make_pair(out.first, *(out.second.begin())));
                        }
                    }

                    first_out_block = false;
                }
                else
                {
                    makes_available.erase(std::remove_if(makes_available.begin(), makes_available.end(), [&block](const pair<SAPFOR::Argument*, int>& p) {
                        auto it = block->getRD_Out().find(p.first);
                       
                        return (it == block->getRD_Out().end()) 
                               || (it->second.size() != 1) 
                               || (*(it->second.begin()) != p.second);
                        }),
                        makes_available.end()
                    );
                }
            }
        }

        for (const auto& p : makes_available)
        {
            auto argument = p.first;
            int instr_number = p.second;

            pair<SAPFOR::Instruction*, SAPFOR::BasicBlock*> instr_and_bb = getInstructionAndBlockByNumber(CFGraph, instr_number);

            if (!isBBlockFromCurrentFile(CFGraph, instr_and_bb.second))
                continue;

            if (getDependencies(argument, instr_and_bb.first, instr_and_bb.second, dependenciesForCurrentFunc))
            {
                //check if all variables with affect on definition could be used there
                bool can_use = true;
                auto dependencies_of_var = dependenciesForCurrentFunc.find(argument);
                if (dependencies_of_var == dependenciesForCurrentFunc.end())
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                for (SAPFOR::Argument* dep : dependencies_of_var->second)
                {
                    if (dep->getMemType() != SAPFOR::CFG_MEM_TYPE::COMMON_/* || dep->getMemType() == SAPFOR::CFG_MEM_TYPE::MODULE_*/)
                    {
                        can_use = false;
                        break;
                    }
                }

                if (can_use)
                    availableForCurrentFunc[argument] = instr_and_bb.first->getOperator()->expr(1);
            }
        }
    }

    auto dependencies_after_func_it = dependenciesForFunc.find(func);
    if(dependencies_after_func_it == dependenciesForFunc.end())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    map<SAPFOR::Argument*, SgExpression*> available;

    for (const auto& byArgument : available_after_func_it->second) {
        auto dependencies_for_argument_it = dependencies_after_func_it->second.find(byArgument.first);
        if(dependencies_for_argument_it == dependencies_after_func_it->second.end())
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        SgExpression* exp_of_def = copyExpr(byArgument.second, processed_function, commonBlocks, true);
        if (exp_of_def) {
            dependencies[byArgument.first] = dependencies_for_argument_it->second;
            available[byArgument.first] = exp_of_def;
        }
    }

    return available;
}

static unordered_map<SgSymbol*, string> globals, locals;
const string& getArgNameBySymbol(const vector<pair<const Variable*, CommonBlock*>>& commonVars, 
                                 const FuncInfo* func, SgSymbol* s, 
                                 unordered_map<SgSymbol*, string>& globals_cache, unordered_map<SgSymbol*, string>& locals_cache)
{
    auto local_it = locals_cache.find(s);

    if (local_it != locals_cache.end())
        return local_it->second;

    auto global_it = globals_cache.find(s);

    if (global_it != globals_cache.end())
        return global_it->second;
    
    CFG_MEM_TYPE mType = CFG_MEM_TYPE::NONE_;

    SgStatement* scope = OriginalSymbol(s)->scope();

    if (!scope)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    string name = createName(commonVars, func, s, scope, mType);
    if (mType == CFG_MEM_TYPE::LOCAL_)
        return locals_cache[s] = name;
    else
        return globals_cache[s] = name;
}

static SgExpression* expandSymbolToExpression(SgExpression* s, const map<string, SgExpression*>& name_to_expr,
                                              const vector<pair<const Variable*, CommonBlock*>>& commonVars,
                                              const FuncInfo* func)
{
    while (s->variant() == VAR_REF && s->symbol() && s->symbol()->identifier())
    {
        string argName = getArgNameBySymbol(commonVars, func, s->symbol(), globals, locals);
        auto it = name_to_expr.find(argName);

        if (it == name_to_expr.end() || it->second->type()->variant() != s->type()->variant())
            break;

        incrementSubstitutionCounter();
        __spf_print(PRINT_PROF_INFO, "%s -> ", s->unparse());

        s = it->second;

        __spf_print(PRINT_PROF_INFO, "%s\n", s->unparse());
    }

    return s;
}

static SgExpression* performExprSubstAndLink_(SgExpression* cur_exp, bool is_left,
                                              const map<string, SgExpression*>& name_to_expr,
                                              const map<SAPFOR::Argument*, SgExpression*>& available,
                                              const vector<pair<const Variable*, CommonBlock*>>& commonVars,
                                              const FuncInfo* func,
                                              SgExpression* exp, SgExpression*& backup)
{
    SgExpression* sub_expr = is_left ? cur_exp->lhs() : cur_exp->rhs();

    if (sub_expr)
        if (sub_expr->symbol() && sub_expr->variant() != PROC_CALL && sub_expr->variant() != FUNC_CALL)
        {
            SgArrayRefExp* array_expr = isSgArrayRefExp(sub_expr);
            if (array_expr)
            {
                if (array_expr->subscripts())
                    return array_expr->subscripts();
            }
            else if (sub_expr->symbol()->identifier())
            {
                SgExpression* replace_by = expandSymbolToExpression(sub_expr, name_to_expr, commonVars, func);

                if (replace_by != sub_expr)
                {
                    if (backup == NULL)
                    {
                        backup = exp->copyPtr();
                        createLinksToCopy(backup, exp);
                    }

                    __spf_print(PRINT_PROF_INFO, "%s -> ", sub_expr->unparse());
                    is_left ? cur_exp->setLhs(replace_by->copy()) : cur_exp->setRhs(replace_by->copy());
                    
                    sub_expr = is_left ? cur_exp->lhs() : cur_exp->rhs();
                    __spf_print(PRINT_PROF_INFO, "%s\n", sub_expr->unparse());

                    incrementSubstitutionCounter();

                    return sub_expr;
                }
            }
        }
        else
            return sub_expr;

    return NULL;
}

// iterate over function or procedure call arguments and sustitute if it possible
static list<SgExpression*> performExprSubstInCall(const char* fname, SgExprListExp* actualArguments,
                                                  const map<string, SgExpression*>& name_to_expr,
                                                  const map<SAPFOR::Argument*, SgExpression*>& available,
                                                  const vector<pair<const Variable*, CommonBlock*>>& commonVars,
                                                  const FuncInfo* func, const map<string, FuncInfo*>& funcByName,
                                                  SgExpression* exp, SgExpression*& backup)
{
    if (!fname || !actualArguments)
        return { };
    
    // Step 1. Get indexes of arguments that can be substituted
    set<int> possible_substitution;

    auto found_func = funcByName.find(fname);
    if (found_func != funcByName.end())
    {
        FuncInfo* called_func = found_func->second;

        int count_of_params = called_func->funcParams.countOfPars;

        for (int i = 0; i < count_of_params; i++)
            if (!called_func->funcParams.isArgOut(i))
                possible_substitution.insert(i);
    }
    else
    {
        if (isIntrinsicFunctionName(fname))
            return { actualArguments }; // main loop can substitute all arguments indiscriminately
    }

    // Step 2. Perform possible substitutions
    list<SgExpression*> res;
    SgExpression* curr = actualArguments;
    int pos = 0;

    while (curr) {
        SgExpression* actual_param = curr->lhs();
        
        if (actual_param)
        {
            if (actual_param->symbol() && actual_param->symbol()->identifier() && actual_param->variant() == VAR_REF)
            {
                if (possible_substitution.find(pos) != possible_substitution.end())
                {
                    SgExpression* to_add = performExprSubstAndLink_(curr, true, name_to_expr, available, commonVars, func, exp, backup);
                    if (to_add)
                        res.push_back(to_add);
                }
            }
            else
            {
                res.push_back(actual_param);
            }
        }

        pos++;
        curr = curr->rhs();
    }
    
    return res;
}

// if replacement occurs, returns a new expression (the new expression is linked to the original one)
// if there were no replacements, then returns NULL
static SgExpression* performExprSubstAndLink(SgStatement* stmt, int i,
                                             const map<SAPFOR::Argument*, SgExpression*>& available,
                                             const vector<pair<const Variable*, CommonBlock*>>& commonVars, 
                                             const FuncInfo* func, const map<string, FuncInfo*>& funcByName)
{
    SgExpression* expr_to_subst = stmt->expr(i);
    map<string, SgExpression*> name_to_expr;
    for (const auto& p : available)
        name_to_expr[p.first->getValue()] = p.second;

    SgExpression* backup = NULL;
    
    list<SgExpression*> to_process;
    SgCallStmt* call = isSgCallStmt(stmt);
    if (!call)
    {
        SgExpression* replace_symbol_by = expandSymbolToExpression(expr_to_subst, name_to_expr, commonVars, func);

        if (replace_symbol_by != expr_to_subst)
        {
            if (backup == NULL)
            {
                backup = expr_to_subst->copyPtr();
                createLinksToCopy(backup, expr_to_subst);
            }

            expr_to_subst = replace_symbol_by->copyPtr();
            stmt->setExpression(i, expr_to_subst);
        }

        to_process = { expr_to_subst };
    }
    else
    {
        auto to_add = performExprSubstInCall(call->name()->identifier(), isSgExprListExp(call->expr(0)),
                                             name_to_expr, available, commonVars, func, funcByName, expr_to_subst, backup);

        for (auto e : to_add)
            to_process.push_back(e);
    }

    while (to_process.size() > 0)
    {
        SgExpression* cur_exp = to_process.front(), *sub_expr;
        to_process.pop_front();

        SgFunctionCallExp* fcall = isSgFunctionCallExp(cur_exp);
        if (fcall)
        {
            auto to_add = performExprSubstInCall(fcall->funName()->identifier(), isSgExprListExp(fcall->args()),
                                   name_to_expr, available, commonVars, func, funcByName, expr_to_subst, backup);

            for (auto e : to_add)
                to_process.push_back(e);
        }
        else 
        {
            sub_expr = performExprSubstAndLink_(cur_exp, true, name_to_expr, available, commonVars, func, expr_to_subst, backup);
            if (sub_expr)
                to_process.push_back(sub_expr);

            sub_expr = performExprSubstAndLink_(cur_exp, false, name_to_expr, available, commonVars, func, expr_to_subst, backup);
            if (sub_expr)
                to_process.push_back(sub_expr);
        }
    }

    if (backup)
    {
        stmt->setExpression(i, backup);
        return expr_to_subst;
    }

    return NULL;
}

// replace argument_to_replace by argument_value in expr.
// if there were no replacements, then returns expr.
// if substitution couldn't be applied due to type mismatch, return NULL
// else returns substituted expression (note that argument_value can be returned)
// argument value shouldn't contains argument_to_replace
static SgExpression* substSingleSymb(SgExpression* expr,
                                     SAPFOR::Argument* argument_to_replace,
                                     SgExpression* argument_value,
                                     const vector<pair<const Variable*, CommonBlock*>>& commonVars,
                                     const FuncInfo* func)
{
    string arg_to_replace_name = argument_to_replace->getValue();

    if (expr->variant() == VAR_REF && expr->symbol()) {
        string arg_from_expr = getArgNameBySymbol(commonVars, func, expr->symbol(), globals, locals);
        if (arg_from_expr == arg_to_replace_name)
            return argument_value;
        else
            return expr;
    }
    // check if argument in expression
    bool symbol_in_expression = false;
    list<SgExpression*> to_process = { expr };

    while (to_process.size() > 0)
    {
        SgExpression* cur_exp = to_process.front();
        to_process.pop_front();
        
        if (cur_exp && cur_exp->variant() != PROC_CALL && cur_exp->variant() != FUNC_CALL)
            if (cur_exp->symbol())
            {
                SgArrayRefExp* array_expr = isSgArrayRefExp(cur_exp);
                if (array_expr)
                {
                    if (array_expr->subscripts())
                        to_process.push_back(array_expr->subscripts());
                }

                else if (cur_exp->variant() == VAR_REF && cur_exp->symbol()->identifier())
                {
                    string argName = getArgNameBySymbol(commonVars, func, cur_exp->symbol(), globals, locals);
                    if (argName == arg_to_replace_name)
                    {
                        symbol_in_expression = true;
                        to_process.clear();
                        break;
                    }
                }
            }
            else
            {
                to_process.push_back(cur_exp->lhs());
                to_process.push_back(cur_exp->rhs());
            }
    }

    if (!symbol_in_expression)
        return expr;

    // perform substitution
    SgExpression* copy = expr->copyPtr();
    to_process = { copy };

    while (to_process.size() > 0)
    {
        SgExpression* cur_exp = to_process.front();
        to_process.pop_front();

        for (auto is_left : { true, false })
        {
            SgExpression* sub_expr = is_left ? cur_exp->lhs() : cur_exp->rhs();

            if (sub_expr && sub_expr->variant() != PROC_CALL && sub_expr->variant() != FUNC_CALL)
                if (sub_expr->symbol())
                {
                    SgArrayRefExp* array_expr = isSgArrayRefExp(sub_expr);
                    if (array_expr)
                    {
                        if (array_expr->subscripts())
                            to_process.push_back(array_expr->subscripts());
                    }
                    else if (sub_expr->variant() == VAR_REF && sub_expr->symbol()->identifier())
                    {
                        string argName = getArgNameBySymbol(commonVars, func, sub_expr->symbol(), globals, locals);
                        
                        if (argName == arg_to_replace_name)
                            if (sub_expr->type()->variant() == argument_value->type()->variant())
                                is_left ? cur_exp->setLhs(argument_value->copy()) : cur_exp->setRhs(argument_value->copy());
                            else
                                return NULL;
                    }
                }
                else
                    to_process.push_back(sub_expr);
        }
    }

    return copy;
}

// replace chains like a->f(arg) by a->f(g()), where arg->g()
// updates available and dependencies structures
static bool joinChains(map<SAPFOR::Argument*, SgExpression*>& available,
                       map<SAPFOR::Argument*, set<SAPFOR::Argument*>>& dependencies,
                       SAPFOR::Argument* arg,
                       const vector<pair<const Variable*, CommonBlock*>>& commonVars,
                       const FuncInfo* func)
{
    auto arg_in_available = available.find(arg);
    if (arg_in_available == available.end())
        return false;

    auto arg_in_dependencies = dependencies.find(arg);
    if (arg_in_dependencies == dependencies.end())
        return false;

    SgExpression* arg_value = arg_in_available->second; // g()
    const auto& arg_dependencies = arg_in_dependencies->second;
    bool joined = false;

    for (auto& p : dependencies)
    {
        if (p.second.count(arg))
        {
            SAPFOR::Argument* curr_var = p.first; // a variable
            auto available_it = available.find(curr_var);
            if (available_it != available.end())
            {
                SgExpression* new_expr = substSingleSymb(available_it->second, arg, arg_value, commonVars, func); //f(g())
                if (new_expr && new_expr != available_it->second)
                {
                    p.second.erase(arg);
                    p.second.insert(arg_dependencies.begin(), arg_dependencies.end());
                    available[curr_var] = new_expr;
                    joined = true;
                }
            }
        }
    }

    return joined;
}

// sorts BasicBlocks from blocks in control flow order
// pass ignored_edges for saving there back edges (used in live variable analysis)
vector<SAPFOR::BasicBlock*> sortCfgNodes(const vector<SAPFOR::BasicBlock*>& blocks, 
                                         set<pair<SAPFOR::BasicBlock*, SAPFOR::BasicBlock*>>* ignored_edges)
{
    list<SAPFOR::BasicBlock*> seq_starts = { };
    for (auto e : blocks)
        if (e->getInstructions().front()->isHeader())
            seq_starts.push_front(e);
        else if(e->getPrev().size() == 0)
            seq_starts.push_back(e);

    if (blocks.size() == 0)
        return { };

    // STEP 1: marks all back edges
    set<pair<SAPFOR::BasicBlock*, SAPFOR::BasicBlock*>> to_ignore;

    unordered_set<SAPFOR::BasicBlock*> in_process = { };
    unordered_set<SAPFOR::BasicBlock*> visited = { }, all_visited = { };
    unordered_set<SAPFOR::BasicBlock*> at_stack = { };
    list<SAPFOR::BasicBlock*> processing_stack, processing_starts = seq_starts;

    at_stack.insert(seq_starts.begin(), seq_starts.end());

    while (processing_starts.size() != 0)
    {
        auto start = processing_starts.front();
        processing_starts.pop_front();

        processing_stack = { start };
        at_stack = { start };

        while (processing_stack.size() != 0)
        {
            auto curr_it = processing_stack.end();
            curr_it--;
            SAPFOR::BasicBlock* curr = *curr_it;
            in_process.insert(curr);
            bool next_added = false;

            for (auto next_node : curr->getNext())
            {
                if (!to_ignore.count({ curr, next_node }))
                {
                    if (in_process.count(next_node))
                    {
                        // this is back edge
                        to_ignore.insert({ curr, next_node });
                    }
                    else if (!visited.count(next_node) && at_stack.insert(next_node).second)
                    {
                        processing_stack.push_back(next_node);
                        next_added = true;
                    }
                    // if next_node has status visited then do nothing
                }
            }

            if (!next_added) {
                // mark as visited
                in_process.erase(curr);
                visited.insert(curr);
                processing_stack.erase(curr_it);
                at_stack.erase(curr);
            }
        }

        in_process.clear();

        all_visited.insert(visited.begin(), visited.end());
        visited.clear();
        processing_stack.clear();
        at_stack.clear();
        
        if (processing_starts.size() == 0 && all_visited.size() < blocks.size())
        {
            //find new entrypoint
            SAPFOR::BasicBlock* new_start = NULL;
            int linenum = 0;

            for (auto block : blocks)
            {
                if (all_visited.count(block) == 0)
                {
                    int curr_line = block->getInstructions().front()->getInstruction()->getOperator()->lineNumber();
                    if (new_start == NULL || curr_line < linenum)
                    {
                        new_start = block;
                        linenum = curr_line;
                    }
                }
            }

            if(new_start == NULL)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            seq_starts.push_back(new_start);
            processing_starts.push_back(new_start);
        }
    }

    all_visited.clear();

    // STEP 2: visit nodes and build sorted array
    vector<SAPFOR::BasicBlock*> result;

    for (const auto& start : seq_starts)
        processing_stack.push_back(start);

    in_process.insert(seq_starts.begin(), seq_starts.end());

    while (processing_stack.size() != 0)
    {
        bool nodes_added = false;
        for (auto block_it = processing_stack.begin(); block_it != processing_stack.end();)
        {
            // check if block could be added to result
            bool add = true;
            auto block = *block_it;
            
            for (auto prev : block->getPrev())
                if (!visited.count(prev) && !to_ignore.count({ prev, block }))
                    add = false;
            if (add)
            {
                nodes_added = true;
                if (visited.insert(block).second)
                    result.push_back(block);

                in_process.erase(block);
                block_it = processing_stack.erase(block_it);
                bool stack_empty = block_it == processing_stack.end();


                for (auto next : block->getNext())
                    if (!visited.count(next) && in_process.insert(next).second)
                        processing_stack.push_back(next);
                
                if (stack_empty)
                    block_it = processing_stack.begin();
            }
            else
            {
                block_it++;
            }
        }

        if (!nodes_added && processing_stack.size() != 0) {
            //there is some blocks in the stack but no one can be processed
            //this code should be unreachable
#if DEBUG_CHECKS
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
#endif // DEBUG_CHECKS
            auto block = processing_stack.back();
            processing_stack.pop_back();
            in_process.erase(block);

            result.push_back(block);
            visited.insert(block);
            nodes_added = true;

            for (auto next : block->getNext())
                if (!visited.count(next) && in_process.insert(next).second)
                    processing_stack.push_back(next);
        }
    }
    
#if DEBUG_CHECKS
    set<SAPFOR::BasicBlock*> verify_unique;
    verify_unique.insert(result.begin(), result.end());

    if (verify_unique.size() != result.size())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    verify_unique.clear();
#endif // DEBUG_CHECKS

#if DEBUG_CHECKS
    set<SAPFOR::BasicBlock*> all_blocks, res_blocks;

    all_blocks.insert(blocks.begin(), blocks.end());
    res_blocks.insert(result.begin(), result.end());
    
    if(res_blocks != all_blocks)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
#endif // DEBUG_CHECKS

    if (ignored_edges)
        ignored_edges->insert(to_ignore.begin(), to_ignore.end()); // return back edges if needed

    return result;
}

map<int, map<SAPFOR::Argument*, SgExpression*>> saved_available;
map<int, map<SAPFOR::Argument*, set<SAPFOR::Argument*>>> saved_dependencies;

// save contents of available and dependencies for specified instruction.
void save_chains(int instr_num, const map<SAPFOR::Argument*, SgExpression*>& available,
                 const map<SAPFOR::Argument*, set<SAPFOR::Argument*>>& dependencies,
                 SAPFOR::Argument* arg)
{
    map<SAPFOR::Argument*, SgExpression*> available_to_save;
    map<SAPFOR::Argument*, set<SAPFOR::Argument*>> dependencies_to_save;
    set<SAPFOR::Argument*> needed_vars, new_needed = { arg };

    while (new_needed.size() != 0)
    {
        needed_vars = new_needed;
        new_needed = { };
        for (auto e : needed_vars)
        {
            auto it_available = available.find(e);
            if (it_available == available.end())
                continue;

            auto it_dependencies = dependencies.find(e);
            if (it_dependencies == dependencies.end())
                continue;

            available_to_save.insert(*it_available);
            dependencies_to_save.insert(*it_dependencies);

            for (auto dep : it_dependencies->second)
            {
                if (!available_to_save.count(dep))
                    new_needed.insert(dep);
            }
        }
    }

    saved_available[instr_num] = available_to_save;
    saved_dependencies[instr_num] = dependencies_to_save;
}

// return number of instructions where we need to save data from available and dependencies
set<int> save_at_indexes(SAPFOR::BasicBlock* bb)
{
    if (!bb || bb->getInstructions().size() == 0)
        return { };

    int bb_start = bb->getInstructions().front()->getNumber();
    int bb_end = bb->getInstructions().back()->getNumber();
    set<int> needed_indexes;

    for (auto p : bb->getRD_Out())
        if (p.second.size() == 1 && *p.second.begin() >= bb_start && *p.second.begin() <= bb_end)
            needed_indexes.insert(*p.second.begin());

    return needed_indexes;
}

bool isArgReaches(int decl_instr, SAPFOR::BasicBlock* decl_bb, 
                  SAPFOR::Argument* arg, SAPFOR::BasicBlock* dest_bb)
{
    if (arg->getType() != CFG_ARG_TYPE::VAR)
        return false;

    const auto& RD_In_of_dest_bb = dest_bb->getRD_In();
    auto dep_from_dest_bb_RD_In = RD_In_of_dest_bb.find(arg);

    if (dep_from_dest_bb_RD_In == RD_In_of_dest_bb.end())
        return decl_bb->getRD_In().count(arg) == 0;

    const auto& RDs_for_arg = dep_from_dest_bb_RD_In->second;

    if (RDs_for_arg.size() == 1)
    {
        const int rd = *RDs_for_arg.begin();
        if (rd >= decl_bb->getInstructions().front()->getNumber() && 
            rd <= decl_bb->getInstructions().back()->getNumber())
            return rd < decl_instr;
    }

    auto arg_in_from_decl_it = decl_bb->getRD_In().find(arg);
    if (arg_in_from_decl_it == decl_bb->getRD_In().end())
        return false;

    if(arg_in_from_decl_it->second != RDs_for_arg)
        return false;

    set<SAPFOR::BasicBlock*> reachable = { decl_bb };
    set<int> banned_instructions;
    for (int instr_def : arg_in_from_decl_it->second)
        if (instr_def >= decl_instr || instr_def < decl_bb->getInstructions().front()->getNumber())
            //try to find way [decl_bb] -> [dest_bb] with redefining of var (that means that var value from decl_bb could be overwrited)
            banned_instructions.insert(instr_def);

    set<SAPFOR::BasicBlock*> worklist = reachable, banned_blocks;
    bool way_found = false;
    while (worklist.size() != 0 && banned_instructions.size() != 0)
    {
        for (SAPFOR::BasicBlock* wl : worklist)
        {
            int start = wl->getInstructions().front()->getNumber(), end = wl->getInstructions().back()->getNumber();

            for (auto banned_it = banned_instructions.begin(); banned_it != banned_instructions.end();)
            {
                if(start <= *banned_it && *banned_it <= end)
                {
                    banned_it = banned_instructions.erase(banned_it);
                    banned_blocks.insert(wl);
                }
                else
                {
                    banned_it++;
                }
            }
        }
        
        set<SAPFOR::BasicBlock*> to_insert;

        for (auto b : worklist)
            for (auto next : b->getNext())
                if (reachable.insert(next).second)
                    to_insert.insert(next);

        worklist = to_insert;
    }

    reachable = banned_blocks;
    worklist = reachable;
    while (worklist.size() != 0 && banned_instructions.size() != 0)
    {
        if(worklist.find(dest_bb) != worklist.end())
            return false;

        set<SAPFOR::BasicBlock*> to_insert;

        for (auto b : worklist)
            for (auto next : b->getNext())
                if(next != decl_bb)
                    if (reachable.insert(next).second)
                        to_insert.insert(next);

        worklist = to_insert;
    }

    return true;
}

// return arg's definition if it really reaches dest_bb
// else - return NULL.
// dont alter returned expression. Otherwise Sage tree may change.
// note: use this function only for decl_bb from the same routine as dest_bb
SgExpression* getRd(int instr_num, SAPFOR::BasicBlock* decl_bb,
                    SAPFOR::Argument* arg, SAPFOR::BasicBlock* dest_bb,
                    map<SAPFOR::Argument*, SgExpression*>& available,
                    map<SAPFOR::Argument*, set<SAPFOR::Argument*>>& dependencies,
                    const vector<pair<const Variable*, CommonBlock*>>& commonVars,
                    FuncInfo* curr_func, 
                    const map<string, CommonBlock*>& commonBlocks)
{
    auto saved_available_it = saved_available.find(instr_num);
    auto saved_dependencies_it = saved_dependencies.find(instr_num);

    if (saved_available_it == saved_available.end() || saved_dependencies_it == saved_dependencies.end())
        return NULL;

    map<SAPFOR::Argument*, SgExpression*> temp_available = saved_available_it->second;
    map<SAPFOR::Argument*, set<SAPFOR::Argument*>> temp_dependencies = saved_dependencies_it->second;
    
    set<SAPFOR::Argument*> arg_rd_dep;
    set<SAPFOR::Argument*> prevents = { };

    int joined = 0;
    bool all_dep_reaches = false;

    while (joined == prevents.size())
    {
        prevents = { };
        joined = 0;
        auto arg_rd_it = temp_available.find(arg);
        auto arg_dep_it = temp_dependencies.find(arg);
        
        if (arg_rd_it == temp_available.end() || arg_dep_it == temp_dependencies.end())
            return NULL;

        for (auto e : arg_dep_it->second)
            if (!isArgReaches(instr_num, decl_bb, e, dest_bb))
                prevents.insert(e);

        if (prevents.size() == 0)
        {
            all_dep_reaches = true;
            break;
        }

        for (auto e : prevents)
            if (joinChains(temp_available, temp_dependencies, e, commonVars, curr_func))
                joined++;
    }

    if (!all_dep_reaches)
        return NULL;

    auto arg_rd_it = temp_available.find(arg);
    auto arg_dep_it = temp_dependencies.find(arg);

    if (arg_rd_it == temp_available.end() || arg_dep_it == temp_dependencies.end())
        return NULL;

    SgExpression* new_exp = copyExpr(arg_rd_it->second, curr_func, commonBlocks);
    if (new_exp)
    {
        dependencies.insert(*arg_dep_it);
        available[arg] = new_exp;
    }

    return new_exp;
}

static SgExpression* replaceSingleConstRef(SgExpression* exp)
{
    if (exp->variant() != CONST_REF)
        return NULL;

    SgExpression* ret = ReplaceParameter_(exp);
    int sign = 1;
    SgExpression* toCalc = ret;
    if (ret->variant() == UNARY_ADD_OP)
        toCalc = ret->lhs();
    if (ret->variant() == MINUS_OP)
    {
        toCalc = ret->lhs();
        sign = -1;
    }
    if (toCalc->isInteger())
        return new SgValueExp(sign * toCalc->valueInteger());
    else
        return exp;
}

static bool replaceConstantAndLink_(SgExpression* cur_exp, bool is_left,
                                    SgExpression* exp, SgExpression*& backup)
{
    SgExpression* sub_expr = is_left ? cur_exp->lhs() : cur_exp->rhs();
    SgExpression* after_subst;

    if (sub_expr)
    {
        if (after_subst = replaceSingleConstRef(sub_expr))
        {
            if (after_subst != sub_expr)
            {
                if (backup == NULL)
                {
                    backup = exp->copyPtr();
                    createLinksToCopy(backup, exp);
                }

                is_left ? cur_exp->setLhs(after_subst) : cur_exp->setRhs(after_subst);
            }

            return false;
        }

        return true;
    }

    return false;
}

static bool calculateAndLink_(SgExpression* cur_exp, bool is_left,
                              SgExpression* exp, SgExpression*& backup)
{
    int err, res;

    SgExpression* sub_expr = is_left ? cur_exp->lhs() : cur_exp->rhs();

    if (sub_expr)
    {
        err = CalculateInteger(sub_expr, res);
        if (err != -1)
        {
            if (backup == NULL)
            {
                backup = exp->copyPtr();
                createLinksToCopy(backup, exp);
            }

            is_left ? cur_exp->setLhs(new SgValueExp(res)) : cur_exp->setRhs(new SgValueExp(res));
            return false;
        }

        return true;
    }

    return false;
}

// if replacement occurs, returns a new expression (the new expression is linked to the original one)
// if there were no replacements, then returns NULL
static SgExpression* replaceConstantAndLink(SgStatement* stmt, int i)
{
    SgExpression* exp = stmt->expr(i);

    SgExpression* is_exp_constant = replaceSingleConstRef(exp);

    if (is_exp_constant != NULL)
        return is_exp_constant == exp ? NULL : is_exp_constant;

    SgExpression* backup = NULL;
    list<SgExpression*> to_process = { exp };

    while (to_process.size() > 0)
    {
        SgExpression* cur_exp = to_process.front();
        to_process.pop_front();

        if (replaceConstantAndLink_(cur_exp, true, exp, backup))
            to_process.push_back(cur_exp->lhs());

        if (replaceConstantAndLink_(cur_exp, false, exp, backup))
            to_process.push_back(cur_exp->rhs());
    }

    int res;
    if (CalculateInteger(exp, res) != -1)
    {
        if (backup)
            stmt->setExpression(i, backup);

        return new SgValueExp(res);
    }

    to_process = { exp };

    while (to_process.size() > 0)
    {
        SgExpression* cur_exp = to_process.front();
        to_process.pop_front();

        if (calculateAndLink_(cur_exp, true, exp, backup))
            to_process.push_back(cur_exp->lhs());

        if (calculateAndLink_(cur_exp, false, exp, backup))
            to_process.push_back(cur_exp->rhs());
    }
    
    if (backup)
    {
        stmt->setExpression(i, backup);
        return exp;
    }
    
    return NULL;
}

static void performExprSubstAndSave(SgStatement* stmt_to_subst, int i,
                                    const map<SAPFOR::Argument*, SgExpression*>& available,
                                    const vector<pair<const Variable*, CommonBlock*>>& commonVars,
                                    const FuncInfo* func, const map<string, FuncInfo*>& funcByName)
{
    SgExpression* expr_to_subst = stmt_to_subst->expr(i);
    SgExpression* expr_with_replacements = NULL;
    
    expr_with_replacements = performExprSubstAndLink(stmt_to_subst, i, available, commonVars, func, funcByName);

    if (expr_with_replacements != NULL)
    {
        replaceConstantRec(expr_with_replacements);
        calculate(expr_with_replacements);
        addReplacementAndBackup(current_file->filename(), stmt_to_subst, i, expr_with_replacements);
    }
    else
    {
        SgExpression* afterReplaceConstants = replaceConstantAndLink(stmt_to_subst, i);

        if (afterReplaceConstants != NULL)
            addReplacementAndBackup(current_file->filename(), stmt_to_subst, i, afterReplaceConstants);
    }

}

void buildSubstitutions(const map<FuncInfo*, vector<SAPFOR::BasicBlock*>>& CFGraph_for_project,
                        const map<string, CommonBlock*>& commonBlocks, SgProject* project)
{
    if (wereSubstitutionsBuilt())
        return;

    resetSubstitutionCounter();

    map<string, FuncInfo*> funcByName;
    for (const auto& byFunc : CFGraph_for_project)
        funcByName[byFunc.first->funcName] = byFunc.first;

    map<FuncInfo*, map<string, SgSymbol*>> commonArgsByFunc = { };
    map<const FuncInfo*, map<SAPFOR::Argument*, SgExpression*>> availableForFunc = { };
    map<const FuncInfo*, map<SAPFOR::Argument*, set<SAPFOR::Argument*>>> dependenciesForFunc = { };

    set<string> analyzedFunctions = {};

    for (auto& byFunc : CFGraph_for_project)
    {
        SgFile::switchToFile(byFunc.first->fileName);
        
        // skip ENTRY, analyze only original functions
        if (!(isSgProgHedrStmt(byFunc.first->funcPointer) || isSgProcHedrStmt(byFunc.first->funcPointer) ||
            isSgFuncHedrStmt(byFunc.first->funcPointer)) || (analyzedFunctions.find(byFunc.first->funcName) != analyzedFunctions.end()))
            continue;

        analyzedFunctions.insert(byFunc.first->funcName);
        
        auto commonVars = getCommonsByFunction(current_file, byFunc.first->funcPointer, commonBlocks);
        const auto& commonArgs = getCommonArgsByFunc(byFunc.first, commonArgsByFunc, commonVars, globals, locals);
        
        unordered_set<SgStatement*> analyzedStmt;

        auto sortedCfgNodes = sortCfgNodes(byFunc.second);
        auto cfg_start_it = sortedCfgNodes.begin();
        auto cfg_end_it = sortedCfgNodes.end();

        for (auto cfg_it = cfg_start_it; cfg_it < cfg_end_it; cfg_it++)
        {
            SAPFOR::BasicBlock* cur_BB = *cfg_it;
            map<SAPFOR::Argument*, SgExpression*> available; //available definitions
            map<SAPFOR::Argument*, set<SAPFOR::Argument*>> dependencies;

            set<int> idx_to_save = save_at_indexes(cur_BB);
            auto idx_it = idx_to_save.begin();
            auto idx_end_it = idx_to_save.end();

            for (const auto& in_val : cur_BB->getRD_In())
            {
                if (in_val.first->getType() == CFG_ARG_TYPE::VAR && in_val.second.size() == 1)
                {
                    int instr_num = *(in_val.second.begin());
                    if (instr_num < 0)
                        continue;

                    auto cfg_find_bb = cfg_it + 1;
                    SAPFOR::BasicBlock* in_val_bb = NULL;
                    do {
                        cfg_find_bb--;
                        if ((*cfg_find_bb)->getInstructions().front()->getNumber() <= instr_num &&
                            (*cfg_find_bb)->getInstructions().back()->getNumber() >= instr_num)
                        {
                            in_val_bb = *cfg_find_bb;
                            break;
                        }
                    } while (cfg_find_bb > cfg_start_it);

                    if (in_val_bb)
                    {
                        SgExpression* rd = getRd(instr_num, in_val_bb, in_val.first, cur_BB, available, dependencies, commonVars, byFunc.first, commonBlocks);
                    }
                    else
                    {
                        pair<SAPFOR::Instruction*, SAPFOR::BasicBlock*> instr_and_bb = getInstructionAndBlockByNumber(CFGraph_for_project, instr_num);
                        if (getDependencies(in_val.first, instr_and_bb.first, instr_and_bb.second, dependencies))
                        {
                            //check if all variables affecting the definition can be used there
                            bool can_use = true;
                            auto dependencies_of_var = dependencies.find(in_val.first);
                            if (dependencies_of_var == dependencies.end())
                                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                            for (SAPFOR::Argument* dep : dependencies_of_var->second)
                            {
                                if (!isArgReaches(instr_num, instr_and_bb.second, dep, cur_BB))
                                {
                                    can_use = false;
                                    break;
                                }

                                if (dep->getMemType() == CFG_MEM_TYPE::COMMON_ && commonArgs.find(dep->getValue()) == commonArgs.end())
                                {
                                    can_use = false;
                                    break;
                                }
                            }

                            if (can_use && isBBlockFromCurrentFile(CFGraph_for_project, instr_and_bb.second))
                            {
                                SgExpression* expr_to_subst = copyExpr(instr_and_bb.first->getOperator()->expr(1), byFunc.first, commonBlocks);
                                if (expr_to_subst)
                                    available[in_val.first] = expr_to_subst;
                            }
                        }   
                    }
                }
            }

            vector<SAPFOR::Argument*> arguments_of_func_call = { };
            SgStatement* prev_stmt = NULL;
            for (SAPFOR::IR_Block* byIR : cur_BB->getInstructions())
            {
                SAPFOR::Instruction* instruction = byIR->getInstruction();
                int instruction_number = instruction->getNumber();
                SAPFOR::CFG_OP instr_type = instruction->getOperation();

                SAPFOR::Argument* instr_res = hasStoreStructure(instr_type) ? instruction->getArg1() : instruction->getResult();

                SgStatement* cur_stmt = instruction->getOperator();
                bool stmt_has_result = cur_stmt ? cur_stmt->variant() == ASSIGN_STAT : false;
                
                if (cur_stmt && cur_stmt != prev_stmt && isIfOrLoopFromBB(cur_stmt, cur_BB) 
                    && cur_stmt->variant() != READ_STAT &&
                    analyzedStmt.insert(cur_stmt).second)
                {
                    prev_stmt = cur_stmt;

                    int start_with_expression = 0;
                    if (stmt_has_result)
                    {
                        start_with_expression = 1;

                        SgExpression* result_of_stmt = cur_stmt->expr(0);
                        SgArrayRefExp* array_ref;
                        if (result_of_stmt && (array_ref = isSgArrayRefExp(result_of_stmt)) && array_ref->subscripts())
                            performExprSubstAndSave(cur_stmt, 0, available, commonVars, byFunc.first, funcByName);
                    }

                    for (int i = start_with_expression; i < 3; i++)
                        if (cur_stmt->expr(i))
                            performExprSubstAndSave(cur_stmt, i, available, commonVars, byFunc.first, funcByName);
                }

                if (instr_res)
                {
                    if (instr_res->getType() == CFG_ARG_TYPE::VAR || instr_res->getType() == CFG_ARG_TYPE::ARRAY)
                    {   
                        if (instr_res->getType() == CFG_ARG_TYPE::VAR)
                        {
                            joinChains(available, dependencies, instr_res, commonVars, byFunc.first);
                            SgExpression* prev_expr = NULL;
                            set<SAPFOR::Argument*> prev_dep = {};

                            auto available_it = available.find(instr_res);
                            if (available_it != available.end())
                            {
                                prev_expr = available_it->second;
                                auto dependencies_it = dependencies.find(instr_res);
                                if (dependencies_it != dependencies.end())
                                    prev_dep = dependencies_it->second;
                                else
                                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                                available.erase(available_it);
                            }

                            if (cur_stmt && stmt_has_result && getDependencies(instr_res, instruction, cur_BB, dependencies))
                            {
                                SgExpression* expr_to_subst = copyExpr(cur_stmt->expr(1), byFunc.first, commonBlocks);

                                auto dependencies_it = dependencies.find(instr_res);
                                if (dependencies_it != dependencies.end())
                                {
                                    if (dependencies_it->second.count(instr_res) && prev_expr != NULL)
                                    {

                                        SgExpression* next_expr_to_subst = substSingleSymb(expr_to_subst, instr_res, prev_expr, commonVars, byFunc.first);
                                        if (next_expr_to_subst && next_expr_to_subst != expr_to_subst)
                                        {
                                            expr_to_subst = next_expr_to_subst;

                                            dependencies_it->second.erase(instr_res);
                                            dependencies_it->second.insert(prev_dep.begin(), prev_dep.end());
                                        }
                                    }
                                }

                                if (expr_to_subst)
                                    available[instr_res] = expr_to_subst;
                            }
                        }

                        for (const auto& p : dependencies)
                            if (p.second.count(instr_res))
                                available.erase(p.first);
                        
                        while (idx_it != idx_end_it && *idx_it < instruction_number)
                            idx_it++;
                        if (idx_it != idx_end_it && *idx_it == instruction_number)
                            save_chains(instruction_number, available, dependencies, instr_res);
                    }
                }

                if (instr_type == CFG_OP::PARAM)
                    arguments_of_func_call.push_back(instruction->getArg1());

                if (instr_type == CFG_OP::F_CALL)
                {
                    string func_name = instruction->getArg1()->getValue();
                    auto func_to_call_it = CFGraph_for_project.begin();
                    auto end_of_CFG = CFGraph_for_project.end();
                    for (; func_to_call_it != end_of_CFG; func_to_call_it++)
                    {
                        const auto& p = func_to_call_it->first;
                        if (p->funcName == func_name)
                            break;
                    }

                    if (func_to_call_it != end_of_CFG)
                    {
                        FuncInfo* func = func_to_call_it->first;

                        int number_of_params = arguments_of_func_call.size();
                        for (int i = 0; i < number_of_params; i++)
                        {
                            if (!func->funcParams.isArgOut(i))
                                arguments_of_func_call[i] = NULL;
                        }

                        const set<SAPFOR::Argument*>& affected_vars = func_affects(func, func_to_call_it->second);

                        for (auto& affected : affected_vars)
                        {
                            joinChains(available, dependencies, affected, commonVars, byFunc.first);

                            for (const auto& p : dependencies)
                                if (p.second.count(affected))
                                    available.erase(p.first);

                            if (available.count(affected))
                                available.erase(affected);
                        }

                        auto add_to_available = func_makes_available(func, project, availableForFunc, dependenciesForFunc, func_to_call_it->second, CFGraph_for_project, dependencies, byFunc.first, commonBlocks);
                        available.insert(add_to_available.begin(), add_to_available.end());
                    }

                    for (auto& func_arg : arguments_of_func_call)
                        if (func_arg &&
                            func_arg->getType() != SAPFOR::CFG_ARG_TYPE::CONST && 
                            func_arg->getType() != SAPFOR::CFG_ARG_TYPE::CONST_STR)
                        {
                            joinChains(available, dependencies, func_arg, commonVars, byFunc.first);

                            for (const auto& p : dependencies)
                                if (p.second.count(func_arg))
                                    available.erase(p.first);

                            available.erase(func_arg);
                        }

                    arguments_of_func_call.clear();
                }
            }
        }

        locals.clear();
        saved_available.clear();
        saved_dependencies.clear();
    }

    setSubstitutionsWereBuilt(true);
}

void performRDSubst(const map<FuncInfo*, vector<SAPFOR::BasicBlock*>>& CFGraph_for_project,
                    const map<string, CommonBlock*>& commonBlocks, SgProject* project)
{
    if (!wereSubstitutionsBuilt())
        buildSubstitutions(CFGraph_for_project, commonBlocks, project);
    else
        updateOldExpression();

    __spf_print(PRINT_PROF_INFO, "total number of substitutions: %d\n", getSubstitutionCounter());
    
    if (!wereSubstitutionsApplied())
    {
        applyReplacement(replacementsInFiles);
        setSubstitutionsApplied(true);
    }
}

void revertSubstitutions()
{
    if (wereSubstitutionsBuilt() && wereSubstitutionsApplied())
    {
        applyReplacement(oldExpressionsInFile);
        setSubstitutionsApplied(false);
    }
}


void cancelRevertionForStatement(string filename, SgStatement* stmt, int i) {
    if (i < 0 || i > 2 || !stmt)
        return;
    
    removeInfo(oldExpressionsInFile, filename, stmt, i);
    removeInfo(replacementsInFiles, filename, stmt, i);
}

/*void destroyReplacementsInFiles()
{
    replacementsInFiles.clear();
    setSubstitutionsWereBuilt(false);
}

void destroyOldExpressionsInFile()
{
    oldExpressionsInFile.clear();
    setSubstitutionsWereBuilt(false);
}

void destroyAllSubstitutionsData()
{
    destroyReplacementsInFiles();
    destroyOldExpressionsInFile();
    setSubstitutionsWereBuilt(false);
    setSubstitutionsApplied(false);
}*/