mirror of
https://github.com/ton-blockchain/ton
synced 2025-02-12 19:22:37 +00:00
[FunC] Add pragma remove-unused-functions for simple dead code elimination
This commit is contained in:
parent
0628e17c7d
commit
acf0043342
4 changed files with 119 additions and 5 deletions
47
crypto/func/auto-tests/tests/remove-unused-functions.fc
Normal file
47
crypto/func/auto-tests/tests/remove-unused-functions.fc
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma remove-unused-functions;
|
||||||
|
|
||||||
|
int unused1() { return 2; }
|
||||||
|
int unused2() { return unused1(); }
|
||||||
|
int unused3(int x) { return x * 2 + unused2(); }
|
||||||
|
|
||||||
|
int used_from_noncall1() { return 10; }
|
||||||
|
int used_as_noncall1() { return used_from_noncall1(); }
|
||||||
|
|
||||||
|
const int int20 = 20;
|
||||||
|
int used_from_noncall2() { return int20; }
|
||||||
|
int used_as_noncall2() { return 0 * 0 + used_from_noncall2() + (0 << 0); }
|
||||||
|
|
||||||
|
global int unused_gv;
|
||||||
|
global _ used_gv;
|
||||||
|
|
||||||
|
(() -> int) receiveGetter() { return used_as_noncall2; }
|
||||||
|
|
||||||
|
(int) usedButOptimizedOut(int x) pure { return x + 2; }
|
||||||
|
|
||||||
|
(int, int, int) main() {
|
||||||
|
used_gv = 1;
|
||||||
|
used_gv = used_gv + 2;
|
||||||
|
var getter1 = used_as_noncall1;
|
||||||
|
var getter2 = receiveGetter();
|
||||||
|
usedButOptimizedOut(used_gv);
|
||||||
|
return (used_gv, getter1(), getter2());
|
||||||
|
}
|
||||||
|
|
||||||
|
{-
|
||||||
|
TESTCASE | 0 | | 3 10 20
|
||||||
|
|
||||||
|
@fif_codegen DECLPROC used_as_noncall1
|
||||||
|
@fif_codegen DECLGLOBVAR used_gv
|
||||||
|
|
||||||
|
@fif_codegen_avoid DECLPROC unused1
|
||||||
|
@fif_codegen_avoid DECLPROC unused2
|
||||||
|
@fif_codegen_avoid DECLPROC unused3
|
||||||
|
@fif_codegen_avoid DECLGLOBVAR unused_gv
|
||||||
|
|
||||||
|
Note, that `usedButOptimizedOut()` (a pure function which result is unused)
|
||||||
|
is currently codegenerated, since it's formally reachable.
|
||||||
|
This is because optimizing code is a moment of codegen for now (later than marking unused symbols).
|
||||||
|
|
||||||
|
@fif_codegen DECLPROC usedButOptimizedOut
|
||||||
|
@fif_codegen_avoid usedButOptimizedOut CALLDICT
|
||||||
|
-}
|
|
@ -40,6 +40,7 @@ bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble;
|
||||||
bool interactive = false;
|
bool interactive = false;
|
||||||
GlobalPragma pragma_allow_post_modification{"allow-post-modification"};
|
GlobalPragma pragma_allow_post_modification{"allow-post-modification"};
|
||||||
GlobalPragma pragma_compute_asm_ltr{"compute-asm-ltr"};
|
GlobalPragma pragma_compute_asm_ltr{"compute-asm-ltr"};
|
||||||
|
GlobalPragma pragma_remove_unused_functions{"remove-unused-functions"};
|
||||||
std::string generated_from, boc_output_filename;
|
std::string generated_from, boc_output_filename;
|
||||||
ReadCallback::Callback read_callback;
|
ReadCallback::Callback read_callback;
|
||||||
|
|
||||||
|
@ -62,6 +63,11 @@ const TypeExpr *SymValFunc::get_arg_type() const {
|
||||||
|
|
||||||
|
|
||||||
bool SymValCodeFunc::does_need_codegen() const {
|
bool SymValCodeFunc::does_need_codegen() const {
|
||||||
|
// when a function is declared, but not referenced from code in any way, don't generate its body
|
||||||
|
if (!is_really_used && pragma_remove_unused_functions.enabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// when a function is referenced like `var a = some_fn;` (or in some other non-call way), its continuation should exist
|
||||||
if (flags & flagUsedAsNonCall) {
|
if (flags & flagUsedAsNonCall) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +75,6 @@ bool SymValCodeFunc::does_need_codegen() const {
|
||||||
// since all its usages are inlined
|
// since all its usages are inlined
|
||||||
return !is_just_wrapper_for_another_f();
|
return !is_just_wrapper_for_another_f();
|
||||||
// in the future, we may want to implement a true AST inlining for `inline` functions also
|
// in the future, we may want to implement a true AST inlining for `inline` functions also
|
||||||
// in the future, unused functions may also be excluded from codegen
|
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<std::string> fs_read_callback(ReadCallback::Kind kind, const char* query) {
|
td::Result<std::string> fs_read_callback(ReadCallback::Kind kind, const char* query) {
|
||||||
|
@ -93,6 +98,55 @@ td::Result<std::string> fs_read_callback(ReadCallback::Kind kind, const char* qu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mark_function_used_dfs(const std::unique_ptr<Op>& op);
|
||||||
|
|
||||||
|
void mark_function_used(SymValCodeFunc* func_val) {
|
||||||
|
if (!func_val->code || func_val->is_really_used) { // already handled
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
func_val->is_really_used = true;
|
||||||
|
mark_function_used_dfs(func_val->code->ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mark_global_var_used(SymValGlobVar* glob_val) {
|
||||||
|
glob_val->is_really_used = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mark_function_used_dfs(const std::unique_ptr<Op>& op) {
|
||||||
|
if (!op) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// op->fun_ref, despite its name, may actually ref global var
|
||||||
|
// note, that for non-calls, e.g. `var a = some_fn` (Op::_Let), some_fn is Op::_GlobVar
|
||||||
|
// (in other words, fun_ref exists not only for direct Op::_Call, but for non-call references also)
|
||||||
|
if (op->fun_ref) {
|
||||||
|
if (auto* func_val = dynamic_cast<SymValCodeFunc*>(op->fun_ref->value)) {
|
||||||
|
mark_function_used(func_val);
|
||||||
|
} else if (auto* glob_val = dynamic_cast<SymValGlobVar*>(op->fun_ref->value)) {
|
||||||
|
mark_global_var_used(glob_val);
|
||||||
|
} else if (auto* asm_val = dynamic_cast<SymValAsmFunc*>(op->fun_ref->value)) {
|
||||||
|
} else {
|
||||||
|
func_assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mark_function_used_dfs(op->next);
|
||||||
|
mark_function_used_dfs(op->block0);
|
||||||
|
mark_function_used_dfs(op->block1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mark_used_symbols() {
|
||||||
|
for (SymDef* func_sym : glob_func) {
|
||||||
|
auto* func_val = dynamic_cast<SymValCodeFunc*>(func_sym->value);
|
||||||
|
std::string name = sym::symbols.get_name(func_sym->sym_idx);
|
||||||
|
if (func_val->method_id.not_null() ||
|
||||||
|
name == "main" || name == "recv_internal" || name == "recv_external" ||
|
||||||
|
name == "run_ticktock" || name == "split_prepare" || name == "split_install") {
|
||||||
|
mark_function_used(func_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* OUTPUT CODE GENERATOR
|
* OUTPUT CODE GENERATOR
|
||||||
|
@ -188,6 +242,7 @@ int generate_output(std::ostream &outs, std::ostream &errs) {
|
||||||
if (program_envelope) {
|
if (program_envelope) {
|
||||||
outs << "PROGRAM{\n";
|
outs << "PROGRAM{\n";
|
||||||
}
|
}
|
||||||
|
mark_used_symbols();
|
||||||
for (SymDef* func_sym : glob_func) {
|
for (SymDef* func_sym : glob_func) {
|
||||||
SymValCodeFunc* func_val = dynamic_cast<SymValCodeFunc*>(func_sym->value);
|
SymValCodeFunc* func_val = dynamic_cast<SymValCodeFunc*>(func_sym->value);
|
||||||
func_assert(func_val);
|
func_assert(func_val);
|
||||||
|
@ -207,7 +262,14 @@ int generate_output(std::ostream &outs, std::ostream &errs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (SymDef* gvar_sym : glob_vars) {
|
for (SymDef* gvar_sym : glob_vars) {
|
||||||
func_assert(dynamic_cast<SymValGlobVar*>(gvar_sym->value));
|
auto* glob_val = dynamic_cast<SymValGlobVar*>(gvar_sym->value);
|
||||||
|
func_assert(glob_val);
|
||||||
|
if (!glob_val->is_really_used && pragma_remove_unused_functions.enabled()) {
|
||||||
|
if (verbosity >= 2) {
|
||||||
|
errs << gvar_sym->name() << ": variable not generated, it's unused\n";
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
std::string name = sym::symbols.get_name(gvar_sym->sym_idx);
|
std::string name = sym::symbols.get_name(gvar_sym->sym_idx);
|
||||||
outs << std::string(indent * 2, ' ') << "DECLGLOBVAR " << name << "\n";
|
outs << std::string(indent * 2, ' ') << "DECLGLOBVAR " << name << "\n";
|
||||||
}
|
}
|
||||||
|
@ -274,6 +336,7 @@ int func_proceed(const std::vector<std::string> &sources, std::ostream &outs, st
|
||||||
}
|
}
|
||||||
pragma_allow_post_modification.check_enable_in_libs();
|
pragma_allow_post_modification.check_enable_in_libs();
|
||||||
pragma_compute_asm_ltr.check_enable_in_libs();
|
pragma_compute_asm_ltr.check_enable_in_libs();
|
||||||
|
pragma_remove_unused_functions.check_enable_in_libs();
|
||||||
return funC::generate_output(outs, errs);
|
return funC::generate_output(outs, errs);
|
||||||
} catch (src::Fatal& fatal) {
|
} catch (src::Fatal& fatal) {
|
||||||
errs << "fatal: " << fatal << std::endl;
|
errs << "fatal: " << fatal << std::endl;
|
||||||
|
|
|
@ -563,7 +563,7 @@ struct Op {
|
||||||
enum { _Disabled = 1, _NoReturn = 4, _Impure = 24 };
|
enum { _Disabled = 1, _NoReturn = 4, _Impure = 24 };
|
||||||
int flags;
|
int flags;
|
||||||
std::unique_ptr<Op> next;
|
std::unique_ptr<Op> next;
|
||||||
SymDef* fun_ref;
|
SymDef* fun_ref; // despite its name, it may actually ref global var; applicable not only to Op::_Call, but for other kinds also
|
||||||
SrcLocation where;
|
SrcLocation where;
|
||||||
VarDescrList var_info;
|
VarDescrList var_info;
|
||||||
std::vector<VarDescr> args;
|
std::vector<VarDescr> args;
|
||||||
|
@ -806,6 +806,7 @@ struct SymValFunc : SymVal {
|
||||||
|
|
||||||
struct SymValCodeFunc : SymValFunc {
|
struct SymValCodeFunc : SymValFunc {
|
||||||
CodeBlob* code;
|
CodeBlob* code;
|
||||||
|
bool is_really_used{false}; // calculated via dfs; unused functions are not codegenerated
|
||||||
~SymValCodeFunc() override = default;
|
~SymValCodeFunc() override = default;
|
||||||
SymValCodeFunc(int val, TypeExpr* _ft, bool marked_as_pure) : SymValFunc(val, _ft, marked_as_pure), code(nullptr) {
|
SymValCodeFunc(int val, TypeExpr* _ft, bool marked_as_pure) : SymValFunc(val, _ft, marked_as_pure), code(nullptr) {
|
||||||
}
|
}
|
||||||
|
@ -825,6 +826,7 @@ struct SymValType : sym::SymValBase {
|
||||||
struct SymValGlobVar : sym::SymValBase {
|
struct SymValGlobVar : sym::SymValBase {
|
||||||
TypeExpr* sym_type;
|
TypeExpr* sym_type;
|
||||||
int out_idx{0};
|
int out_idx{0};
|
||||||
|
bool is_really_used{false}; // calculated via dfs from used functions; unused globals are not codegenerated
|
||||||
#ifdef FUNC_DEBUG
|
#ifdef FUNC_DEBUG
|
||||||
std::string name; // seeing variable name in debugger makes it much easier to delve into FunC sources
|
std::string name; // seeing variable name in debugger makes it much easier to delve into FunC sources
|
||||||
#endif
|
#endif
|
||||||
|
@ -1788,7 +1790,7 @@ class GlobalPragma {
|
||||||
bool enabled_ = false;
|
bool enabled_ = false;
|
||||||
std::vector<SrcLocation> locs_;
|
std::vector<SrcLocation> locs_;
|
||||||
};
|
};
|
||||||
extern GlobalPragma pragma_allow_post_modification, pragma_compute_asm_ltr;
|
extern GlobalPragma pragma_allow_post_modification, pragma_compute_asm_ltr, pragma_remove_unused_functions;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
|
|
|
@ -287,7 +287,7 @@ void parse_const_decl(Lexer& lex) {
|
||||||
new_value = new SymValConst{const_cnt++, x->intval};
|
new_value = new SymValConst{const_cnt++, x->intval};
|
||||||
} else if (x->cls == Expr::_SliceConst) { // Slice constant (string)
|
} else if (x->cls == Expr::_SliceConst) { // Slice constant (string)
|
||||||
new_value = new SymValConst{const_cnt++, x->strval};
|
new_value = new SymValConst{const_cnt++, x->strval};
|
||||||
} else if (x->cls == Expr::_Apply) {
|
} else if (x->cls == Expr::_Apply) { // even "1 + 2" is Expr::_Apply (it applies `_+_`)
|
||||||
code.emplace_back(loc, Op::_Import, std::vector<var_idx_t>());
|
code.emplace_back(loc, Op::_Import, std::vector<var_idx_t>());
|
||||||
auto tmp_vars = x->pre_compile(code);
|
auto tmp_vars = x->pre_compile(code);
|
||||||
code.emplace_back(loc, Op::_Return, std::move(tmp_vars));
|
code.emplace_back(loc, Op::_Return, std::move(tmp_vars));
|
||||||
|
@ -1798,6 +1798,8 @@ void parse_pragma(Lexer& lex) {
|
||||||
pragma_allow_post_modification.enable(lex.cur().loc);
|
pragma_allow_post_modification.enable(lex.cur().loc);
|
||||||
} else if (pragma_name == pragma_compute_asm_ltr.name()) {
|
} else if (pragma_name == pragma_compute_asm_ltr.name()) {
|
||||||
pragma_compute_asm_ltr.enable(lex.cur().loc);
|
pragma_compute_asm_ltr.enable(lex.cur().loc);
|
||||||
|
} else if (pragma_name == pragma_remove_unused_functions.name()) {
|
||||||
|
pragma_remove_unused_functions.enable(lex.cur().loc);
|
||||||
} else {
|
} else {
|
||||||
lex.cur().error(std::string{"unknown pragma `"} + pragma_name + "`");
|
lex.cur().error(std::string{"unknown pragma `"} + pragma_name + "`");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue