From 653c88aa9d658e578c41fd4185c0d98e76d86b36 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Fri, 13 Jan 2023 12:45:04 +0300 Subject: [PATCH] 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 --- crypto/fift/lib/Asm.fif | 12 +- .../tests/allow_post_modification.fc | 78 ++++++++++++ crypto/func/auto-tests/tests/asm_arg_order.fc | 113 ++++++++++++++++++ crypto/func/func.cpp | 6 +- crypto/func/func.h | 59 ++++++--- crypto/func/gen-abscode.cpp | 106 ++++++++++------ crypto/func/parse-func.cpp | 30 ++++- crypto/parser/srcread.h | 1 + 8 files changed, 344 insertions(+), 61 deletions(-) create mode 100644 crypto/func/auto-tests/tests/allow_post_modification.fc create mode 100644 crypto/func/auto-tests/tests/asm_arg_order.fc diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif index 5ae44c11..01fb8dd0 100644 --- a/crypto/fift/lib/Asm.fif +++ b/crypto/fift/lib/Asm.fif @@ -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 diff --git a/crypto/func/auto-tests/tests/allow_post_modification.fc b/crypto/func/auto-tests/tests/allow_post_modification.fc new file mode 100644 index 00000000..c6082252 --- /dev/null +++ b/crypto/func/auto-tests/tests/allow_post_modification.fc @@ -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 +-} diff --git a/crypto/func/auto-tests/tests/asm_arg_order.fc b/crypto/func/auto-tests/tests/asm_arg_order.fc new file mode 100644 index 00000000..b53419e3 --- /dev/null +++ b/crypto/func/auto-tests/tests/asm_arg_order.fc @@ -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 ] +-} diff --git a/crypto/func/func.cpp b/crypto/func/func.cpp index 3daac5d7..be44d2ea 100644 --- a/crypto/func/func.cpp +++ b/crypto/func/func.cpp @@ -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 &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 &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; diff --git a/crypto/func/func.h b/crypto/func/func.h index 81911ade..0eeb9557 100644 --- a/crypto/func/func.h +++ b/crypto/func/func.h @@ -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 where; - size_t modify_forbidden = 0; + std::vector> 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 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 ops; std::unique_ptr* cur_ops; std::stack*> 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 @@ -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 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 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 locs_; +}; +extern GlobalPragma pragma_allow_post_modification, pragma_compute_asm_ltr; + /* * * OUTPUT CODE GENERATOR diff --git a/crypto/func/gen-abscode.cpp b/crypto/func/gen-abscode.cpp index 6fcb1599..cff40379 100644 --- a/crypto/func/gen-abscode.cpp +++ b/crypto/func/gen-abscode.cpp @@ -16,6 +16,7 @@ Copyright 2017-2020 Telegram Systems LLP */ +#include #include "func.h" using namespace std::literals::string_literals; @@ -255,13 +256,75 @@ std::vector Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rh std::vector> 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 pre_compile_tensor(const std::vector args, CodeBlob &code, + std::vector> *lval_globs, + std::vector 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> res_lists(args.size()); + + struct ModifiedVar { + size_t i, j; + Op* op; + }; + auto modified_vars = std::make_shared>(); + 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(), std::vector()); + 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 res; + for (const auto& list : res_lists) { + res.insert(res.end(), list.cbegin(), list.cend()); + } + return res; +} + std::vector Expr::pre_compile(CodeBlob& code, std::vector>* 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 Expr::pre_compile(CodeBlob& code, std::vector 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 res; auto func = dynamic_cast(sym->value); - if (func && func->arg_order.size() == args.size()) { + std::vector 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> 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 Expr::pre_compile(CodeBlob& code, std::vectorpre_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); diff --git a/crypto/func/parse-func.cpp b/crypto/func/parse-func.cpp index fe86bc1e..82462e79 100644 --- a/crypto/func/parse-func.cpp +++ b/crypto/func/parse-func.cpp @@ -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 source_fdescr; -std::vector source_files; +std::map source_files; std::stack 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); } diff --git a/crypto/parser/srcread.h b/crypto/parser/srcread.h index 9352a242..61128eff 100644 --- a/crypto/parser/srcread.h +++ b/crypto/parser/srcread.h @@ -35,6 +35,7 @@ struct FileDescr { std::string text; std::vector 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);