1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-02-13 03:32:22 +00:00
ton/tolk/builtins.cpp
tolk-vm 5a3e3595d6
[Tolk] Compilation pipeline, register global symbols in advance
Since I've implemented AST, now I can drop forward declarations.
Instead, I traverse AST of all files and register global symbols
(functions, constants, global vars) as a separate step, in advance.

That's why, while converting AST to Expr/Op, all available symbols are
already registered.
This greatly simplifies "intermediate state" of yet unknown functions
and checking them afterward.

Redeclaration of local variables (inside the same scope)
is now also prohibited.
2024-11-02 01:33:09 +04:00

1234 lines
42 KiB
C++

/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tolk.h"
#include "compiler-state.h"
namespace tolk {
using namespace std::literals::string_literals;
/*
*
* SYMBOL VALUES
*
*/
SymDef* define_builtin_func_impl(const std::string& name, SymValAsmFunc* func_val) {
sym_idx_t name_idx = G.symbols.lookup_add(name);
SymDef* def = define_global_symbol(name_idx);
if (!def) {
std::cerr << "fatal: global function `" << name << "` already defined" << std::endl;
std::exit(1);
}
func_val->flags |= SymValFunc::flagBuiltinFunction;
def->value = func_val;
#ifdef TOLK_DEBUG
dynamic_cast<SymValAsmFunc*>(def->value)->name = name;
#endif
return def;
}
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<int> arg_order,
std::initializer_list<int> 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 compile_func_t& func, std::initializer_list<int> arg_order,
std::initializer_list<int> 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<int> arg_order, std::initializer_list<int> ret_order = {},
bool impure = false) {
return define_builtin_func_impl(name, new SymValAsmFunc{func_type, make_simple_compile(macro), arg_order, ret_order, !impure});
}
bool SymValAsmFunc::compile(AsmOpList& dest, std::vector<VarDescr>& out, std::vector<VarDescr>& in,
SrcLocation where) const {
if (simple_compile) {
return dest.append(simple_compile(out, in, where));
} else if (ext_compile) {
return ext_compile(dest, out, in);
} else {
return false;
}
}
/*
*
* DEFINE BUILT-IN FUNCTIONS
*
*/
int emulate_negate(int a) {
int f = VarDescr::_Pos | VarDescr::_Neg;
if ((a & f) && (~a & f)) {
a ^= f;
}
f = VarDescr::_Bit | VarDescr::_Bool;
if ((a & f) && (~a & f)) {
a ^= f;
}
return a;
}
int emulate_add(int a, int b) {
if (b & VarDescr::_Zero) {
return a;
} else if (a & VarDescr::_Zero) {
return b;
}
int u = a & b, v = a | b;
int r = VarDescr::_Int;
int t = u & (VarDescr::_Pos | VarDescr::_Neg);
if (v & VarDescr::_Nan) {
return r | VarDescr::_Nan;
}
// non-quiet addition always returns finite results!
r |= t | VarDescr::_Finite;
if (t) {
r |= v & VarDescr::_NonZero;
}
r |= v & VarDescr::_Nan;
if (u & (VarDescr::_Odd | VarDescr::_Even)) {
r |= VarDescr::_Even;
} else if (!(~v & (VarDescr::_Odd | VarDescr::_Even))) {
r |= VarDescr::_Odd | VarDescr::_NonZero;
}
return r;
}
int emulate_sub(int a, int b) {
return emulate_add(a, emulate_negate(b));
}
int emulate_mul(int a, int b) {
if ((b & (VarDescr::_NonZero | VarDescr::_Bit)) == (VarDescr::_NonZero | VarDescr::_Bit)) {
return a;
} else if ((a & (VarDescr::_NonZero | VarDescr::_Bit)) == (VarDescr::_NonZero | VarDescr::_Bit)) {
return b;
}
int u = a & b, v = a | b;
int r = VarDescr::_Int;
if (v & VarDescr::_Nan) {
return r | VarDescr::_Nan;
}
// non-quiet multiplication always yields finite results, if any
r |= VarDescr::_Finite;
if (v & VarDescr::_Zero) {
// non-quiet multiplication
// the result is zero, if any result at all
return VarDescr::ConstZero;
}
if (u & (VarDescr::_Pos | VarDescr::_Neg)) {
r |= VarDescr::_Pos;
} else if (!(~v & (VarDescr::_Pos | VarDescr::_Neg))) {
r |= VarDescr::_Neg;
}
if (u & (VarDescr::_Bit | VarDescr::_Bool)) {
r |= VarDescr::_Bit;
} else if (!(~v & (VarDescr::_Bit | VarDescr::_Bool))) {
r |= VarDescr::_Bool;
}
r |= v & VarDescr::_Even;
r |= u & (VarDescr::_Odd | VarDescr::_NonZero);
return r;
}
int emulate_bitwise_and(int a, int b) {
int both = a & b, any = a | b;
int r = VarDescr::_Int;
if (any & VarDescr::_Nan) {
return r | VarDescr::_Nan;
}
r |= VarDescr::_Finite;
if (any & VarDescr::_Zero) {
return VarDescr::ConstZero;
}
r |= both & (VarDescr::_Even | VarDescr::_Odd);
r |= both & (VarDescr::_Bit | VarDescr::_Bool);
if (both & VarDescr::_Odd) {
r |= VarDescr::_NonZero;
}
return r;
}
int emulate_bitwise_or(int a, int b) {
if (b & VarDescr::_Zero) {
return a;
} else if (a & VarDescr::_Zero) {
return b;
}
int both = a & b, any = a | b;
int r = VarDescr::_Int;
if (any & VarDescr::_Nan) {
return r | VarDescr::_Nan;
}
r |= VarDescr::_Finite;
r |= any & VarDescr::_NonZero;
r |= any & VarDescr::_Odd;
r |= both & VarDescr::_Even;
return r;
}
int emulate_bitwise_xor(int a, int b) {
if (b & VarDescr::_Zero) {
return a;
} else if (a & VarDescr::_Zero) {
return b;
}
int both = a & b, any = a | b;
int r = VarDescr::_Int;
if (any & VarDescr::_Nan) {
return r | VarDescr::_Nan;
}
r |= VarDescr::_Finite;
r |= both & VarDescr::_Even;
if (both & VarDescr::_Odd) {
r |= VarDescr::_Even;
}
return r;
}
int emulate_bitwise_not(int a) {
if ((a & VarDescr::ConstZero) == VarDescr::ConstZero) {
return VarDescr::ConstTrue;
}
if ((a & VarDescr::ConstTrue) == VarDescr::ConstTrue) {
return VarDescr::ConstZero;
}
int a2 = a;
int f = VarDescr::_Even | VarDescr::_Odd;
if ((a2 & f) && (~a2 & f)) {
a2 ^= f;
}
a2 &= ~(VarDescr::_Zero | VarDescr::_NonZero | VarDescr::_Bit | VarDescr::_Pos | VarDescr::_Neg);
if ((a & VarDescr::_Neg) && (a & VarDescr::_NonZero)) {
a2 |= VarDescr::_Pos;
}
if (a & VarDescr::_Pos) {
a2 |= VarDescr::_Neg;
}
return a2;
}
int emulate_lshift(int a, int b) {
if (((a | b) & VarDescr::_Nan) || !(~b & (VarDescr::_Neg | VarDescr::_NonZero))) {
return VarDescr::_Int | VarDescr::_Nan;
}
if (b & VarDescr::_Zero) {
return a;
}
int t = ((b & VarDescr::_NonZero) ? VarDescr::_Even : 0);
t |= b & VarDescr::_Finite;
return emulate_mul(a, VarDescr::_Int | VarDescr::_Pos | VarDescr::_NonZero | t);
}
int emulate_div(int a, int b) {
if ((b & (VarDescr::_NonZero | VarDescr::_Bit)) == (VarDescr::_NonZero | VarDescr::_Bit)) {
return a;
} else if ((b & (VarDescr::_NonZero | VarDescr::_Bool)) == (VarDescr::_NonZero | VarDescr::_Bool)) {
return emulate_negate(a);
}
if (b & VarDescr::_Zero) {
return VarDescr::_Int | VarDescr::_Nan;
}
int u = a & b, v = a | b;
int r = VarDescr::_Int;
if (v & VarDescr::_Nan) {
return r | VarDescr::_Nan;
}
// non-quiet division always yields finite results, if any
r |= VarDescr::_Finite;
if (a & VarDescr::_Zero) {
// non-quiet division
// the result is zero, if any result at all
return VarDescr::ConstZero;
}
if (u & (VarDescr::_Pos | VarDescr::_Neg)) {
r |= VarDescr::_Pos;
} else if (!(~v & (VarDescr::_Pos | VarDescr::_Neg))) {
r |= VarDescr::_Neg;
}
if (u & (VarDescr::_Bit | VarDescr::_Bool)) {
r |= VarDescr::_Bit;
} else if (!(~v & (VarDescr::_Bit | VarDescr::_Bool))) {
r |= VarDescr::_Bool;
}
return r;
}
int emulate_rshift(int a, int b) {
if (((a | b) & VarDescr::_Nan) || !(~b & (VarDescr::_Neg | VarDescr::_NonZero))) {
return VarDescr::_Int | VarDescr::_Nan;
}
if (b & VarDescr::_Zero) {
return a;
}
int t = ((b & VarDescr::_NonZero) ? VarDescr::_Even : 0);
t |= b & VarDescr::_Finite;
return emulate_div(a, VarDescr::_Int | VarDescr::_Pos | VarDescr::_NonZero | t);
}
int emulate_mod(int a, int b, int round_mode = -1) {
if ((b & (VarDescr::_NonZero | VarDescr::_Bit)) == (VarDescr::_NonZero | VarDescr::_Bit)) {
return VarDescr::ConstZero;
} else if ((b & (VarDescr::_NonZero | VarDescr::_Bool)) == (VarDescr::_NonZero | VarDescr::_Bool)) {
return VarDescr::ConstZero;
}
if (b & VarDescr::_Zero) {
return VarDescr::_Int | VarDescr::_Nan;
}
int r = VarDescr::_Int;
if ((a | b) & VarDescr::_Nan) {
return r | VarDescr::_Nan;
}
// non-quiet division always yields finite results, if any
r |= VarDescr::_Finite;
if (a & VarDescr::_Zero) {
// non-quiet division
// the result is zero, if any result at all
return VarDescr::ConstZero;
}
if (round_mode < 0) {
r |= b & (VarDescr::_Pos | VarDescr::_Neg);
} else if (round_mode > 0) {
r |= emulate_negate(b) & (VarDescr::_Pos | VarDescr::_Neg);
}
if (a & (VarDescr::_Bit | VarDescr::_Bool)) {
if (r & VarDescr::_Pos) {
r |= VarDescr::_Bit;
}
if (r & VarDescr::_Neg) {
r |= VarDescr::_Bool;
}
}
if (b & VarDescr::_Even) {
r |= a & (VarDescr::_Even | VarDescr::_Odd);
}
return r;
}
bool VarDescr::always_less(const VarDescr& other) const {
if (is_int_const() && other.is_int_const()) {
return int_const < other.int_const;
}
return (always_nonpos() && other.always_pos()) || (always_neg() && other.always_nonneg());
}
bool VarDescr::always_leq(const VarDescr& other) const {
if (is_int_const() && other.is_int_const()) {
return int_const <= other.int_const;
}
return always_nonpos() && other.always_nonneg();
}
bool VarDescr::always_greater(const VarDescr& other) const {
return other.always_less(*this);
}
bool VarDescr::always_geq(const VarDescr& other) const {
return other.always_leq(*this);
}
bool VarDescr::always_equal(const VarDescr& other) const {
return is_int_const() && other.is_int_const() && *int_const == *other.int_const;
}
bool VarDescr::always_neq(const VarDescr& other) const {
if (is_int_const() && other.is_int_const()) {
return *int_const != *other.int_const;
}
return always_greater(other) || always_less(other) || (always_even() && other.always_odd()) ||
(always_odd() && other.always_even());
}
AsmOp exec_op(std::string op) {
return AsmOp::Custom(op);
}
AsmOp exec_op(std::string op, int args, int retv = 1) {
return AsmOp::Custom(op, args, retv);
}
AsmOp exec_arg_op(std::string op, long long arg) {
std::ostringstream os;
os << arg << ' ' << op;
return AsmOp::Custom(os.str());
}
AsmOp exec_arg_op(std::string op, long long arg, int args, int retv) {
std::ostringstream os;
os << arg << ' ' << op;
return AsmOp::Custom(os.str(), args, retv);
}
AsmOp exec_arg_op(std::string op, td::RefInt256 arg) {
std::ostringstream os;
os << arg << ' ' << op;
return AsmOp::Custom(os.str());
}
AsmOp exec_arg_op(std::string op, td::RefInt256 arg, int args, int retv) {
std::ostringstream os;
os << arg << ' ' << op;
return AsmOp::Custom(os.str(), args, retv);
}
AsmOp exec_arg2_op(std::string op, long long imm1, long long imm2, int args, int retv) {
std::ostringstream os;
os << imm1 << ' ' << imm2 << ' ' << op;
return AsmOp::Custom(os.str(), args, retv);
}
AsmOp push_const(td::RefInt256 x) {
return AsmOp::IntConst(std::move(x));
}
AsmOp compile_add(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
tolk_assert(res.size() == 1 && args.size() == 2);
VarDescr &r = res[0], &x = args[0], &y = args[1];
if (x.is_int_const() && y.is_int_const()) {
r.set_const(x.int_const + y.int_const);
if (!r.int_const->is_valid()) {
throw ParseError(where, "integer overflow");
}
x.unused();
y.unused();
return push_const(r.int_const);
}
r.val = emulate_add(x.val, y.val);
if (y.is_int_const() && y.int_const->signed_fits_bits(8)) {
y.unused();
if (y.always_zero()) {
return AsmOp::Nop();
}
if (*y.int_const == 1) {
return exec_op("INC", 1);
}
if (*y.int_const == -1) {
return exec_op("DEC", 1);
}
return exec_arg_op("ADDCONST", y.int_const, 1);
}
if (x.is_int_const() && x.int_const->signed_fits_bits(8)) {
x.unused();
if (x.always_zero()) {
return AsmOp::Nop();
}
if (*x.int_const == 1) {
return exec_op("INC", 1);
}
if (*x.int_const == -1) {
return exec_op("DEC", 1);
}
return exec_arg_op("ADDCONST", x.int_const, 1);
}
return exec_op("ADD", 2);
}
AsmOp compile_sub(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
tolk_assert(res.size() == 1 && args.size() == 2);
VarDescr &r = res[0], &x = args[0], &y = args[1];
if (x.is_int_const() && y.is_int_const()) {
r.set_const(x.int_const - y.int_const);
if (!r.int_const->is_valid()) {
throw ParseError(where, "integer overflow");
}
x.unused();
y.unused();
return push_const(r.int_const);
}
r.val = emulate_sub(x.val, y.val);
if (y.is_int_const() && (-y.int_const)->signed_fits_bits(8)) {
y.unused();
if (y.always_zero()) {
return {};
}
if (*y.int_const == 1) {
return exec_op("DEC", 1);
}
if (*y.int_const == -1) {
return exec_op("INC", 1);
}
return exec_arg_op("ADDCONST", -y.int_const, 1);
}
if (x.always_zero()) {
x.unused();
return exec_op("NEGATE", 1);
}
return exec_op("SUB", 2);
}
AsmOp compile_unary_minus(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
tolk_assert(res.size() == 1 && args.size() == 1);
VarDescr &r = res[0], &x = args[0];
if (x.is_int_const()) {
r.set_const(-x.int_const);
if (!r.int_const->is_valid()) {
throw ParseError(where, "integer overflow");
}
x.unused();
return push_const(r.int_const);
}
r.val = emulate_negate(x.val);
return exec_op("NEGATE", 1);
}
AsmOp compile_unary_plus(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
tolk_assert(res.size() == 1 && args.size() == 1);
VarDescr &r = res[0], &x = args[0];
if (x.is_int_const()) {
r.set_const(x.int_const);
x.unused();
return push_const(r.int_const);
}
r.val = x.val;
return AsmOp::Nop();
}
AsmOp compile_bitwise_and(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
tolk_assert(res.size() == 1 && args.size() == 2);
VarDescr &r = res[0], &x = args[0], &y = args[1];
if (x.is_int_const() && y.is_int_const()) {
r.set_const(x.int_const & y.int_const);
x.unused();
y.unused();
return push_const(r.int_const);
}
r.val = emulate_bitwise_and(x.val, y.val);
return exec_op("AND", 2);
}
AsmOp compile_bitwise_or(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
tolk_assert(res.size() == 1 && args.size() == 2);
VarDescr &r = res[0], &x = args[0], &y = args[1];
if (x.is_int_const() && y.is_int_const()) {
r.set_const(x.int_const | y.int_const);
x.unused();
y.unused();
return push_const(r.int_const);
}
r.val = emulate_bitwise_or(x.val, y.val);
return exec_op("OR", 2);
}
AsmOp compile_bitwise_xor(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
tolk_assert(res.size() == 1 && args.size() == 2);
VarDescr &r = res[0], &x = args[0], &y = args[1];
if (x.is_int_const() && y.is_int_const()) {
r.set_const(x.int_const ^ y.int_const);
x.unused();
y.unused();
return push_const(r.int_const);
}
r.val = emulate_bitwise_xor(x.val, y.val);
return exec_op("XOR", 2);
}
AsmOp compile_bitwise_not(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
tolk_assert(res.size() == 1 && args.size() == 1);
VarDescr &r = res[0], &x = args[0];
if (x.is_int_const()) {
r.set_const(~x.int_const);
x.unused();
return push_const(r.int_const);
}
r.val = emulate_bitwise_not(x.val);
return exec_op("NOT", 1);
}
AsmOp compile_mul_internal(VarDescr& r, VarDescr& x, VarDescr& y, SrcLocation where) {
if (x.is_int_const() && y.is_int_const()) {
r.set_const(x.int_const * y.int_const);
if (!r.int_const->is_valid()) {
throw ParseError(where, "integer overflow");
}
x.unused();
y.unused();
return push_const(r.int_const);
}
r.val = emulate_mul(x.val, y.val);
if (y.is_int_const()) {
int k = is_pos_pow2(y.int_const);
if (y.int_const->signed_fits_bits(8) && k < 0) {
y.unused();
if (y.always_zero() && x.always_finite()) {
// dubious optimization: NaN * 0 = ?
r.set_const(y.int_const);
x.unused();
return push_const(r.int_const);
}
if (*y.int_const == 1 && x.always_finite()) {
return AsmOp::Nop();
}
if (*y.int_const == -1) {
return exec_op("NEGATE", 1);
}
return exec_arg_op("MULCONST", y.int_const, 1);
}
if (k > 0) {
y.unused();
return exec_arg_op("LSHIFT#", k, 1);
}
if (k == 0) {
y.unused();
return AsmOp::Nop();
}
}
if (x.is_int_const()) {
int k = is_pos_pow2(x.int_const);
if (x.int_const->signed_fits_bits(8) && k < 0) {
x.unused();
if (x.always_zero() && y.always_finite()) {
// dubious optimization: NaN * 0 = ?
r.set_const(x.int_const);
y.unused();
return push_const(r.int_const);
}
if (*x.int_const == 1 && y.always_finite()) {
return AsmOp::Nop();
}
if (*x.int_const == -1) {
return exec_op("NEGATE", 1);
}
return exec_arg_op("MULCONST", x.int_const, 1);
}
if (k > 0) {
x.unused();
return exec_arg_op("LSHIFT#", k, 1);
}
if (k == 0) {
x.unused();
return AsmOp::Nop();
}
}
return exec_op("MUL", 2);
}
AsmOp compile_mul(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
tolk_assert(res.size() == 1 && args.size() == 2);
return compile_mul_internal(res[0], args[0], args[1], where);
}
AsmOp compile_lshift(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
tolk_assert(res.size() == 1 && args.size() == 2);
VarDescr &r = res[0], &x = args[0], &y = args[1];
if (y.is_int_const()) {
auto yv = y.int_const->to_long();
if (yv < 0 || yv > 256) {
throw ParseError(where, "lshift argument is out of range");
} else if (x.is_int_const()) {
r.set_const(x.int_const << (int)yv);
if (!r.int_const->is_valid()) {
throw ParseError(where, "integer overflow");
}
x.unused();
y.unused();
return push_const(r.int_const);
}
}
r.val = emulate_lshift(x.val, y.val);
if (y.is_int_const()) {
int k = (int)(y.int_const->to_long());
if (!k /* && x.always_finite() */) {
// dubious optimization: what if x=NaN ?
y.unused();
return AsmOp::Nop();
}
y.unused();
return exec_arg_op("LSHIFT#", k, 1);
}
if (x.is_int_const()) {
auto xv = x.int_const->to_long();
if (xv == 1) {
x.unused();
return exec_op("POW2", 1);
}
if (xv == -1) {
x.unused();
return exec_op("-1 PUSHINT SWAP LSHIFT", 1);
}
}
return exec_op("LSHIFT", 2);
}
AsmOp compile_rshift(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where,
int round_mode) {
tolk_assert(res.size() == 1 && args.size() == 2);
VarDescr &r = res[0], &x = args[0], &y = args[1];
if (y.is_int_const()) {
auto yv = y.int_const->to_long();
if (yv < 0 || yv > 256) {
throw ParseError(where, "rshift argument is out of range");
} else if (x.is_int_const()) {
r.set_const(td::rshift(x.int_const, (int)yv, round_mode));
x.unused();
y.unused();
return push_const(r.int_const);
}
}
r.val = emulate_rshift(x.val, y.val);
std::string rshift = (round_mode < 0 ? "RSHIFT" : (round_mode ? "RSHIFTC" : "RSHIFTR"));
if (y.is_int_const()) {
int k = (int)(y.int_const->to_long());
if (!k /* && x.always_finite() */) {
// dubious optimization: what if x=NaN ?
y.unused();
return AsmOp::Nop();
}
y.unused();
return exec_arg_op(rshift + "#", k, 1);
}
return exec_op(rshift, 2);
}
AsmOp compile_div_internal(VarDescr& r, VarDescr& x, VarDescr& y, SrcLocation where, int round_mode) {
if (x.is_int_const() && y.is_int_const()) {
r.set_const(div(x.int_const, y.int_const, round_mode));
if (!r.int_const->is_valid()) {
throw ParseError(where, *y.int_const == 0 ? "division by zero" : "integer overflow");
}
x.unused();
y.unused();
return push_const(r.int_const);
}
r.val = emulate_div(x.val, y.val);
if (y.is_int_const()) {
if (*y.int_const == 0) {
throw ParseError(where, "division by zero");
}
if (*y.int_const == 1 && x.always_finite()) {
y.unused();
return AsmOp::Nop();
}
if (*y.int_const == -1) {
y.unused();
return exec_op("NEGATE", 1);
}
int k = is_pos_pow2(y.int_const);
if (k > 0) {
y.unused();
std::string op = "RSHIFT";
if (round_mode >= 0) {
op += (round_mode > 0 ? 'C' : 'R');
}
return exec_arg_op(op + '#', k, 1);
}
}
std::string op = "DIV";
if (round_mode >= 0) {
op += (round_mode > 0 ? 'C' : 'R');
}
return exec_op(op, 2);
}
AsmOp compile_div(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where, int round_mode) {
tolk_assert(res.size() == 1 && args.size() == 2);
return compile_div_internal(res[0], args[0], args[1], where, round_mode);
}
AsmOp compile_mod(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where,
int round_mode) {
tolk_assert(res.size() == 1 && args.size() == 2);
VarDescr &r = res[0], &x = args[0], &y = args[1];
if (x.is_int_const() && y.is_int_const()) {
r.set_const(mod(x.int_const, y.int_const, round_mode));
if (!r.int_const->is_valid()) {
throw ParseError(where, *y.int_const == 0 ? "division by zero" : "integer overflow");
}
x.unused();
y.unused();
return push_const(r.int_const);
}
r.val = emulate_mod(x.val, y.val);
if (y.is_int_const()) {
if (*y.int_const == 0) {
throw ParseError(where, "division by zero");
}
if ((*y.int_const == 1 || *y.int_const == -1) && x.always_finite()) {
x.unused();
y.unused();
r.set_const(td::zero_refint());
return push_const(r.int_const);
}
int k = is_pos_pow2(y.int_const);
if (k > 0) {
y.unused();
std::string op = "MODPOW2";
if (round_mode >= 0) {
op += (round_mode > 0 ? 'C' : 'R');
}
return exec_arg_op(op + '#', k, 1);
}
}
std::string op = "MOD";
if (round_mode >= 0) {
op += (round_mode > 0 ? 'C' : 'R');
}
return exec_op(op, 2);
}
AsmOp compile_muldiv(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where,
int round_mode) {
tolk_assert(res.size() == 1 && args.size() == 3);
VarDescr &r = res[0], &x = args[0], &y = args[1], &z = args[2];
if (x.is_int_const() && y.is_int_const() && z.is_int_const()) {
r.set_const(muldiv(x.int_const, y.int_const, z.int_const, round_mode));
if (!r.int_const->is_valid()) {
throw ParseError(where, *z.int_const == 0 ? "division by zero" : "integer overflow");
}
x.unused();
y.unused();
z.unused();
return push_const(r.int_const);
}
if (x.always_zero() || y.always_zero()) {
// dubious optimization for z=0...
x.unused();
y.unused();
z.unused();
r.set_const(td::make_refint(0));
return push_const(r.int_const);
}
char c = (round_mode < 0) ? 0 : (round_mode > 0 ? 'C' : 'R');
r.val = emulate_div(emulate_mul(x.val, y.val), z.val);
if (z.is_int_const()) {
if (*z.int_const == 0) {
throw ParseError(where, "division by zero");
}
if (*z.int_const == 1) {
z.unused();
return compile_mul_internal(r, x, y, where);
}
}
if (y.is_int_const() && *y.int_const == 1) {
y.unused();
return compile_div_internal(r, x, z, where, round_mode);
}
if (x.is_int_const() && *x.int_const == 1) {
x.unused();
return compile_div_internal(r, y, z, where, round_mode);
}
if (z.is_int_const()) {
int k = is_pos_pow2(z.int_const);
if (k > 0) {
z.unused();
std::string op = "MULRSHIFT";
if (c) {
op += c;
}
return exec_arg_op(op + '#', k, 2);
}
}
if (y.is_int_const()) {
int k = is_pos_pow2(y.int_const);
if (k > 0) {
y.unused();
std::string op = "LSHIFT#DIV";
if (c) {
op += c;
}
return exec_arg_op(op, k, 2);
}
}
if (x.is_int_const()) {
int k = is_pos_pow2(x.int_const);
if (k > 0) {
x.unused();
std::string op = "LSHIFT#DIV";
if (c) {
op += c;
}
return exec_arg_op(op, k, 2);
}
}
std::string op = "MULDIV";
if (c) {
op += c;
}
return exec_op(op, 3);
}
int compute_compare(td::RefInt256 x, td::RefInt256 y, int mode) {
int s = td::cmp(x, y);
if (mode == 7) {
return s;
} else {
return -((mode >> (1 - s)) & 1);
}
}
// return value:
// 4 -> constant 1
// 2 -> constant 0
// 1 -> constant -1
// 3 -> 0 or -1
int compute_compare(const VarDescr& x, const VarDescr& y, int mode) {
switch (mode) {
case 1: // >
return x.always_greater(y) ? 1 : (x.always_leq(y) ? 2 : 3);
case 2: // =
return x.always_equal(y) ? 1 : (x.always_neq(y) ? 2 : 3);
case 3: // >=
return x.always_geq(y) ? 1 : (x.always_less(y) ? 2 : 3);
case 4: // <
return x.always_less(y) ? 1 : (x.always_geq(y) ? 2 : 3);
case 5: // <>
return x.always_neq(y) ? 1 : (x.always_equal(y) ? 2 : 3);
case 6: // <=
return x.always_leq(y) ? 1 : (x.always_greater(y) ? 2 : 3);
case 7: // <=>
return x.always_less(y)
? 1
: (x.always_equal(y)
? 2
: (x.always_greater(y)
? 4
: (x.always_leq(y) ? 3 : (x.always_geq(y) ? 6 : (x.always_neq(y) ? 5 : 7)))));
default:
return 7;
}
}
AsmOp compile_cmp_int(std::vector<VarDescr>& res, std::vector<VarDescr>& args, int mode) {
tolk_assert(mode >= 1 && mode <= 7);
tolk_assert(res.size() == 1 && args.size() == 2);
VarDescr &r = res[0], &x = args[0], &y = args[1];
if (x.is_int_const() && y.is_int_const()) {
int v = compute_compare(x.int_const, y.int_const, mode);
r.set_const(v);
x.unused();
y.unused();
return mode == 7 ? push_const(r.int_const) : AsmOp::BoolConst(v != 0);
}
int v = compute_compare(x, y, mode);
// std::cerr << "compute_compare(" << x << ", " << y << ", " << mode << ") = " << v << std::endl;
tolk_assert(v);
if (!(v & (v - 1))) {
r.set_const(v - (v >> 2) - 2);
x.unused();
y.unused();
return mode == 7 ? push_const(r.int_const) : AsmOp::BoolConst(v & 1);
}
r.val = ~0;
if (v & 1) {
r.val &= VarDescr::ConstTrue;
}
if (v & 2) {
r.val &= VarDescr::ConstZero;
}
if (v & 4) {
r.val &= VarDescr::ConstOne;
}
// std::cerr << "result: " << r << std::endl;
static const char* cmp_int_names[] = {"", "GTINT", "EQINT", "GTINT", "LESSINT", "NEQINT", "LESSINT"};
static const char* cmp_names[] = {"", "GREATER", "EQUAL", "GEQ", "LESS", "NEQ", "LEQ", "CMP"};
static int cmp_int_delta[] = {0, 0, 0, -1, 0, 0, 1};
if (mode != 7) {
if (y.is_int_const() && y.int_const >= -128 && y.int_const <= 127) {
y.unused();
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) {
x.unused();
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_op(cmp_names[mode], 2);
}
AsmOp compile_throw(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
tolk_assert(res.empty() && args.size() == 1);
VarDescr& x = args[0];
if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) {
x.unused();
return exec_arg_op("THROW", x.int_const, 0, 0);
} else {
return exec_op("THROWANY", 1, 0);
}
}
AsmOp compile_cond_throw(std::vector<VarDescr>& res, std::vector<VarDescr>& args, bool mode) {
tolk_assert(res.empty() && args.size() == 2);
VarDescr &x = args[0], &y = args[1];
std::string suff = (mode ? "IF" : "IFNOT");
bool skip_cond = false;
if (y.always_true() || y.always_false()) {
y.unused();
skip_cond = true;
if (y.always_true() != mode) {
x.unused();
return AsmOp::Nop();
}
}
if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) {
x.unused();
return skip_cond ? exec_arg_op("THROW", x.int_const, 0, 0) : exec_arg_op("THROW"s + suff, x.int_const, 1, 0);
} else {
return skip_cond ? exec_op("THROWANY", 1, 0) : exec_op("THROWANY"s + suff, 2, 0);
}
}
AsmOp compile_throw_arg(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
tolk_assert(res.empty() && args.size() == 2);
VarDescr &x = args[1];
if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) {
x.unused();
return exec_arg_op("THROWARG", x.int_const, 1, 0);
} else {
return exec_op("THROWARGANY", 2, 0);
}
}
AsmOp compile_cond_throw_arg(std::vector<VarDescr>& res, std::vector<VarDescr>& args, bool mode) {
tolk_assert(res.empty() && args.size() == 3);
VarDescr &x = args[1], &y = args[2];
std::string suff = (mode ? "IF" : "IFNOT");
bool skip_cond = false;
if (y.always_true() || y.always_false()) {
y.unused();
skip_cond = true;
if (y.always_true() != mode) {
x.unused();
return AsmOp::Nop();
}
}
if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) {
x.unused();
return skip_cond ? exec_arg_op("THROWARG", x.int_const, 1, 0) : exec_arg_op("THROWARG"s + suff, x.int_const, 2, 0);
} else {
return skip_cond ? exec_op("THROWARGANY", 2, 0) : exec_op("THROWARGANY"s + suff, 3, 0);
}
}
AsmOp compile_bool_const(std::vector<VarDescr>& res, std::vector<VarDescr>& args, bool val) {
tolk_assert(res.size() == 1 && args.empty());
VarDescr& r = res[0];
r.set_const(val ? -1 : 0);
return AsmOp::Const(val ? "TRUE" : "FALSE");
}
// (slice, int) load_int(slice s, int len) asm(s len -> 1 0) "LDIX";
// (slice, int) load_uint(slice s, int len) asm( -> 1 0) "LDUX";
// int preload_int(slice s, int len) asm "PLDIX";
// int preload_uint(slice s, int len) asm "PLDUX";
AsmOp compile_fetch_int(std::vector<VarDescr>& res, std::vector<VarDescr>& args, bool fetch, bool sgnd) {
tolk_assert(args.size() == 2 && res.size() == 1 + (unsigned)fetch);
auto &y = args[1], &r = res.back();
r.val = (sgnd ? VarDescr::FiniteInt : VarDescr::FiniteUInt);
int v = -1;
if (y.is_int_const() && y.int_const >= 0 && y.int_const <= 256) {
v = (int)y.int_const->to_long();
if (!v) {
r.val = VarDescr::ConstZero;
}
if (v == 1) {
r.val = (sgnd ? VarDescr::ValBool : VarDescr::ValBit);
}
if (v > 0) {
y.unused();
return exec_arg_op((fetch ? "LD"s : "PLD"s) + (sgnd ? 'I' : 'U'), v, 1, 1 + (unsigned)fetch);
}
}
return exec_op((fetch ? "LD"s : "PLD"s) + (sgnd ? "IX" : "UX"), 2, 1 + (unsigned)fetch);
}
// builder store_uint(builder b, int x, int len) asm(x b len) "STUX";
// builder store_int(builder b, int x, int len) asm(x b len) "STIX";
AsmOp compile_store_int(std::vector<VarDescr>& res, std::vector<VarDescr>& args, bool sgnd) {
tolk_assert(args.size() == 3 && res.size() == 1);
auto& z = args[2];
if (z.is_int_const() && z.int_const > 0 && z.int_const <= 256) {
z.unused();
return exec_arg_op("ST"s + (sgnd ? 'I' : 'U'), z.int_const, 2, 1);
}
return exec_op("ST"s + (sgnd ? "IX" : "UX"), 3, 1);
}
AsmOp compile_fetch_slice(std::vector<VarDescr>& res, std::vector<VarDescr>& args, bool fetch) {
tolk_assert(args.size() == 2 && res.size() == 1 + (unsigned)fetch);
auto& y = args[1];
int v = -1;
if (y.is_int_const() && y.int_const > 0 && y.int_const <= 256) {
v = (int)y.int_const->to_long();
if (v > 0) {
y.unused();
return exec_arg_op(fetch ? "LDSLICE" : "PLDSLICE", v, 1, 1 + (unsigned)fetch);
}
}
return exec_op(fetch ? "LDSLICEX" : "PLDSLICEX", 2, 1 + (unsigned)fetch);
}
// <type> <type>_at(tuple t, int index) asm "INDEXVAR";
AsmOp compile_tuple_at(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
tolk_assert(args.size() == 2 && res.size() == 1);
auto& y = args[1];
if (y.is_int_const() && y.int_const >= 0 && y.int_const < 16) {
y.unused();
return exec_arg_op("INDEX", y.int_const, 1, 1);
}
return exec_op("INDEXVAR", 2, 1);
}
// int null?(X arg)
AsmOp compile_is_null(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
tolk_assert(args.size() == 1 && res.size() == 1);
auto &x = args[0], &r = res[0];
if (x.always_null() || x.always_not_null()) {
x.unused();
r.set_const(x.always_null() ? -1 : 0);
return push_const(r.int_const);
}
res[0].val = VarDescr::ValBool;
return exec_op("ISNULL", 1, 1);
}
void define_builtins() {
using namespace std::placeholders;
auto Unit = TypeExpr::new_unit();
auto Int = TypeExpr::new_atomic(TypeExpr::_Int);
auto Cell = TypeExpr::new_atomic(TypeExpr::_Cell);
auto Slice = TypeExpr::new_atomic(TypeExpr::_Slice);
auto Builder = TypeExpr::new_atomic(TypeExpr::_Builder);
// auto Null = TypeExpr::new_atomic(TypeExpr::_Null);
auto Tuple = TypeExpr::new_atomic(TypeExpr::_Tuple);
auto Int2 = TypeExpr::new_tensor({Int, Int});
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(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);
auto impure_bin_op = TypeExpr::new_map(Int2, Unit);
auto impure_un_op = TypeExpr::new_map(Int, Unit);
auto fetch_int_op = TypeExpr::new_map(SliceInt, SliceInt);
auto prefetch_int_op = TypeExpr::new_map(SliceInt, Int);
auto store_int_op = TypeExpr::new_map(TypeExpr::new_tensor({Builder, Int, Int}), Builder);
auto store_int_method =
TypeExpr::new_map(TypeExpr::new_tensor({Builder, Int, Int}), TypeExpr::new_tensor({Builder, Unit}));
auto fetch_slice_op = TypeExpr::new_map(SliceInt, TypeExpr::new_tensor({Slice, Slice}));
auto prefetch_slice_op = TypeExpr::new_map(SliceInt, Slice);
//auto arith_null_op = TypeExpr::new_map(TypeExpr::new_unit(), Int);
auto throw_arg_op = TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({X, Int}), Unit));
auto cond_throw_arg_op = TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({X, Int, Int}), Unit));
// prevent unused vars warnings (there vars are created to acquire initial id of TypeExpr::value)
static_cast<void>(Z);
static_cast<void>(XY);
static_cast<void>(Cell);
define_builtin_func("_+_", arith_bin_op, compile_add);
define_builtin_func("_-_", arith_bin_op, compile_sub);
define_builtin_func("-_", arith_un_op, compile_unary_minus);
define_builtin_func("+_", arith_un_op, compile_unary_plus);
define_builtin_func("_*_", arith_bin_op, compile_mul);
define_builtin_func("_/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1));
define_builtin_func("_~/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0));
define_builtin_func("_^/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1));
define_builtin_func("_%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1));
define_builtin_func("_~%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 0));
define_builtin_func("_^%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 1));
define_builtin_func("_/%_", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2));
define_builtin_func("divmod", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2));
define_builtin_func("~divmod", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2));
define_builtin_func("moddiv", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2), {}, {1, 0});
define_builtin_func("~moddiv", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2), {}, {1, 0});
define_builtin_func("_<<_", arith_bin_op, compile_lshift);
define_builtin_func("_>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1));
define_builtin_func("_~>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0));
define_builtin_func("_^>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 1));
define_builtin_func("_&_", arith_bin_op, compile_bitwise_and);
define_builtin_func("_|_", arith_bin_op, compile_bitwise_or);
define_builtin_func("_^_", arith_bin_op, compile_bitwise_xor);
define_builtin_func("~_", arith_un_op, compile_bitwise_not);
define_builtin_func("^_+=_", arith_bin_op, compile_add);
define_builtin_func("^_-=_", arith_bin_op, compile_sub);
define_builtin_func("^_*=_", arith_bin_op, compile_mul);
define_builtin_func("^_/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1));
define_builtin_func("^_~/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0));
define_builtin_func("^_^/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1));
define_builtin_func("^_%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1));
define_builtin_func("^_~%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 0));
define_builtin_func("^_^%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 1));
define_builtin_func("^_<<=_", arith_bin_op, compile_lshift);
define_builtin_func("^_>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1));
define_builtin_func("^_~>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0));
define_builtin_func("^_^>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 1));
define_builtin_func("^_&=_", arith_bin_op, compile_bitwise_and);
define_builtin_func("^_|=_", arith_bin_op, compile_bitwise_or);
define_builtin_func("^_^=_", arith_bin_op, compile_bitwise_xor);
define_builtin_func("muldiv", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, -1));
define_builtin_func("muldivr", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 0));
define_builtin_func("muldivc", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 1));
define_builtin_func("muldivmod", TypeExpr::new_map(Int3, Int2), AsmOp::Custom("MULDIVMOD", 3, 2));
define_builtin_func("_==_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 2));
define_builtin_func("_!=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 5));
define_builtin_func("_<_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 4));
define_builtin_func("_>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 1));
define_builtin_func("_<=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 6));
define_builtin_func("_>=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 3));
define_builtin_func("_<=>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 7));
define_builtin_func("true", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("TRUE") */ std::bind(compile_bool_const, _1, _2, true));
define_builtin_func("false", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("FALSE") */ std::bind(compile_bool_const, _1, _2, false));
// define_builtin_func("null", Null, AsmOp::Const("PUSHNULL"));
define_builtin_func("nil", TypeExpr::new_map(TypeExpr::new_unit(), Tuple), AsmOp::Const("PUSHNULL"));
define_builtin_func("null?", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Int)), compile_is_null);
define_builtin_func("throw", impure_un_op, compile_throw, true);
define_builtin_func("throw_if", impure_bin_op, std::bind(compile_cond_throw, _1, _2, true), true);
define_builtin_func("throw_unless", impure_bin_op, std::bind(compile_cond_throw, _1, _2, false), true);
define_builtin_func("throw_arg", throw_arg_op, compile_throw_arg, true);
define_builtin_func("throw_arg_if", cond_throw_arg_op, std::bind(compile_cond_throw_arg, _1, _2, true), true);
define_builtin_func("throw_arg_unless", cond_throw_arg_op, std::bind(compile_cond_throw_arg, _1, _2, false), true);
define_builtin_func("load_int", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, true), {}, {1, 0});
define_builtin_func("load_uint", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, false), {}, {1, 0});
define_builtin_func("preload_int", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, true));
define_builtin_func("preload_uint", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, false));
define_builtin_func("store_int", store_int_op, std::bind(compile_store_int, _1, _2, true), {1, 0, 2});
define_builtin_func("store_uint", store_int_op, std::bind(compile_store_int, _1, _2, false), {1, 0, 2});
define_builtin_func("~store_int", store_int_method, std::bind(compile_store_int, _1, _2, true), {1, 0, 2});
define_builtin_func("~store_uint", store_int_method, std::bind(compile_store_int, _1, _2, false), {1, 0, 2});
define_builtin_func("load_bits", fetch_slice_op, std::bind(compile_fetch_slice, _1, _2, true), {}, {1, 0});
define_builtin_func("preload_bits", prefetch_slice_op, std::bind(compile_fetch_slice, _1, _2, false));
define_builtin_func("at", TypeExpr::new_forall({X}, TypeExpr::new_map(TupleInt, X)), compile_tuple_at);
define_builtin_func("touch", TypeExpr::new_forall({X}, TypeExpr::new_map(X, X)), AsmOp::Nop());
define_builtin_func("~touch", TypeExpr::new_forall({X}, TypeExpr::new_map(X, TypeExpr::new_tensor({X, Unit}))),
AsmOp::Nop());
define_builtin_func("~dump", TypeExpr::new_forall({X}, TypeExpr::new_map(X, TypeExpr::new_tensor({X, Unit}))),
AsmOp::Custom("s0 DUMP", 1, 1), true);
define_builtin_func("~strdump", TypeExpr::new_forall({X}, TypeExpr::new_map(X, TypeExpr::new_tensor({X, Unit}))),
AsmOp::Custom("STRDUMP", 1, 1), true);
}
} // namespace tolk