1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-02-12 19:22:37 +00:00

Add pragmas to funC for precise control of computation order (#589)

* FunC pragmas: allow-post-modification and compute-asm-ltr

* Warn if #pragma is enabled only in included files

* Add tests for new pragmas

* Add special ops for "allow-post-modification" only when needed

* Update FunC version to 0.4.1

* Allow empty inlines (#10)

Co-authored-by: SpyCheese <mikle98@yandex.ru>
This commit is contained in:
EmelyanenkoK 2023-01-13 12:45:04 +03:00 committed by GitHub
parent 6b49d6a382
commit 653c88aa9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 344 additions and 61 deletions

View file

@ -875,9 +875,15 @@ x{EDFB} @Defop SAMEALTSAVE
} dup : PREPARE : PREPAREDICT
//
// inline support
{ dup sbits { @addop } {
dup srefs 1- abort"exactly one reference expected in inline"
ref@ CALLREF } cond
{ dup sbits
{ @addop }
{
dup srefs //
{ ref@ CALLREF }
{ drop }
cond
}
cond
} : INLINE
//
// throwing and handling exceptions

View file

@ -0,0 +1,78 @@
#pragma allow-post-modification;
forall X -> tuple unsafe_tuple(X x) asm "NOP";
(int, int) inc(int x, int y) {
return (x + y, y * 10);
}
(int, int, int, int, int, int, int) test_return(int x) method_id(11) {
return (x, x~inc(x / 20), x, x = x * 2, x, x += 1, x);
}
(int, int, int, int, int, int, int) test_assign(int x) method_id(12) {
(int x1, int x2, int x3, int x4, int x5, int x6, int x7) = (x, x~inc(x / 20), x, x = x * 2, x, x += 1, x);
return (x1, x2, x3, x4, x5, x6, x7);
}
tuple test_tuple(int x) method_id(13) {
tuple t = unsafe_tuple([x, x~inc(x / 20), x, x = x * 2, x, x += 1, x]);
return t;
}
(int, int, int, int, int, int, int) test_tuple_assign(int x) method_id(14) {
[int x1, int x2, int x3, int x4, int x5, int x6, int x7] = [x, x~inc(x / 20), x, x = x * 2, x, x += 1, x];
return (x1, x2, x3, x4, x5, x6, x7);
}
(int, int, int, int, int, int, int) foo1(int x1, int x2, int x3, int x4, int x5, int x6, int x7) {
return (x1, x2, x3, x4, x5, x6, x7);
}
(int, int, int, int, int, int, int) test_call_1(int x) method_id(15) {
return foo1(x, x~inc(x / 20), x, x = x * 2, x, x += 1, x);
}
(int, int, int, int, int, int, int) foo2(int x1, int x2, (int, int, int, int) x3456, int x7) {
(int x3, int x4, int x5, int x6) = x3456;
return (x1, x2, x3, x4, x5, x6, x7);
}
(int, int, int, int, int, int, int) test_call_2(int x) method_id(16) {
return foo2(x, x~inc(x / 20), (x, x = x * 2, x, x += 1), x);
}
(int, int, int, int, int, int, int) asm_func(int x1, int x2, int x3, int x4, int x5, int x6, int x7) asm
(x4 x5 x6 x7 x1 x2 x3 -> 0 1 2 3 4 5 6) "NOP";
(int, int, int, int, int, int, int) test_call_asm_old(int x) method_id(17) {
return asm_func(x, x += 1, x, x, x~inc(x / 20), x, x = x * 2);
}
#pragma compute-asm-ltr;
(int, int, int, int, int, int, int) test_call_asm_new(int x) method_id(18) {
return asm_func(x, x~inc(x / 20), x, x = x * 2, x, x += 1, x);
}
global int xx;
(int, int, int, int, int, int, int) test_global(int x) method_id(19) {
xx = x;
return (xx, xx~inc(xx / 20), xx, xx = xx * 2, xx, xx += 1, xx);
}
() main() {
}
{-
method_id | in | out
TESTCASE | 11 | 100 | 100 50 105 210 210 211 211
TESTCASE | 12 | 100 | 100 50 105 210 210 211 211
TESTCASE | 13 | 100 | [ 100 50 105 210 210 211 211 ]
TESTCASE | 14 | 100 | 100 50 105 210 210 211 211
TESTCASE | 15 | 100 | 100 50 105 210 210 211 211
TESTCASE | 16 | 100 | 100 50 105 210 210 211 211
TESTCASE | 17 | 100 | 100 50 105 210 210 211 211
TESTCASE | 18 | 100 | 210 210 211 211 100 50 105
TESTCASE | 19 | 100 | 100 50 105 210 210 211 211
-}

View file

@ -0,0 +1,113 @@
tuple empty_tuple() asm "NIL";
forall X -> (tuple, ()) tpush(tuple t, X x) asm "TPUSH";
tuple asm_func_1(int x, int y, int z) asm "3 TUPLE";
tuple asm_func_2(int x, int y, int z) asm (z y x -> 0) "3 TUPLE";
tuple asm_func_3(int x, int y, int z) asm (y z x -> 0) "3 TUPLE";
tuple asm_func_4(int a, (int, (int, int)) b, int c) asm (b a c -> 0) "5 TUPLE";
(tuple, ()) asm_func_modify(tuple a, int b, int c) asm (c b a -> 0) "SWAP TPUSH SWAP TPUSH";
global tuple t;
int foo(int x) {
t~tpush(x);
return x * 10;
}
(tuple, tuple) test_old_1() method_id(11) {
t = empty_tuple();
tuple t2 = asm_func_1(foo(11), foo(22), foo(33));
return (t, t2);
}
(tuple, tuple) test_old_2() method_id(12) {
t = empty_tuple();
tuple t2 = asm_func_2(foo(11), foo(22), foo(33));
return (t, t2);
}
(tuple, tuple) test_old_3() method_id(13) {
t = empty_tuple();
tuple t2 = asm_func_3(foo(11), foo(22), foo(33));
return (t, t2);
}
(tuple, tuple) test_old_4() method_id(14) {
t = empty_tuple();
tuple t2 = empty_tuple();
;; This actually computes left-to-right even without compute-asm-ltr
tuple t2 = asm_func_4(foo(11), (foo(22), (foo(33), foo(44))), foo(55));
return (t, t2);
}
(tuple, tuple) test_old_modify() method_id(15) {
t = empty_tuple();
tuple t2 = empty_tuple();
t2~asm_func_modify(foo(22), foo(33));
return (t, t2);
}
(tuple, tuple) test_old_dot() method_id(16) {
t = empty_tuple();
tuple t2 = foo(11).asm_func_3(foo(22), foo(33));
return (t, t2);
}
#pragma compute-asm-ltr;
(tuple, tuple) test_new_1() method_id(21) {
t = empty_tuple();
tuple t2 = asm_func_1(foo(11), foo(22), foo(33));
return (t, t2);
}
(tuple, tuple) test_new_2() method_id(22) {
t = empty_tuple();
tuple t2 = asm_func_2(foo(11), foo(22), foo(33));
return (t, t2);
}
(tuple, tuple) test_new_3() method_id(23) {
t = empty_tuple();
tuple t2 = asm_func_3(foo(11), foo(22), foo(33));
return (t, t2);
}
(tuple, tuple) test_new_4() method_id(24) {
t = empty_tuple();
tuple t2 = asm_func_4(foo(11), (foo(22), (foo(33), foo(44))), foo(55));
return (t, t2);
}
(tuple, tuple) test_new_modify() method_id(25) {
t = empty_tuple();
tuple t2 = empty_tuple();
t2~asm_func_modify(foo(22), foo(33));
return (t, t2);
}
(tuple, tuple) test_new_dot() method_id(26) {
t = empty_tuple();
tuple t2 = foo(11).asm_func_3(foo(22), foo(33));
return (t, t2);
}
() main() {
}
{-
method_id | in | out
TESTCASE | 11 | | [ 11 22 33 ] [ 110 220 330 ]
TESTCASE | 12 | | [ 33 22 11 ] [ 330 220 110 ]
TESTCASE | 13 | | [ 22 33 11 ] [ 220 330 110 ]
TESTCASE | 14 | | [ 11 22 33 44 55 ] [ 220 330 440 110 550 ]
TESTCASE | 15 | | [ 33 22 ] [ 220 330 ]
TESTCASE | 16 | | [ 22 33 11 ] [ 220 330 110 ]
TESTCASE | 21 | | [ 11 22 33 ] [ 110 220 330 ]
TESTCASE | 22 | | [ 11 22 33 ] [ 330 220 110 ]
TESTCASE | 23 | | [ 11 22 33 ] [ 220 330 110 ]
TESTCASE | 24 | | [ 11 22 33 44 55 ] [ 220 330 440 110 550 ]
TESTCASE | 25 | | [ 22 33 ] [ 220 330 ]
TESTCASE | 26 | | [ 11 22 33 ] [ 220 330 110 ]
-}

View file

@ -36,6 +36,8 @@ namespace funC {
int verbosity, indent, opt_level = 2;
bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble;
bool interactive = false;
GlobalPragma pragma_allow_post_modification{"allow-post-modification"};
GlobalPragma pragma_compute_asm_ltr{"compute-asm-ltr"};
std::string generated_from, boc_output_filename;
/*
@ -194,7 +196,7 @@ int func_proceed(const std::vector<std::string> &sources, std::ostream &outs, st
int ok = 0, proc = 0;
try {
for (auto src : sources) {
ok += funC::parse_source_file(src.c_str());
ok += funC::parse_source_file(src.c_str(), {}, true);
proc++;
}
if (funC::interactive) {
@ -208,6 +210,8 @@ int func_proceed(const std::vector<std::string> &sources, std::ostream &outs, st
if (!proc) {
throw src::Fatal{"no source files, no output"};
}
pragma_allow_post_modification.check_enable_in_libs();
pragma_compute_asm_ltr.check_enable_in_libs();
return funC::generate_output(outs, errs);
} catch (src::Fatal& fatal) {
errs << "fatal: " << fatal << std::endl;

View file

@ -39,7 +39,7 @@ extern std::string generated_from;
constexpr int optimize_depth = 20;
const std::string func_version{"0.4.0"};
const std::string func_version{"0.4.1"};
enum Keyword {
_Eof = -1,
@ -306,7 +306,7 @@ struct TmpVar {
sym_idx_t name;
int coord;
std::unique_ptr<SrcLocation> where;
size_t modify_forbidden = 0;
std::vector<std::function<void(const SrcLocation &)>> on_modification;
TmpVar(var_idx_t _idx, int _cls, TypeExpr* _type = 0, SymDef* sym = 0, const SrcLocation* loc = 0);
void show(std::ostream& os, int omit_idx = 0) const;
void dump(std::ostream& os) const;
@ -681,6 +681,7 @@ typedef std::vector<FormalArg> FormalArgList;
struct AsmOpList;
struct CodeBlob {
enum { _AllowPostModification = 1, _ComputeAsmLtr = 2 };
int var_cnt, in_var_cnt, op_cnt;
TypeExpr* ret_type;
std::string name;
@ -689,6 +690,7 @@ struct CodeBlob {
std::unique_ptr<Op> ops;
std::unique_ptr<Op>* cur_ops;
std::stack<std::unique_ptr<Op>*> cur_ops_stack;
int flags = 0;
CodeBlob(TypeExpr* ret = nullptr) : var_cnt(0), in_var_cnt(0), op_cnt(0), ret_type(ret), cur_ops(&ops) {
}
template <typename... Args>
@ -729,19 +731,9 @@ struct CodeBlob {
void generate_code(AsmOpList& out_list, int mode = 0);
void generate_code(std::ostream& os, int mode = 0, int indent = 0);
void mark_modify_forbidden(var_idx_t idx) {
++vars.at(idx).modify_forbidden;
}
void unmark_modify_forbidden(var_idx_t idx) {
assert(vars.at(idx).modify_forbidden > 0);
--vars.at(idx).modify_forbidden;
}
void check_modify_forbidden(var_idx_t idx, const SrcLocation& here) const {
if (vars.at(idx).modify_forbidden) {
throw src::ParseError{here, PSTRING() << "Modifying local variable " << vars[idx].to_string()
<< " after using it in the same expression"};
void on_var_modification(var_idx_t idx, const SrcLocation& here) const {
for (auto& f : vars.at(idx).on_modification) {
f(here);
}
}
};
@ -855,7 +847,7 @@ extern std::vector<SymDef*> glob_func, glob_vars;
// defined in parse-func.cpp
bool parse_source(std::istream* is, const src::FileDescr* fdescr);
bool parse_source_file(const char* filename, src::Lexem lex = {});
bool parse_source_file(const char* filename, src::Lexem lex = {}, bool is_main = false);
bool parse_source_stdin();
extern std::stack<src::SrcLocation> inclusion_locations;
@ -1700,6 +1692,41 @@ extern int verbosity, indent, opt_level;
extern bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble, interactive;
extern std::string generated_from, boc_output_filename;
class GlobalPragma {
public:
explicit GlobalPragma(std::string name) : name_(std::move(name)) {
}
const std::string& name() const {
return name_;
}
bool enabled() const {
return enabled_;
}
void enable(SrcLocation loc) {
enabled_ = true;
locs_.push_back(std::move(loc));
}
void check_enable_in_libs() {
if (locs_.empty()) {
return;
}
for (const SrcLocation& loc : locs_) {
if (loc.fdescr->is_main) {
return;
}
}
locs_[0].show_warning(PSTRING() << "#pragma " << name_
<< " is enabled in included libraries, it may change the behavior of your code. "
<< "Add this #pragma to the main source file to suppress this warning.");
}
private:
std::string name_;
bool enabled_ = false;
std::vector<SrcLocation> locs_;
};
extern GlobalPragma pragma_allow_post_modification, pragma_compute_asm_ltr;
/*
*
* OUTPUT CODE GENERATOR

View file

@ -16,6 +16,7 @@
Copyright 2017-2020 Telegram Systems LLP
*/
#include <numeric>
#include "func.h"
using namespace std::literals::string_literals;
@ -255,13 +256,75 @@ std::vector<var_idx_t> Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rh
std::vector<std::pair<SymDef*, var_idx_t>> globs;
auto left = lhs->pre_compile(code, &globs);
for (var_idx_t v : left) {
code.check_modify_forbidden(v, here);
code.on_var_modification(v, here);
}
code.emplace_back(here, Op::_Let, std::move(left), right);
add_set_globs(code, globs, here);
return right;
}
std::vector<var_idx_t> pre_compile_tensor(const std::vector<Expr *> args, CodeBlob &code,
std::vector<std::pair<SymDef*, var_idx_t>> *lval_globs,
std::vector<int> arg_order) {
if (arg_order.empty()) {
arg_order.resize(args.size());
std::iota(arg_order.begin(), arg_order.end(), 0);
}
assert(args.size() == arg_order.size());
std::vector<std::vector<var_idx_t>> res_lists(args.size());
struct ModifiedVar {
size_t i, j;
Op* op;
};
auto modified_vars = std::make_shared<std::vector<ModifiedVar>>();
for (size_t i : arg_order) {
res_lists[i] = args[i]->pre_compile(code, lval_globs);
for (size_t j = 0; j < res_lists[i].size(); ++j) {
TmpVar& var = code.vars.at(res_lists[i][j]);
if (code.flags & CodeBlob::_AllowPostModification) {
if (!lval_globs && (var.cls & TmpVar::_Named)) {
Op *op = &code.emplace_back(nullptr, Op::_Let, std::vector<var_idx_t>(), std::vector<var_idx_t>());
op->flags |= Op::_Disabled;
var.on_modification.push_back([modified_vars, i, j, op, done = false](const SrcLocation &here) mutable {
if (!done) {
done = true;
modified_vars->push_back({i, j, op});
}
});
} else {
var.on_modification.push_back([](const SrcLocation &) {
});
}
} else {
var.on_modification.push_back([name = var.to_string()](const SrcLocation &here) {
throw src::ParseError{here, PSTRING() << "Modifying local variable " << name
<< " after using it in the same expression"};
});
}
}
}
for (const auto& list : res_lists) {
for (var_idx_t v : list) {
assert(!code.vars.at(v).on_modification.empty());
code.vars.at(v).on_modification.pop_back();
}
}
for (const ModifiedVar &m : *modified_vars) {
var_idx_t& v = res_lists[m.i][m.j];
var_idx_t v2 = code.create_tmp_var(code.vars[v].v_type, code.vars[v].where.get());
m.op->left = {v2};
m.op->right = {v};
m.op->flags &= ~Op::_Disabled;
v = v2;
}
std::vector<var_idx_t> res;
for (const auto& list : res_lists) {
res.insert(res.end(), list.cbegin(), list.cend());
}
return res;
}
std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code, std::vector<std::pair<SymDef*, var_idx_t>>* lval_globs) const {
if (lval_globs && !(cls == _Tensor || cls == _Var || cls == _Hole || cls == _TypeApply || cls == _GlobVar)) {
std::cerr << "lvalue expression constructor is " << cls << std::endl;
@ -269,46 +332,17 @@ std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code, std::vector<std::pair<S
}
switch (cls) {
case _Tensor: {
std::vector<var_idx_t> res;
for (const auto& x : args) {
auto add = x->pre_compile(code, lval_globs);
for (var_idx_t v : add) {
code.mark_modify_forbidden(v);
}
res.insert(res.end(), add.cbegin(), add.cend());
}
for (var_idx_t v : res) {
code.unmark_modify_forbidden(v);
}
return res;
return pre_compile_tensor(args, code, lval_globs, {});
}
case _Apply: {
assert(sym);
std::vector<var_idx_t> res;
auto func = dynamic_cast<SymValFunc*>(sym->value);
if (func && func->arg_order.size() == args.size()) {
std::vector<var_idx_t> res;
if (func && func->arg_order.size() == args.size() && !(code.flags & CodeBlob::_ComputeAsmLtr)) {
//std::cerr << "!!! reordering " << args.size() << " arguments of " << sym->name() << std::endl;
std::vector<std::vector<var_idx_t>> add_list(args.size());
for (int i : func->arg_order) {
add_list[i] = args[i]->pre_compile(code);
for (var_idx_t v : add_list[i]) {
code.mark_modify_forbidden(v);
}
}
for (const auto& add : add_list) {
res.insert(res.end(), add.cbegin(), add.cend());
}
res = pre_compile_tensor(args, code, lval_globs, func->arg_order);
} else {
for (const auto& x : args) {
auto add = x->pre_compile(code);
for (var_idx_t v : add) {
code.mark_modify_forbidden(v);
}
res.insert(res.end(), add.cbegin(), add.cend());
}
}
for (var_idx_t v : res) {
code.unmark_modify_forbidden(v);
res = pre_compile_tensor(args, code, lval_globs, {});
}
auto rvect = new_tmp_vect(code);
auto& op = code.emplace_back(here, Op::_Call, rvect, std::move(res), sym);
@ -371,7 +405,7 @@ std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code, std::vector<std::pair<S
auto left = args[0]->pre_compile(code, lval_globs);
left.push_back(rvect[0]);
for (var_idx_t v : left) {
code.check_modify_forbidden(v, here);
code.on_var_modification(v, here);
}
code.emplace_back(here, Op::_Let, std::move(left), std::move(right));
add_set_globs(code, local_globs, here);

View file

@ -261,6 +261,12 @@ void parse_const_decl(Lexer& lex) {
}
lex.next();
CodeBlob code;
if (pragma_allow_post_modification.enabled()) {
code.flags |= CodeBlob::_AllowPostModification;
}
if (pragma_compute_asm_ltr.enabled()) {
code.flags |= CodeBlob::_ComputeAsmLtr;
}
// Handles processing and resolution of literals and consts
auto x = parse_expr(lex, code, false); // also does lex.next() !
if (x->flags != Expr::_IsRvalue) {
@ -1210,6 +1216,12 @@ blk_fl::val parse_stmt(Lexer& lex, CodeBlob& code) {
CodeBlob* parse_func_body(Lexer& lex, FormalArgList arg_list, TypeExpr* ret_type) {
lex.expect('{');
CodeBlob* blob = new CodeBlob{ret_type};
if (pragma_allow_post_modification.enabled()) {
blob->flags |= CodeBlob::_AllowPostModification;
}
if (pragma_compute_asm_ltr.enabled()) {
blob->flags |= CodeBlob::_ComputeAsmLtr;
}
blob->import_params(std::move(arg_list));
blk_fl::val res = blk_fl::init;
bool warned = false;
@ -1676,6 +1688,10 @@ void parse_pragma(Lexer& lex) {
}
func_ver_test = lex.cur().str;
lex.next();
} else if (pragma_name == pragma_allow_post_modification.name()) {
pragma_allow_post_modification.enable(lex.cur().loc);
} else if (pragma_name == pragma_compute_asm_ltr.name()) {
pragma_compute_asm_ltr.enable(lex.cur().loc);
} else {
lex.cur().error(std::string{"unknown pragma `"} + pragma_name + "`");
}
@ -1684,7 +1700,7 @@ void parse_pragma(Lexer& lex) {
std::vector<const src::FileDescr*> source_fdescr;
std::vector<std::string> source_files;
std::map<std::string, src::FileDescr*> source_files;
std::stack<src::SrcLocation> inclusion_locations;
void parse_include(Lexer& lex, const src::FileDescr* fdescr) {
@ -1700,7 +1716,7 @@ void parse_include(Lexer& lex, const src::FileDescr* fdescr) {
}
lex.next();
lex.expect(';');
if (!parse_source_file(val.c_str(), include)) {
if (!parse_source_file(val.c_str(), include, false)) {
include.error(std::string{"failed parsing included file `"} + val + "`");
}
}
@ -1724,7 +1740,7 @@ bool parse_source(std::istream* is, src::FileDescr* fdescr) {
return true;
}
bool parse_source_file(const char* filename, src::Lexem lex) {
bool parse_source_file(const char* filename, src::Lexem lex, bool is_main) {
if (!filename || !*filename) {
auto msg = "source file name is an empty string";
if (lex.tp) {
@ -1741,7 +1757,9 @@ bool parse_source_file(const char* filename, src::Lexem lex) {
return false;
}
std::string real_filename = path_res.move_as_ok();
if (std::count(source_files.begin(), source_files.end(), real_filename)) {
auto it = source_files.find(real_filename);
if (it != source_files.end()) {
it->second->is_main |= is_main;
if (verbosity >= 2) {
if (lex.tp) {
lex.loc.show_warning(std::string{"skipping file "} + real_filename + " because it was already included");
@ -1755,8 +1773,9 @@ bool parse_source_file(const char* filename, src::Lexem lex) {
funC::generated_from += std::string{"incl:"};
}
funC::generated_from += std::string{"`"} + filename + "` ";
source_files.push_back(real_filename);
src::FileDescr* cur_source = new src::FileDescr{filename};
source_files[real_filename] = cur_source;
cur_source->is_main = is_main;
source_fdescr.push_back(cur_source);
std::ifstream ifs{filename};
if (ifs.fail()) {
@ -1775,6 +1794,7 @@ bool parse_source_file(const char* filename, src::Lexem lex) {
bool parse_source_stdin() {
src::FileDescr* cur_source = new src::FileDescr{"stdin", true};
cur_source->is_main = true;
source_fdescr.push_back(cur_source);
return parse_source(&std::cin, cur_source);
}

View file

@ -35,6 +35,7 @@ struct FileDescr {
std::string text;
std::vector<long> line_offs;
bool is_stdin;
bool is_main = false;
FileDescr(std::string _fname, bool _stdin = false) : filename(std::move(_fname)), is_stdin(_stdin) {
}
const char* push_line(std::string new_line);