1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

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.
This commit is contained in:
Skydev0h 2020-01-05 02:42:58 +02:00
parent acf16718e6
commit 2008feb907
6 changed files with 149 additions and 79 deletions

View file

@ -285,6 +285,7 @@ void Op::show(std::ostream& os, const std::vector<TmpVar>& vars, std::string pfx
os << " ]\n"; os << " ]\n";
} }
std::string dis = disabled() ? "<disabled> " : ""; std::string dis = disabled() ? "<disabled> " : "";
if (replaced()) dis = "<replaced> "; // not just disabled
if (noreturn()) { if (noreturn()) {
dis += "<noret> "; dis += "<noret> ";
} }
@ -426,6 +427,9 @@ void Op::show_var_list(std::ostream& os, const std::vector<VarDescr>& list, cons
if (list[i].is_unused()) { if (list[i].is_unused()) {
os << '?'; os << '?';
} }
if (list[i].is_replaced()) {
os << "#";
}
os << vars.at(list[i].idx) << ':'; os << vars.at(list[i].idx) << ':';
list[i].show_value(os); list[i].show_value(os);
} }

View file

@ -185,6 +185,17 @@ std::size_t VarDescrList::count_used(const std::vector<var_idx_t> idx_list) cons
return res; return res;
} }
std::size_t VarDescrList::count_unreplaced(const std::vector<var_idx_t> 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) { VarDescrList& VarDescrList::operator-=(var_idx_t idx) {
auto it = std::lower_bound(list.begin(), list.end(), idx); auto it = std::lower_bound(list.begin(), list.end(), idx);
if (it != list.end() && it->idx == idx) { if (it != list.end() && it->idx == idx) {
@ -200,19 +211,22 @@ VarDescrList& VarDescrList::operator-=(const std::vector<var_idx_t>& idx_list) {
return *this; 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); auto it = std::lower_bound(list.begin(), list.end(), idx);
if (it == list.end() || it->idx != 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) { } else if (it->is_unused() && !unused) {
it->clear_unused(); it->clear_unused();
} else if (it->is_replaced() && !replaced) {
it->clear_replaced();
} }
return *this; return *this;
} }
VarDescrList& VarDescrList::add_vars(const std::vector<var_idx_t>& idx_list, bool unused) { VarDescrList& VarDescrList::add_vars(const std::vector<var_idx_t>& idx_list, bool unused, bool replaced) {
for (var_idx_t idx : idx_list) { for (var_idx_t idx : idx_list) {
add_var(idx, unused); add_var(idx, unused, replaced);
} }
return *this; return *this;
} }
@ -328,7 +342,7 @@ VarDescrList& VarDescrList::import_values(const VarDescrList& values) {
return *this; return *this;
} }
bool Op::std_compute_used_vars(bool disabled) { bool Op::std_compute_used_vars(bool disabled, bool replaced) {
// left = OP right // left = OP right
// var_info := (var_info - left) + right // var_info := (var_info - left) + right
VarDescrList new_var_info{next->var_info}; VarDescrList new_var_info{next->var_info};
@ -336,10 +350,10 @@ bool Op::std_compute_used_vars(bool disabled) {
new_var_info.clear_last(); new_var_info.clear_last();
if (args.size() == right.size() && !disabled) { if (args.size() == right.size() && !disabled) {
for (const VarDescr& arg : args) { 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 { } 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)); 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; // left = EXEC right;
if (!next_var_info.count_used(left) && is_pure()) { if (!next_var_info.count_used(left) && is_pure()) {
// all variables in `left` are not needed // all variables in `left` are not needed
bool repl = left.size() && !next_var_info.count_unreplaced(left);
if (edit) { if (edit) {
disable(); 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(); return std_compute_used_vars();
} }
@ -375,6 +392,7 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
case _Let: { case _Let: {
// left = right // left = right
std::size_t cnt = next_var_info.count_used(left); 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()); assert(left.size() == right.size());
auto l_it = left.cbegin(), r_it = right.cbegin(); auto l_it = left.cbegin(), r_it = right.cbegin();
VarDescrList new_var_info{next_var_info}; 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) { for (; l_it < left.cend(); ++l_it, ++r_it) {
if (std::find(l_it + 1, left.cend(), *l_it) == left.cend()) { if (std::find(l_it + 1, left.cend(), *l_it) == left.cend()) {
auto p = next_var_info[*l_it]; 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_left.push_back(*l_it);
new_right.push_back(*r_it); new_right.push_back(*r_it);
} }
@ -396,6 +414,8 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
if (!cnt && edit) { if (!cnt && edit) {
// all variables in `left` are not needed // all variables in `left` are not needed
disable(); disable();
if (left.size() && !unr)
replaced();
} }
return set_var_info(std::move(new_var_info)); return set_var_info(std::move(new_var_info));
} }

View file

@ -351,13 +351,13 @@ AsmOp compile_add(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
VarDescr &r = res[0], &x = args[0], &y = args[1]; VarDescr &r = res[0], &x = args[0], &y = args[1];
if (x.is_int_const() && y.is_int_const()) { if (x.is_int_const() && y.is_int_const()) {
r.set_const(x.int_const + y.int_const); r.set_const(x.int_const + y.int_const);
x.unused(); x.replaced();
y.unused(); y.replaced();
return push_const(r.int_const); return push_const(r.int_const);
} }
r.val = emulate_add(x.val, y.val); r.val = emulate_add(x.val, y.val);
if (y.is_int_const() && y.int_const->signed_fits_bits(8)) { if (y.is_int_const() && y.int_const->signed_fits_bits(8)) {
y.unused(); y.replaced();
if (y.always_zero()) { if (y.always_zero()) {
return AsmOp::Nop(); return AsmOp::Nop();
} }
@ -370,7 +370,7 @@ AsmOp compile_add(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
return exec_arg_op("ADDCONST", y.int_const, 1); return exec_arg_op("ADDCONST", y.int_const, 1);
} }
if (x.is_int_const() && x.int_const->signed_fits_bits(8)) { if (x.is_int_const() && x.int_const->signed_fits_bits(8)) {
x.unused(); x.replaced();
if (x.always_zero()) { if (x.always_zero()) {
return AsmOp::Nop(); return AsmOp::Nop();
} }
@ -390,13 +390,13 @@ AsmOp compile_sub(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
VarDescr &r = res[0], &x = args[0], &y = args[1]; VarDescr &r = res[0], &x = args[0], &y = args[1];
if (x.is_int_const() && y.is_int_const()) { if (x.is_int_const() && y.is_int_const()) {
r.set_const(x.int_const - y.int_const); r.set_const(x.int_const - y.int_const);
x.unused(); x.replaced();
y.unused(); y.replaced();
return push_const(r.int_const); return push_const(r.int_const);
} }
r.val = emulate_sub(x.val, y.val); r.val = emulate_sub(x.val, y.val);
if (y.is_int_const() && (-y.int_const)->signed_fits_bits(8)) { if (y.is_int_const() && (-y.int_const)->signed_fits_bits(8)) {
y.unused(); y.replaced();
if (y.always_zero()) { if (y.always_zero()) {
return {}; return {};
} }
@ -409,7 +409,7 @@ AsmOp compile_sub(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
return exec_arg_op("ADDCONST", -y.int_const, 1); return exec_arg_op("ADDCONST", -y.int_const, 1);
} }
if (x.always_zero()) { if (x.always_zero()) {
x.unused(); x.replaced();
return exec_op("NEGATE", 1); return exec_op("NEGATE", 1);
} }
return exec_op("SUB", 2); return exec_op("SUB", 2);
@ -420,7 +420,7 @@ AsmOp compile_negate(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
VarDescr &r = res[0], &x = args[0]; VarDescr &r = res[0], &x = args[0];
if (x.is_int_const()) { if (x.is_int_const()) {
r.set_const(-x.int_const); r.set_const(-x.int_const);
x.unused(); x.replaced();
return push_const(r.int_const); return push_const(r.int_const);
} }
r.val = emulate_negate(x.val); r.val = emulate_negate(x.val);
@ -432,15 +432,15 @@ AsmOp compile_mul(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
VarDescr &r = res[0], &x = args[0], &y = args[1]; VarDescr &r = res[0], &x = args[0], &y = args[1];
if (x.is_int_const() && y.is_int_const()) { if (x.is_int_const() && y.is_int_const()) {
r.set_const(x.int_const * y.int_const); r.set_const(x.int_const * y.int_const);
x.unused(); x.replaced();
y.unused(); y.replaced();
return push_const(r.int_const); return push_const(r.int_const);
} }
r.val = emulate_mul(x.val, y.val); r.val = emulate_mul(x.val, y.val);
if (y.is_int_const()) { if (y.is_int_const()) {
int k = is_pos_pow2(y.int_const); int k = is_pos_pow2(y.int_const);
if (y.int_const->signed_fits_bits(8) && k < 0) { if (y.int_const->signed_fits_bits(8) && k < 0) {
y.unused(); y.replaced();
if (y.always_zero() && x.always_finite()) { if (y.always_zero() && x.always_finite()) {
// dubious optimization: NaN * 0 = ? // dubious optimization: NaN * 0 = ?
r.set_const(y.int_const); r.set_const(y.int_const);
@ -455,18 +455,18 @@ AsmOp compile_mul(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
return exec_arg_op("MULCONST", y.int_const, 1); return exec_arg_op("MULCONST", y.int_const, 1);
} }
if (k > 0) { if (k > 0) {
y.unused(); y.replaced();
return exec_arg_op("LSHIFT#", k, 1); return exec_arg_op("LSHIFT#", k, 1);
} }
if (k == 0) { if (k == 0) {
y.unused(); y.replaced();
return AsmOp::Nop(); return AsmOp::Nop();
} }
} }
if (x.is_int_const()) { if (x.is_int_const()) {
int k = is_pos_pow2(x.int_const); int k = is_pos_pow2(x.int_const);
if (x.int_const->signed_fits_bits(8) && k < 0) { if (x.int_const->signed_fits_bits(8) && k < 0) {
x.unused(); x.replaced();
if (x.always_zero() && y.always_finite()) { if (x.always_zero() && y.always_finite()) {
// dubious optimization: NaN * 0 = ? // dubious optimization: NaN * 0 = ?
r.set_const(x.int_const); r.set_const(x.int_const);
@ -481,11 +481,11 @@ AsmOp compile_mul(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
return exec_arg_op("MULCONST", x.int_const, 1); return exec_arg_op("MULCONST", x.int_const, 1);
} }
if (k > 0) { if (k > 0) {
x.unused(); x.replaced();
return exec_arg_op("LSHIFT#", k, 1); return exec_arg_op("LSHIFT#", k, 1);
} }
if (k == 0) { if (k == 0) {
x.unused(); x.replaced();
return AsmOp::Nop(); return AsmOp::Nop();
} }
} }
@ -499,13 +499,13 @@ AsmOp compile_lshift(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
auto yv = y.int_const->to_long(); auto yv = y.int_const->to_long();
if (yv < 0 || yv > 256) { if (yv < 0 || yv > 256) {
r.set_const_nan(); r.set_const_nan();
x.unused(); x.replaced();
y.unused(); y.replaced();
return push_const(r.int_const); return push_const(r.int_const);
} else if (x.is_int_const()) { } else if (x.is_int_const()) {
r.set_const(x.int_const << (int)yv); r.set_const(x.int_const << (int)yv);
x.unused(); x.replaced();
y.unused(); y.replaced();
return push_const(r.int_const); return push_const(r.int_const);
} }
} }
@ -514,20 +514,20 @@ AsmOp compile_lshift(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
int k = (int)(y.int_const->to_long()); int k = (int)(y.int_const->to_long());
if (!k /* && x.always_finite() */) { if (!k /* && x.always_finite() */) {
// dubious optimization: what if x=NaN ? // dubious optimization: what if x=NaN ?
y.unused(); y.replaced();
return AsmOp::Nop(); return AsmOp::Nop();
} }
y.unused(); y.replaced();
return exec_arg_op("LSHIFT#", k, 1); return exec_arg_op("LSHIFT#", k, 1);
} }
if (x.is_int_const()) { if (x.is_int_const()) {
auto xv = x.int_const->to_long(); auto xv = x.int_const->to_long();
if (xv == 1) { if (xv == 1) {
x.unused(); x.replaced();
return exec_op("POW2", 1); return exec_op("POW2", 1);
} }
if (xv == -1) { if (xv == -1) {
x.unused(); x.replaced();
return exec_op("NEGPOW2", 1); return exec_op("NEGPOW2", 1);
} }
} }
@ -541,13 +541,13 @@ AsmOp compile_rshift(std::vector<VarDescr>& res, std::vector<VarDescr>& args, in
auto yv = y.int_const->to_long(); auto yv = y.int_const->to_long();
if (yv < 0 || yv > 256) { if (yv < 0 || yv > 256) {
r.set_const_nan(); r.set_const_nan();
x.unused(); x.replaced();
y.unused(); y.replaced();
return push_const(r.int_const); return push_const(r.int_const);
} else if (x.is_int_const()) { } else if (x.is_int_const()) {
r.set_const(td::rshift(x.int_const, (int)yv, round_mode)); r.set_const(td::rshift(x.int_const, (int)yv, round_mode));
x.unused(); x.replaced();
y.unused(); y.replaced();
return push_const(r.int_const); return push_const(r.int_const);
} }
} }
@ -557,10 +557,10 @@ AsmOp compile_rshift(std::vector<VarDescr>& res, std::vector<VarDescr>& args, in
int k = (int)(y.int_const->to_long()); int k = (int)(y.int_const->to_long());
if (!k /* && x.always_finite() */) { if (!k /* && x.always_finite() */) {
// dubious optimization: what if x=NaN ? // dubious optimization: what if x=NaN ?
y.unused(); y.replaced();
return AsmOp::Nop(); return AsmOp::Nop();
} }
y.unused(); y.replaced();
return exec_arg_op(rshift + "#", k, 1); return exec_arg_op(rshift + "#", k, 1);
} }
return exec_op(rshift, 2); return exec_op(rshift, 2);
@ -571,29 +571,29 @@ AsmOp compile_div(std::vector<VarDescr>& res, std::vector<VarDescr>& args, int r
VarDescr &r = res[0], &x = args[0], &y = args[1]; VarDescr &r = res[0], &x = args[0], &y = args[1];
if (x.is_int_const() && y.is_int_const()) { if (x.is_int_const() && y.is_int_const()) {
r.set_const(div(x.int_const, y.int_const, round_mode)); r.set_const(div(x.int_const, y.int_const, round_mode));
x.unused(); x.replaced();
y.unused(); y.replaced();
return push_const(r.int_const); return push_const(r.int_const);
} }
r.val = emulate_div(x.val, y.val); r.val = emulate_div(x.val, y.val);
if (y.is_int_const()) { if (y.is_int_const()) {
if (*y.int_const == 0) { if (*y.int_const == 0) {
x.unused(); x.replaced();
y.unused(); y.replaced();
r.set_const(div(y.int_const, y.int_const)); r.set_const(div(y.int_const, y.int_const));
return push_const(r.int_const); return push_const(r.int_const);
} }
if (*y.int_const == 1 && x.always_finite()) { if (*y.int_const == 1 && x.always_finite()) {
y.unused(); y.replaced();
return AsmOp::Nop(); return AsmOp::Nop();
} }
if (*y.int_const == -1) { if (*y.int_const == -1) {
y.unused(); y.replaced();
return exec_op("NEGATE", 1); return exec_op("NEGATE", 1);
} }
int k = is_pos_pow2(y.int_const); int k = is_pos_pow2(y.int_const);
if (k > 0) { if (k > 0) {
y.unused(); y.replaced();
std::string op = "RSHIFT"; std::string op = "RSHIFT";
if (round_mode >= 0) { if (round_mode >= 0) {
op += (round_mode > 0 ? 'C' : 'R'); op += (round_mode > 0 ? 'C' : 'R');
@ -613,27 +613,27 @@ AsmOp compile_mod(std::vector<VarDescr>& res, std::vector<VarDescr>& args, int r
VarDescr &r = res[0], &x = args[0], &y = args[1]; VarDescr &r = res[0], &x = args[0], &y = args[1];
if (x.is_int_const() && y.is_int_const()) { if (x.is_int_const() && y.is_int_const()) {
r.set_const(mod(x.int_const, y.int_const, round_mode)); r.set_const(mod(x.int_const, y.int_const, round_mode));
x.unused(); x.replaced();
y.unused(); y.replaced();
return push_const(r.int_const); return push_const(r.int_const);
} }
r.val = emulate_mod(x.val, y.val); r.val = emulate_mod(x.val, y.val);
if (y.is_int_const()) { if (y.is_int_const()) {
if (*y.int_const == 0) { if (*y.int_const == 0) {
x.unused(); x.replaced();
y.unused(); y.replaced();
r.set_const(mod(y.int_const, y.int_const)); r.set_const(mod(y.int_const, y.int_const));
return push_const(r.int_const); return push_const(r.int_const);
} }
if ((*y.int_const == 1 || *y.int_const == -1) && x.always_finite()) { if ((*y.int_const == 1 || *y.int_const == -1) && x.always_finite()) {
x.unused(); x.replaced();
y.unused(); y.replaced();
r.set_const(td::RefInt256{true, 0}); r.set_const(td::RefInt256{true, 0});
return push_const(r.int_const); return push_const(r.int_const);
} }
int k = is_pos_pow2(y.int_const); int k = is_pos_pow2(y.int_const);
if (k > 0) { if (k > 0) {
y.unused(); y.replaced();
std::string op = "MODPOW2"; std::string op = "MODPOW2";
if (round_mode >= 0) { if (round_mode >= 0) {
op += (round_mode > 0 ? 'C' : 'R'); op += (round_mode > 0 ? 'C' : 'R');
@ -696,8 +696,8 @@ AsmOp compile_cmp_int(std::vector<VarDescr>& res, std::vector<VarDescr>& args, i
if (x.is_int_const() && y.is_int_const()) { if (x.is_int_const() && y.is_int_const()) {
int v = compute_compare(x.int_const, y.int_const, mode); int v = compute_compare(x.int_const, y.int_const, mode);
r.set_const(v); r.set_const(v);
x.unused(); x.replaced();
y.unused(); y.replaced();
return mode == 7 ? push_const(r.int_const) : AsmOp::BoolConst(v != 0); return mode == 7 ? push_const(r.int_const) : AsmOp::BoolConst(v != 0);
} }
int v = compute_compare(x, y, mode); int v = compute_compare(x, y, mode);
@ -705,8 +705,8 @@ AsmOp compile_cmp_int(std::vector<VarDescr>& res, std::vector<VarDescr>& args, i
assert(v); assert(v);
if (!(v & (v - 1))) { if (!(v & (v - 1))) {
r.set_const(v - (v >> 2) - 2); r.set_const(v - (v >> 2) - 2);
x.unused(); x.replaced();
y.unused(); y.replaced();
return mode == 7 ? push_const(r.int_const) : AsmOp::BoolConst(v & 1); return mode == 7 ? push_const(r.int_const) : AsmOp::BoolConst(v & 1);
} }
r.val = ~0; r.val = ~0;
@ -725,11 +725,11 @@ AsmOp compile_cmp_int(std::vector<VarDescr>& res, std::vector<VarDescr>& args, i
static int cmp_int_delta[] = {0, 0, 0, -1, 0, 0, 1}; static int cmp_int_delta[] = {0, 0, 0, -1, 0, 0, 1};
if (mode != 7) { if (mode != 7) {
if (y.is_int_const() && y.int_const >= -128 && y.int_const <= 127) { 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); 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) { 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); 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); 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<VarDescr>& res, std::vector<VarDescr>& args) {
assert(res.empty() && args.size() == 1); assert(res.empty() && args.size() == 1);
VarDescr& x = args[0]; VarDescr& x = args[0];
if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) { 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); return exec_arg_op("THROW", x.int_const, 0, 0);
} else { } else {
return exec_op("THROWANY", 1, 0); return exec_op("THROWANY", 1, 0);
@ -754,15 +754,15 @@ AsmOp compile_cond_throw(std::vector<VarDescr>& res, std::vector<VarDescr>& args
std::string suff = (mode ? "IF" : "IFNOT"); std::string suff = (mode ? "IF" : "IFNOT");
bool skip_cond = false; bool skip_cond = false;
if (y.always_true() || y.always_false()) { if (y.always_true() || y.always_false()) {
y.unused(); y.replaced();
skip_cond = true; skip_cond = true;
if (y.always_true() != mode) { if (y.always_true() != mode) {
x.unused(); x.replaced();
return AsmOp::Nop(); return AsmOp::Nop();
} }
} }
if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) { 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); return skip_cond ? exec_arg_op("THROW", x.int_const, 0, 0) : exec_arg_op("THROW"s + suff, x.int_const, 1, 0);
} else { } else {
return skip_cond ? exec_op("THROWANY", 1, 0) : exec_arg_op("THROWANY"s + suff, 2, 0); 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<VarDescr>& res, std::vector<VarDescr>& args,
r.val = (sgnd ? VarDescr::ValBool : VarDescr::ValBit); r.val = (sgnd ? VarDescr::ValBool : VarDescr::ValBit);
} }
if (v > 0) { if (v > 0) {
y.unused(); y.replaced();
return exec_arg_op((fetch ? "LD"s : "PLD"s) + (sgnd ? 'I' : 'U'), v, 1, 1 + (unsigned)fetch); 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<VarDescr>& res, std::vector<VarDescr>& args,
assert(args.size() == 3 && res.size() == 1); assert(args.size() == 3 && res.size() == 1);
auto& z = args[2]; auto& z = args[2];
if (z.is_int_const() && z.int_const > 0 && z.int_const <= 256) { 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_arg_op("ST"s + (sgnd ? 'I' : 'U'), z.int_const, 2, 1);
} }
return exec_op("ST"s + (sgnd ? "IX" : "UX"), 3, 1); return exec_op("ST"s + (sgnd ? "IX" : "UX"), 3, 1);
@ -820,7 +820,7 @@ AsmOp compile_fetch_slice(std::vector<VarDescr>& res, std::vector<VarDescr>& arg
if (y.is_int_const() && y.int_const > 0 && y.int_const <= 256) { if (y.is_int_const() && y.int_const > 0 && y.int_const <= 256) {
v = (int)y.int_const->to_long(); v = (int)y.int_const->to_long();
if (v > 0) { if (v > 0) {
y.unused(); y.replaced();
return exec_arg_op(fetch ? "LDSLICE" : "PLDSLICE", v, 1, 1 + (unsigned)fetch); return exec_arg_op(fetch ? "LDSLICE" : "PLDSLICE", v, 1, 1 + (unsigned)fetch);
} }
} }
@ -832,7 +832,7 @@ AsmOp compile_tuple_at(std::vector<VarDescr>& res, std::vector<VarDescr>& args)
assert(args.size() == 2 && res.size() == 1); assert(args.size() == 2 && res.size() == 1);
auto& y = args[1]; auto& y = args[1];
if (y.is_int_const() && y.int_const >= 0 && y.int_const < 16) { 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_arg_op("INDEX", y.int_const, 1, 1);
} }
return exec_op("INDEXVAR", 2, 1); return exec_op("INDEXVAR", 2, 1);
@ -843,7 +843,7 @@ AsmOp compile_is_null(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
assert(args.size() == 1 && res.size() == 1); assert(args.size() == 1 && res.size() == 1);
auto &x = args[0], &r = res[0]; auto &x = args[0], &r = res[0];
if (x.always_null() || x.always_not_null()) { if (x.always_null() || x.always_not_null()) {
x.unused(); x.replaced();
r.set_const(x.always_null() ? -1 : 0); r.set_const(x.always_null() ? -1 : 0);
return push_const(r.int_const); return push_const(r.int_const);
} }
@ -856,7 +856,7 @@ bool compile_run_method(AsmOpList& code, std::vector<VarDescr>& res, std::vector
assert(args.size() == (unsigned)n + 1 && res.size() == (unsigned)has_value); assert(args.size() == (unsigned)n + 1 && res.size() == (unsigned)has_value);
auto& x = args[0]; auto& x = args[0];
if (x.is_int_const() && x.int_const->unsigned_fits_bits(14)) { 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); code << exec_arg_op("PREPAREDICT", x.int_const, 0, 2);
} else { } else {
code << exec_op("c3 PUSH", 0, 1); code << exec_op("c3 PUSH", 0, 1);

View file

@ -362,10 +362,13 @@ bool Op::generate_code_step(Stack& stack) {
int i = 0; int i = 0;
std::vector<bool> active; std::vector<bool> active;
active.reserve(left.size()); active.reserve(left.size());
int unused = 0;
for (std::size_t k = 0; k < left.size(); k++) { for (std::size_t k = 0; k < left.size(); k++) {
var_idx_t y = left[k]; // "y" = "x" var_idx_t y = left[k]; // "y" = "x"
auto p = next_var_info[y]; auto p = next_var_info[y];
active.push_back(p && !p->is_unused()); 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++) { for (std::size_t k = 0; k < left.size(); k++) {
if (!active[k]) { if (!active[k]) {
@ -394,11 +397,31 @@ bool Op::generate_code_step(Stack& stack) {
stack.assign_var(left[k], --i); 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; return true;
} }
case _Call: case _Call:
case _CallInd: { case _CallInd: {
if (disabled()) { 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; return true;
} }
SymValFunc* func = (fun_ref ? dynamic_cast<SymValFunc*>(fun_ref->value) : nullptr); SymValFunc* func = (fun_ref ? dynamic_cast<SymValFunc*>(fun_ref->value) : nullptr);

View file

@ -34,7 +34,7 @@
namespace funC { 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; bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble;
std::ostream* outs = &std::cout; std::ostream* outs = &std::cout;
std::string generated_from, boc_output_filename; 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" "-S\tInclude stack layout comments in the output code\n"
"-R\tInclude operation rewrite comments in the output code\n" "-R\tInclude operation rewrite comments in the output code\n"
"-W<output-boc-file>\tInclude Fift code to serialize and save generated code into specified BoC file. Enables " "-W<output-boc-file>\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); std::exit(2);
} }
@ -180,7 +181,7 @@ std::string output_filename;
int main(int argc, char* const argv[]) { int main(int argc, char* const argv[]) {
int i; int i;
bool interactive = false; 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) { switch (i) {
case 'A': case 'A':
funC::asm_preamble = true; funC::asm_preamble = true;
@ -206,6 +207,9 @@ int main(int argc, char* const argv[]) {
case 'S': case 'S':
funC::stack_layout_comments = true; funC::stack_layout_comments = true;
break; break;
case 'u':
++funC::warn_unused;
break;
case 'v': case 'v':
++funC::verbosity; ++funC::verbosity;
break; break;

View file

@ -33,7 +33,7 @@
namespace funC { namespace funC {
extern int verbosity; extern int verbosity, warn_unused;
extern bool op_rewrite_comments; extern bool op_rewrite_comments;
constexpr int optimize_depth = 12; constexpr int optimize_depth = 12;
@ -290,7 +290,7 @@ struct TmpVar {
struct VarDescr { struct VarDescr {
var_idx_t idx; var_idx_t idx;
enum { _Last = 1, _Unused = 2 }; enum { _Last = 1, _Unused = 2, _Replaced = 4 };
int flags; int flags;
enum { enum {
_Const = 16, _Const = 16,
@ -325,6 +325,9 @@ struct VarDescr {
bool is_unused() const { bool is_unused() const {
return flags & _Unused; return flags & _Unused;
} }
bool is_replaced() const {
return flags & _Replaced;
}
bool is_last() const { bool is_last() const {
return flags & _Last; return flags & _Last;
} }
@ -382,8 +385,16 @@ struct VarDescr {
void unused() { void unused() {
flags |= _Unused; flags |= _Unused;
} }
void replaced() {
unused(); // replaced var is always unused
flags |= _Replaced;
}
void clear_unused() { void clear_unused() {
flags &= ~_Unused; flags &= ~_Unused;
clear_replaced();
}
void clear_replaced() {
flags &= ~_Replaced;
} }
void set_const(long long value); void set_const(long long value);
void set_const(td::RefInt256 value); void set_const(td::RefInt256 value);
@ -433,12 +444,13 @@ struct VarDescrList {
VarDescrList& operator+=(const std::vector<var_idx_t>& idx_list) { VarDescrList& operator+=(const std::vector<var_idx_t>& idx_list) {
return add_vars(idx_list); return add_vars(idx_list);
} }
VarDescrList& add_var(var_idx_t idx, bool unused = false); VarDescrList& add_var(var_idx_t idx, bool unused = false, bool replaced = false);
VarDescrList& add_vars(const std::vector<var_idx_t>& idx_list, bool unused = false); VarDescrList& add_vars(const std::vector<var_idx_t>& idx_list, bool unused = false, bool replaced = false);
VarDescrList& operator-=(const std::vector<var_idx_t>& idx_list); VarDescrList& operator-=(const std::vector<var_idx_t>& idx_list);
VarDescrList& operator-=(var_idx_t idx); VarDescrList& operator-=(var_idx_t idx);
std::size_t count(const std::vector<var_idx_t> idx_list) const; std::size_t count(const std::vector<var_idx_t> idx_list) const;
std::size_t count_used(const std::vector<var_idx_t> idx_list) const; std::size_t count_used(const std::vector<var_idx_t> idx_list) const;
std::size_t count_unreplaced(const std::vector<var_idx_t> idx_list) const;
VarDescr& add(var_idx_t idx); VarDescr& add(var_idx_t idx);
VarDescr& add_newval(var_idx_t idx); VarDescr& add_newval(var_idx_t idx);
VarDescrList& operator&=(const VarDescrList& values); VarDescrList& operator&=(const VarDescrList& values);
@ -512,7 +524,7 @@ struct Op {
_Again _Again
}; };
int cl; 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; int flags;
std::unique_ptr<Op> next; std::unique_ptr<Op> next;
SymDef* fun_ref; SymDef* fun_ref;
@ -544,12 +556,19 @@ struct Op {
bool disabled() const { bool disabled() const {
return flags & _Disabled; return flags & _Disabled;
} }
bool replaced() const {
return flags & _Replaced;
}
bool enabled() const { bool enabled() const {
return !disabled(); return !disabled();
} }
void disable() { void disable() {
flags |= _Disabled; flags |= _Disabled;
} }
void replace() {
disable();
flags |= _Replaced;
}
bool unreachable() { bool unreachable() {
return !(flags & _Reachable); return !(flags & _Reachable);
} }
@ -562,7 +581,7 @@ struct Op {
void split_vars(const std::vector<TmpVar>& vars); void split_vars(const std::vector<TmpVar>& vars);
static void split_var_list(std::vector<var_idx_t>& var_list, const std::vector<TmpVar>& vars); static void split_var_list(std::vector<var_idx_t>& var_list, const std::vector<TmpVar>& vars);
bool compute_used_vars(const CodeBlob& code, bool edit); 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(const VarDescrList& new_var_info);
bool set_var_info(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_idx_t>& var_list); bool set_var_info_except(const VarDescrList& new_var_info, const std::vector<var_idx_t>& var_list);