From 2008feb9073377efd549cd0f8704b3e5c00dc376 Mon Sep 17 00:00:00 2001 From: Skydev0h Date: Sun, 5 Jan 2020 02:42:58 +0200 Subject: [PATCH] Highly refined algorithm of detection of builtins and unused vars (-u, -uu) Now builtin replacements are marked with special _Replaced flag Unused calls and variables only have the _Unused flag This way, it is possbile to separate builtin-precalculated from really unused vars Added -u and -uu flags that print out optimized out (disabled) calls and assigns -u prints only unused assigns while -uu prints also all unused calls (to stderr) Such option allows to find out all sorts of different bugs: copy-paste, incorrect pure/impure markings, mistypes or accidental variable redefinitions, etc. --- crypto/func/abscode.cpp | 4 ++ crypto/func/analyzer.cpp | 38 +++++++++--- crypto/func/builtins.cpp | 122 +++++++++++++++++++-------------------- crypto/func/codegen.cpp | 23 ++++++++ crypto/func/func.cpp | 10 +++- crypto/func/func.h | 31 ++++++++-- 6 files changed, 149 insertions(+), 79 deletions(-) diff --git a/crypto/func/abscode.cpp b/crypto/func/abscode.cpp index 3d3348a1..4a280b82 100644 --- a/crypto/func/abscode.cpp +++ b/crypto/func/abscode.cpp @@ -285,6 +285,7 @@ void Op::show(std::ostream& os, const std::vector& vars, std::string pfx os << " ]\n"; } std::string dis = disabled() ? " " : ""; + if (replaced()) dis = " "; // not just disabled if (noreturn()) { dis += " "; } @@ -426,6 +427,9 @@ void Op::show_var_list(std::ostream& os, const std::vector& list, cons if (list[i].is_unused()) { os << '?'; } + if (list[i].is_replaced()) { + os << "#"; + } os << vars.at(list[i].idx) << ':'; list[i].show_value(os); } diff --git a/crypto/func/analyzer.cpp b/crypto/func/analyzer.cpp index 14d0a084..89e2e7b7 100644 --- a/crypto/func/analyzer.cpp +++ b/crypto/func/analyzer.cpp @@ -185,6 +185,17 @@ std::size_t VarDescrList::count_used(const std::vector idx_list) cons return res; } +std::size_t VarDescrList::count_unreplaced(const std::vector idx_list) const { + std::size_t res = 0; + for (var_idx_t idx : idx_list) { + auto v = operator[](idx); + if (!v || !v->is_replaced()) { + ++res; + } + } + return res; +} + VarDescrList& VarDescrList::operator-=(var_idx_t idx) { auto it = std::lower_bound(list.begin(), list.end(), idx); if (it != list.end() && it->idx == idx) { @@ -200,19 +211,22 @@ VarDescrList& VarDescrList::operator-=(const std::vector& idx_list) { return *this; } -VarDescrList& VarDescrList::add_var(var_idx_t idx, bool unused) { +VarDescrList& VarDescrList::add_var(var_idx_t idx, bool unused, bool replaced) { auto it = std::lower_bound(list.begin(), list.end(), idx); if (it == list.end() || it->idx != idx) { - list.emplace(it, idx, VarDescr::_Last | (unused ? VarDescr::_Unused : 0)); + list.emplace(it, idx, VarDescr::_Last | (unused ? VarDescr::_Unused : 0) + | (replaced ? (VarDescr::_Unused | VarDescr::_Replaced) : 0)); } else if (it->is_unused() && !unused) { it->clear_unused(); + } else if (it->is_replaced() && !replaced) { + it->clear_replaced(); } return *this; } -VarDescrList& VarDescrList::add_vars(const std::vector& idx_list, bool unused) { +VarDescrList& VarDescrList::add_vars(const std::vector& idx_list, bool unused, bool replaced) { for (var_idx_t idx : idx_list) { - add_var(idx, unused); + add_var(idx, unused, replaced); } return *this; } @@ -328,7 +342,7 @@ VarDescrList& VarDescrList::import_values(const VarDescrList& values) { return *this; } -bool Op::std_compute_used_vars(bool disabled) { +bool Op::std_compute_used_vars(bool disabled, bool replaced) { // left = OP right // var_info := (var_info - left) + right VarDescrList new_var_info{next->var_info}; @@ -336,10 +350,10 @@ bool Op::std_compute_used_vars(bool disabled) { new_var_info.clear_last(); if (args.size() == right.size() && !disabled) { for (const VarDescr& arg : args) { - new_var_info.add_var(arg.idx, arg.is_unused()); + new_var_info.add_var(arg.idx, arg.is_unused(), arg.is_replaced()); } } else { - new_var_info.add_vars(right, disabled); + new_var_info.add_vars(right, disabled, replaced); } return set_var_info(std::move(new_var_info)); } @@ -358,10 +372,13 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) { // left = EXEC right; if (!next_var_info.count_used(left) && is_pure()) { // all variables in `left` are not needed + bool repl = left.size() && !next_var_info.count_unreplaced(left); if (edit) { disable(); + if (repl) + replace(); // mark as replaced } - return std_compute_used_vars(true); + return std_compute_used_vars(true, repl); } return std_compute_used_vars(); } @@ -375,6 +392,7 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) { case _Let: { // left = right std::size_t cnt = next_var_info.count_used(left); + std::size_t unr = next_var_info.count_unreplaced(left); assert(left.size() == right.size()); auto l_it = left.cbegin(), r_it = right.cbegin(); VarDescrList new_var_info{next_var_info}; @@ -384,7 +402,7 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) { for (; l_it < left.cend(); ++l_it, ++r_it) { if (std::find(l_it + 1, left.cend(), *l_it) == left.cend()) { auto p = next_var_info[*l_it]; - new_var_info.add_var(*r_it, !p || p->is_unused()); + new_var_info.add_var(*r_it, !p || p->is_unused(), p && p->is_replaced()); new_left.push_back(*l_it); new_right.push_back(*r_it); } @@ -396,6 +414,8 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) { if (!cnt && edit) { // all variables in `left` are not needed disable(); + if (left.size() && !unr) + replaced(); } return set_var_info(std::move(new_var_info)); } diff --git a/crypto/func/builtins.cpp b/crypto/func/builtins.cpp index 41951a53..73b5140f 100644 --- a/crypto/func/builtins.cpp +++ b/crypto/func/builtins.cpp @@ -351,13 +351,13 @@ AsmOp compile_add(std::vector& res, std::vector& args) { VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { r.set_const(x.int_const + y.int_const); - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); return push_const(r.int_const); } r.val = emulate_add(x.val, y.val); if (y.is_int_const() && y.int_const->signed_fits_bits(8)) { - y.unused(); + y.replaced(); if (y.always_zero()) { return AsmOp::Nop(); } @@ -370,7 +370,7 @@ AsmOp compile_add(std::vector& res, std::vector& args) { return exec_arg_op("ADDCONST", y.int_const, 1); } if (x.is_int_const() && x.int_const->signed_fits_bits(8)) { - x.unused(); + x.replaced(); if (x.always_zero()) { return AsmOp::Nop(); } @@ -390,13 +390,13 @@ AsmOp compile_sub(std::vector& res, std::vector& args) { VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { r.set_const(x.int_const - y.int_const); - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); return push_const(r.int_const); } r.val = emulate_sub(x.val, y.val); if (y.is_int_const() && (-y.int_const)->signed_fits_bits(8)) { - y.unused(); + y.replaced(); if (y.always_zero()) { return {}; } @@ -409,7 +409,7 @@ AsmOp compile_sub(std::vector& res, std::vector& args) { return exec_arg_op("ADDCONST", -y.int_const, 1); } if (x.always_zero()) { - x.unused(); + x.replaced(); return exec_op("NEGATE", 1); } return exec_op("SUB", 2); @@ -420,7 +420,7 @@ AsmOp compile_negate(std::vector& res, std::vector& args) { VarDescr &r = res[0], &x = args[0]; if (x.is_int_const()) { r.set_const(-x.int_const); - x.unused(); + x.replaced(); return push_const(r.int_const); } r.val = emulate_negate(x.val); @@ -432,15 +432,15 @@ AsmOp compile_mul(std::vector& res, std::vector& args) { VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { r.set_const(x.int_const * y.int_const); - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); return push_const(r.int_const); } r.val = emulate_mul(x.val, y.val); if (y.is_int_const()) { int k = is_pos_pow2(y.int_const); if (y.int_const->signed_fits_bits(8) && k < 0) { - y.unused(); + y.replaced(); if (y.always_zero() && x.always_finite()) { // dubious optimization: NaN * 0 = ? r.set_const(y.int_const); @@ -455,18 +455,18 @@ AsmOp compile_mul(std::vector& res, std::vector& args) { return exec_arg_op("MULCONST", y.int_const, 1); } if (k > 0) { - y.unused(); + y.replaced(); return exec_arg_op("LSHIFT#", k, 1); } if (k == 0) { - y.unused(); + y.replaced(); return AsmOp::Nop(); } } if (x.is_int_const()) { int k = is_pos_pow2(x.int_const); if (x.int_const->signed_fits_bits(8) && k < 0) { - x.unused(); + x.replaced(); if (x.always_zero() && y.always_finite()) { // dubious optimization: NaN * 0 = ? r.set_const(x.int_const); @@ -481,11 +481,11 @@ AsmOp compile_mul(std::vector& res, std::vector& args) { return exec_arg_op("MULCONST", x.int_const, 1); } if (k > 0) { - x.unused(); + x.replaced(); return exec_arg_op("LSHIFT#", k, 1); } if (k == 0) { - x.unused(); + x.replaced(); return AsmOp::Nop(); } } @@ -499,13 +499,13 @@ AsmOp compile_lshift(std::vector& res, std::vector& args) { auto yv = y.int_const->to_long(); if (yv < 0 || yv > 256) { r.set_const_nan(); - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); return push_const(r.int_const); } else if (x.is_int_const()) { r.set_const(x.int_const << (int)yv); - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); return push_const(r.int_const); } } @@ -514,20 +514,20 @@ AsmOp compile_lshift(std::vector& res, std::vector& args) { int k = (int)(y.int_const->to_long()); if (!k /* && x.always_finite() */) { // dubious optimization: what if x=NaN ? - y.unused(); + y.replaced(); return AsmOp::Nop(); } - y.unused(); + y.replaced(); return exec_arg_op("LSHIFT#", k, 1); } if (x.is_int_const()) { auto xv = x.int_const->to_long(); if (xv == 1) { - x.unused(); + x.replaced(); return exec_op("POW2", 1); } if (xv == -1) { - x.unused(); + x.replaced(); return exec_op("NEGPOW2", 1); } } @@ -541,13 +541,13 @@ AsmOp compile_rshift(std::vector& res, std::vector& args, in auto yv = y.int_const->to_long(); if (yv < 0 || yv > 256) { r.set_const_nan(); - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); return push_const(r.int_const); } else if (x.is_int_const()) { r.set_const(td::rshift(x.int_const, (int)yv, round_mode)); - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); return push_const(r.int_const); } } @@ -557,10 +557,10 @@ AsmOp compile_rshift(std::vector& res, std::vector& args, in int k = (int)(y.int_const->to_long()); if (!k /* && x.always_finite() */) { // dubious optimization: what if x=NaN ? - y.unused(); + y.replaced(); return AsmOp::Nop(); } - y.unused(); + y.replaced(); return exec_arg_op(rshift + "#", k, 1); } return exec_op(rshift, 2); @@ -571,29 +571,29 @@ AsmOp compile_div(std::vector& res, std::vector& args, int r VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { r.set_const(div(x.int_const, y.int_const, round_mode)); - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); return push_const(r.int_const); } r.val = emulate_div(x.val, y.val); if (y.is_int_const()) { if (*y.int_const == 0) { - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); r.set_const(div(y.int_const, y.int_const)); return push_const(r.int_const); } if (*y.int_const == 1 && x.always_finite()) { - y.unused(); + y.replaced(); return AsmOp::Nop(); } if (*y.int_const == -1) { - y.unused(); + y.replaced(); return exec_op("NEGATE", 1); } int k = is_pos_pow2(y.int_const); if (k > 0) { - y.unused(); + y.replaced(); std::string op = "RSHIFT"; if (round_mode >= 0) { op += (round_mode > 0 ? 'C' : 'R'); @@ -613,27 +613,27 @@ AsmOp compile_mod(std::vector& res, std::vector& args, int r VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { r.set_const(mod(x.int_const, y.int_const, round_mode)); - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); return push_const(r.int_const); } r.val = emulate_mod(x.val, y.val); if (y.is_int_const()) { if (*y.int_const == 0) { - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); r.set_const(mod(y.int_const, y.int_const)); return push_const(r.int_const); } if ((*y.int_const == 1 || *y.int_const == -1) && x.always_finite()) { - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); r.set_const(td::RefInt256{true, 0}); return push_const(r.int_const); } int k = is_pos_pow2(y.int_const); if (k > 0) { - y.unused(); + y.replaced(); std::string op = "MODPOW2"; if (round_mode >= 0) { op += (round_mode > 0 ? 'C' : 'R'); @@ -696,8 +696,8 @@ AsmOp compile_cmp_int(std::vector& res, std::vector& args, i if (x.is_int_const() && y.is_int_const()) { int v = compute_compare(x.int_const, y.int_const, mode); r.set_const(v); - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); return mode == 7 ? push_const(r.int_const) : AsmOp::BoolConst(v != 0); } int v = compute_compare(x, y, mode); @@ -705,8 +705,8 @@ AsmOp compile_cmp_int(std::vector& res, std::vector& args, i assert(v); if (!(v & (v - 1))) { r.set_const(v - (v >> 2) - 2); - x.unused(); - y.unused(); + x.replaced(); + y.replaced(); return mode == 7 ? push_const(r.int_const) : AsmOp::BoolConst(v & 1); } r.val = ~0; @@ -725,11 +725,11 @@ AsmOp compile_cmp_int(std::vector& res, std::vector& args, i static int cmp_int_delta[] = {0, 0, 0, -1, 0, 0, 1}; if (mode != 7) { if (y.is_int_const() && y.int_const >= -128 && y.int_const <= 127) { - y.unused(); + y.replaced(); return exec_arg_op(cmp_int_names[mode], y.int_const + cmp_int_delta[mode], 1); } if (x.is_int_const() && x.int_const >= -128 && x.int_const <= 127) { - x.unused(); + x.replaced(); mode = ((mode & 4) >> 2) | (mode & 2) | ((mode & 1) << 2); return exec_arg_op(cmp_int_names[mode], x.int_const + cmp_int_delta[mode], 1); } @@ -741,7 +741,7 @@ AsmOp compile_throw(std::vector& res, std::vector& args) { assert(res.empty() && args.size() == 1); VarDescr& x = args[0]; if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) { - x.unused(); + x.replaced(); return exec_arg_op("THROW", x.int_const, 0, 0); } else { return exec_op("THROWANY", 1, 0); @@ -754,15 +754,15 @@ AsmOp compile_cond_throw(std::vector& res, std::vector& args std::string suff = (mode ? "IF" : "IFNOT"); bool skip_cond = false; if (y.always_true() || y.always_false()) { - y.unused(); + y.replaced(); skip_cond = true; if (y.always_true() != mode) { - x.unused(); + x.replaced(); return AsmOp::Nop(); } } if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) { - x.unused(); + x.replaced(); return skip_cond ? exec_arg_op("THROW", x.int_const, 0, 0) : exec_arg_op("THROW"s + suff, x.int_const, 1, 0); } else { return skip_cond ? exec_op("THROWANY", 1, 0) : exec_arg_op("THROWANY"s + suff, 2, 0); @@ -794,7 +794,7 @@ AsmOp compile_fetch_int(std::vector& res, std::vector& args, r.val = (sgnd ? VarDescr::ValBool : VarDescr::ValBit); } if (v > 0) { - y.unused(); + y.replaced(); return exec_arg_op((fetch ? "LD"s : "PLD"s) + (sgnd ? 'I' : 'U'), v, 1, 1 + (unsigned)fetch); } } @@ -807,7 +807,7 @@ AsmOp compile_store_int(std::vector& res, std::vector& args, assert(args.size() == 3 && res.size() == 1); auto& z = args[2]; if (z.is_int_const() && z.int_const > 0 && z.int_const <= 256) { - z.unused(); + z.replaced(); return exec_arg_op("ST"s + (sgnd ? 'I' : 'U'), z.int_const, 2, 1); } return exec_op("ST"s + (sgnd ? "IX" : "UX"), 3, 1); @@ -820,7 +820,7 @@ AsmOp compile_fetch_slice(std::vector& res, std::vector& arg if (y.is_int_const() && y.int_const > 0 && y.int_const <= 256) { v = (int)y.int_const->to_long(); if (v > 0) { - y.unused(); + y.replaced(); return exec_arg_op(fetch ? "LDSLICE" : "PLDSLICE", v, 1, 1 + (unsigned)fetch); } } @@ -832,7 +832,7 @@ AsmOp compile_tuple_at(std::vector& res, std::vector& args) assert(args.size() == 2 && res.size() == 1); auto& y = args[1]; if (y.is_int_const() && y.int_const >= 0 && y.int_const < 16) { - y.unused(); + y.replaced(); return exec_arg_op("INDEX", y.int_const, 1, 1); } return exec_op("INDEXVAR", 2, 1); @@ -843,7 +843,7 @@ AsmOp compile_is_null(std::vector& res, std::vector& args) { assert(args.size() == 1 && res.size() == 1); auto &x = args[0], &r = res[0]; if (x.always_null() || x.always_not_null()) { - x.unused(); + x.replaced(); r.set_const(x.always_null() ? -1 : 0); return push_const(r.int_const); } @@ -856,7 +856,7 @@ bool compile_run_method(AsmOpList& code, std::vector& res, std::vector assert(args.size() == (unsigned)n + 1 && res.size() == (unsigned)has_value); auto& x = args[0]; if (x.is_int_const() && x.int_const->unsigned_fits_bits(14)) { - x.unused(); + x.replaced(); code << exec_arg_op("PREPAREDICT", x.int_const, 0, 2); } else { code << exec_op("c3 PUSH", 0, 1); diff --git a/crypto/func/codegen.cpp b/crypto/func/codegen.cpp index 91bb0be9..8e644564 100644 --- a/crypto/func/codegen.cpp +++ b/crypto/func/codegen.cpp @@ -362,10 +362,13 @@ bool Op::generate_code_step(Stack& stack) { int i = 0; std::vector active; active.reserve(left.size()); + int unused = 0; for (std::size_t k = 0; k < left.size(); k++) { var_idx_t y = left[k]; // "y" = "x" auto p = next_var_info[y]; active.push_back(p && !p->is_unused()); + if (p && p->is_unused() && !p->is_replaced()) + ++unused; } for (std::size_t k = 0; k < left.size(); k++) { if (!active[k]) { @@ -394,11 +397,31 @@ bool Op::generate_code_step(Stack& stack) { stack.assign_var(left[k], --i); } } + if (funC::warn_unused >= 1 && unused > 0) { + where.show(std::cerr); + if (left.size() != 1) { + std::cerr << "\tWarning: unused " << unused + << " out of " << left.size() << " assigned variables" << std::endl; + } else { + std::cerr << "\tWarning: unused variable assignment" << std::endl; + } + where.show_context(std::cerr); + } return true; } case _Call: case _CallInd: { if (disabled()) { + if (funC::warn_unused >= 2 && !replaced()) { + where.show(std::cerr); + std::cerr << "\tWarning: unused "; + if (cl == _Call) + std::cerr << "call"; + else + std::cerr << "indirect call"; + std::cerr << " to " << fun_ref->name() << "\n"; + where.show_context(std::cerr); + } return true; } SymValFunc* func = (fun_ref ? dynamic_cast(fun_ref->value) : nullptr); diff --git a/crypto/func/func.cpp b/crypto/func/func.cpp index 5b937c83..6771e140 100644 --- a/crypto/func/func.cpp +++ b/crypto/func/func.cpp @@ -34,7 +34,7 @@ namespace funC { -int verbosity, indent, opt_level = 2; +int verbosity, indent, opt_level = 2, warn_unused; bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble; std::ostream* outs = &std::cout; std::string generated_from, boc_output_filename; @@ -171,7 +171,8 @@ void usage(const char* progname) { "-S\tInclude stack layout comments in the output code\n" "-R\tInclude operation rewrite comments in the output code\n" "-W\tInclude Fift code to serialize and save generated code into specified BoC file. Enables " - "-A and -P.\n"; + "-A and -P.\n" + "-u\tEnable warnings about unused calls and variables (once for assigns, twice also for calls)\n"; std::exit(2); } @@ -180,7 +181,7 @@ std::string output_filename; int main(int argc, char* const argv[]) { int i; bool interactive = false; - while ((i = getopt(argc, argv, "Ahi:Io:O:PRSvW:")) != -1) { + while ((i = getopt(argc, argv, "Ahi:Io:O:PRSuvW:")) != -1) { switch (i) { case 'A': funC::asm_preamble = true; @@ -206,6 +207,9 @@ int main(int argc, char* const argv[]) { case 'S': funC::stack_layout_comments = true; break; + case 'u': + ++funC::warn_unused; + break; case 'v': ++funC::verbosity; break; diff --git a/crypto/func/func.h b/crypto/func/func.h index 789319c5..f7e0601c 100644 --- a/crypto/func/func.h +++ b/crypto/func/func.h @@ -33,7 +33,7 @@ namespace funC { -extern int verbosity; +extern int verbosity, warn_unused; extern bool op_rewrite_comments; constexpr int optimize_depth = 12; @@ -290,7 +290,7 @@ struct TmpVar { struct VarDescr { var_idx_t idx; - enum { _Last = 1, _Unused = 2 }; + enum { _Last = 1, _Unused = 2, _Replaced = 4 }; int flags; enum { _Const = 16, @@ -325,6 +325,9 @@ struct VarDescr { bool is_unused() const { return flags & _Unused; } + bool is_replaced() const { + return flags & _Replaced; + } bool is_last() const { return flags & _Last; } @@ -382,8 +385,16 @@ struct VarDescr { void unused() { flags |= _Unused; } + void replaced() { + unused(); // replaced var is always unused + flags |= _Replaced; + } void clear_unused() { flags &= ~_Unused; + clear_replaced(); + } + void clear_replaced() { + flags &= ~_Replaced; } void set_const(long long value); void set_const(td::RefInt256 value); @@ -433,12 +444,13 @@ struct VarDescrList { VarDescrList& operator+=(const std::vector& idx_list) { return add_vars(idx_list); } - VarDescrList& add_var(var_idx_t idx, bool unused = false); - VarDescrList& add_vars(const std::vector& idx_list, bool unused = false); + VarDescrList& add_var(var_idx_t idx, bool unused = false, bool replaced = false); + VarDescrList& add_vars(const std::vector& idx_list, bool unused = false, bool replaced = false); VarDescrList& operator-=(const std::vector& idx_list); VarDescrList& operator-=(var_idx_t idx); std::size_t count(const std::vector idx_list) const; std::size_t count_used(const std::vector idx_list) const; + std::size_t count_unreplaced(const std::vector idx_list) const; VarDescr& add(var_idx_t idx); VarDescr& add_newval(var_idx_t idx); VarDescrList& operator&=(const VarDescrList& values); @@ -512,7 +524,7 @@ struct Op { _Again }; int cl; - enum { _Disabled = 1, _Reachable = 2, _NoReturn = 4, _ImpureR = 8, _ImpureW = 16, _Impure = 24 }; + enum { _Disabled = 1, _Reachable = 2, _NoReturn = 4, _ImpureR = 8, _ImpureW = 16, _Impure = 24, _Replaced = 32 }; int flags; std::unique_ptr next; SymDef* fun_ref; @@ -544,12 +556,19 @@ struct Op { bool disabled() const { return flags & _Disabled; } + bool replaced() const { + return flags & _Replaced; + } bool enabled() const { return !disabled(); } void disable() { flags |= _Disabled; } + void replace() { + disable(); + flags |= _Replaced; + } bool unreachable() { return !(flags & _Reachable); } @@ -562,7 +581,7 @@ struct Op { void split_vars(const std::vector& vars); static void split_var_list(std::vector& var_list, const std::vector& vars); bool compute_used_vars(const CodeBlob& code, bool edit); - bool std_compute_used_vars(bool disabled = false); + bool std_compute_used_vars(bool disabled = false, bool replaced = false); bool set_var_info(const VarDescrList& new_var_info); bool set_var_info(VarDescrList&& new_var_info); bool set_var_info_except(const VarDescrList& new_var_info, const std::vector& var_list);