diff --git a/crypto/func/auto-tests/tests/builtin-keyword.fc b/crypto/func/auto-tests/tests/builtin-keyword.fc new file mode 100644 index 00000000..9a1050dc --- /dev/null +++ b/crypto/func/auto-tests/tests/builtin-keyword.fc @@ -0,0 +1,33 @@ +(int, int) moddiv(int x, int y) pure builtin; +() throw_if(int excno, int cond) builtin; + +() throwIf(int excNo, int cond) { return throw_if(excNo, cond); } + +forall X -> int null?(X x) pure builtin; + +_ test1() method_id(101) { + try { + var (rem, _) = moddiv(1, 1); + throwIf(101, rem != 100500); + return 0; + } catch(_, excNo) { + return excNo; + } +} + +(int, int, int) main() { + int x = 112; + int y = 3; + var wh = x~moddiv(y); + throwIf(103, wh != 37); + var cc = nil; + return (wh, x, null?(cc)); +} + +{- +TESTCASE | 0 | | 37 1 -1 +TESTCASE | 101 | | 101 + +@fif_codegen_avoid DECLPROC moddiv +@fif_codegen_avoid DECLPROC throwIf +-} diff --git a/crypto/func/auto-tests/tests/invalid-builtin-1.fc b/crypto/func/auto-tests/tests/invalid-builtin-1.fc new file mode 100644 index 00000000..9a8b8b73 --- /dev/null +++ b/crypto/func/auto-tests/tests/invalid-builtin-1.fc @@ -0,0 +1,10 @@ +(int, int) moddiv2(int x, int y) builtin; + +{- +@compilation_should_fail +@stderr +""" +`builtin` used for non-builtin function +(int, int) moddiv2 +""" +-} diff --git a/crypto/func/builtins.cpp b/crypto/func/builtins.cpp index a0d6d951..aafa9bef 100644 --- a/crypto/func/builtins.cpp +++ b/crypto/func/builtins.cpp @@ -31,7 +31,7 @@ int glob_func_cnt, undef_func_cnt, glob_var_cnt, const_cnt; std::vector glob_func, glob_vars; std::set prohibited_var_names; -SymDef* predefine_builtin_func(std::string name, TypeExpr* func_type) { +SymDef* define_builtin_func_impl(const std::string& name, SymValAsmFunc* func_val) { if (name.back() == '_') { prohibited_var_names.insert(name); } @@ -44,39 +44,40 @@ SymDef* predefine_builtin_func(std::string name, TypeExpr* func_type) { std::cerr << "fatal: global function `" << name << "` already defined" << std::endl; std::exit(1); } - return def; -} - -template -SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const T& func, bool impure = false) { - SymDef* def = predefine_builtin_func(name, func_type); - def->value = new SymValAsmFunc{func_type, func, !impure}; + func_val->flags |= SymValFunc::flagBuiltinFunction; + def->value = func_val; #ifdef FUNC_DEBUG dynamic_cast(def->value)->name = name; #endif return def; } -template -SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const T& func, std::initializer_list arg_order, +SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, bool impure = false) { + return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, !impure}); +} + +SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const compile_func_t& func, bool impure = false) { + return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, !impure}); +} + +SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const AsmOp& macro, bool impure = false) { + return define_builtin_func_impl(name, new SymValAsmFunc{func_type, make_simple_compile(macro), !impure}); +} + +SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, std::initializer_list arg_order, std::initializer_list ret_order = {}, bool impure = false) { - SymDef* def = predefine_builtin_func(name, func_type); - def->value = new SymValAsmFunc{func_type, func, arg_order, ret_order, !impure}; -#ifdef FUNC_DEBUG - dynamic_cast(def->value)->name = name; -#endif - return def; + return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, arg_order, ret_order, !impure}); } -SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const AsmOp& macro, +SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const compile_func_t& func, std::initializer_list arg_order, + std::initializer_list ret_order = {}, bool impure = false) { + return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, arg_order, ret_order, !impure}); +} + +SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const AsmOp& macro, std::initializer_list arg_order, std::initializer_list ret_order = {}, bool impure = false) { - SymDef* def = predefine_builtin_func(name, func_type); - def->value = new SymValAsmFunc{func_type, make_simple_compile(macro), arg_order, ret_order, !impure}; -#ifdef FUNC_DEBUG - dynamic_cast(def->value)->name = name; -#endif - return def; + return define_builtin_func_impl(name, new SymValAsmFunc{func_type, make_simple_compile(macro), arg_order, ret_order, !impure}); } SymDef* force_autoapply(SymDef* def) { @@ -1152,9 +1153,9 @@ void define_builtins() { auto Int3 = TypeExpr::new_tensor({Int, Int, Int}); auto TupleInt = TypeExpr::new_tensor({Tuple, Int}); auto SliceInt = TypeExpr::new_tensor({Slice, Int}); - auto X = TypeExpr::new_var(); - auto Y = TypeExpr::new_var(); - auto Z = TypeExpr::new_var(); + auto X = TypeExpr::new_var(0); + auto Y = TypeExpr::new_var(1); + auto Z = TypeExpr::new_var(2); auto XY = TypeExpr::new_tensor({X, Y}); auto arith_bin_op = TypeExpr::new_map(Int2, Int); auto arith_un_op = TypeExpr::new_map(Int, Int); diff --git a/crypto/func/func.h b/crypto/func/func.h index f168b933..07737cb9 100644 --- a/crypto/func/func.h +++ b/crypto/func/func.h @@ -113,6 +113,7 @@ enum Keyword { _Extern, _Inline, _InlineRef, + _Builtin, _AutoApply, _MethodId, _Operator, @@ -767,6 +768,7 @@ struct SymValFunc : SymVal { flagWrapsAnotherF = 4, // (T) thisF(...args) { return anotherF(...args); } (calls to thisF will be replaced) flagUsedAsNonCall = 8, // used not only as `f()`, but as a 1-st class function (assigned to var, pushed to tuple, etc.) flagMarkedAsPure = 16, // declared as `pure`, can't call impure and access globals, unused invocations are optimized out + flagBuiltinFunction = 32, // was created via `define_builtin_func()`, not from source code }; td::RefInt256 method_id; // todo why int256? it's small @@ -802,6 +804,9 @@ struct SymValFunc : SymVal { bool is_marked_as_pure() const { return flags & flagMarkedAsPure; } + bool is_builtin() const { + return flags & flagBuiltinFunction; + } }; struct SymValCodeFunc : SymValFunc { @@ -1700,8 +1705,8 @@ inline simple_compile_func_t make_simple_compile(AsmOp op) { return [op](std::vector& out, std::vector& in, const SrcLocation&) -> AsmOp { return op; }; } -inline compile_func_t make_ext_compile(std::vector ops) { - return [ops = std::move(ops)](AsmOpList & dest, std::vector & out, std::vector & in)->bool { +inline compile_func_t make_ext_compile(std::vector&& ops) { + return [ops = std::move(ops)](AsmOpList& dest, std::vector& out, std::vector& in)->bool { return dest.append(ops); }; } @@ -1716,10 +1721,7 @@ struct SymValAsmFunc : SymValFunc { compile_func_t ext_compile; td::uint64 crc; ~SymValAsmFunc() override = default; - SymValAsmFunc(TypeExpr* ft, const AsmOp& _macro, bool marked_as_pure) - : SymValFunc(-1, ft, marked_as_pure), simple_compile(make_simple_compile(_macro)) { - } - SymValAsmFunc(TypeExpr* ft, std::vector _macro, bool marked_as_pure) + SymValAsmFunc(TypeExpr* ft, std::vector&& _macro, bool marked_as_pure) : SymValFunc(-1, ft, marked_as_pure), ext_compile(make_ext_compile(std::move(_macro))) { } SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, bool marked_as_pure) diff --git a/crypto/func/keywords.cpp b/crypto/func/keywords.cpp index 0248a65a..9de9fb73 100644 --- a/crypto/func/keywords.cpp +++ b/crypto/func/keywords.cpp @@ -123,6 +123,7 @@ void define_keywords() { .add_keyword("pure", Kw::_Pure) .add_keyword("inline", Kw::_Inline) .add_keyword("inline_ref", Kw::_InlineRef) + .add_keyword("builtin", Kw::_Builtin) .add_keyword("auto_apply", Kw::_AutoApply) .add_keyword("method_id", Kw::_MethodId) .add_keyword("operator", Kw::_Operator) diff --git a/crypto/func/parse-func.cpp b/crypto/func/parse-func.cpp index c9b5f2cb..b06933db 100644 --- a/crypto/func/parse-func.cpp +++ b/crypto/func/parse-func.cpp @@ -1358,7 +1358,7 @@ SymValAsmFunc* parse_asm_func_body(Lexer& lex, TypeExpr* func_type, const Formal for (const int& x : ret_order) { crc_s += std::string((const char*) (&x), (const char*) (&x + 1)); } - auto res = new SymValAsmFunc{func_type, asm_ops, marked_as_pure}; + auto res = new SymValAsmFunc{func_type, std::move(asm_ops), marked_as_pure}; res->arg_order = std::move(arg_order); res->ret_order = std::move(ret_order); res->crc = td::crc64(crc_s); @@ -1564,11 +1564,27 @@ void parse_func_def(Lexer& lex) { method_id = td::make_refint((crc & 0xffff) | 0x10000); } } - if (lex.tp() != ';' && lex.tp() != '{' && lex.tp() != _Asm) { - lex.expect('{', "function body block expected"); - } TypeExpr* func_type = TypeExpr::new_map(extract_total_arg_type(arg_list), ret_type); func_type = compute_type_closure(func_type, type_vars); + if (lex.tp() == _Builtin) { + const SymDef* builtin_func = sym::lookup_symbol(func_name.str); + const SymValFunc* func_val = builtin_func ? dynamic_cast(builtin_func->value) : nullptr; + if (!func_val || !func_val->is_builtin()) { + lex.cur().error("`builtin` used for non-builtin function"); + } +#ifdef FUNC_DEBUG + // in release, we don't need this check, since `builtin` is used only in stdlib.fc, which is our responsibility + if (!func_val->sym_type->equals_to(func_type) || func_val->is_marked_as_pure() != marked_as_pure) { + lex.cur().error("declaration for `builtin` function doesn't match an actual one"); + } +#endif + lex.next(); + lex.expect(';'); + return; + } + if (lex.tp() != ';' && lex.tp() != '{' && lex.tp() != _Asm) { + lex.expect('{', "function body block"); + } if (verbosity >= 1) { std::cerr << "function " << func_name.str << " : " << func_type << std::endl; }