1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

initial commit

This commit is contained in:
initial commit 2019-09-07 14:03:22 +04:00 committed by vvaltman
commit c2da007f40
1610 changed files with 398047 additions and 0 deletions

492
crypto/func/abscode.cpp Normal file
View file

@ -0,0 +1,492 @@
/*
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/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "func.h"
namespace funC {
/*
*
* ABSTRACT CODE
*
*/
TmpVar::TmpVar(var_idx_t _idx, int _cls, TypeExpr* _type, SymDef* sym, const SrcLocation* loc)
: v_type(_type), idx(_idx), cls(_cls), coord(0) {
if (sym) {
name = sym->sym_idx;
sym->value->idx = _idx;
}
if (loc) {
where = std::make_unique<SrcLocation>(*loc);
}
if (!_type) {
v_type = TypeExpr::new_hole();
}
}
void TmpVar::set_location(const SrcLocation& loc) {
if (where) {
*where = loc;
} else {
where = std::make_unique<SrcLocation>(loc);
}
}
void TmpVar::dump(std::ostream& os) const {
show(os);
os << " : " << v_type << " (width ";
v_type->show_width(os);
os << ")";
if (coord > 0) {
os << " = _" << (coord >> 8) << '.' << (coord & 255);
} else if (coord < 0) {
int n = (~coord >> 8), k = (~coord & 0xff);
if (k) {
os << " = (_" << n << ".._" << (n + k - 1) << ")";
} else {
os << " = ()";
}
}
os << std::endl;
}
void TmpVar::show(std::ostream& os, int omit_idx) const {
if (cls & _Named) {
os << sym::symbols.get_name(name);
if (omit_idx && (omit_idx >= 2 || (cls & _UniqueName))) {
return;
}
}
os << '_' << idx;
}
std::ostream& operator<<(std::ostream& os, const TmpVar& var) {
var.show(os);
return os;
}
void VarDescr::show_value(std::ostream& os) const {
if (val & _Int) {
os << 'i';
}
if (val & _Const) {
os << 'c';
}
if (val & _Zero) {
os << '0';
}
if (val & _NonZero) {
os << '!';
}
if (val & _Pos) {
os << '>';
}
if (val & _Neg) {
os << '<';
}
if (val & _Bool) {
os << 'B';
}
if (val & _Bit) {
os << 'b';
}
if (val & _Even) {
os << 'E';
}
if (val & _Odd) {
os << 'O';
}
if (val & _Finite) {
os << 'f';
}
if (val & _Nan) {
os << 'N';
}
if (int_const.not_null()) {
os << '=' << int_const;
}
}
void VarDescr::show(std::ostream& os, const char* name) const {
if (flags & _Last) {
os << '*';
}
if (flags & _Unused) {
os << '?';
}
if (name) {
os << name;
}
os << '_' << idx;
show_value(os);
}
void VarDescr::set_const(long long value) {
return set_const(td::RefInt256{true, value});
}
void VarDescr::set_const(td::RefInt256 value) {
int_const = std::move(value);
if (!int_const->signed_fits_bits(257)) {
int_const.write().invalidate();
}
val = _Const | _Int;
int s = sgn(int_const);
if (s < -1) {
val |= _Nan | _NonZero;
} else if (s < 0) {
val |= _NonZero | _Neg | _Finite;
if (*int_const == -1) {
val |= _Bool;
}
} else if (s > 0) {
val |= _NonZero | _Pos | _Finite;
} else if (!s) {
if (*int_const == 1) {
val |= _Bit;
}
val |= _Zero | _Neg | _Pos | _Finite | _Bool | _Bit;
}
if (val & _Finite) {
val |= int_const->get_bit(0) ? _Odd : _Even;
}
}
void VarDescr::set_const_nan() {
set_const(td::RefInt256{true});
}
void VarDescr::operator|=(const VarDescr& y) {
val &= y.val;
if (is_int_const() && cmp(int_const, y.int_const) != 0) {
val &= ~_Const;
}
if (!(val & _Const)) {
int_const.clear();
}
}
void VarDescr::operator&=(const VarDescr& y) {
val |= y.val;
if (y.int_const.not_null() && int_const.is_null()) {
int_const = y.int_const;
}
}
void VarDescr::set_value(const VarDescr& y) {
val = y.val;
int_const = y.int_const;
}
void VarDescr::set_value(VarDescr&& y) {
val = y.val;
int_const = std::move(y.int_const);
}
void VarDescr::clear_value() {
val = 0;
int_const.clear();
}
void VarDescrList::show(std::ostream& os) const {
os << "[";
for (const auto& v : list) {
os << ' ' << v;
}
os << " ]\n";
}
void Op::flags_set_clear(int set, int clear) {
flags = (flags | set) & ~clear;
for (auto& op : block0) {
op.flags_set_clear(set, clear);
}
for (auto& op : block1) {
op.flags_set_clear(set, clear);
}
}
void Op::split_vars(const std::vector<TmpVar>& vars) {
split_var_list(left, vars);
split_var_list(right, vars);
for (auto& op : block0) {
op.split_vars(vars);
}
for (auto& op : block1) {
op.split_vars(vars);
}
}
void Op::split_var_list(std::vector<var_idx_t>& var_list, const std::vector<TmpVar>& vars) {
int new_size = 0, changes = 0;
for (var_idx_t v : var_list) {
int c = vars.at(v).coord;
if (c < 0) {
++changes;
new_size += (~c & 0xff);
} else {
++new_size;
}
}
if (!changes) {
return;
}
std::vector<var_idx_t> new_var_list;
new_var_list.reserve(new_size);
for (var_idx_t v : var_list) {
int c = vars.at(v).coord;
if (c < 0) {
int n = (~c >> 8), k = (~c & 0xff);
while (k-- > 0) {
new_var_list.push_back(n++);
}
} else {
new_var_list.push_back(v);
}
}
var_list = std::move(new_var_list);
}
void Op::show(std::ostream& os, const std::vector<TmpVar>& vars, std::string pfx, int mode) const {
if (mode & 2) {
os << pfx << " [";
for (const auto& v : var_info.list) {
os << ' ';
if (v.flags & VarDescr::_Last) {
os << '*';
}
if (v.flags & VarDescr::_Unused) {
os << '?';
}
os << vars[v.idx];
if (mode & 4) {
os << ':';
v.show_value(os);
}
}
os << " ]\n";
}
std::string dis = disabled() ? "<disabled> " : "";
if (noreturn()) {
dis += "<noret> ";
}
if (!is_pure()) {
dis += "<impure> ";
}
switch (cl) {
case _Undef:
os << pfx << dis << "???\n";
break;
case _Nop:
os << pfx << dis << "NOP\n";
break;
case _Call:
os << pfx << dis << "CALL: ";
show_var_list(os, left, vars);
os << " := " << (fun_ref ? fun_ref->name() : "(null)") << " ";
if ((mode & 4) && args.size() == right.size()) {
show_var_list(os, args, vars);
} else {
show_var_list(os, right, vars);
}
os << std::endl;
break;
case _CallInd:
os << pfx << dis << "CALLIND: ";
show_var_list(os, left, vars);
os << " := EXEC ";
show_var_list(os, right, vars);
os << std::endl;
break;
case _Let:
os << pfx << dis << "LET ";
show_var_list(os, left, vars);
os << " := ";
show_var_list(os, right, vars);
os << std::endl;
break;
case _IntConst:
os << pfx << dis << "CONST ";
show_var_list(os, left, vars);
os << " := " << int_const << std::endl;
break;
case _Import:
os << pfx << dis << "IMPORT ";
show_var_list(os, left, vars);
os << std::endl;
break;
case _Return:
os << pfx << dis << "RETURN ";
show_var_list(os, left, vars);
os << std::endl;
break;
case _GlobVar:
os << pfx << dis << "GLOBVAR ";
show_var_list(os, left, vars);
os << " := " << (fun_ref ? fun_ref->name() : "(null)") << std::endl;
break;
case _Repeat:
os << pfx << dis << "REPEAT ";
show_var_list(os, left, vars);
os << ' ';
show_block(os, block0.get(), vars, pfx, mode);
os << std::endl;
break;
case _If:
os << pfx << dis << "IF ";
show_var_list(os, left, vars);
os << ' ';
show_block(os, block0.get(), vars, pfx, mode);
os << " ELSE ";
show_block(os, block1.get(), vars, pfx, mode);
os << std::endl;
break;
case _While:
os << pfx << dis << "WHILE ";
show_var_list(os, left, vars);
os << ' ';
show_block(os, block0.get(), vars, pfx, mode);
os << " DO ";
show_block(os, block1.get(), vars, pfx, mode);
os << std::endl;
break;
case _Until:
os << pfx << dis << "UNTIL ";
show_var_list(os, left, vars);
os << ' ';
show_block(os, block0.get(), vars, pfx, mode);
os << std::endl;
break;
case _Again:
os << pfx << dis << "AGAIN ";
show_var_list(os, left, vars);
os << ' ';
show_block(os, block0.get(), vars, pfx, mode);
os << std::endl;
break;
default:
os << pfx << dis << "<???" << cl << "> ";
show_var_list(os, left, vars);
os << " -- ";
show_var_list(os, right, vars);
os << std::endl;
break;
}
}
void Op::show_var_list(std::ostream& os, const std::vector<var_idx_t>& idx_list,
const std::vector<TmpVar>& vars) const {
if (!idx_list.size()) {
os << "()";
} else if (idx_list.size() == 1) {
os << vars.at(idx_list[0]);
} else {
os << "(" << vars.at(idx_list[0]);
for (std::size_t i = 1; i < idx_list.size(); i++) {
os << "," << vars.at(idx_list[i]);
}
os << ")";
}
}
void Op::show_var_list(std::ostream& os, const std::vector<VarDescr>& list, const std::vector<TmpVar>& vars) const {
auto n = list.size();
if (!n) {
os << "()";
} else {
os << "( ";
for (std::size_t i = 0; i < list.size(); i++) {
if (i) {
os << ", ";
}
if (list[i].is_unused()) {
os << '?';
}
os << vars.at(list[i].idx) << ':';
list[i].show_value(os);
}
os << " )";
}
}
void Op::show_block(std::ostream& os, const Op* block, const std::vector<TmpVar>& vars, std::string pfx, int mode) {
os << "{" << std::endl;
std::string pfx2 = pfx + " ";
for (const Op& op : block) {
op.show(os, vars, pfx2, mode);
}
os << pfx << "}";
}
void CodeBlob::flags_set_clear(int set, int clear) {
for (auto& op : ops) {
op.flags_set_clear(set, clear);
}
}
std::ostream& operator<<(std::ostream& os, const CodeBlob& code) {
code.print(os);
return os;
}
// flags: +1 = show variable definition locations; +2 = show vars after each op; +4 = show var abstract value info after each op; +8 = show all variables at start
void CodeBlob::print(std::ostream& os, int flags) const {
os << "CODE BLOB: " << var_cnt << " variables, " << in_var_cnt << " input\n";
if ((flags & 8) != 0) {
for (const auto& var : vars) {
var.dump(os);
if (var.where && (flags & 1) != 0) {
var.where->show(os);
os << " defined here:\n";
var.where->show_context(os);
}
}
}
os << "------- BEGIN --------\n";
for (const auto& op : ops) {
op.show(os, vars, "", flags);
}
os << "-------- END ---------\n\n";
}
var_idx_t CodeBlob::create_var(int cls, TypeExpr* var_type, SymDef* sym, const SrcLocation* location) {
vars.emplace_back(var_cnt, cls, var_type, sym, location);
if (sym) {
sym->value->idx = var_cnt;
}
return var_cnt++;
}
bool CodeBlob::import_params(FormalArgList arg_list) {
if (var_cnt || in_var_cnt || op_cnt) {
return false;
}
std::vector<var_idx_t> list;
for (const auto& par : arg_list) {
TypeExpr* arg_type;
SymDef* arg_sym;
SrcLocation arg_loc;
std::tie(arg_type, arg_sym, arg_loc) = par;
list.push_back(create_var(arg_sym ? (TmpVar::_In | TmpVar::_Named) : TmpVar::_In, arg_type, arg_sym, &arg_loc));
}
emplace_back(loc, Op::_Import, list);
in_var_cnt = var_cnt;
return true;
}
} // namespace funC

846
crypto/func/analyzer.cpp Normal file
View file

@ -0,0 +1,846 @@
/*
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/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "func.h"
namespace funC {
/*
*
* ANALYZE AND PREPROCESS ABSTRACT CODE
*
*/
void CodeBlob::simplify_var_types() {
for (TmpVar& var : vars) {
TypeExpr::remove_indirect(var.v_type);
}
}
int CodeBlob::split_vars(bool strict) {
int n = var_cnt, changes = 0;
for (int j = 0; j < var_cnt; j++) {
TmpVar& var = vars[j];
if (strict && var.v_type->minw != var.v_type->maxw) {
throw src::ParseError{var.where.get(), "variable does not have fixed width, cannot manipulate it"};
}
std::vector<TypeExpr*> comp_types;
int k = var.v_type->extract_components(comp_types);
assert(k <= 254 && n <= 0x7fff00);
assert((unsigned)k == comp_types.size());
if (k != 1) {
var.coord = ~((n << 8) + k);
for (int i = 0; i < k; i++) {
auto v = create_var(vars[j].cls, comp_types[i], 0, vars[j].where.get());
assert(v == n + i);
assert(vars[v].idx == v);
vars[v].name = vars[j].name;
vars[v].coord = ((int)j << 8) + i + 1;
}
n += k;
++changes;
} else if (strict && var.v_type->minw != 1) {
throw src::ParseError{var.where.get(),
"cannot work with variable or variable component of width greater than one"};
}
}
if (!changes) {
return 0;
}
for (auto& op : ops) {
op.split_vars(vars);
}
return changes;
}
bool CodeBlob::compute_used_code_vars() {
VarDescrList empty_var_info;
return compute_used_code_vars(ops, empty_var_info, true);
}
bool CodeBlob::compute_used_code_vars(std::unique_ptr<Op>& ops_ptr, const VarDescrList& var_info, bool edit) const {
assert(ops_ptr);
if (!ops_ptr->next) {
assert(ops_ptr->cl == Op::_Nop);
return ops_ptr->set_var_info(var_info);
}
return compute_used_code_vars(ops_ptr->next, var_info, edit) | ops_ptr->compute_used_vars(*this, edit);
}
bool operator==(const VarDescrList& x, const VarDescrList& y) {
if (x.size() != y.size()) {
return false;
}
for (std::size_t i = 0; i < x.size(); i++) {
if (x.list[i].idx != y.list[i].idx || x.list[i].flags != y.list[i].flags) {
return false;
}
}
return true;
}
bool same_values(const VarDescr& x, const VarDescr& y) {
if (x.val != y.val || x.int_const.is_null() != y.int_const.is_null()) {
return false;
}
if (x.int_const.not_null() && cmp(x.int_const, y.int_const) != 0) {
return false;
}
return true;
}
bool same_values(const VarDescrList& x, const VarDescrList& y) {
if (x.size() != y.size()) {
return false;
}
for (std::size_t i = 0; i < x.size(); i++) {
if (x.list[i].idx != y.list[i].idx || !same_values(x.list[i], y.list[i])) {
return false;
}
}
return true;
}
bool Op::set_var_info(const VarDescrList& new_var_info) {
if (var_info == new_var_info) {
return false;
}
var_info = new_var_info;
return true;
}
bool Op::set_var_info(VarDescrList&& new_var_info) {
if (var_info == new_var_info) {
return false;
}
var_info = std::move(new_var_info);
return true;
}
bool Op::set_var_info_except(const VarDescrList& new_var_info, const std::vector<var_idx_t>& var_list) {
if (!var_list.size()) {
return set_var_info(new_var_info);
}
VarDescrList tmp_info{new_var_info};
tmp_info -= var_list;
return set_var_info(new_var_info);
}
bool Op::set_var_info_except(VarDescrList&& new_var_info, const std::vector<var_idx_t>& var_list) {
if (var_list.size()) {
new_var_info -= var_list;
}
return set_var_info(std::move(new_var_info));
}
std::vector<var_idx_t> sort_unique_vars(const std::vector<var_idx_t>& var_list) {
std::vector<var_idx_t> vars{var_list}, unique_vars;
std::sort(vars.begin(), vars.end());
vars.erase(std::unique(vars.begin(), vars.end()), vars.end());
return vars;
}
VarDescr* VarDescrList::operator[](var_idx_t idx) {
auto it = std::lower_bound(list.begin(), list.end(), idx);
return it != list.end() && it->idx == idx ? &*it : nullptr;
}
const VarDescr* VarDescrList::operator[](var_idx_t idx) const {
auto it = std::lower_bound(list.begin(), list.end(), idx);
return it != list.end() && it->idx == idx ? &*it : nullptr;
}
std::size_t VarDescrList::count(const std::vector<var_idx_t> idx_list) const {
std::size_t res = 0;
for (var_idx_t idx : idx_list) {
if (operator[](idx)) {
++res;
}
}
return res;
}
std::size_t VarDescrList::count_used(const std::vector<var_idx_t> idx_list) const {
std::size_t res = 0;
for (var_idx_t idx : idx_list) {
auto v = operator[](idx);
if (v && !v->is_unused()) {
++res;
}
}
return res;
}
VarDescrList& VarDescrList::operator-=(var_idx_t idx) {
auto it = std::lower_bound(list.begin(), list.end(), idx);
if (it != list.end() && it->idx == idx) {
list.erase(it);
}
return *this;
}
VarDescrList& VarDescrList::operator-=(const std::vector<var_idx_t>& idx_list) {
for (var_idx_t idx : idx_list) {
*this -= idx;
}
return *this;
}
VarDescrList& VarDescrList::add_var(var_idx_t idx, bool unused) {
auto it = std::lower_bound(list.begin(), list.end(), idx);
if (it == list.end() || it->idx != idx) {
list.emplace(it, idx, VarDescr::_Last | (unused ? VarDescr::_Unused : 0));
} else if (it->is_unused() && !unused) {
it->clear_unused();
}
return *this;
}
VarDescrList& VarDescrList::add_vars(const std::vector<var_idx_t>& idx_list, bool unused) {
for (var_idx_t idx : idx_list) {
add_var(idx, unused);
}
return *this;
}
VarDescr& VarDescrList::add(var_idx_t idx) {
auto it = std::lower_bound(list.begin(), list.end(), idx);
if (it == list.end() || it->idx != idx) {
it = list.emplace(it, idx);
}
return *it;
}
VarDescr& VarDescrList::add_newval(var_idx_t idx) {
auto it = std::lower_bound(list.begin(), list.end(), idx);
if (it == list.end() || it->idx != idx) {
return *list.emplace(it, idx);
} else {
it->clear_value();
return *it;
}
}
VarDescrList& VarDescrList::clear_last() {
for (auto& var : list) {
if (var.flags & VarDescr::_Last) {
var.flags &= ~VarDescr::_Last;
}
}
return *this;
}
VarDescrList VarDescrList::operator+(const VarDescrList& y) const {
VarDescrList res;
auto it1 = list.cbegin();
auto it2 = y.list.cbegin();
while (it1 != list.cend() && it2 != y.list.cend()) {
if (it1->idx < it2->idx) {
res.list.push_back(*it1++);
} else if (it1->idx > it2->idx) {
res.list.push_back(*it2++);
} else {
res.list.push_back(*it1++);
res.list.back() += *it2++;
}
}
while (it1 != list.cend()) {
res.list.push_back(*it1++);
}
while (it2 != y.list.cend()) {
res.list.push_back(*it2++);
}
return res;
}
VarDescrList& VarDescrList::operator+=(const VarDescrList& y) {
return *this = *this + y;
}
VarDescrList VarDescrList::operator|(const VarDescrList& y) const {
VarDescrList res;
auto it1 = list.cbegin();
auto it2 = y.list.cbegin();
while (it1 != list.cend() && it2 != y.list.cend()) {
if (it1->idx < it2->idx) {
it1++;
} else if (it1->idx > it2->idx) {
it2++;
} else {
res.list.push_back(*it1++);
res.list.back() |= *it2++;
}
}
return res;
}
VarDescrList& VarDescrList::operator|=(const VarDescrList& y) {
return *this = *this | y;
}
VarDescrList& VarDescrList::operator&=(const VarDescrList& values) {
for (const VarDescr& vd : values.list) {
VarDescr* item = operator[](vd.idx);
if (item) {
*item &= vd;
}
}
return *this;
}
VarDescrList& VarDescrList::import_values(const VarDescrList& values) {
for (const VarDescr& vd : values.list) {
VarDescr* item = operator[](vd.idx);
if (item) {
item->set_value(vd);
}
}
return *this;
}
bool Op::std_compute_used_vars(bool disabled) {
// left = OP right
// var_info := (var_info - left) + right
VarDescrList new_var_info{next->var_info};
new_var_info -= left;
new_var_info.clear_last();
if (args.size() == right.size() && !disabled) {
for (const VarDescr& arg : args) {
new_var_info.add_var(arg.idx, arg.is_unused());
}
} else {
new_var_info.add_vars(right, disabled);
}
return set_var_info(std::move(new_var_info));
}
bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
assert(next);
const VarDescrList& next_var_info = next->var_info;
if (cl == _Nop) {
return set_var_info_except(next_var_info, left);
}
switch (cl) {
case _IntConst:
case _GlobVar:
case _Call:
case _CallInd: {
// left = EXEC right;
if (!next_var_info.count_used(left) && is_pure()) {
// all variables in `left` are not needed
if (edit) {
disable();
}
return std_compute_used_vars(true);
}
return std_compute_used_vars();
}
case _Let: {
// left = right
std::size_t cnt = next_var_info.count_used(left);
assert(left.size() == right.size());
auto l_it = left.cbegin(), r_it = right.cbegin();
VarDescrList new_var_info{next_var_info};
new_var_info -= left;
new_var_info.clear_last();
std::vector<var_idx_t> new_left, new_right;
for (; l_it < left.cend(); ++l_it, ++r_it) {
if (std::find(l_it + 1, left.cend(), *l_it) == left.cend()) {
auto p = next_var_info[*l_it];
new_var_info.add_var(*r_it, !p || p->is_unused());
new_left.push_back(*l_it);
new_right.push_back(*r_it);
}
}
if (new_left.size() < left.size()) {
left = std::move(new_left);
right = std::move(new_right);
}
if (!cnt && edit) {
// all variables in `left` are not needed
disable();
}
return set_var_info(std::move(new_var_info));
}
case _Return: {
// return left
if (var_info.count(left) == left.size()) {
return false;
}
std::vector<var_idx_t> unique_vars = sort_unique_vars(left);
var_info.list.clear();
for (var_idx_t i : unique_vars) {
var_info.list.emplace_back(i, VarDescr::_Last);
}
return true;
}
case _Import: {
// import left
std::vector<var_idx_t> unique_vars = sort_unique_vars(left);
var_info.list.clear();
for (var_idx_t i : unique_vars) {
var_info.list.emplace_back(i, next_var_info[i] ? 0 : VarDescr::_Last);
}
return true;
}
case _If: {
// if (left) then block0 else block1
// VarDescrList nx_var_info = next_var_info;
// nx_var_info.clear_last();
code.compute_used_code_vars(block0, next_var_info, edit);
VarDescrList merge_info;
if (block1) {
code.compute_used_code_vars(block1, next_var_info, edit);
merge_info = block0->var_info + block1->var_info;
} else {
merge_info = block0->var_info + next_var_info;
}
merge_info.clear_last();
merge_info += left;
return set_var_info(std::move(merge_info));
}
case _While: {
// while (block0 || left) block1;
// ... { block0 left block1 } block0 left next
VarDescrList after_cond_first{next_var_info};
after_cond_first += left;
code.compute_used_code_vars(block0, after_cond_first, false);
VarDescrList new_var_info{block0->var_info};
bool changes = false;
do {
code.compute_used_code_vars(block1, block0->var_info, changes);
VarDescrList after_cond{block1->var_info};
after_cond += left;
code.compute_used_code_vars(block0, after_cond, changes);
std::size_t n = new_var_info.size();
new_var_info += block0->var_info;
new_var_info.clear_last();
if (changes) {
break;
}
changes = (new_var_info.size() == n);
} while (changes <= edit);
return set_var_info(std::move(new_var_info));
}
case _Until: {
// until (block0 || left);
// .. { block0 left } block0 left next
VarDescrList after_cond_first{next_var_info};
after_cond_first += left;
code.compute_used_code_vars(block0, after_cond_first, false);
VarDescrList new_var_info{block0->var_info};
bool changes = false;
do {
VarDescrList after_cond{new_var_info};
after_cond += next_var_info;
after_cond += left;
code.compute_used_code_vars(block0, after_cond, changes);
std::size_t n = new_var_info.size();
new_var_info += block0->var_info;
new_var_info.clear_last();
if (changes) {
break;
}
changes = (new_var_info.size() == n);
} while (changes <= edit);
return set_var_info(std::move(new_var_info) + next_var_info);
}
case _Repeat: {
// repeat (left) block0
// left { block0 } next
VarDescrList new_var_info{next_var_info};
bool changes = false;
do {
code.compute_used_code_vars(block0, new_var_info, changes);
std::size_t n = new_var_info.size();
new_var_info += block0->var_info;
new_var_info.clear_last();
if (changes) {
break;
}
changes = (new_var_info.size() == n);
} while (changes <= edit);
new_var_info += left;
return set_var_info(std::move(new_var_info));
}
case _Again: {
// for(;;) block0
// { block0 }
VarDescrList new_var_info;
bool changes = false;
do {
code.compute_used_code_vars(block0, new_var_info, changes);
std::size_t n = new_var_info.size();
new_var_info += block0->var_info;
new_var_info.clear_last();
if (changes) {
break;
}
changes = (new_var_info.size() == n);
} while (changes <= edit);
return set_var_info(std::move(new_var_info));
}
default:
std::cerr << "fatal: unknown operation <??" << cl << "> in compute_used_vars()\n";
throw src::ParseError{where, "unknown operation"};
}
}
bool prune_unreachable(std::unique_ptr<Op>& ops) {
if (!ops) {
return true;
}
Op& op = *ops;
if (op.cl == Op::_Nop) {
if (op.next) {
ops = std::move(op.next);
return prune_unreachable(ops);
}
return true;
}
bool reach;
switch (op.cl) {
case Op::_IntConst:
case Op::_GlobVar:
case Op::_Call:
case Op::_CallInd:
case Op::_Import:
reach = true;
break;
case Op::_Let: {
reach = true;
break;
}
case Op::_Return:
reach = false;
break;
case Op::_If: {
// if left then block0 else block1; ...
VarDescr* c_var = op.var_info[op.left[0]];
if (c_var && c_var->always_true()) {
op.block0->last().next = std::move(op.next);
ops = std::move(op.block0);
return prune_unreachable(ops);
} else if (c_var && c_var->always_false()) {
op.block1->last().next = std::move(op.next);
ops = std::move(op.block1);
return prune_unreachable(ops);
} else {
reach = prune_unreachable(op.block0) | prune_unreachable(op.block1);
}
break;
}
case Op::_While: {
// while (block0 || left) block1;
if (!prune_unreachable(op.block0)) {
// computation of block0 never returns
ops = std::move(op.block0);
return prune_unreachable(ops);
}
VarDescr* c_var = op.block0->last().var_info[op.left[0]];
if (c_var && c_var->always_false()) {
// block1 never executed
op.block0->last().next = std::move(op.next);
ops = std::move(op.block0);
return false;
} else if (c_var && c_var->always_true()) {
if (!prune_unreachable(op.block1)) {
// block1 never returns
op.block0->last().next = std::move(op.block1);
ops = std::move(op.block0);
return false;
}
// infinite loop
op.cl = Op::_Again;
op.block0->last().next = std::move(op.block1);
op.left.clear();
reach = false;
} else {
if (!prune_unreachable(op.block1)) {
// block1 never returns, while equivalent to block0 ; if left then block1 else next
op.cl = Op::_If;
std::unique_ptr<Op> new_op = std::move(op.block0);
op.block0 = std::move(op.block1);
op.block1 = std::make_unique<Op>(op.next->where, Op::_Nop);
new_op->last().next = std::move(ops);
ops = std::move(new_op);
}
reach = true; // block1 may be never executed
}
break;
}
case Op::_Repeat: {
// repeat (left) block0
VarDescr* c_var = op.var_info[op.left[0]];
if (c_var && c_var->always_nonpos()) {
// loop never executed
ops = std::move(op.next);
return prune_unreachable(ops);
}
if (c_var && c_var->always_pos()) {
if (!prune_unreachable(op.block0)) {
// block0 executed at least once, and it never returns
// replace code with block0
ops = std::move(op.block0);
return false;
}
} else {
prune_unreachable(op.block0);
}
reach = true;
break;
}
case Op::_Until:
case Op::_Again: {
// do block0 until left; ...
if (!prune_unreachable(op.block0)) {
// block0 never returns, replace loop by block0
ops = std::move(op.block0);
return false;
}
reach = true;
break;
}
default:
std::cerr << "fatal: unknown operation <??" << op.cl << ">\n";
throw src::ParseError{op.where, "unknown operation in prune_unreachable()"};
}
if (reach) {
return prune_unreachable(op.next);
} else {
while (op.next->next) {
op.next = std::move(op.next->next);
}
return false;
}
}
void CodeBlob::prune_unreachable_code() {
if (prune_unreachable(ops)) {
throw src::ParseError{loc, "control reaches end of function"};
}
}
void CodeBlob::fwd_analyze() {
VarDescrList values;
assert(ops && ops->cl == Op::_Import);
for (var_idx_t i : ops->left) {
values += i;
if (vars[i].v_type->is_int()) {
values[i]->val |= VarDescr::_Int;
}
}
ops->fwd_analyze(values);
}
void Op::prepare_args(VarDescrList values) {
if (args.size() != right.size()) {
args.clear();
for (var_idx_t i : right) {
args.emplace_back(i);
}
}
for (std::size_t i = 0; i < right.size(); i++) {
const VarDescr* val = values[right[i]];
if (val) {
args[i].set_value(*val);
args[i].clear_unused();
}
}
}
VarDescrList Op::fwd_analyze(VarDescrList values) {
var_info.import_values(values);
switch (cl) {
case _Nop:
case _Import:
break;
case _Return:
values.list.clear();
break;
case _IntConst: {
values.add_newval(left[0]).set_const(int_const);
break;
}
case _GlobVar:
case _Call: {
prepare_args(values);
auto func = dynamic_cast<const SymValAsmFunc*>(fun_ref->value);
if (func) {
std::vector<VarDescr> res;
res.reserve(left.size());
for (var_idx_t i : left) {
res.emplace_back(i);
}
AsmOpList tmp;
func->compile(tmp, res, args); // abstract interpretation of res := f (args)
int j = 0;
for (var_idx_t i : left) {
values.add_newval(i).set_value(res[j++]);
}
} else {
for (var_idx_t i : left) {
values.add_newval(i);
}
}
break;
}
case _CallInd: {
for (var_idx_t i : left) {
values.add_newval(i);
}
break;
}
case _Let: {
std::vector<VarDescr> old_val;
assert(left.size() == right.size());
for (std::size_t i = 0; i < right.size(); i++) {
const VarDescr* ov = values[right[i]];
if (!ov && verbosity >= 5) {
std::cerr << "FATAL: error in assignment at right component #" << i << " (no value for _" << right[i] << ")"
<< std::endl;
for (auto x : left) {
std::cerr << '_' << x << " ";
}
std::cerr << "= ";
for (auto x : right) {
std::cerr << '_' << x << " ";
}
std::cerr << std::endl;
}
// assert(ov);
if (ov) {
old_val.push_back(*ov);
} else {
old_val.emplace_back();
}
}
for (std::size_t i = 0; i < left.size(); i++) {
values.add_newval(left[i]).set_value(std::move(old_val[i]));
}
break;
}
case _If: {
VarDescrList val1 = block0->fwd_analyze(values);
VarDescrList val2 = block1 ? block1->fwd_analyze(std::move(values)) : std::move(values);
values = val1 | val2;
break;
}
case _Repeat: {
bool atl1 = (values[left[0]] && values[left[0]]->always_pos());
VarDescrList next_values = block0->fwd_analyze(values);
while (true) {
VarDescrList new_values = values | next_values;
if (same_values(new_values, values)) {
break;
}
values = std::move(new_values);
next_values = block0->fwd_analyze(values);
}
if (atl1) {
values = std::move(next_values);
}
break;
}
case _While: {
values = block0->fwd_analyze(values);
if (values[left[0]] && values[left[0]]->always_false()) {
// block1 never executed
block1->fwd_analyze(values);
break;
}
while (true) {
VarDescrList next_values = values | block0->fwd_analyze(block1->fwd_analyze(values));
if (same_values(next_values, values)) {
break;
}
values = std::move(next_values);
}
break;
}
case _Until:
case _Again: {
while (true) {
VarDescrList next_values = values | block0->fwd_analyze(values);
if (same_values(next_values, values)) {
break;
}
values = std::move(next_values);
}
values = block0->fwd_analyze(values);
break;
}
default:
std::cerr << "fatal: unknown operation <??" << cl << ">\n";
throw src::ParseError{where, "unknown operation in fwd_analyze()"};
}
if (next) {
return next->fwd_analyze(std::move(values));
} else {
return values;
}
}
bool Op::set_noreturn(bool nr) {
if (nr) {
flags |= _NoReturn;
} else {
flags &= ~_NoReturn;
}
return nr;
}
bool Op::mark_noreturn() {
switch (cl) {
case _Nop:
if (!next) {
return set_noreturn(false);
}
// fallthrough
case _Import:
case _IntConst:
case _Let:
case _GlobVar:
case _CallInd:
case _Call:
return set_noreturn(next->mark_noreturn());
case _Return:
return set_noreturn(true);
case _If:
return set_noreturn((block0->mark_noreturn() & (block1 && block1->mark_noreturn())) | next->mark_noreturn());
case _Again:
block0->mark_noreturn();
return set_noreturn(false);
case _Until:
return set_noreturn(block0->mark_noreturn() | next->mark_noreturn());
case _While:
block1->mark_noreturn();
return set_noreturn(block0->mark_noreturn() | next->mark_noreturn());
case _Repeat:
block0->mark_noreturn();
return set_noreturn(next->mark_noreturn());
default:
std::cerr << "fatal: unknown operation <??" << cl << ">\n";
throw src::ParseError{where, "unknown operation in mark_noreturn()"};
}
}
void CodeBlob::mark_noreturn() {
ops->mark_noreturn();
}
} // namespace funC

329
crypto/func/asmops.cpp Normal file
View file

@ -0,0 +1,329 @@
/*
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/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "parser/srcread.h"
#include "func.h"
#include <iostream>
namespace funC {
/*
*
* ASM-OP LIST FUNCTIONS
*
*/
int is_pos_pow2(td::RefInt256 x) {
if (sgn(x) > 0 && !sgn(x & (x - 1))) {
return x->bit_size(false) - 1;
} else {
return -1;
}
}
int is_neg_pow2(td::RefInt256 x) {
return sgn(x) < 0 ? is_pos_pow2(-x) : 0;
}
std::ostream& operator<<(std::ostream& os, AsmOp::SReg stack_reg) {
int i = stack_reg.idx;
if (i >= 0) {
if (i < 16) {
return os << 's' << i;
} else {
return os << i << " s()";
}
} else if (i >= -2) {
return os << "s(" << i << ')';
} else {
return os << i << " s()";
}
}
AsmOp AsmOp::Const(int arg, std::string push_op) {
std::ostringstream os;
os << arg << ' ' << push_op;
return AsmOp::Const(os.str());
}
AsmOp AsmOp::make_stk2(int a, int b, const char* str, int delta) {
std::ostringstream os;
os << SReg(a) << ' ' << SReg(b) << ' ' << str;
int c = std::max(a, b) + 1;
return AsmOp::Custom(os.str(), c, c + delta);
}
AsmOp AsmOp::make_stk3(int a, int b, int c, const char* str, int delta) {
std::ostringstream os;
os << SReg(a) << ' ' << SReg(b) << ' ' << SReg(c) << ' ' << str;
int m = std::max(a, std::max(b, c)) + 1;
return AsmOp::Custom(os.str(), m, m + delta);
}
AsmOp AsmOp::BlkSwap(int a, int b) {
std::ostringstream os;
if (a == 1 && b == 1) {
return AsmOp::Xchg(0, 1);
} else if (a == 1) {
if (b == 2) {
os << "ROT";
} else {
os << b << " ROLL";
}
} else if (b == 1) {
if (a == 2) {
os << "-ROT";
} else {
os << a << " -ROLL";
}
} else {
os << a << " " << b << " BLKSWAP";
}
return AsmOp::Custom(os.str(), a + b, a + b);
}
AsmOp AsmOp::BlkPush(int a, int b) {
std::ostringstream os;
if (a == 1) {
return AsmOp::Push(b);
} else if (a == 2 && b == 1) {
os << "2DUP";
} else {
os << a << " " << b << " BLKPUSH";
}
return AsmOp::Custom(os.str(), b + 1, a + b + 1);
}
AsmOp AsmOp::BlkDrop(int a) {
std::ostringstream os;
if (a == 1) {
return AsmOp::Pop();
} else if (a == 2) {
os << "2DROP";
} else {
os << a << " BLKDROP";
}
return AsmOp::Custom(os.str(), a, 0);
}
AsmOp AsmOp::BlkReverse(int a, int b) {
std::ostringstream os;
os << a << " " << b << " REVERSE";
return AsmOp::Custom(os.str(), a + b, a + b);
}
AsmOp AsmOp::IntConst(td::RefInt256 x) {
if (x->signed_fits_bits(8)) {
return AsmOp::Const(dec_string(std::move(x)) + " PUSHINT");
}
if (!x->is_valid()) {
return AsmOp::Const("PUSHNAN");
}
int k = is_pos_pow2(x);
if (k >= 0) {
return AsmOp::Const(k, "PUSHPOW2");
}
k = is_pos_pow2(x + 1);
if (k >= 0) {
return AsmOp::Const(k, "PUSHPOW2DEC");
}
k = is_pos_pow2(-x);
if (k >= 0) {
return AsmOp::Const(k, "PUSHNEGPOW2");
}
return AsmOp::Const(dec_string(std::move(x)) + " PUSHINT");
}
AsmOp AsmOp::Parse(std::string custom_op) {
if (custom_op == "NOP") {
return AsmOp::Nop();
} else if (custom_op == "SWAP") {
return AsmOp::Xchg(1);
} else if (custom_op == "DROP") {
return AsmOp::Pop(0);
} else if (custom_op == "NIP") {
return AsmOp::Pop(1);
} else if (custom_op == "DUP") {
return AsmOp::Push(0);
} else if (custom_op == "OVER") {
return AsmOp::Push(1);
} else {
return AsmOp::Custom(custom_op);
}
}
AsmOp AsmOp::Parse(std::string custom_op, int args, int retv) {
auto res = Parse(custom_op);
if (res.is_custom()) {
res.a = args;
res.b = retv;
}
return res;
}
void AsmOp::out(std::ostream& os) const {
if (!op.empty()) {
os << op;
return;
}
switch (t) {
case a_none:
break;
case a_xchg:
if (!a && !(b & -2)) {
os << (b ? "SWAP" : "NOP");
break;
}
os << "s" << a << " s" << b << " XCHG";
break;
case a_push:
if (!(a & -2)) {
os << (a ? "OVER" : "DUP");
break;
}
os << "s" << a << " PUSH";
break;
case a_pop:
if (!(a & -2)) {
os << (a ? "NIP" : "DROP");
break;
}
os << "s" << a << " POP";
break;
default:
throw src::Fatal{"unknown assembler operation"};
}
}
void AsmOp::out_indent_nl(std::ostream& os, bool no_eol) const {
for (int i = 0; i < indent; i++) {
os << " ";
}
out(os);
if (!no_eol) {
os << std::endl;
}
}
std::string AsmOp::to_string() const {
if (!op.empty()) {
return op;
} else {
std::ostringstream os;
out(os);
return os.str();
}
}
bool AsmOpList::append(const std::vector<AsmOp>& ops) {
for (const auto& op : ops) {
if (!append(op)) {
return false;
}
}
return true;
}
const_idx_t AsmOpList::register_const(Const new_const) {
if (new_const.is_null()) {
return not_const;
}
unsigned idx;
for (idx = 0; idx < constants_.size(); idx++) {
if (!td::cmp(new_const, constants_[idx])) {
return idx;
}
}
constants_.push_back(std::move(new_const));
return (const_idx_t)idx;
}
Const AsmOpList::get_const(const_idx_t idx) {
if ((unsigned)idx < constants_.size()) {
return constants_[idx];
} else {
return {};
}
}
void AsmOpList::show_var(std::ostream& os, var_idx_t idx) const {
if (!var_names_ || (unsigned)idx >= var_names_->size()) {
os << '_' << idx;
} else {
var_names_->at(idx).show(os, 2);
}
}
void AsmOpList::show_var_ext(std::ostream& os, std::pair<var_idx_t, const_idx_t> idx_pair) const {
auto i = idx_pair.first;
auto j = idx_pair.second;
if (!var_names_ || (unsigned)i >= var_names_->size()) {
os << '_' << i;
} else {
var_names_->at(i).show(os, 2);
}
if ((unsigned)j < constants_.size() && constants_[j].not_null()) {
os << '=' << constants_[j];
}
}
void AsmOpList::out(std::ostream& os, int mode) const {
if (!(mode & 2)) {
for (const auto& op : list_) {
op.out_indent_nl(os);
}
} else {
std::size_t n = list_.size();
for (std::size_t i = 0; i < n; i++) {
const auto& op = list_[i];
if (!op.is_comment() && i + 1 < n && list_[i + 1].is_comment()) {
op.out_indent_nl(os, true);
os << '\t';
do {
i++;
} while (i + 1 < n && list_[i + 1].is_comment());
list_[i].out(os);
os << std::endl;
} else {
op.out_indent_nl(os, false);
}
}
}
}
bool apply_op(StackTransform& trans, const AsmOp& op) {
if (!trans.is_valid()) {
return false;
}
switch (op.t) {
case AsmOp::a_none:
return true;
case AsmOp::a_xchg:
return trans.apply_xchg(op.a, op.b, true);
case AsmOp::a_push:
return trans.apply_push(op.a);
case AsmOp::a_pop:
return trans.apply_pop(op.a);
case AsmOp::a_const:
return !op.a && op.b == 1 && trans.apply_push_newconst();
default:
return false;
}
}
} // namespace funC

974
crypto/func/builtins.cpp Normal file
View file

@ -0,0 +1,974 @@
/*
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/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "func.h"
namespace funC {
using namespace std::literals::string_literals;
/*
*
* SYMBOL VALUES
*
*/
int glob_func_cnt, undef_func_cnt;
std::vector<SymDef*> glob_func;
SymDef* predefine_builtin_func(std::string name, TypeExpr* func_type) {
sym_idx_t name_idx = sym::symbols.lookup(name, 1);
if (sym::symbols.is_keyword(name_idx)) {
std::cerr << "fatal: global function `" << name << "` already defined as a keyword" << std::endl;
}
SymDef* def = sym::define_global_symbol(name_idx, true);
if (!def) {
std::cerr << "fatal: global function `" << name << "` already defined" << std::endl;
std::exit(1);
}
return def;
}
template <typename T>
void 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};
}
template <typename T>
void define_builtin_func_x(std::string name, TypeExpr* func_type, const T& func, std::initializer_list<int> arg_order,
std::initializer_list<int> 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};
}
void define_builtin_func_x(std::string name, TypeExpr* func_type, const AsmOp& macro,
std::initializer_list<int> arg_order, std::initializer_list<int> 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};
}
bool SymValAsmFunc::compile(AsmOpList& dest, std::vector<VarDescr>& in, std::vector<VarDescr>& out) const {
if (simple_compile) {
return dest.append(simple_compile(in, out));
} else if (ext_compile) {
return ext_compile(dest, in, out);
} 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_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 | VarDescr::_Even | 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(b);
}
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 | VarDescr::_Even | 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 push_const(td::RefInt256 x) {
return AsmOp::IntConst(std::move(x));
}
AsmOp compile_add(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
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_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) {
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_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_negate(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
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_negate(x.val);
return exec_op("NEGATE", 1);
}
AsmOp compile_mul(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
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_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);
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 (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);
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);
}
}
return exec_op("MUL", 2);
}
AsmOp compile_lshift(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
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) {
r.set_const_nan();
x.unused();
y.unused();
return push_const(r.int_const);
} else if (x.is_int_const()) {
r.set_const(x.int_const << (int)yv);
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("NEGPOW2", 1);
}
}
return exec_op("LSHIFT", 2);
}
AsmOp compile_rshift(std::vector<VarDescr>& res, std::vector<VarDescr>& args, int round_mode) {
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) {
r.set_const_nan();
x.unused();
y.unused();
return push_const(r.int_const);
} 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(std::vector<VarDescr>& res, std::vector<VarDescr>& args, int round_mode) {
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(div(x.int_const, y.int_const, round_mode));
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) {
x.unused();
y.unused();
r.set_const(div(y.int_const, y.int_const));
return push_const(r.int_const);
}
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_mod(std::vector<VarDescr>& res, std::vector<VarDescr>& args, int round_mode) {
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));
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) {
x.unused();
y.unused();
r.set_const(mod(y.int_const, y.int_const));
return push_const(r.int_const);
}
if ((*y.int_const == 1 || *y.int_const == -1) && x.always_finite()) {
x.unused();
y.unused();
r.set_const(td::RefInt256{true, 0});
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);
}
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_geq(y) ? 1 : (x.always_less(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) {
assert(mode >= 1 && mode <= 7);
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(compute_compare(x.int_const, y.int_const, mode));
x.unused();
y.unused();
return push_const(r.int_const);
}
int v = compute_compare(x, y, mode);
assert(v);
if (!(v & (v - 1))) {
r.set_const(v - (v >> 2) - 2);
x.unused();
y.unused();
return push_const(r.int_const);
}
r.val = ~0;
if (v & 1) {
r.val &= VarDescr::ConstTrue;
}
if (v & 2) {
r.val &= VarDescr::ConstZero;
}
if (v & 4) {
r.val &= VarDescr::ConstOne;
}
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) {
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) {
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_arg_op("THROWANY"s + suff, 2, 0);
}
}
AsmOp compile_bool_const(std::vector<VarDescr>& res, std::vector<VarDescr>& args, bool val) {
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) {
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) {
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) {
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) {
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) {
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);
}
bool compile_run_method(AsmOpList& code, std::vector<VarDescr>& res, std::vector<VarDescr>& args, int n,
bool has_value) {
assert(args.size() == (unsigned)n + 1 && res.size() == (unsigned)has_value);
auto& x = args[0];
if (x.is_int_const() && x.int_const->unsigned_fits_bits(14)) {
x.unused();
code << exec_arg_op("PREPAREDICT", x.int_const, 0, 2);
} else {
code << exec_op("c3 PUSH", 0, 1);
}
code << exec_arg_op(has_value ? "1 CALLXARGS" : "0 CALLXARGS", n, n + 2, (unsigned)has_value);
return true;
}
void define_builtins() {
using namespace std::placeholders;
auto Unit = TypeExpr::new_unit();
auto Int = TypeExpr::new_atomic(_Int);
auto Cell = TypeExpr::new_atomic(_Cell);
auto Slice = TypeExpr::new_atomic(_Slice);
auto Builder = TypeExpr::new_atomic(_Builder);
// auto Null = TypeExpr::new_atomic(_Null);
auto Tuple = TypeExpr::new_atomic(_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();
auto Y = TypeExpr::new_var();
auto Z = TypeExpr::new_var();
auto T = TypeExpr::new_var();
auto XY = TypeExpr::new_tensor({X, Y});
auto XYZ = TypeExpr::new_tensor({X, Y, Z});
auto XYZT = TypeExpr::new_tensor({X, Y, Z, T});
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);
define_builtin_func("_+_", arith_bin_op, compile_add);
define_builtin_func("_-_", arith_bin_op, compile_sub);
define_builtin_func("-_", arith_un_op, compile_negate);
define_builtin_func("_*_", arith_bin_op, compile_mul);
define_builtin_func("_/_", arith_bin_op, std::bind(compile_div, _1, _2, -1));
define_builtin_func("_/~_", arith_bin_op, std::bind(compile_div, _1, _2, 0));
define_builtin_func("_/^_", arith_bin_op, std::bind(compile_div, _1, _2, 1));
define_builtin_func("_%_", arith_bin_op, std::bind(compile_mod, _1, _2, -1));
define_builtin_func("_%~_", arith_bin_op, std::bind(compile_mod, _1, _2, 0));
define_builtin_func("_%^_", arith_bin_op, std::bind(compile_mod, _1, _2, -1));
define_builtin_func("_/%_", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2));
define_builtin_func("_<<_", arith_bin_op, compile_lshift);
define_builtin_func("_>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, -1));
define_builtin_func("_>>~_", arith_bin_op, std::bind(compile_rshift, _1, _2, 0));
define_builtin_func("_>>^_", arith_bin_op, std::bind(compile_rshift, _1, _2, 1));
define_builtin_func("_&_", arith_bin_op, AsmOp::Custom("AND", 2));
define_builtin_func("_|_", arith_bin_op, AsmOp::Custom("OR", 2));
define_builtin_func("_^_", arith_bin_op, AsmOp::Custom("XOR", 2));
define_builtin_func("~_", arith_un_op, AsmOp::Custom("NOT", 1));
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, -1));
define_builtin_func("^_/~=_", arith_bin_op, std::bind(compile_div, _1, _2, 0));
define_builtin_func("^_/^=_", arith_bin_op, std::bind(compile_div, _1, _2, 1));
define_builtin_func("^_%=_", arith_bin_op, std::bind(compile_mod, _1, _2, -1));
define_builtin_func("^_%~=_", arith_bin_op, std::bind(compile_mod, _1, _2, 0));
define_builtin_func("^_%^=_", arith_bin_op, std::bind(compile_mod, _1, _2, 1));
define_builtin_func("^_<<=_", arith_bin_op, compile_lshift);
define_builtin_func("^_>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, -1));
define_builtin_func("^_>>~=_", arith_bin_op, std::bind(compile_rshift, _1, _2, 0));
define_builtin_func("^_>>^=_", arith_bin_op, std::bind(compile_rshift, _1, _2, 1));
define_builtin_func("^_&=_", arith_bin_op, AsmOp::Custom("AND", 2));
define_builtin_func("^_|=_", arith_bin_op, AsmOp::Custom("OR", 2));
define_builtin_func("^_^=_", arith_bin_op, AsmOp::Custom("XOR", 2));
define_builtin_func("muldivr", TypeExpr::new_map(Int3, Int), AsmOp::Custom("MULDIVR", 3));
define_builtin_func("muldiv", TypeExpr::new_map(Int3, Int), AsmOp::Custom("MULDIV", 3));
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", Int, /* AsmOp::Const("TRUE") */ std::bind(compile_bool_const, _1, _2, true));
define_builtin_func("false", Int, /* AsmOp::Const("FALSE") */ std::bind(compile_bool_const, _1, _2, false));
// define_builtin_func("null", Null, AsmOp::Const("PUSHNULL"));
define_builtin_func("nil", Tuple, AsmOp::Const("PUSHNULL"));
define_builtin_func("null?", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Int)), compile_is_null);
define_builtin_func("cons", TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor(X, Tuple), Tuple)),
AsmOp::Custom("CONS", 2, 1));
define_builtin_func("uncons", TypeExpr::new_forall({X}, TypeExpr::new_map(Tuple, TypeExpr::new_tensor(X, Tuple))),
AsmOp::Custom("UNCONS", 1, 2));
define_builtin_func_x("list_next",
TypeExpr::new_forall({X}, TypeExpr::new_map(Tuple, TypeExpr::new_tensor(Tuple, X))),
AsmOp::Custom("UNCONS", 1, 2), {}, {1, 0});
define_builtin_func("car", TypeExpr::new_forall({X}, TypeExpr::new_map(Tuple, X)), AsmOp::Custom("CAR", 1, 1));
define_builtin_func("cdr", TypeExpr::new_map(Tuple, Tuple), AsmOp::Custom("CDR", 1, 1));
define_builtin_func("pair", TypeExpr::new_forall({X, Y}, TypeExpr::new_map(XY, Tuple)), AsmOp::Custom("PAIR", 2, 1));
define_builtin_func("unpair", TypeExpr::new_forall({X, Y}, TypeExpr::new_map(Tuple, XY)),
AsmOp::Custom("UNPAIR", 1, 2));
define_builtin_func("triple", TypeExpr::new_forall({X, Y, Z}, TypeExpr::new_map(XYZ, Tuple)),
AsmOp::Custom("TRIPLE", 3, 1));
define_builtin_func("untriple", TypeExpr::new_forall({X, Y, Z}, TypeExpr::new_map(Tuple, XYZ)),
AsmOp::Custom("UNTRIPLE", 1, 3));
define_builtin_func("tuple4", TypeExpr::new_forall({X, Y, Z, T}, TypeExpr::new_map(XYZT, Tuple)),
AsmOp::Custom("4 TUPLE", 4, 1));
define_builtin_func("untuple4", TypeExpr::new_forall({X, Y, Z, T}, TypeExpr::new_map(Tuple, XYZT)),
AsmOp::Custom("4 UNTUPLE", 1, 4));
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_x("load_int", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, true), {}, {1, 0});
define_builtin_func_x("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_x("store_int", store_int_op, std::bind(compile_store_int, _1, _2, true), {1, 0, 2});
define_builtin_func_x("store_uint", store_int_op, std::bind(compile_store_int, _1, _2, false), {1, 0, 2});
define_builtin_func_x("~store_int", store_int_method, std::bind(compile_store_int, _1, _2, true), {1, 0, 2});
define_builtin_func_x("~store_uint", store_int_method, std::bind(compile_store_int, _1, _2, false), {1, 0, 2});
define_builtin_func_x("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("int_at", TypeExpr::new_map(TupleInt, Int), compile_tuple_at);
define_builtin_func("cell_at", TypeExpr::new_map(TupleInt, Cell), compile_tuple_at);
define_builtin_func("slice_at", TypeExpr::new_map(TupleInt, Cell), compile_tuple_at);
define_builtin_func("tuple_at", TypeExpr::new_map(TupleInt, Tuple), compile_tuple_at);
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("touch2", TypeExpr::new_forall({X, Y}, TypeExpr::new_map(XY, XY)), AsmOp::Nop());
define_builtin_func("~touch2", TypeExpr::new_forall({X, Y}, TypeExpr::new_map(XY, TypeExpr::new_tensor({XY, 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));
define_builtin_func(
"run_method0", TypeExpr::new_map(Int, Unit),
[](auto a, auto b, auto c) { return compile_run_method(a, b, c, 0, false); }, true);
define_builtin_func_x(
"run_method1", TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({Int, X}), Unit)),
[](auto a, auto b, auto c) { return compile_run_method(a, b, c, 1, false); }, {1, 0}, {}, true);
define_builtin_func_x(
"run_method2", TypeExpr::new_forall({X, Y}, TypeExpr::new_map(TypeExpr::new_tensor({Int, X, Y}), Unit)),
[](auto a, auto b, auto c) { return compile_run_method(a, b, c, 2, false); }, {1, 2, 0}, {}, true);
define_builtin_func_x(
"run_method3", TypeExpr::new_forall({X, Y, Z}, TypeExpr::new_map(TypeExpr::new_tensor({Int, X, Y, Z}), Unit)),
[](auto a, auto b, auto c) { return compile_run_method(a, b, c, 3, false); }, {1, 2, 3, 0}, {}, true);
}
} // namespace funC

689
crypto/func/codegen.cpp Normal file
View file

@ -0,0 +1,689 @@
/*
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/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "func.h"
namespace funC {
/*
*
* GENERATE TVM STACK CODE
*
*/
StackLayout Stack::vars() const {
StackLayout res;
res.reserve(s.size());
for (auto x : s) {
res.push_back(x.first);
}
return res;
}
int Stack::find(var_idx_t var, int from) const {
for (int i = from; i < depth(); i++) {
if (at(i).first == var) {
return i;
}
}
return -1;
}
// finds var in [from .. to)
int Stack::find(var_idx_t var, int from, int to) const {
for (int i = from; i < depth() && i < to; i++) {
if (at(i).first == var) {
return i;
}
}
return -1;
}
// finds var outside [from .. to)
int Stack::find_outside(var_idx_t var, int from, int to) const {
from = std::max(from, 0);
if (from >= to) {
return find(var);
} else {
int t = find(var, 0, from);
return t >= 0 ? t : find(var, to);
}
}
int Stack::find_const(const_idx_t cst, int from) const {
for (int i = from; i < depth(); i++) {
if (at(i).second == cst) {
return i;
}
}
return -1;
}
void Stack::forget_const() {
for (auto& vc : s) {
if (vc.second != not_const) {
vc.second = not_const;
}
}
}
void Stack::issue_pop(int i) {
validate(i);
o << AsmOp::Pop(i);
at(i) = get(0);
s.pop_back();
modified();
}
void Stack::issue_push(int i) {
validate(i);
o << AsmOp::Push(i);
s.push_back(get(i));
modified();
}
void Stack::issue_xchg(int i, int j) {
validate(i);
validate(j);
if (i != j && get(i) != get(j)) {
o << AsmOp::Xchg(i, j);
std::swap(at(i), at(j));
modified();
}
}
int Stack::drop_vars_except(const VarDescrList& var_info, int excl_var) {
int dropped = 0, changes;
do {
changes = 0;
int n = depth();
for (int i = 0; i < n; i++) {
var_idx_t idx = at(i).first;
if (((!var_info[idx] || var_info[idx]->is_unused()) && idx != excl_var) || find(idx, 0, i - 1) >= 0) {
// unneeded
issue_pop(i);
changes = 1;
break;
}
}
dropped += changes;
} while (changes);
return dropped;
}
void Stack::show(int flags) {
std::ostringstream os;
for (auto i : s) {
os << ' ';
o.show_var_ext(os, i);
}
o << AsmOp::Comment(os.str());
mode |= _Shown;
}
void Stack::forget_var(var_idx_t idx) {
for (auto& x : s) {
if (x.first == idx) {
x = std::make_pair(_Garbage, not_const);
modified();
}
}
}
void Stack::push_new_var(var_idx_t idx) {
forget_var(idx);
s.emplace_back(idx, not_const);
modified();
}
void Stack::push_new_const(var_idx_t idx, const_idx_t cidx) {
forget_var(idx);
s.emplace_back(idx, cidx);
modified();
}
void Stack::assign_var(var_idx_t new_idx, var_idx_t old_idx) {
int i = find(old_idx);
assert(i >= 0 && "variable not found in stack");
if (new_idx != old_idx) {
at(i).first = new_idx;
modified();
}
}
void Stack::do_copy_var(var_idx_t new_idx, var_idx_t old_idx) {
int i = find(old_idx);
assert(i >= 0 && "variable not found in stack");
if (find(old_idx, i + 1) < 0) {
issue_push(i);
assert(at(0).first == old_idx);
}
assign_var(new_idx, old_idx);
}
void Stack::enforce_state(const StackLayout& req_stack) {
int k = (int)req_stack.size();
for (int i = 0; i < k; i++) {
var_idx_t x = req_stack[i];
if (i < depth() && s[i].first == x) {
continue;
}
int j = find(x);
if (j >= depth() - i) {
issue_push(j);
j = 0;
}
issue_xchg(j, depth() - i - 1);
assert(s[i].first == x);
}
while (depth() > k) {
issue_pop(0);
}
assert(depth() == k);
for (int i = 0; i < k; i++) {
assert(s[i].first == req_stack[i]);
}
}
void Stack::merge_const(const Stack& req_stack) {
assert(s.size() == req_stack.s.size());
for (std::size_t i = 0; i < s.size(); i++) {
assert(s[i].first == req_stack.s[i].first);
if (s[i].second != req_stack.s[i].second) {
s[i].second = not_const;
}
}
}
void Stack::merge_state(const Stack& req_stack) {
enforce_state(req_stack.vars());
merge_const(req_stack);
}
void Stack::rearrange_top(const StackLayout& top, std::vector<bool> last) {
while (last.size() < top.size()) {
last.push_back(false);
}
int k = (int)top.size();
for (int i = 0; i < k; i++) {
for (int j = i + 1; j < k; j++) {
if (top[i] == top[j]) {
last[i] = false;
break;
}
}
}
int ss = 0;
for (int i = 0; i < k; i++) {
if (last[i]) {
++ss;
}
}
for (int i = 0; i < k; i++) {
var_idx_t x = top[i];
// find s(j) containing x with j not in [ss, ss+i)
int j = find_outside(x, ss, ss + i);
if (last[i]) {
// rearrange x to be at s(ss-1)
issue_xchg(--ss, j);
assert(get(ss).first == x);
} else {
// create a new copy of x
issue_push(j);
issue_xchg(0, ss);
assert(get(ss).first == x);
}
}
assert(!ss);
}
void Stack::rearrange_top(var_idx_t top, bool last) {
int i = find(top);
if (last) {
issue_xchg(0, i);
} else {
issue_push(i);
}
assert(get(0).first == top);
}
bool Op::generate_code_step(Stack& stack) {
stack.opt_show();
stack.drop_vars_except(var_info);
stack.opt_show();
const auto& next_var_info = next->var_info;
switch (cl) {
case _Nop:
case _Import:
return true;
case _Return: {
stack.enforce_state(left);
stack.opt_show();
return false;
}
case _IntConst: {
auto p = next_var_info[left[0]];
if (!p || p->is_unused()) {
return true;
}
auto cidx = stack.o.register_const(int_const);
int i = stack.find_const(cidx);
if (i < 0) {
stack.o << push_const(int_const);
stack.push_new_const(left[0], cidx);
} else {
assert(stack.at(i).second == cidx);
stack.do_copy_var(left[0], stack[i]);
}
return true;
}
case _GlobVar: {
assert(left.size() == 1);
auto p = next_var_info[left[0]];
if (!p || p->is_unused() || disabled()) {
return true;
}
auto func = dynamic_cast<const SymValAsmFunc*>(fun_ref->value);
if (func) {
std::vector<VarDescr> res;
res.reserve(left.size());
for (var_idx_t i : left) {
res.emplace_back(i);
}
func->compile(stack.o, res, args); // compile res := f (args)
} else {
std::string name = sym::symbols.get_name(fun_ref->sym_idx);
stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size());
}
stack.push_new_var(left[0]);
return true;
}
case _Let: {
assert(left.size() == right.size());
int i = 0;
std::vector<bool> active;
active.reserve(left.size());
for (std::size_t k = 0; k < left.size(); k++) {
var_idx_t y = left[k]; // "y" = "x"
auto p = next_var_info[y];
active.push_back(p && !p->is_unused());
}
for (std::size_t k = 0; k < left.size(); k++) {
if (!active[k]) {
continue;
}
var_idx_t x = right[k]; // "y" = "x"
bool is_last = true;
for (std::size_t l = k + 1; l < right.size(); l++) {
if (right[l] == x && active[l]) {
is_last = false;
}
}
if (is_last) {
auto info = var_info[x];
is_last = (info && info->is_last());
}
if (is_last) {
stack.assign_var(--i, x);
} else {
stack.do_copy_var(--i, x);
}
}
i = 0;
for (std::size_t k = 0; k < left.size(); k++) {
if (active[k]) {
stack.assign_var(left[k], --i);
}
}
return true;
}
case _Call:
case _CallInd: {
if (disabled()) {
return true;
}
SymValFunc* func = (fun_ref ? dynamic_cast<SymValFunc*>(fun_ref->value) : nullptr);
auto arg_order = (func ? func->get_arg_order() : nullptr);
auto ret_order = (func ? func->get_ret_order() : nullptr);
assert(!arg_order || arg_order->size() == right.size());
assert(!ret_order || ret_order->size() == left.size());
std::vector<var_idx_t> right1;
if (args.size()) {
assert(args.size() == right.size());
for (int i = 0; i < (int)right.size(); i++) {
int j = arg_order ? arg_order->at(i) : i;
const VarDescr& arg = args.at(j);
if (!arg.is_unused()) {
assert(var_info[arg.idx] && !var_info[arg.idx]->is_unused());
right1.push_back(arg.idx);
}
}
} else if (arg_order) {
for (int i = 0; i < (int)right.size(); i++) {
right1.push_back(right.at(arg_order->at(i)));
}
} else {
right1 = right;
}
std::vector<bool> last;
for (var_idx_t x : right1) {
last.push_back(var_info[x] && var_info[x]->is_last());
}
stack.rearrange_top(right1, std::move(last));
stack.opt_show();
int k = (int)stack.depth() - (int)right1.size();
assert(k >= 0);
for (int i = 0; i < (int)right1.size(); i++) {
if (stack.s[k + i].first != right1[i]) {
std::cerr << stack.o;
}
assert(stack.s[k + i].first == right1[i]);
}
if (cl == _CallInd) {
stack.o << exec_arg_op("CALLARGS", (int)right.size() - 1, -1, (int)right.size() - 1);
} else {
auto func = dynamic_cast<const SymValAsmFunc*>(fun_ref->value);
if (func) {
std::vector<VarDescr> res;
res.reserve(left.size());
for (var_idx_t i : left) {
res.emplace_back(i);
}
func->compile(stack.o, res, args); // compile res := f (args)
} else {
std::string name = sym::symbols.get_name(fun_ref->sym_idx);
stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size());
}
}
stack.s.resize(k);
for (int i = 0; i < (int)left.size(); i++) {
int j = ret_order ? ret_order->at(i) : i;
stack.push_new_var(left.at(j));
}
return true;
}
case _If: {
if (block0->is_empty() && block1->is_empty()) {
return true;
}
var_idx_t x = left[0];
stack.rearrange_top(x, var_info[x] && var_info[x]->is_last());
assert(stack[0] == x);
stack.opt_show();
stack.s.pop_back();
stack.modified();
if (block1->is_empty()) {
// if (left) block0; ...
if (block0->noreturn()) {
stack.o << "IFJMP:<{";
stack.o.indent();
Stack stack_copy{stack};
block0->generate_code_all(stack_copy);
stack.o.undent();
stack.o << "}>";
return true;
}
stack.o << "IF:<{";
stack.o.indent();
Stack stack_copy{stack};
block0->generate_code_all(stack_copy);
stack_copy.drop_vars_except(var_info);
stack_copy.opt_show();
if (stack_copy == stack) {
stack.o.undent();
stack.o << "}>";
return true;
}
stack_copy.drop_vars_except(next->var_info);
stack_copy.opt_show();
if (stack_copy.vars() == stack.vars()) {
stack.o.undent();
stack.o << "}>";
stack.merge_const(stack_copy);
return true;
}
stack.o.undent();
stack.o << "}>ELSE<{";
stack.o.indent();
stack.merge_state(stack_copy);
stack.opt_show();
stack.o.undent();
stack.o << "}>";
return true;
}
if (block0->is_empty()) {
// if (!left) block1; ...
if (block1->noreturn()) {
stack.o << "IFNOTJMP:<{";
stack.o.indent();
Stack stack_copy{stack};
block1->generate_code_all(stack_copy);
stack.o.undent();
stack.o << "}>";
return true;
}
stack.o << "IFNOT:<{";
stack.o.indent();
Stack stack_copy{stack};
block1->generate_code_all(stack_copy);
stack_copy.drop_vars_except(var_info);
stack_copy.opt_show();
if (stack_copy.vars() == stack.vars()) {
stack.o.undent();
stack.o << "}>";
stack.merge_const(stack_copy);
return true;
}
stack_copy.drop_vars_except(next->var_info);
stack_copy.opt_show();
if (stack_copy.vars() == stack.vars()) {
stack.o.undent();
stack.o << "}>";
stack.merge_const(stack_copy);
return true;
}
stack.o.undent();
stack.o << "}>ELSE<{";
stack.o.indent();
stack.merge_state(stack_copy);
stack.opt_show();
stack.o.undent();
stack.o << "}>";
return true;
}
if (block0->noreturn()) {
stack.o << "IFJMP:<{";
stack.o.indent();
Stack stack_copy{stack};
block0->generate_code_all(stack_copy);
stack.o.undent();
stack.o << "}>";
return block1->generate_code_all(stack);
}
if (block1->noreturn()) {
stack.o << "IFNOTJMP:<{";
stack.o.indent();
Stack stack_copy{stack};
block1->generate_code_all(stack_copy);
stack.o.undent();
stack.o << "}>";
return block0->generate_code_all(stack);
}
stack.o << "IF:<{";
stack.o.indent();
Stack stack_copy{stack};
block0->generate_code_all(stack_copy);
stack_copy.drop_vars_except(next->var_info);
stack_copy.opt_show();
stack.o.undent();
stack.o << "}>ELSE<{";
stack.o.indent();
block1->generate_code_all(stack);
stack.merge_state(stack_copy);
stack.opt_show();
stack.o.undent();
stack.o << "}>";
return true;
}
case _Repeat: {
var_idx_t x = left[0];
//stack.drop_vars_except(block0->var_info, x);
stack.rearrange_top(x, var_info[x] && var_info[x]->is_last());
assert(stack[0] == x);
stack.opt_show();
stack.s.pop_back();
stack.modified();
if (!next->is_empty()) {
stack.o << "REPEAT:<{";
stack.o.indent();
stack.forget_const();
StackLayout layout1 = stack.vars();
block0->generate_code_all(stack);
stack.enforce_state(std::move(layout1));
stack.opt_show();
stack.o.undent();
stack.o << "}>";
return true;
} else {
stack.o << "REPEATEND";
stack.forget_const();
StackLayout layout1 = stack.vars();
block0->generate_code_all(stack);
stack.enforce_state(std::move(layout1));
stack.opt_show();
return false;
}
}
case _Again: {
stack.drop_vars_except(block0->var_info);
stack.opt_show();
if (!next->is_empty()) {
stack.o << "AGAIN:<{";
stack.o.indent();
stack.forget_const();
StackLayout layout1 = stack.vars();
block0->generate_code_all(stack);
stack.enforce_state(std::move(layout1));
stack.opt_show();
stack.o.undent();
stack.o << "}>";
return true;
} else {
stack.o << "AGAINEND";
stack.forget_const();
StackLayout layout1 = stack.vars();
block0->generate_code_all(stack);
stack.enforce_state(std::move(layout1));
stack.opt_show();
return false;
}
}
case _Until: {
// stack.drop_vars_except(block0->var_info);
// stack.opt_show();
if (!next->is_empty()) {
stack.o << "UNTIL:<{";
stack.o.indent();
stack.forget_const();
auto layout1 = stack.vars();
block0->generate_code_all(stack);
layout1.push_back(left[0]);
stack.enforce_state(std::move(layout1));
stack.opt_show();
stack.o.undent();
stack.o << "}>";
stack.s.pop_back();
stack.modified();
return true;
} else {
stack.o << "UNTILEND";
stack.forget_const();
StackLayout layout1 = stack.vars();
block0->generate_code_all(stack);
layout1.push_back(left[0]);
stack.enforce_state(std::move(layout1));
stack.opt_show();
return false;
}
}
case _While: {
// while (block0 | left) block1; ...next
var_idx_t x = left[0];
stack.drop_vars_except(block0->var_info);
stack.opt_show();
StackLayout layout1 = stack.vars();
bool next_empty = next->is_empty();
stack.o << (next_empty ? "WHILEEND:<{" : "WHILE:<{");
stack.o.indent();
stack.forget_const();
block0->generate_code_all(stack);
stack.rearrange_top(x, !next->var_info[x] && !block1->var_info[x]);
stack.opt_show();
stack.s.pop_back();
stack.modified();
stack.o.undent();
Stack stack_copy{stack};
stack.o << (next_empty ? "}>" : "}>DO<{");
if (!next_empty) {
stack.o.indent();
}
stack_copy.opt_show();
block1->generate_code_all(stack_copy);
stack_copy.enforce_state(std::move(layout1));
stack_copy.opt_show();
if (!next_empty) {
stack.o.undent();
stack.o << "}>";
return true;
} else {
return false;
}
}
default:
std::cerr << "fatal: unknown operation <??" << cl << ">\n";
throw src::ParseError{where, "unknown operation in generate_code()"};
}
}
bool Op::generate_code_all(Stack& stack) {
if (generate_code_step(stack) && next) {
return next->generate_code_all(stack);
} else {
return false;
}
}
void CodeBlob::generate_code(AsmOpList& out, int mode) {
Stack stack{out, mode};
assert(ops && ops->cl == Op::_Import);
for (var_idx_t x : ops->left) {
stack.push_new_var(x);
}
ops->generate_code_all(stack);
if (!(mode & Stack::_DisableOpt)) {
optimize_code(out);
}
}
void CodeBlob::generate_code(std::ostream& os, int mode, int indent) {
AsmOpList out_list(indent, &vars);
generate_code(out_list, mode);
out_list.out(os, mode);
}
} // namespace funC

248
crypto/func/func.cpp Normal file
View file

@ -0,0 +1,248 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
You must obey the GNU General Public License in all respects for all
of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version. If you delete this exception statement
from all source files in the program, then also delete it here.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "func.h"
#include "parser/srcread.h"
#include "parser/lexer.h"
#include "parser/symtable.h"
#include <getopt.h>
#include <fstream>
namespace funC {
int verbosity, indent, opt_level = 2;
bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble;
std::ostream* outs = &std::cout;
std::string generated_from, boc_output_filename;
/*
*
* OUTPUT CODE GENERATOR
*
*/
void generate_output_func(SymDef* func_sym) {
SymValCodeFunc* func_val = dynamic_cast<SymValCodeFunc*>(func_sym->value);
assert(func_val);
std::string name = sym::symbols.get_name(func_sym->sym_idx);
if (verbosity >= 2) {
std::cerr << "\n\n=========================\nfunction " << name << " : " << func_val->get_type() << std::endl;
}
if (!func_val->code) {
std::cerr << "( function `" << name << "` undefined )\n";
} else {
CodeBlob& code = *(func_val->code);
if (verbosity >= 3) {
code.print(std::cerr, 9);
}
code.simplify_var_types();
// std::cerr << "after simplify_var_types: \n"; code.print(std::cerr, 0);
code.prune_unreachable_code();
// std::cerr << "after prune_unreachable: \n"; code.print(std::cerr, 0);
code.split_vars(true);
// std::cerr << "after split_vars: \n"; code.print(std::cerr, 0);
for (int i = 0; i < 8; i++) {
code.compute_used_code_vars();
if (verbosity >= 5) {
std::cerr << "after compute_used_vars: \n";
code.print(std::cerr, 6);
}
code.fwd_analyze();
// std::cerr << "after fwd_analyze: \n"; code.print(std::cerr, 6);
code.prune_unreachable_code();
// std::cerr << "after prune_unreachable: \n"; code.print(std::cerr, 6);
}
code.mark_noreturn();
if (verbosity >= 3) {
code.print(std::cerr, 15);
}
if (verbosity >= 2) {
std::cerr << "\n---------- resulting code for " << name << " -------------\n";
}
*outs << std::string(indent * 2, ' ') << name << " PROC:<{\n";
code.generate_code(
*outs,
(stack_layout_comments ? Stack::_StkCmt | Stack::_CptStkCmt : 0) | (opt_level < 2 ? Stack::_DisableOpt : 0),
indent + 1);
*outs << std::string(indent * 2, ' ') << "}>\n";
if (verbosity >= 2) {
std::cerr << "--------------\n";
}
}
}
int generate_output() {
if (asm_preamble) {
*outs << "\"Asm.fif\" include\n";
}
*outs << "// automatically generated from " << generated_from << std::endl;
if (program_envelope) {
*outs << "PROGRAM{\n";
}
for (SymDef* func_sym : glob_func) {
SymValCodeFunc* func_val = dynamic_cast<SymValCodeFunc*>(func_sym->value);
assert(func_val);
std::string name = sym::symbols.get_name(func_sym->sym_idx);
*outs << std::string(indent * 2, ' ');
if (func_val->method_id.is_null()) {
*outs << "DECLPROC " << name << "\n";
} else {
*outs << func_val->method_id << " DECLMETHOD " << name << "\n";
}
}
int errors = 0;
for (SymDef* func_sym : glob_func) {
try {
generate_output_func(func_sym);
} catch (src::Error& err) {
std::cerr << "cannot generate code for function `" << sym::symbols.get_name(func_sym->sym_idx) << "`:\n"
<< err << std::endl;
++errors;
}
}
if (program_envelope) {
*outs << "}END>c\n";
}
if (!boc_output_filename.empty()) {
*outs << "2 boc+>B \"" << boc_output_filename << "\" B>file\n";
}
return errors;
}
} // namespace funC
void usage(const char* progname) {
std::cerr
<< "usage: " << progname
<< " [-vIAPSR][-O<level>][-i<indent-spc>][-o<output-filename>][-W<boc-filename>] {<func-source-filename> ...}\n"
"\tGenerates Fift TVM assembler code from a funC source\n"
"-I\tEnables interactive mode (parse stdin)\n"
"-o<fift-output-filename>\tWrites generated code into specified file instead of stdout\n"
"-v\tIncreases verbosity level (extra information output into stderr)\n"
"-i<indent>\tSets indentation for the output code (in two-space units)\n"
"-A\tPrefix code with `\"Asm.fif\" include` preamble\n"
"-O<level>\tSets optimization level (2 by default)\n"
"-P\tEnvelope code into PROGRAM{ ... }END>c\n"
"-S\tInclude stack layout comments in the output code\n"
"-R\tInclude operation rewrite comments in the output code\n"
"-W<output-boc-file>\tInclude Fift code to serialize and save generated code into specified BoC file. Enables "
"-A and -P.\n";
std::exit(2);
}
std::string output_filename;
int main(int argc, char* const argv[]) {
int i;
bool interactive = false;
while ((i = getopt(argc, argv, "Ahi:Io:O:PRSvW:")) != -1) {
switch (i) {
case 'A':
funC::asm_preamble = true;
break;
case 'I':
interactive = true;
break;
case 'i':
funC::indent = std::max(0, atoi(optarg));
break;
case 'o':
output_filename = optarg;
break;
case 'O':
funC::opt_level = std::max(0, atoi(optarg));
break;
case 'P':
funC::program_envelope = true;
break;
case 'R':
funC::op_rewrite_comments = true;
break;
case 'S':
funC::stack_layout_comments = true;
break;
case 'v':
++funC::verbosity;
break;
case 'W':
funC::boc_output_filename = optarg;
funC::asm_preamble = funC::program_envelope = true;
break;
case 'h':
default:
usage(argv[0]);
}
}
if (funC::program_envelope && !funC::indent) {
funC::indent = 1;
}
funC::define_keywords();
funC::define_builtins();
int ok = 0, proc = 0;
try {
while (optind < argc) {
funC::generated_from += std::string{"`"} + argv[optind] + "` ";
ok += funC::parse_source_file(argv[optind++]);
proc++;
}
if (interactive) {
funC::generated_from += "stdin ";
ok += funC::parse_source_stdin();
proc++;
}
if (ok < proc) {
throw src::Fatal{"output code generation omitted because of errors"};
}
if (!proc) {
throw src::Fatal{"no source files, no output"};
}
std::unique_ptr<std::fstream> fs;
if (!output_filename.empty()) {
fs = std::make_unique<std::fstream>(output_filename, fs->trunc | fs->out);
if (!fs->is_open()) {
std::cerr << "failed to create output file " << output_filename << '\n';
return 2;
}
funC::outs = fs.get();
}
funC::generate_output();
} catch (src::Fatal& fatal) {
std::cerr << "fatal: " << fatal << std::endl;
std::exit(1);
} catch (src::Error& error) {
std::cerr << error << std::endl;
std::exit(1);
} catch (funC::UnifyError& unif_err) {
std::cerr << "fatal: ";
unif_err.print_message(std::cerr);
std::cerr << std::endl;
std::exit(1);
}
}

1445
crypto/func/func.h Normal file

File diff suppressed because it is too large Load diff

325
crypto/func/gen-abscode.cpp Normal file
View file

@ -0,0 +1,325 @@
/*
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/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "func.h"
namespace funC {
/*
*
* EXPRESSIONS
*
*/
Expr* Expr::copy() const {
auto res = new Expr{*this};
for (auto& arg : res->args) {
arg = arg->copy();
}
return res;
}
Expr::Expr(int c, sym_idx_t name_idx, std::initializer_list<Expr*> _arglist) : cls(c), args(std::move(_arglist)) {
sym = sym::lookup_symbol(name_idx);
if (!sym) {
}
}
void Expr::chk_rvalue(const Lexem& lem) const {
if (!is_rvalue()) {
lem.error_at("rvalue expected before `", "`");
}
}
void Expr::chk_lvalue(const Lexem& lem) const {
if (!is_lvalue()) {
lem.error_at("lvalue expected before `", "`");
}
}
void Expr::chk_type(const Lexem& lem) const {
if (!is_type()) {
lem.error_at("type expression expected before `", "`");
}
}
bool Expr::deduce_type(const Lexem& lem) {
if (e_type) {
return true;
}
switch (cls) {
case _Apply: {
if (!sym) {
return false;
}
SymVal* sym_val = dynamic_cast<SymVal*>(sym->value);
if (!sym_val || !sym_val->get_type()) {
return false;
}
std::vector<TypeExpr*> arg_types;
for (const auto& arg : args) {
arg_types.push_back(arg->e_type);
}
TypeExpr* fun_type = TypeExpr::new_map(TypeExpr::new_tensor(arg_types), TypeExpr::new_hole());
try {
unify(fun_type, sym_val->sym_type);
} catch (UnifyError& ue) {
std::ostringstream os;
os << "cannot apply function " << sym->name() << " : " << sym_val->get_type() << " to arguments of type "
<< fun_type->args[0] << ": " << ue;
lem.error(os.str());
}
e_type = fun_type->args[1];
TypeExpr::remove_indirect(e_type);
return true;
}
case _VarApply: {
assert(args.size() == 2);
TypeExpr* fun_type = TypeExpr::new_map(args[1]->e_type, TypeExpr::new_hole());
try {
unify(fun_type, args[0]->e_type);
} catch (UnifyError& ue) {
std::ostringstream os;
os << "cannot apply expression of type " << args[0]->e_type << " to an expression of type " << args[1]->e_type
<< ": " << ue;
lem.error(os.str());
}
e_type = fun_type->args[1];
TypeExpr::remove_indirect(e_type);
return true;
}
case _Letop: {
assert(args.size() == 2);
try {
// std::cerr << "in assignment: " << args[0]->e_type << " from " << args[1]->e_type << std::endl;
unify(args[0]->e_type, args[1]->e_type);
} catch (UnifyError& ue) {
std::ostringstream os;
os << "cannot assign an expression of type " << args[1]->e_type << " to a variable or pattern of type "
<< args[0]->e_type << ": " << ue;
lem.error(os.str());
}
e_type = args[0]->e_type;
TypeExpr::remove_indirect(e_type);
return true;
}
case _LetFirst: {
assert(args.size() == 2);
TypeExpr* rhs_type = TypeExpr::new_tensor({args[0]->e_type, TypeExpr::new_hole()});
try {
// std::cerr << "in implicit assignment of a modifying method: " << rhs_type << " and " << args[1]->e_type << std::endl;
unify(rhs_type, args[1]->e_type);
} catch (UnifyError& ue) {
std::ostringstream os;
os << "cannot implicitly assign an expression of type " << args[1]->e_type
<< " to a variable or pattern of type " << rhs_type << " in modifying method `" << sym::symbols.get_name(val)
<< "` : " << ue;
lem.error(os.str());
}
e_type = rhs_type->args[1];
TypeExpr::remove_indirect(e_type);
// std::cerr << "result type is " << e_type << std::endl;
return true;
}
case _CondExpr: {
assert(args.size() == 3);
auto flag_type = TypeExpr::new_atomic(_Int);
try {
unify(args[0]->e_type, flag_type);
} catch (UnifyError& ue) {
std::ostringstream os;
os << "condition in a conditional expression has non-integer type " << args[0]->e_type << ": " << ue;
lem.error(os.str());
}
try {
unify(args[1]->e_type, args[2]->e_type);
} catch (UnifyError& ue) {
std::ostringstream os;
os << "the two variants in a conditional expression have different types " << args[1]->e_type << " and "
<< args[2]->e_type << " : " << ue;
lem.error(os.str());
}
e_type = args[1]->e_type;
TypeExpr::remove_indirect(e_type);
return true;
}
}
return false;
}
int Expr::define_new_vars(CodeBlob& code) {
switch (cls) {
case _Tuple:
case _TypeApply: {
int res = 0;
for (const auto& x : args) {
res += x->define_new_vars(code);
}
return res;
}
case _Var:
if (val < 0) {
val = code.create_var(TmpVar::_Named, e_type, sym, &here);
return 1;
}
break;
case _Hole:
if (val < 0) {
val = code.create_var(TmpVar::_Tmp, e_type, nullptr, &here);
}
break;
}
return 0;
}
int Expr::predefine_vars() {
switch (cls) {
case _Tuple:
case _TypeApply: {
int res = 0;
for (const auto& x : args) {
res += x->predefine_vars();
}
return res;
}
case _Var:
if (!sym) {
assert(val < 0 && here.defined());
sym = sym::define_symbol(~val, false, here);
if (!sym) {
throw src::ParseError{here, std::string{"redefined variable `"} + sym::symbols.get_name(~val) + "`"};
}
sym->value = new SymVal{SymVal::_Var, -1, e_type};
return 1;
}
break;
}
return 0;
}
std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code) const {
switch (cls) {
case _Tuple: {
std::vector<var_idx_t> res;
for (const auto& x : args) {
auto add = x->pre_compile(code);
res.insert(res.end(), add.cbegin(), add.cend());
}
return res;
}
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::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 (const auto& add : add_list) {
res.insert(res.end(), add.cbegin(), add.cend());
}
} else {
for (const auto& x : args) {
auto add = x->pre_compile(code);
res.insert(res.end(), add.cbegin(), add.cend());
}
}
var_idx_t rv = code.create_var(TmpVar::_Tmp, e_type, nullptr, &here);
std::vector<var_idx_t> rvect{rv};
auto& op = code.emplace_back(here, Op::_Call, rvect, std::move(res), sym);
if (flags & _IsImpure) {
op.flags |= Op::_Impure;
}
return rvect;
}
case _TypeApply:
return args[0]->pre_compile(code);
case _Var:
case _Hole:
return {val};
case _VarApply:
if (args[0]->cls == _Glob) {
std::vector<var_idx_t> res = args[1]->pre_compile(code);
var_idx_t rv = code.create_var(TmpVar::_Tmp, e_type, nullptr, &here);
std::vector<var_idx_t> rvect{rv};
auto& op = code.emplace_back(here, Op::_Call, rvect, std::move(res), args[0]->sym);
if (args[0]->flags & _IsImpure) {
op.flags |= Op::_Impure;
}
return rvect;
} else {
std::vector<var_idx_t> res = args[1]->pre_compile(code);
std::vector<var_idx_t> tfunc = args[0]->pre_compile(code);
if (tfunc.size() != 1) {
throw src::Fatal{"stack tuple used as a function"};
}
res.push_back(tfunc[0]);
var_idx_t rv = code.create_var(TmpVar::_Tmp, e_type, nullptr, &here);
std::vector<var_idx_t> rvect{rv};
code.emplace_back(here, Op::_CallInd, rvect, std::move(res));
return rvect;
}
case _Const: {
var_idx_t rv = code.create_var(TmpVar::_Tmp, e_type, nullptr, &here);
std::vector<var_idx_t> rvect{rv};
code.emplace_back(here, Op::_IntConst, rvect, intval);
return rvect;
}
case _Glob: {
var_idx_t rv = code.create_var(TmpVar::_Tmp, e_type, nullptr, &here);
std::vector<var_idx_t> rvect{rv};
code.emplace_back(here, Op::_GlobVar, rvect, std::vector<var_idx_t>{}, sym);
return rvect;
}
case _Letop: {
std::vector<var_idx_t> right = args[1]->pre_compile(code);
std::vector<var_idx_t> left = args[0]->pre_compile(code);
code.emplace_back(here, Op::_Let, left, std::move(right));
return left;
}
case _LetFirst: {
var_idx_t rv = code.create_var(TmpVar::_Tmp, e_type, nullptr, &here);
std::vector<var_idx_t> right = args[1]->pre_compile(code);
std::vector<var_idx_t> left = args[0]->pre_compile(code);
left.push_back(rv);
code.emplace_back(here, Op::_Let, std::move(left), std::move(right));
return std::vector<var_idx_t>{rv};
}
case _CondExpr: {
auto cond = args[0]->pre_compile(code);
assert(cond.size() == 1);
var_idx_t rv = code.create_var(TmpVar::_Tmp, e_type, nullptr, &here);
std::vector<var_idx_t> rvect{rv};
Op& if_op = code.emplace_back(here, Op::_If, cond);
code.push_set_cur(if_op.block0);
code.emplace_back(here, Op::_Let, rvect, args[1]->pre_compile(code));
code.close_pop_cur(args[1]->here);
code.push_set_cur(if_op.block1);
code.emplace_back(here, Op::_Let, rvect, args[2]->pre_compile(code));
code.close_pop_cur(args[2]->here);
return rvect;
}
default:
std::cerr << "expression constructor is " << cls << std::endl;
throw src::Fatal{"cannot compile expression with unknown constructor"};
}
}
} // namespace funC

115
crypto/func/keywords.cpp Normal file
View file

@ -0,0 +1,115 @@
/*
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/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "func.h"
namespace src {
int lexem_is_special(std::string str) {
return 0; // no special lexems
}
} // namespace src
namespace funC {
/*
*
* KEYWORD DEFINITION
*
*/
void define_keywords() {
sym::symbols.add_kw_char('+')
.add_kw_char('-')
.add_kw_char('*')
.add_kw_char('/')
.add_kw_char('%')
.add_kw_char('?')
.add_kw_char(':')
.add_kw_char(',')
.add_kw_char(';')
.add_kw_char('(')
.add_kw_char(')')
.add_kw_char('{')
.add_kw_char('}')
.add_kw_char('=')
.add_kw_char('_')
.add_kw_char('<')
.add_kw_char('>')
.add_kw_char('&')
.add_kw_char('|')
.add_kw_char('~');
using Kw = funC::Keyword;
sym::symbols.add_keyword("==", Kw::_Eq)
.add_keyword("!=", Kw::_Neq)
.add_keyword("<=", Kw::_Leq)
.add_keyword(">=", Kw::_Geq)
.add_keyword("<=>", Kw::_Spaceship)
.add_keyword("<<", Kw::_Lshift)
.add_keyword(">>", Kw::_Rshift)
.add_keyword(">>~", Kw::_RshiftR)
.add_keyword(">>^", Kw::_RshiftC)
.add_keyword("/~", Kw::_DivR)
.add_keyword("/^", Kw::_DivC)
.add_keyword("/%", Kw::_DivMod)
.add_keyword("+=", Kw::_PlusLet)
.add_keyword("-=", Kw::_MinusLet)
.add_keyword("*=", Kw::_TimesLet)
.add_keyword("/=", Kw::_DivLet)
.add_keyword("%=", Kw::_ModLet)
.add_keyword("/~=", Kw::_DivRLet)
.add_keyword("/^=", Kw::_DivCLet)
.add_keyword("<<=", Kw::_LshiftLet)
.add_keyword(">>=", Kw::_RshiftLet)
.add_keyword(">>~=", Kw::_RshiftRLet)
.add_keyword(">>^=", Kw::_RshiftCLet);
sym::symbols.add_keyword("return", Kw::_Return)
.add_keyword("var", Kw::_Var)
.add_keyword("repeat", Kw::_Repeat)
.add_keyword("do", Kw::_Do)
.add_keyword("while", Kw::_While)
.add_keyword("until", Kw::_Until)
.add_keyword("if", Kw::_If)
.add_keyword("ifnot", Kw::_Ifnot)
.add_keyword("then", Kw::_Then)
.add_keyword("else", Kw::_Else)
.add_keyword("elseif", Kw::_Elseif)
.add_keyword("elseifnot", Kw::_Elseifnot);
sym::symbols.add_keyword("int", Kw::_Int)
.add_keyword("cell", Kw::_Cell)
.add_keyword("slice", Kw::_Slice)
.add_keyword("builder", Kw::_Builder)
.add_keyword("cont", Kw::_Cont)
.add_keyword("tuple", Kw::_Tuple)
.add_keyword("->", Kw::_Mapsto);
sym::symbols.add_keyword("extern", Kw::_Extern)
.add_keyword("asm", Kw::_Asm)
.add_keyword("impure", Kw::_Impure)
.add_keyword("method_id", Kw::_MethodId)
.add_keyword("operator", Kw::_Operator)
.add_keyword("infix", Kw::_Infix)
.add_keyword("infixl", Kw::_Infixl)
.add_keyword("infixr", Kw::_Infixr);
}
} // namespace funC

539
crypto/func/optimize.cpp Normal file
View file

@ -0,0 +1,539 @@
/*
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/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "func.h"
namespace funC {
/*
*
* PEEPHOLE OPTIMIZER
*
*/
void Optimizer::set_code(AsmOpConsList code) {
code_ = std::move(code);
unpack();
}
void Optimizer::unpack() {
int i = 0, j = 0;
for (AsmOpCons *p = code_.get(); p && i < n; p = p->cdr.get(), ++j) {
if (p->car->is_very_custom()) {
break;
}
if (p->car->is_comment()) {
continue;
}
op_cons_[i] = p;
op_[i] = std::move(p->car);
offs_[i] = j;
++i;
}
l_ = i;
indent_ = (i ? op_[0]->indent : 0);
}
void Optimizer::pack() {
for (int i = 0; i < l_; i++) {
op_cons_[i]->car = std::move(op_[i]);
op_cons_[i] = nullptr;
}
l_ = 0;
}
void Optimizer::apply() {
if (!p_ && !q_) {
return;
}
assert(p_ > 0 && p_ <= l_ && q_ >= 0 && q_ <= n && l_ <= n);
for (int i = p_; i < l_; i++) {
assert(op_[i]);
op_cons_[i]->car = std::move(op_[i]);
op_cons_[i] = nullptr;
}
for (int c = offs_[p_ - 1]; c >= 0; --c) {
code_ = std::move(code_->cdr);
}
for (int j = q_ - 1; j >= 0; j--) {
assert(oq_[j]);
oq_[j]->indent = indent_;
code_ = AsmOpCons::cons(std::move(oq_[j]), std::move(code_));
}
l_ = 0;
}
AsmOpConsList Optimizer::extract_code() {
pack();
return std::move(code_);
}
void Optimizer::show_head() const {
if (!debug_) {
return;
}
std::cerr << "optimizing";
for (int i = 0; i < l_; i++) {
if (op_[i]) {
std::cerr << ' ' << *op_[i] << ' ';
} else {
std::cerr << " (null) ";
}
}
std::cerr << std::endl;
}
void Optimizer::show_left() const {
if (!debug_) {
return;
}
std::cerr << "// *** rewriting";
for (int i = 0; i < p_; i++) {
if (op_[i]) {
std::cerr << ' ' << *op_[i] << ' ';
} else {
std::cerr << " (null) ";
}
}
}
void Optimizer::show_right() const {
if (!debug_) {
return;
}
std::cerr << "->";
for (int i = 0; i < q_; i++) {
if (oq_[i]) {
std::cerr << ' ' << *oq_[i] << ' ';
} else {
std::cerr << " (null) ";
}
}
std::cerr << std::endl;
}
bool Optimizer::say(std::string str) const {
if (debug_) {
std::cerr << str << std::endl;
}
return true;
}
bool Optimizer::is_const_push_swap() const {
return l_ >= 3 && op_[0]->is_gconst() && op_[1]->is_push() && op_[1]->a >= 1 && op_[2]->is_swap();
}
// PUSHCONST c ; PUSH s(i+1) ; SWAP -> PUSH s(i) ; PUSHCONST c
bool Optimizer::rewrite_const_push_swap() {
p_ = 3;
q_ = 2;
show_left();
oq_[1] = std::move(op_[0]);
oq_[0] = std::move(op_[1]);
(oq_[0]->a)--;
show_right();
return true;
}
bool Optimizer::is_const_push_xchgs() {
if (!(pb_ >= 2 && pb_ <= l2_ && op_[0]->is_gconst())) {
return false;
}
StackTransform t;
int pos = 0, i;
for (i = 1; i < pb_; i++) {
int a, b;
if (op_[i]->is_xchg(&a, &b)) {
if (pos == a) {
pos = b;
} else if (pos == b) {
pos = a;
} else {
t.apply_xchg(a - (a > pos), b - (b > pos));
}
} else if (op_[i]->is_push(&a)) {
if (pos == a) {
return false;
}
t.apply_push(a - (a > pos));
++pos;
} else {
return false;
}
}
if (pos) {
return false;
}
t.apply_push_newconst();
if (t <= tr_[i - 1]) {
p_ = i;
return true;
} else {
return false;
}
}
bool Optimizer::rewrite_const_push_xchgs() {
if (!p_) {
return false;
}
show_left();
auto c_op = std::move(op_[0]);
assert(c_op->is_gconst());
StackTransform t;
q_ = 0;
int pos = 0;
for (int i = 1; i < p_; i++) {
int a, b;
if (op_[i]->is_xchg(&a, &b)) {
if (a == pos) {
pos = b;
} else if (b == pos) {
pos = a;
} else {
oq_[q_] = std::move(op_[i]);
if (a > pos) {
oq_[q_]->a = a - 1;
}
if (b > pos) {
oq_[q_]->b = b - 1;
}
assert(apply_op(t, *oq_[q_]));
++q_;
}
} else {
assert(op_[i]->is_push(&a));
assert(a != pos);
oq_[q_] = std::move(op_[i]);
if (a > pos) {
oq_[q_]->a = a - 1;
}
assert(apply_op(t, *oq_[q_]));
++q_;
++pos;
}
}
assert(!pos);
t.apply_push_newconst();
assert(t <= tr_[p_ - 1]);
oq_[q_++] = std::move(c_op);
show_right();
return true;
}
bool Optimizer::simple_rewrite(int p, AsmOp&& new_op) {
assert(p > 0 && p <= l_);
p_ = p;
q_ = 1;
show_left();
oq_[0] = std::move(op_[0]);
*oq_[0] = new_op;
show_right();
return true;
}
bool Optimizer::simple_rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2) {
assert(p > 1 && p <= l_);
p_ = p;
q_ = 2;
show_left();
oq_[0] = std::move(op_[0]);
*oq_[0] = new_op1;
oq_[1] = std::move(op_[1]);
*oq_[1] = new_op2;
show_right();
return true;
}
bool Optimizer::simple_rewrite_nop() {
assert(p_ > 0 && p_ <= l_);
q_ = 0;
show_left();
show_right();
return true;
}
bool Optimizer::is_pred(const std::function<bool(const StackTransform&)>& pred, int min_p) {
min_p = std::max(min_p, pb_);
for (int p = l2_; p >= min_p; p--) {
if (pred(tr_[p - 1])) {
p_ = p;
return true;
}
}
return false;
}
bool Optimizer::is_same_as(const StackTransform& trans, int min_p) {
return is_pred([&trans](const auto& t) { return t >= trans; }, min_p);
}
// s1 s3 XCHG ; s0 s2 XCHG -> 2SWAP
bool Optimizer::is_2swap() {
static const StackTransform t_2swap{2, 3, 0, 1, 4};
return is_same_as(t_2swap);
}
// s3 PUSH ; s3 PUSH -> 2OVER
bool Optimizer::is_2over() {
static const StackTransform t_2over{2, 3, 0};
return is_same_as(t_2over);
}
bool Optimizer::is_2dup() {
static const StackTransform t_2dup{0, 1, 0};
return is_same_as(t_2dup);
}
bool Optimizer::is_tuck() {
static const StackTransform t_tuck{0, 1, 0, 2};
return is_same_as(t_tuck);
}
bool Optimizer::is_2drop() {
static const StackTransform t_2drop{2};
return is_same_as(t_2drop);
}
bool Optimizer::is_rot() {
return is_pred([](const auto& t) { return t.is_rot(); });
}
bool Optimizer::is_rotrev() {
return is_pred([](const auto& t) { return t.is_rotrev(); });
}
bool Optimizer::is_nop() {
return is_pred([](const auto& t) { return t.is_id(); }, 1);
}
bool Optimizer::is_xchg(int* i, int* j) {
return is_pred([i, j](const auto& t) { return t.is_xchg(i, j) && ((*i < 16 && *j < 16) || (!*i && *j < 256)); });
}
bool Optimizer::is_push(int* i) {
return is_pred([i](const auto& t) { return t.is_push(i) && *i < 256; });
}
bool Optimizer::is_pop(int* i) {
return is_pred([i](const auto& t) { return t.is_pop(i) && *i < 256; });
}
bool Optimizer::is_xchg2(int* i, int* j) {
return is_pred([i, j](const auto& t) { return t.is_xchg2(i, j) && *i < 16 && *j < 16; });
}
bool Optimizer::is_xcpu(int* i, int* j) {
return is_pred([i, j](const auto& t) { return t.is_xcpu(i, j) && *i < 16 && *j < 16; });
}
bool Optimizer::is_puxc(int* i, int* j) {
return is_pred([i, j](const auto& t) { return t.is_puxc(i, j) && *i < 16 && *j < 15; });
}
bool Optimizer::is_push2(int* i, int* j) {
return is_pred([i, j](const auto& t) { return t.is_push2(i, j) && *i < 16 && *j < 16; });
}
bool Optimizer::is_xchg3(int* i, int* j, int* k) {
return is_pred([i, j, k](const auto& t) { return t.is_xchg3(i, j, k) && *i < 16 && *j < 16 && *k < 16; });
}
bool Optimizer::is_xc2pu(int* i, int* j, int* k) {
return is_pred([i, j, k](const auto& t) { return t.is_xc2pu(i, j, k) && *i < 16 && *j < 16 && *k < 16; });
}
bool Optimizer::is_xcpuxc(int* i, int* j, int* k) {
return is_pred([i, j, k](const auto& t) { return t.is_xcpuxc(i, j, k) && *i < 16 && *j < 16 && *k < 15; });
}
bool Optimizer::is_xcpu2(int* i, int* j, int* k) {
return is_pred([i, j, k](const auto& t) { return t.is_xcpu2(i, j, k) && *i < 16 && *j < 16 && *k < 16; });
}
bool Optimizer::is_puxc2(int* i, int* j, int* k) {
return is_pred([i, j, k](const auto& t) { return t.is_puxc2(i, j, k) && *i < 16 && *j < 15 && *k < 15; });
}
bool Optimizer::is_puxcpu(int* i, int* j, int* k) {
return is_pred([i, j, k](const auto& t) { return t.is_puxcpu(i, j, k) && *i < 16 && *j < 15 && *k < 15; });
}
bool Optimizer::is_pu2xc(int* i, int* j, int* k) {
return is_pred([i, j, k](const auto& t) { return t.is_pu2xc(i, j, k) && *i < 16 && *j < 15 && *k < 14; });
}
bool Optimizer::is_push3(int* i, int* j, int* k) {
return is_pred([i, j, k](const auto& t) { return t.is_push3(i, j, k) && *i < 16 && *j < 16 && *k < 16; });
}
bool Optimizer::is_blkswap(int* i, int* j) {
return is_pred([i, j](const auto& t) { return t.is_blkswap(i, j) && *i > 0 && *j > 0 && *i <= 16 && *j <= 16; });
}
bool Optimizer::is_blkpush(int* i, int* j) {
return is_pred([i, j](const auto& t) { return t.is_blkpush(i, j) && *i > 0 && *i < 16 && *j < 16; });
}
bool Optimizer::is_blkdrop(int* i) {
return is_pred([i](const auto& t) { return t.is_blkdrop(i) && *i > 0 && *i < 16; });
}
bool Optimizer::is_reverse(int* i, int* j) {
return is_pred([i, j](const auto& t) { return t.is_reverse(i, j) && *i >= 2 && *i <= 17 && *j < 16; });
}
bool Optimizer::is_nip_seq(int* i, int* j) {
return is_pred([i, j](const auto& t) { return t.is_nip_seq(i, j) && *i >= 3 && *i <= 15; });
}
bool Optimizer::compute_stack_transforms() {
StackTransform trans;
for (int i = 0; i < l_; i++) {
if (!apply_op(trans, *op_[i])) {
l2_ = i;
return true;
}
tr_[i] = trans;
}
l2_ = l_;
return true;
}
bool Optimizer::show_stack_transforms() const {
show_head();
// slow version
/*
StackTransform trans2;
std::cerr << "id = " << trans2 << std::endl;
for (int i = 0; i < l_; i++) {
StackTransform op;
if (!apply_op(op, *op_[i])) {
std::cerr << "* (" << *op_[i] << " = invalid)\n";
break;
}
trans2 *= op;
std::cerr << "* " << *op_[i] << " = " << op << " -> " << trans2 << std::endl;
}
*/
// fast version
StackTransform trans;
for (int i = 0; i < l_; i++) {
std::cerr << trans << std::endl << *op_[i] << " -> ";
if (!apply_op(trans, *op_[i])) {
std::cerr << " <not-applicable>" << std::endl;
return true;
}
}
std::cerr << trans << std::endl;
return true;
}
bool Optimizer::find_at_least(int pb) {
p_ = q_ = 0;
pb_ = pb;
// show_stack_transforms();
int i = -100, j = -100, k = -100;
return (is_const_push_swap() && 3 >= pb && rewrite_const_push_swap()) || (is_nop() && simple_rewrite_nop()) ||
(is_const_push_xchgs() && rewrite_const_push_xchgs()) ||
(is_xchg(&i, &j) && simple_rewrite(AsmOp::Xchg(i, j))) || (is_push(&i) && simple_rewrite(AsmOp::Push(i))) ||
(is_pop(&i) && simple_rewrite(AsmOp::Pop(i))) || (is_rot() && simple_rewrite(AsmOp::Custom("ROT", 3, 3))) ||
(is_rotrev() && simple_rewrite(AsmOp::Custom("-ROT", 3, 3))) ||
(is_2dup() && simple_rewrite(AsmOp::Custom("2DUP", 2, 4))) ||
(is_2swap() && simple_rewrite(AsmOp::Custom("2SWAP", 2, 4))) ||
(is_2over() && simple_rewrite(AsmOp::Custom("2OVER", 2, 4))) ||
(is_tuck() && simple_rewrite(AsmOp::Custom("TUCK", 2, 3))) ||
(is_2drop() && simple_rewrite(AsmOp::Custom("2DROP", 2, 0))) ||
(is_xchg2(&i, &j) && simple_rewrite(AsmOp::Xchg2(i, j))) ||
(is_xcpu(&i, &j) && simple_rewrite(AsmOp::XcPu(i, j))) ||
(is_puxc(&i, &j) && simple_rewrite(AsmOp::PuXc(i, j))) ||
(is_push2(&i, &j) && simple_rewrite(AsmOp::Push2(i, j))) ||
(is_blkswap(&i, &j) && simple_rewrite(AsmOp::BlkSwap(i, j))) ||
(is_blkpush(&i, &j) && simple_rewrite(AsmOp::BlkPush(i, j))) ||
(is_blkdrop(&i) && simple_rewrite(AsmOp::BlkDrop(i))) ||
(is_reverse(&i, &j) && simple_rewrite(AsmOp::BlkReverse(i, j))) ||
(is_nip_seq(&i, &j) && simple_rewrite(AsmOp::Xchg(i, j), AsmOp::BlkDrop(i))) ||
(is_xchg3(&i, &j, &k) && simple_rewrite(AsmOp::Xchg3(i, j, k))) ||
(is_xc2pu(&i, &j, &k) && simple_rewrite(AsmOp::Xc2Pu(i, j, k))) ||
(is_xcpuxc(&i, &j, &k) && simple_rewrite(AsmOp::XcPuXc(i, j, k))) ||
(is_xcpu2(&i, &j, &k) && simple_rewrite(AsmOp::XcPu2(i, j, k))) ||
(is_puxc2(&i, &j, &k) && simple_rewrite(AsmOp::PuXc2(i, j, k))) ||
(is_puxcpu(&i, &j, &k) && simple_rewrite(AsmOp::PuXcPu(i, j, k))) ||
(is_pu2xc(&i, &j, &k) && simple_rewrite(AsmOp::Pu2Xc(i, j, k))) ||
(is_push3(&i, &j, &k) && simple_rewrite(AsmOp::Push3(i, j, k)));
}
bool Optimizer::find() {
if (!compute_stack_transforms()) {
return false;
}
for (int pb = l_; pb > 0; --pb) {
if (find_at_least(pb)) {
return true;
}
}
return false;
}
bool Optimizer::optimize() {
bool f = false;
while (find()) {
f = true;
apply();
unpack();
}
return f;
}
AsmOpConsList optimize_code_head(AsmOpConsList op_list) {
Optimizer opt(std::move(op_list), op_rewrite_comments);
opt.optimize();
return opt.extract_code();
}
AsmOpConsList optimize_code(AsmOpConsList op_list) {
std::vector<std::unique_ptr<AsmOp>> v;
while (op_list) {
if (!op_list->car->is_comment()) {
op_list = optimize_code_head(std::move(op_list));
}
if (op_list) {
v.push_back(std::move(op_list->car));
op_list = std::move(op_list->cdr);
}
}
for (auto it = v.rbegin(); it < v.rend(); ++it) {
op_list = AsmOpCons::cons(std::move(*it), std::move(op_list));
}
return std::move(op_list);
}
void optimize_code(AsmOpList& ops) {
std::unique_ptr<AsmOpCons> op_list;
for (auto it = ops.list_.rbegin(); it < ops.list_.rend(); ++it) {
op_list = AsmOpCons::cons(std::make_unique<AsmOp>(std::move(*it)), std::move(op_list));
}
op_list = optimize_code(std::move(op_list));
ops.list_.clear();
while (op_list) {
ops.list_.push_back(std::move(*(op_list->car)));
op_list = std::move(op_list->cdr);
}
}
} // namespace funC

1105
crypto/func/parse-func.cpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,826 @@
/*
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/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "func.h"
namespace funC {
/*
*
* GENERIC STACK TRANSFORMATIONS
*
*/
StackTransform::StackTransform(std::initializer_list<int> list) {
*this = list;
}
StackTransform &StackTransform::operator=(std::initializer_list<int> list) {
if (list.size() > 255) {
invalidate();
return *this;
}
set_id();
if (!list.size()) {
return *this;
}
int m = (int)list.size();
d = list.begin()[m - 1] - (m - 1);
if (d >= 128 || d < -128) {
invalidate();
return *this;
}
for (int i = 0; i < m - 1; i++) {
int x = d + i;
int y = list.begin()[i];
if (y != x) {
if (x != (short)x || y != (short)y || n == max_n) {
invalidate();
return *this;
}
dp = std::max(dp, std::max(x, y) + 1);
A[n++] = std::make_pair((short)x, (short)y);
}
}
return *this;
}
bool StackTransform::assign(const StackTransform &other) {
if (!other.is_valid() || (unsigned)other.n > max_n) {
return invalidate();
}
d = other.d;
n = other.n;
dp = other.dp;
c = other.c;
invalid = false;
for (int i = 0; i < n; i++) {
A[i] = other.A[i];
}
return true;
}
int StackTransform::get(int x) const {
if (!is_valid()) {
return -1;
}
if (x <= c_start) {
return x - c;
}
x += d;
int i;
for (i = 0; i < n && A[i].first < x; i++) {
}
if (i < n && A[i].first == x) {
return A[i].second;
} else {
return x;
}
}
bool StackTransform::set(int x, int y, bool relaxed) {
if (!is_valid()) {
return false;
}
if (x < 0) {
return (relaxed && y == x + d) || invalidate();
}
if (!relaxed) {
touch(x);
}
x += d;
int i;
for (i = 0; i < n && A[i].first < x; i++) {
}
if (i < n && A[i].first == x) {
if (x != y) {
if (y != (short)y) {
return invalidate();
}
A[i].second = (short)y;
} else {
--n;
for (; i < n; i++) {
A[i] = A[i + 1];
}
}
} else {
if (x != y) {
if (x != (short)x || y != (short)y || n == max_n) {
return invalidate();
}
for (int j = n++; j > i; j--) {
A[j] = A[j - 1];
}
A[i].first = (short)x;
A[i].second = (short)y;
touch(x - d);
touch(y);
}
}
return true;
}
// f(x') = x' + d for all x' >= x ?
bool StackTransform::is_trivial_after(int x) const {
return is_valid() && (!n || A[n - 1].first < x + d);
}
// card f^{-1}(y)
int StackTransform::preimage_count(int y) const {
if (!is_valid()) {
return -1;
}
int count = (y >= d);
for (const auto &pair : A) {
if (pair.second == y) {
++count;
} else if (pair.first == y) {
--count;
}
}
return count;
}
// f^{-1}(y)
std::vector<int> StackTransform::preimage(int y) const {
if (!is_valid()) {
return {};
}
std::vector<int> res;
bool f = (y >= d);
for (const auto &pair : A) {
if (pair.first > y && f) {
res.push_back(y - d);
f = false;
}
if (pair.first == y) {
f = false;
} else if (pair.second == y) {
res.push_back(pair.first - d);
}
}
return res;
}
// is f:N->N bijective ?
bool StackTransform::is_permutation() const {
if (!is_valid() || d) {
return false;
}
assert(n <= max_n);
std::array<int, max_n> X, Y;
for (int i = 0; i < n; i++) {
X[i] = A[i].first;
Y[i] = A[i].second;
if (Y[i] < 0) {
return false;
}
}
std::sort(Y.begin(), Y.begin() + n);
for (int i = 0; i < n; i++) {
if (X[i] != Y[i]) {
return false;
}
}
return true;
}
bool StackTransform::remove_negative() {
int s = 0;
while (s < n && A[s].first < d) {
++s;
}
if (s) {
n -= s;
for (int i = 0; i < n; i++) {
A[i] = A[i + s];
}
}
return true;
}
int StackTransform::try_load(int &i, int offs) const {
return i < n ? A[i++].first + offs : inf_x;
}
bool StackTransform::try_store(int x, int y) {
if (x == y || x < d) {
return true;
}
if (n == max_n || x != (short)x || y != (short)y) {
return invalidate();
}
A[n].first = (short)x;
A[n++].second = (short)y;
return true;
}
// c := a * b
bool StackTransform::compose(const StackTransform &a, const StackTransform &b, StackTransform &c) {
if (!a.is_valid() || !b.is_valid()) {
return c.invalidate();
}
c.d = a.d + b.d;
c.n = 0;
c.dp = std::max(a.dp, b.dp + a.d);
c.c = a.c + b.c;
c.invalid = false;
int i = 0, j = 0;
int x1 = a.try_load(i);
int x2 = b.try_load(j, a.d);
while (true) {
if (x1 < x2) {
int y = a.A[i - 1].second;
if (!c.try_store(x1, y)) {
return false;
}
x1 = a.try_load(i);
} else if (x2 < inf_x) {
if (x1 == x2) {
x1 = a.try_load(i);
}
int y = b.A[j - 1].second;
if (!c.try_store(x2, a(y))) {
return false;
}
x2 = b.try_load(j, a.d);
} else {
return true;
}
}
}
// this = this * other
bool StackTransform::apply(const StackTransform &other) {
StackTransform res;
if (!compose(*this, other, res)) {
return invalidate();
}
return assign(res);
}
// this = other * this
bool StackTransform::preapply(const StackTransform &other) {
StackTransform res;
if (!compose(other, *this, res)) {
return invalidate();
}
return assign(res);
}
StackTransform StackTransform::operator*(const StackTransform &b) const & {
StackTransform res;
compose(*this, b, res);
return res;
}
// this = this * other
StackTransform &StackTransform::operator*=(const StackTransform &other) {
StackTransform res;
(compose(*this, other, res) && assign(res)) || invalidate();
return *this;
}
bool StackTransform::apply_xchg(int i, int j, bool relaxed) {
if (!is_valid() || i < 0 || j < 0) {
return invalidate();
}
if (i == j) {
return relaxed || touch(i);
}
int u = touch_get(i), v = touch_get(j);
return set(i, v) && set(j, u);
}
bool StackTransform::apply_push(int i) {
if (!is_valid() || i < 0) {
return invalidate();
}
int u = touch_get(i);
return shift(-1) && set(0, u);
}
bool StackTransform::apply_push_newconst() {
if (!is_valid()) {
return false;
}
return shift(-1) && set(0, c_start - c++);
}
bool StackTransform::apply_pop(int i) {
if (!is_valid() || i < 0) {
return invalidate();
}
if (!i) {
return touch(0) && shift(1);
} else {
return set(i, get(0)) && shift(1);
}
}
bool StackTransform::equal(const StackTransform &other, bool relaxed) const {
if (!is_valid() || !other.is_valid()) {
return false;
}
if (!(n == other.n && d == other.d)) {
return false;
}
for (int i = 0; i < n; i++) {
if (A[i] != other.A[i]) {
return false;
}
}
return relaxed || dp == other.dp;
}
StackTransform StackTransform::Xchg(int i, int j, bool relaxed) {
StackTransform t;
t.apply_xchg(i, j, relaxed);
return t;
}
StackTransform StackTransform::Push(int i) {
StackTransform t;
t.apply_push(i);
return t;
}
StackTransform StackTransform::Pop(int i) {
StackTransform t;
t.apply_pop(i);
return t;
}
bool StackTransform::is_xchg(int i, int j) const {
if (i == j) {
return is_id();
}
return is_valid() && !d && n == 2 && i >= 0 && j >= 0 && get(i) == j && get(j) == i;
}
bool StackTransform::is_xchg(int *i, int *j) const {
if (!is_valid() || d || n > 2 || !dp) {
return false;
}
if (!n) {
*i = *j = 0;
return true;
}
if (n != 2) {
return false;
}
int a = A[0].first, b = A[1].first;
if (A[0].second != b || A[1].second != a) {
return false;
}
*i = std::min(a, b);
*j = std::max(a, b);
return true;
}
bool StackTransform::is_push(int i) const {
return is_valid() && d == -1 && n == 1 && A[0].first == -1 && A[0].second == i;
}
bool StackTransform::is_push(int *i) const {
if (is_valid() && d == -1 && n == 1 && A[0].first == -1 && A[0].second >= 0) {
*i = A[0].second;
return true;
} else {
return false;
}
}
// 1 2 3 4 .. = pop0
// 0 2 3 4 .. = pop1
// 1 0 3 4 .. = pop2
// 1 2 0 4 .. = pop3
bool StackTransform::is_pop(int i) const {
if (!is_valid() || d != 1 || n > 1 || i < 0) {
return false;
}
if (!i) {
return !n;
}
return n == 1 && A[0].first == i && !A[0].second;
}
bool StackTransform::is_pop(int *i) const {
if (!is_valid() || d != 1 || n > 1) {
return false;
}
if (!n) {
*i = 0;
return true;
}
if (n == 1 && !A[0].second) {
*i = A[0].first;
return true;
}
return false;
}
const StackTransform StackTransform::rot{2, 0, 1, 3};
const StackTransform StackTransform::rot_rev{1, 2, 0, 3};
bool StackTransform::is_rot() const {
return equal(rot, true);
}
bool StackTransform::is_rotrev() const {
return equal(rot_rev, true);
}
// XCHG s1,s(i) ; XCHG s0,s(j)
bool StackTransform::is_xchg2(int i, int j) const {
StackTransform t;
return is_valid() && !d && t.apply_xchg(1, i) && t.apply_xchg(0, j) && t <= *this;
}
bool StackTransform::is_xchg2(int *i, int *j) const {
if (!is_valid() || d || n > 4 || n == 1 || dp < 2) {
return false;
}
*i = get(1);
*j = get(0);
if (!n) {
return true;
}
if (*i < 0 || *j < 0) {
return false;
}
if (n != 3) {
return is_xchg2(*i, *j);
}
if (*i) {
// XCHG2 s(i),s(i) = XCHG s1,s(i) ; XCHG s0,s(i) : 0->1, 1->i
*j = *i;
} // XCHG2 s0,s(i) = XCHG s0,s1 ; XCHG s0,s(i) : 0->i, 1->0
return is_xchg2(*i, *j);
}
// XCHG s0,s(i) ; PUSH s(j) = PUSH s(j') ; XCHG s1,s(i+1)
// j'=j if j!=0, j!=i
// j'=0 if j=i
// j'=i if j=0
bool StackTransform::is_xcpu(int i, int j) const {
StackTransform t;
return is_valid() && d == -1 && t.apply_xchg(0, i) && t.apply_push(j) && t <= *this;
}
bool StackTransform::is_xcpu(int *i, int *j) const {
if (!is_valid() || d != -1 || n > 3 || dp < 1) {
return false;
}
*i = get(1);
*j = get(0);
if (!*j) {
*j = *i;
} else if (*j == *i) {
*j = 0;
}
return is_xcpu(*i, *j);
}
// PUSH s(i) ; XCHG s0, s1 ; XCHG s0, s(j+1)
bool StackTransform::is_puxc(int i, int j) const {
StackTransform t;
return is_valid() && d == -1 && t.apply_push(i) && t.apply_xchg(0, 1) && t.apply_xchg(0, j + 1) && t <= *this;
}
// j > 0 : 0 -> j, 1 -> i
// j = 0 : 0 -> i, 1 -> 0 ( PUSH s(i) )
// j = -1 : 0 -> 0, 1 -> i ( PUSH s(i) ; XCHG s0, s1 )
bool StackTransform::is_puxc(int *i, int *j) const {
if (!is_valid() || d != -1 || n > 3) {
return false;
}
*i = get(1);
*j = get(0);
if (!*i && is_push(*j)) {
std::swap(*i, *j);
return is_puxc(*i, *j);
}
if (!*j) {
--*j;
}
return is_puxc(*i, *j);
}
// PUSH s(i) ; PUSH s(j+1)
bool StackTransform::is_push2(int i, int j) const {
StackTransform t;
return is_valid() && d == -2 && t.apply_push(i) && t.apply_push(j + 1) && t <= *this;
}
bool StackTransform::is_push2(int *i, int *j) const {
if (!is_valid() || d != -2 || n > 2) {
return false;
}
*i = get(1);
*j = get(0);
return is_push2(*i, *j);
}
// XCHG s2,s(i) ; XCHG s1,s(j) ; XCHG s0,s(k)
bool StackTransform::is_xchg3(int *i, int *j, int *k) const {
if (!is_valid() || d || dp < 3 || !is_permutation()) {
return false;
}
for (int s = 2; s >= 0; s--) {
*i = get(s);
StackTransform t = Xchg(2, *i) * *this;
if (t.is_xchg2(j, k)) {
return true;
}
}
return false;
}
// XCHG s1,s(i) ; XCHG s0,s(j) ; PUSH s(k)
bool StackTransform::is_xc2pu(int *i, int *j, int *k) const {
if (!is_valid() || d != -1 || dp < 2) {
return false;
}
for (int s = 2; s >= 1; s--) {
*i = get(s);
StackTransform t = Xchg(1, *i) * *this;
if (t.is_xcpu(j, k)) {
return true;
}
}
return false;
}
// XCHG s1,s(i) ; PUSH s(j) ; XCHG s0,s1 ; XCHG s0,s(k+1)
bool StackTransform::is_xcpuxc(int *i, int *j, int *k) const {
if (!is_valid() || d != -1 || dp < 2) {
return false;
}
for (int s = 2; s >= 0; s--) {
*i = get(s);
StackTransform t = Xchg(1, *i) * *this;
if (t.is_puxc(j, k)) {
return true;
}
}
return false;
}
// XCHG s0,s(i) ; PUSH s(j) ; PUSH s(k+1)
bool StackTransform::is_xcpu2(int *i, int *j, int *k) const {
if (!is_valid() || d != -2 || dp < 1) {
return false;
}
*i = get(2);
StackTransform t = Xchg(0, *i) * *this;
return t.is_push2(j, k);
}
// PUSH s(i) ; XCHG s0,s2 ; XCHG s1,s(j+1) ; XCHG s0,s(k+1)
// 0 -> i or 1 -> i or 2 -> i ; i has two preimages
// 0 -> k if k >= 2, k != j
// 1 -> j=k if j = k >= 2
// 1 -> j if j >= 2, k != 0
// 0 -> j if j >= 2, k = 0
// => i in {f(0), f(1), f(2)} ; j in {-1, 0, 1, f(0), f(1)} ; k in {-1, 0, 1, f(0), f(1)}
bool StackTransform::is_puxc2(int *i, int *j, int *k) const {
if (!is_valid() || d != -1 || dp < 2) {
return false;
}
for (int s = 2; s >= 0; s--) {
*i = get(s);
if (preimage_count(*i) != 2) {
continue;
}
for (int u = -1; u <= 3; u++) {
*j = (u >= 2 ? get(u - 2) : u);
for (int v = -1; v <= 3; v++) {
*k = (v >= 2 ? get(v - 2) : v);
if (is_puxc2(*i, *j, *k)) {
return true;
}
}
}
}
return false;
}
// PUSH s(i) ; XCHG s0,s2 ; XCHG s1,s(j+1) ; XCHG s0,s(k+1)
bool StackTransform::is_puxc2(int i, int j, int k) const {
StackTransform t;
return is_valid() && d == -1 && dp >= 2 // basic checks
&& t.apply_push(i) && t.apply_xchg(0, 2) // PUSH s(i) ; XCHG s0,s2
&& t.apply_xchg(1, j + 1) // XCHG s1,s(j+1)
&& t.apply_xchg(0, k + 1) && t <= *this; // XCHG s0,s(k+2)
}
// PUSH s(i) ; XCHG s0,s1 ; XCHG s0,s(j+1) ; PUSH s(k+1)
bool StackTransform::is_puxcpu(int *i, int *j, int *k) const {
if (!is_valid() || d != -2 || dp < 1) {
return false;
}
StackTransform t = *this;
if (t.apply_pop() && t.is_puxc(i, j)) {
int y = get(0);
auto v = t.preimage(y);
if (!v.empty()) {
*k = v[0] - 1;
t.apply_push(*k + 1);
return t <= *this;
}
}
return false;
}
// PUSH s(i) ; XCHG s0,s1 ; PUSH s(j+1) ; XCHG s0,s1 ; XCHG s0,s(k+2)
// 2 -> i; 1 -> j (if j >= 1, k != -1), 1 -> i (if j = 0, k != -1), 1 -> 0 (if j = -1, k != -1)
// 0 -> k (if k >= 1), 0 -> i (if k = 0), 0 -> j (if k = -1, j >= 1)
bool StackTransform::is_pu2xc(int *i, int *j, int *k) const {
if (!is_valid() || d != -2 || dp < 1) {
return false;
}
*i = get(2);
for (int v = -2; v <= 1; v++) {
*k = (v <= 0 ? v : get(0)); // one of -2, -1, 0, get(0)
for (int u = -1; u <= 1; u++) {
*j = (u <= 0 ? u : get(v != -1)); // one of -1, 0, get(0), get(1)
if (is_pu2xc(*i, *j, *k)) {
return true;
}
}
}
return false;
}
bool StackTransform::is_pu2xc(int i, int j, int k) const {
StackTransform t;
return is_valid() && d == -2 && dp >= 1 // basic checks
&& t.apply_push(i) && t.apply_xchg(0, 1) // PUSH s(i) ; XCHG s0,s1
&& t.apply_push(j + 1) && t.apply_xchg(0, 1) // PUSH s(j+1) ; XCHG s0,s1
&& t.apply_xchg(0, k + 2) && t <= *this; // XCHG s0,s(k+2)
}
// PUSH s(i) ; PUSH s(j+1) ; PUSH s(k+2)
bool StackTransform::is_push3(int i, int j, int k) const {
StackTransform t;
return is_valid() && d == -3 && t.apply_push(i) && t.apply_push(j + 1) && t.apply_push(k + 2) && t <= *this;
}
bool StackTransform::is_push3(int *i, int *j, int *k) const {
if (!is_valid() || d != -3 || n > 3) {
return false;
}
*i = get(2);
*j = get(1);
*k = get(0);
return is_push3(*i, *j, *k);
}
bool StackTransform::is_blkswap(int *i, int *j) const {
if (!is_valid() || d || !is_permutation()) {
return false;
}
*j = get(0);
if (*j <= 0) {
return false;
}
auto v = preimage(0);
if (v.size() != 1) {
return false;
}
*i = v[0];
return *i > 0 && is_blkswap(*i, *j);
}
bool StackTransform::is_blkswap(int i, int j) const {
if (!is_valid() || d || i <= 0 || j <= 0 || dp < i + j || !is_trivial_after(i + j)) {
return false;
}
for (int s = 0; s < i; s++) {
if (get(s) != s + j) {
return false;
}
}
for (int s = 0; s < j; s++) {
if (get(s + i) != s) {
return false;
}
}
return true;
}
// equivalent to i times DROP
bool StackTransform::is_blkdrop(int *i) const {
if (is_valid() && d > 0 && !n) {
*i = d;
return true;
}
return false;
}
// equivalent to i times PUSH s(j)
bool StackTransform::is_blkpush(int *i, int *j) const {
if (!is_valid() || d >= 0) {
return false;
}
*i = -d;
*j = get(*i - 1);
return is_blkpush(*i, *j);
}
bool StackTransform::is_blkpush(int i, int j) const {
if (!is_valid() || d >= 0 || d != -i || j < 0 || dp < i + j || !is_trivial_after(i)) {
return false;
}
StackTransform t;
for (int s = 0; s < i; s++) {
if (!t.apply_push(j)) {
return false;
}
}
return t <= *this;
}
bool StackTransform::is_reverse(int *i, int *j) const {
if (!is_valid() || d || !is_permutation() || n < 2) {
return false;
}
*j = A[0].first;
*i = A[n - 1].first - A[0].first + 1;
return is_reverse(*i, *j);
}
bool StackTransform::is_reverse(int i, int j) const {
if (!is_valid() || d || !is_trivial_after(i + j) || n < 2 || A[0].first != j || A[n - 1].first != j + i - 1) {
return false;
}
for (int s = 0; s < i; s++) {
if (get(j + s) != j + i - 1 - s) {
return false;
}
}
return true;
}
// 0 i+1 i+2 ... == i*NIP
// j i+1 i+2 ... == XCHG s(i),s(j) ; BLKDROP i
bool StackTransform::is_nip_seq(int i, int j) const {
return is_valid() && d == i && i > j && j >= 0 && n == 1 && A[0].first == i && A[0].second == j;
}
bool StackTransform::is_nip_seq(int *i) const {
*i = d;
return is_nip_seq(*i);
}
bool StackTransform::is_nip_seq(int *i, int *j) const {
if (is_valid() && n > 0) {
*i = d;
*j = A[0].second;
return is_nip_seq(*i, *j);
} else {
return false;
}
}
void StackTransform::show(std::ostream &os, int mode) const {
if (!is_valid()) {
os << "<invalid>";
return;
}
int mi = 0, ma = 0;
if (n > 0 && A[0].first < d) {
mi = A[0].first - d;
}
if (n > 0) {
ma = std::max(ma, A[n - 1].first - d + 1);
}
ma = std::max(ma + 1, dp - d);
os << '{';
if (dp == d) {
os << '|';
}
for (int i = mi; i < ma; i++) {
os << get(i) << (i == -1 ? '?' : (i == dp - d - 1 ? '|' : ' '));
}
os << get(ma) << "..}";
}
} // namespace funC

85
crypto/func/test/a6.fc Normal file
View file

@ -0,0 +1,85 @@
(int, int) f(int a, int b, int c, int d, int e, int f) {
;; solve a 2x2 linear equation
int D = a * d - b * c;
int Dx = e * d - b * f;
int Dy = a * f - e * c;
return (Dx / D, Dy / D);
}
int calc_phi() {
var n = 1;
repeat (70) { n *= 10; }
var p = var q = 1;
do {
(p, q) = (q, p + q);
} until (q > n);
return muldivr(p, n, q);
}
int calc_sqrt2() {
var n = 1;
repeat (70) { n *= 10; }
var p = var q = 1;
do {
var t = p + q;
(p, q) = (q, t + q);
} until (q > n);
return muldivr(p, n, q);
}
var calc_root(m) {
int base = 1;
repeat(70) { base *= 10; }
var (a, b, c) = (1, 0, - m);
var (p1, q1, p2, q2) = (1, 0, 0, 1);
do {
int k = -1;
var (a1, b1, c1) = (0, 0, 0);
do {
k += 1;
(a1, b1, c1) = (a, b, c);
c += b;
c += b += a;
} until (c > 0);
(a, b, c) = (- c1, - b1, - a1);
(p1, q1) = (k * p1 + q1, p1);
(p2, q2) = (k * p2 + q2, p2);
} until (p1 > base);
return (p1, q1, p2, q2);
}
{-
operator _/%_ infix 20;
(int, int) ((int x) /% (int y)) {
return (x / y, x % y);
}
(int, int) _/%_ (int x, int y) {
return (x / y, x % y);
}
-}
{-
< type A, type B, type C >
(B, C, A) rot (A x, B y, C z) {
return (y, z, x);
}
-}
int ataninv(int base, int q) { ;; computes base*atan(1/q)
base /~= q;
q *= - q;
int sum = 0;
int n = 1;
do {
sum += base /~ n;
base /~= q;
n += 2;
} until base == 0;
return sum;
}
int calc_pi() {
int base = 64;
repeat (70) { base *= 10; }
return (ataninv(base << 2, 5) - ataninv(base, 239)) >>~ 4;
}

75
crypto/func/test/a6.fp Normal file
View file

@ -0,0 +1,75 @@
f(int a, int b, int c, int d, int e, int f) : (int, int) {
var D = a * d - b * c;
var Dx : int = e * d - b * f;
var Dy : int = a * f - e * c;
return (Dx / D, Dy / D);
}
calc_phi() : int = {
var n : int = 1;
repeat (10) {
n *= 10;
}
var p = var q = 1;
do {
(p, q) = (q, p + q);
} until q > n;
return muldivr(p, n, q);
}
calc_sqrt2() : int = {
var n = 1;
repeat (70) { n *= 10; }
var p = var q = 1;
do {
var t = p + q;
(p, q) = (q, t + q);
} until q > n;
return muldivr(p, n, q);
}
calc_phi() : int = {
var n = 1;
repeat (70) { n *= 10; }
var p = var q = 1;
do {
(p, q) = (q, p + q);
} until q > n;
return muldivr(p, n, q);
}
operator _/%_ infix 20;
(x : int) /% (y : int) : (int, int) = {
return (x / y, x % y);
}
{-
_/%_ (int x, int y) : (int, int) = {
return (x / y, x % y);
}
-}
rot < A : type, B : type, C : type >
(x : A, y : B, z : C) : (B, C, A) {
return (y, z, x);
}
ataninv(base : int, q : int) : int { ;; computes base*atan(1/q)
base /~= q;
q *= - q;
var sum : int = 0;
var n = 1;
do {
sum += base /~ n;
base /~= q;
n += 2;
} while base;
return sum;
}
calc_pi() : int {
var base = 64;
repeat (70) { base *= 10; }
return (ataninv(base << 2, 5) - ataninv(base, 239)) >>~ 4;
}

6
crypto/func/test/a6_1.fc Normal file
View file

@ -0,0 +1,6 @@
(int, int) f(int a, int b, int c, int d, int e, int f) {
int D = a * d - b * c;
int Dx = e * d - b * f;
int Dy = a * f - e * c;
return (Dx / D, Dy / D);
}

130
crypto/func/test/a6_2.fc Normal file
View file

@ -0,0 +1,130 @@
var f(a, b, c, d, e, f) {
var D = a * d - b * c;
var Dx = e * d - b * f;
var Dy = a * f - e * c;
return (Dx / D, Dy / D);
}
var test8() {
return -4;
}
var test3(a) {
int x = a * 10;
a += 1;
int x = x + 3;
{
int y = x + 2;
x = y + a;
}
int z = 5;
return x * (z + a);
}
var test2(a) {
(var x, var y) = a /% 10;
return x + y;
}
var test2a(a) {
var (x, y) = a /% 10;
return x + y;
}
var test2b(a) {
var z = a /% 10;
return _+_ z;
}
var test2c(a) {
return _+_ (a /% 10);
}
var twice(f, x) {
return f (f x);
}
var test() {
return f(1, 2, 3, 4, 7, 17);
}
var rot(a, b, c) {
return (b, c, a);
}
var rot_int(int a, int b, int c) {
return (b, c, a);
}
(int, _) dup_int(x) {
return (x, x);
}
var calc_phi() {
var n = 10;
n *= n;
n *= n;
n *= n;
n *= n;
n *= n;
n *= n;
var p = var q = 1;
(p, q) = (q, p + q);
(p, q) = (q, p + q);
(p, q) = (q, p + q);
(p, q) = (q, p + q);
(p, q) = (q, p + q);
(p, q) = (q, p + q);
(p, q) = (q, p + q);
return muldivr(p, n, q);
}
var t0() {
var x = 1;
return x;
}
var t1() {
return 2;
}
var t2(int x) {
return 2;
return x += 1;
}
var t3(int x, var y) {
int z = (x + y) * (x - y);
}
var t3b(int x, var y) {
int z = (x + y) * (x - y);
return z;
}
var t4((int, int, int) z) {
var (_, u, _) = z;
return u;
}
int foo(int t);
var t5(x) {
var f = t2;
return twice(f, x) * f(x);
}
var proj1(a, b) { return a; }
var proj1a(a, b) { var c = a; return c; }
var proj1b(a, b) { int c = a; return c; }
var proj1c(a, b) { var c = a + 2; return c; }
var proj1d(a, b) { return a + 2; }
var proj1e(int a, _) { return a; }
int proj1f(_ a, int) { return a; }
int proj1g(int, a) { return a; }
var test1(a) {
a = a + 1;
return a;
}

12
crypto/func/test/a7.fc Normal file
View file

@ -0,0 +1,12 @@
int steps(int x) {
var n = 0;
while (x > 1) {
n += 1;
if (x & 1) {
x = 3 * x + 1;
} else {
x >>= 1;
}
}
return n;
}

28
crypto/func/test/a8.fc Normal file
View file

@ -0,0 +1,28 @@
int A(int a, int b, int c, int d, int e, int f) {
return muldiv(a, b, c) + muldiv(d, e, f);
}
int B(int a, int b, int c, int d, int e, int f) {
return muldiv(a, b, c) + muldiv(b, d, e) + f;
}
int C(int a, int b, int c, int d, int e, int f) {
return muldiv(a, b, c) + muldiv(b, d, c) + muldiv(d, d, f);
}
int D(int a, int b, int c, int d, int e, int f) {
return muldiv(a, b, c) + muldiv(b, d, c) + muldiv(e, e, f);
}
int E(int a, int b, int c, int d, int e, int f) {
return muldiv(a, b, c) + muldiv(c, d, e) + muldiv(c, c, f);
}
int F(int a, int b, int c, int d, int e, int f) {
return muldiv(a, b, c) + muldiv(a, d, c) + muldiv(f, f, e);
}
int G(int a, int b, int c, int d, int e, int f) {
return muldiv(a, b, c) + muldiv(b, c, d) + muldiv(b, c, e) + f;
}

23
crypto/func/test/a9.fc Normal file
View file

@ -0,0 +1,23 @@
int f(int x) {
if (2 * x + 1 == 7) {
return x + 17;
} else {
return 239;
}
}
int f2(int x) {
return 2 * x + 1 == 6 ? x + 17 : 239;
}
int g(int x) {
return x & 1 ? 3 * x + 1 : x / 2;
}
_ H(int x, int y) {
return (x < y, x <= y, x == y, x >= y, x > y, x != y, x <=> y);
}
int q() {
return 4;
}

28
crypto/func/test/a9_1.fc Normal file
View file

@ -0,0 +1,28 @@
_ F(int x) {
x = 2;
return x + 1;
}
_ G(x) {
var y = x + 1;
x = 2;
return (x - 1, y);
}
_ H(x) {
var y = x + 1;
x = 2;
return (x * y, y);
}
_ I(x) {
int y = 17;
int z = x - y;
return (z, y);
}
_ J(x) {
int y = 239;
int z = x - y;
return (z, y);
}

63
crypto/func/test/b1.fc Normal file
View file

@ -0,0 +1,63 @@
int now() asm "NOW";
int cell_hash(cell c)
asm "HASHCU";
int slice_hash(slice s)
asm "HASHSU";
int check_signature(int hash, slice signature, int public_key)
asm "CHKSIGNU";
;; () throw_if(int excno, int cond) impure
;; asm "THROWARGIF";
cell get_data() asm "c4 PUSH";
() set_data(cell c) impure asm "c4 POP";
() accept_message() impure asm "ACCEPT";
slice begin_parse(cell c) asm "CTOS";
() end_parse(slice s) impure asm "ENDS";
(cell, slice) load_ref(slice s) asm "LDREF";
(int, slice) zload_int(slice s, int len) asm "LDIX";
(int, slice) zload_uint(slice s, int len) asm "LDUX";
int zpreload_int(slice s, int len) asm "PLDIX";
int zpreload_uint(slice s, int len) asm "PLDUX";
(slice, slice) load_bits(slice s, int len) asm "LDSLICEX";
slice preload_bits(slice s, int len) asm "PLDSLICEX";
cell set_idict_ref(cell value, int index, cell dict, int key_len) asm "DICTISETREF";
builder begin_cell() asm "NEWC";
builder store_ref(cell c, builder b) asm "STREF";
builder zstore_uint(int x, builder b, int len) asm "STUX";
builder zstore_int(int x, builder b, int len) asm "STIX";
cell end_cell(builder b) asm "ENDC";
;; Simple configuration smart contract
() recv_internal(cell in_msg) impure {
;; do nothing for internal messages
}
() recv_external(cell in_msg) impure {
var (signature, cs0) = load_bits(begin_parse(in_msg), 512);
var (msg_seqno, cs) = zload_uint(cs0, 32);
(var valid_until, cs) = zload_uint(cs, 32);
throw_if(35, valid_until < now());
var (cfg_dict, cs2) = load_ref(begin_parse(get_data()));
(var stored_seqno, cs2) = zload_uint(cs2, 32);
(var public_key, cs2) = zload_uint(cs2, 256);
end_parse(cs2);
throw_unless(33, msg_seqno == stored_seqno);
throw_unless(34, check_signature(slice_hash(cs0), signature, public_key));
accept_message();
(var param_index, cs) = zload_uint(cs, 32);
(var param_value, cs) = load_ref(cs);
end_parse(cs);
;; cfg_dict = set_idict_ref(param_value, param_index, cfg_dict, 32);
;; var cb = begin_cell();
;; cb = store_ref(cfg_dict, cb);
var cb = store_ref(set_idict_ref(param_value, param_index, cfg_dict, 32), begin_cell());
cb = zstore_uint(stored_seqno + 1, cb, 32);
cb = zstore_uint(public_key, cb, 256);
set_data(end_cell(cb));
}

60
crypto/func/test/b2.fc Normal file
View file

@ -0,0 +1,60 @@
int now() asm "NOW";
int cell_hash(cell c)
asm "HASHCU";
int slice_hash(slice s)
asm "HASHSU";
int check_signature(int hash, slice signature, int public_key)
asm "CHKSIGNU";
;; () throw_if(int excno, int cond) impure
;; asm "THROWARGIF";
cell get_data() asm "c4 PUSH";
() set_data(cell c) impure asm "c4 POP";
() accept_message() impure asm "ACCEPT";
slice begin_parse(cell c) asm "CTOS";
() end_parse(slice s) impure asm "ENDS";
(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF";
;; (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";
(slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX";
slice preload_bits(slice s, int len) asm "PLDSLICEX";
cell set_idict_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF";
builder begin_cell() asm "NEWC";
builder store_ref(builder b, cell c) asm(c b) "STREF";
;; 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";
cell end_cell(builder b) asm "ENDC";
;; Simple configuration smart contract
() recv_internal(cell in_msg) impure {
;; do nothing for internal messages
}
() recv_external(cell in_msg) impure {
var (cs0, signature) = load_bits(begin_parse(in_msg), 512);
var (cs, msg_seqno) = load_uint(cs0, 32);
(cs, var valid_until) = load_uint(cs, 32);
throw_if(35, valid_until < now());
var (cs2, cfg_dict) = load_ref(begin_parse(get_data()));
(cs2, var stored_seqno) = load_uint(cs2, 32);
(cs2, var public_key) = load_uint(cs2, 256);
end_parse(cs2);
throw_unless(33, msg_seqno == stored_seqno);
throw_unless(34, check_signature(slice_hash(cs0), signature, public_key));
accept_message();
(cs, var param_index) = load_uint(cs, 32);
(cs, var param_value) = load_ref(cs);
end_parse(cs);
var cb = store_ref(begin_cell(), set_idict_ref(cfg_dict, 32, param_index, param_value));
cb = store_uint(cb, stored_seqno + 1, 32);
cb = store_uint(cb, public_key, 256);
set_data(end_cell(cb));
}

63
crypto/func/test/b2_0.fc Normal file
View file

@ -0,0 +1,63 @@
int now() asm "NOW";
int cell_hash(cell c)
asm "HASHCU";
int slice_hash(slice s)
asm "HASHSU";
int check_signature(int hash, slice signature, int public_key)
asm "CHKSIGNU";
;; () throw_if(int excno, int cond) impure
;; asm "THROWARGIF";
cell get_data() asm "c4 PUSH";
() set_data(cell c) impure asm "c4 POP";
() accept_message() impure asm "ACCEPT";
slice begin_parse(cell c) asm "CTOS";
() end_parse(slice s) impure asm "ENDS";
(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF";
(slice, int) zload_int(slice s, int len) asm(s len -> 1 0) "LDIX";
(slice, int) zload_uint(slice s, int len) asm( -> 1 0) "LDUX";
int zpreload_int(slice s, int len) asm "PLDIX";
int zpreload_uint(slice s, int len) asm "PLDUX";
(slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX";
slice preload_bits(slice s, int len) asm "PLDSLICEX";
cell set_idict_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF";
builder begin_cell() asm "NEWC";
builder store_ref(builder b, cell c) asm(c b) "STREF";
builder zstore_uint(builder b, int x, int len) asm(x b len) "STUX";
builder zstore_int(builder b, int x, int len) asm(x b len) "STIX";
cell end_cell(builder b) asm "ENDC";
;; Simple configuration smart contract
() recv_internal(cell in_msg) impure {
;; do nothing for internal messages
}
() recv_external(cell in_msg) impure {
var (cs0, signature) = load_bits(begin_parse(in_msg), 512);
var (cs, msg_seqno) = zload_uint(cs0, 32);
(cs, var valid_until) = zload_uint(cs, 32);
throw_if(35, valid_until < now());
var (cs2, cfg_dict) = load_ref(begin_parse(get_data()));
(cs2, var stored_seqno) = zload_uint(cs2, 32);
(cs2, var public_key) = zload_uint(cs2, 256);
end_parse(cs2);
throw_unless(33, msg_seqno == stored_seqno);
throw_unless(34, check_signature(slice_hash(cs0), signature, public_key));
accept_message();
(cs, var param_index) = zload_uint(cs, 32);
(cs, var param_value) = load_ref(cs);
end_parse(cs);
;; cfg_dict = set_idict_ref(cfg_dict, 32, param_index, param_value);
;; var cb = begin_cell();
;; cb = store_ref(cb, cfg_dict);
var cb = store_ref(begin_cell(), set_idict_ref(cfg_dict, 32, param_index, param_value));
cb = zstore_uint(cb, stored_seqno + 1, 32);
cb = zstore_uint(cb, public_key, 256);
set_data(end_cell(cb));
}

62
crypto/func/test/b2_1.fc Normal file
View file

@ -0,0 +1,62 @@
int now() asm "NOW";
int cell_hash(cell c)
asm "HASHCU";
int slice_hash(slice s)
asm "HASHSU";
int check_signature(int hash, slice signature, int public_key)
asm "CHKSIGNU";
;; () throw_if(int excno, int cond) impure
;; asm "THROWARGIF";
cell get_data() asm "c4 PUSH";
() set_data(cell c) impure asm "c4 POP";
() accept_message() impure asm "ACCEPT";
slice begin_parse(cell c) asm "CTOS";
() end_parse(slice s) impure asm "ENDS";
(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF";
;; (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";
(slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX";
slice preload_bits(slice s, int len) asm "PLDSLICEX";
cell set_idict_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF";
(cell, ()) ~set_idict_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF";
builder begin_cell() asm "NEWC";
builder store_ref(builder b, cell c) asm(c b) "STREF";
;; 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";
cell end_cell(builder b) asm "ENDC";
;; Simple configuration smart contract
() recv_internal(cell in_msg) impure {
;; do nothing for internal messages
}
() recv_external(cell in_msg) impure {
var cs = begin_parse(in_msg);
var signature = cs~load_bits(512);
var cs0 = cs;
int msg_seqno = cs~load_uint(32);
var valid_until = cs~load_uint(32);
throw_if(35, valid_until < now());
var cs2 = begin_parse(get_data());
var cfg_dict = cs2~load_ref();
var stored_seqno = cs2~load_uint(32);
var public_key = cs2~load_uint(256);
cs2.end_parse();
throw_unless(33, msg_seqno == stored_seqno);
throw_unless(34, check_signature(slice_hash(cs0), signature, public_key));
accept_message();
var param_index = cs~load_uint(32);
var param_value = cs~load_ref();
cs.end_parse();
cfg_dict~set_idict_ref(32, param_index, param_value);
set_data(begin_cell().store_ref(cfg_dict).store_uint(stored_seqno + 1, 32).store_uint(public_key, 256).end_cell());
}

58
crypto/func/test/b2_2.fc Normal file
View file

@ -0,0 +1,58 @@
int now() asm "NOW";
int cell_hash(cell c)
asm "HASHCU";
int slice_hash(slice s)
asm "HASHSU";
int check_signature(int hash, slice signature, int public_key)
asm "CHKSIGNU";
;; () throw_if(int excno, int cond) impure
;; asm "THROWARGIF";
cell get_data() asm "c4 PUSH";
() set_data(cell c) impure asm "c4 POP";
() accept_message() impure asm "ACCEPT";
slice begin_parse(cell c) asm "CTOS";
() end_parse(slice s) impure asm "ENDS";
(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF";
;; (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";
(slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX";
;; slice .preload_bits(slice s, int len) asm "PLDSLICEX";
cell set_idict_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF";
builder begin_cell() asm "NEWC";
builder store_ref(builder b, cell c) asm(c b) "STREF";
;;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";
cell .end_cell(builder b) asm "ENDC";
;; Simple configuration smart contract
() recv_internal(cell in_msg) impure {
;; do nothing for internal messages
}
() recv_external(cell in_msg) impure {
var (cs0, signature) = load_bits(begin_parse(in_msg), 512);
var (cs, msg_seqno) = load_uint(cs0, 32);
(cs, var valid_until) = load_uint(cs, 32);
throw_if(35, valid_until < now());
var (cs2, cfg_dict) = load_ref(begin_parse(get_data()));
(cs2, var stored_seqno) = load_uint(cs2, 32);
(cs2, var public_key) = load_uint(cs2, 256);
end_parse(cs2);
throw_unless(33, msg_seqno == stored_seqno);
throw_unless(34, check_signature(slice_hash(cs0), signature, public_key));
accept_message();
(cs, var param_index) = load_uint(cs, 32);
(cs, var param_value) = load_ref(cs);
end_parse(cs);
set_data(begin_cell().store_ref(cfg_dict.set_idict_ref(32, param_index, param_value))
.store_uint(stored_seqno + 1, 32).store_uint(public_key, 256).end_cell());
}

384
crypto/func/unify-types.cpp Normal file
View file

@ -0,0 +1,384 @@
/*
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/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "func.h"
namespace funC {
/*
*
* TYPE EXPRESSIONS
*
*/
int TypeExpr::holes = 0, TypeExpr::type_vars = 0; // not thread safe, but it is ok for now
void TypeExpr::compute_width() {
switch (constr) {
case te_Atomic:
case te_Map:
minw = maxw = 1;
break;
case te_Tensor:
minw = maxw = 0;
for (TypeExpr* arg : args) {
minw += arg->minw;
maxw += arg->maxw;
}
if (minw > w_inf) {
minw = w_inf;
}
if (maxw > w_inf) {
maxw = w_inf;
}
break;
case te_Indirect:
minw = args[0]->minw;
maxw = args[0]->maxw;
break;
default:
minw = 0;
maxw = w_inf;
break;
}
}
bool TypeExpr::recompute_width() {
switch (constr) {
case te_Tensor:
case te_Indirect: {
int min = 0, max = 0;
for (TypeExpr* arg : args) {
min += arg->minw;
max += arg->maxw;
}
if (min > maxw || max < minw) {
return false;
}
if (min > w_inf) {
min = w_inf;
}
if (max > w_inf) {
max = w_inf;
}
if (minw < min) {
minw = min;
}
if (maxw > max) {
maxw = max;
}
return true;
}
default:
return false;
}
}
int TypeExpr::extract_components(std::vector<TypeExpr*>& comp_list) {
if (constr != te_Indirect && constr != te_Tensor) {
comp_list.push_back(this);
return 1;
}
int res = 0;
for (TypeExpr* arg : args) {
res += arg->extract_components(comp_list);
}
return res;
}
TypeExpr* TypeExpr::new_map(TypeExpr* from, TypeExpr* to) {
return new TypeExpr{te_Map, std::vector<TypeExpr*>{from, to}};
}
void TypeExpr::replace_with(TypeExpr* te2) {
if (te2 == this) {
return;
}
constr = te_Indirect;
value = 0;
minw = te2->minw;
maxw = te2->maxw;
args.clear();
args.push_back(te2);
}
bool TypeExpr::remove_indirect(TypeExpr*& te, TypeExpr* forbidden) {
assert(te);
while (te->constr == te_Indirect) {
te = te->args[0];
}
if (te->constr == te_Unknown) {
return te != forbidden;
}
bool res = true;
for (auto& x : te->args) {
res &= remove_indirect(x, forbidden);
}
return res;
}
bool TypeExpr::remove_forall(TypeExpr*& te) {
assert(te);
if (te->constr != te_ForAll) {
return false;
}
assert(te->args.size() >= 1);
std::vector<TypeExpr*> new_vars;
for (std::size_t i = 1; i < te->args.size(); i++) {
new_vars.push_back(new_hole(1));
}
TypeExpr* te2 = te;
// std::cerr << "removing universal quantifier in " << te << std::endl;
te = te->args[0];
remove_forall_in(te, te2, new_vars);
// std::cerr << "-> " << te << std::endl;
return true;
}
bool TypeExpr::remove_forall_in(TypeExpr*& te, TypeExpr* te2, const std::vector<TypeExpr*>& new_vars) {
assert(te);
assert(te2 && te2->constr == te_ForAll);
if (te->constr == te_Unknown) {
for (std::size_t i = 0; i < new_vars.size(); i++) {
if (te == te2->args[i + 1]) {
te = new_vars[i];
return true;
}
}
return false;
}
if (te->constr == te_ForAll) {
return false;
}
if (te->args.empty()) {
return false;
}
auto te1 = new TypeExpr(*te);
bool res = false;
for (auto& arg : te1->args) {
res |= remove_forall_in(arg, te2, new_vars);
}
if (res) {
te = te1;
} else {
delete te1;
}
return res;
}
void TypeExpr::show_width(std::ostream& os) {
os << minw;
if (maxw != minw) {
os << "..";
if (maxw < w_inf) {
os << maxw;
}
}
}
std::ostream& operator<<(std::ostream& os, TypeExpr* type_expr) {
if (!type_expr) {
return os << "(null-type-ptr)";
}
return type_expr->print(os);
}
std::ostream& TypeExpr::print(std::ostream& os, int lex_level) {
switch (constr) {
case te_Unknown:
if (value >= 0) {
return os << "??" << value;
} else if (value >= -26) {
return os << (char)(64 - value);
} else {
return os << "TVAR" << -value;
}
case te_Indirect:
return os << args[0];
case te_Atomic: {
switch (value) {
case _Int:
return os << "int";
case _Cell:
return os << "cell";
case _Slice:
return os << "slice";
case _Builder:
return os << "builder";
case _Cont:
return os << "cont";
case _Tuple:
return os << "tuple";
case _Type:
return os << "type";
default:
return os << "atomic-type-" << value;
}
}
case te_Tensor: {
os << "(";
auto c = args.size();
if (c) {
for (const auto& x : args) {
x->print(os);
if (--c) {
os << ", ";
}
}
}
return os << ")";
}
case te_Map: {
assert(args.size() == 2);
if (lex_level > 0) {
os << "(";
}
args[0]->print(os, 1);
os << " -> ";
args[1]->print(os);
if (lex_level > 0) {
os << ")";
}
return os;
}
case te_ForAll: {
assert(args.size() >= 1);
if (lex_level > 0) {
os << '(';
}
os << "Forall ";
for (std::size_t i = 1; i < args.size(); i++) {
os << (i > 1 ? ' ' : '(');
args[i]->print(os);
}
os << ") ";
args[0]->print(os);
if (lex_level > 0) {
os << ')';
}
return os;
}
default:
return os << "unknown-type-expr-" << constr;
}
}
void UnifyError::print_message(std::ostream& os) const {
os << "cannot unify type " << te1 << " with " << te2;
if (!msg.empty()) {
os << ": " << msg;
}
}
std::ostream& operator<<(std::ostream& os, const UnifyError& ue) {
ue.print_message(os);
return os;
}
std::string UnifyError::message() const {
std::ostringstream os;
UnifyError::print_message(os);
return os.str();
}
void check_width_compat(TypeExpr* te1, TypeExpr* te2) {
if (te1->minw > te2->maxw || te2->minw > te1->maxw) {
std::ostringstream os{"cannot unify types of widths "};
te1->show_width(os);
os << " and ";
te2->show_width(os);
throw UnifyError{te1, te2, os.str()};
}
}
void check_update_widths(TypeExpr* te1, TypeExpr* te2) {
check_width_compat(te1, te2);
te1->minw = te2->minw = std::max(te1->minw, te2->minw);
te1->maxw = te2->maxw = std::min(te1->maxw, te2->maxw);
assert(te1->minw <= te2->minw);
}
void unify(TypeExpr*& te1, TypeExpr*& te2) {
assert(te1 && te2);
// std::cerr << "unify( " << te1 << " , " << te2 << " )\n";
while (te1->constr == TypeExpr::te_Indirect) {
te1 = te1->args[0];
}
while (te2->constr == TypeExpr::te_Indirect) {
te2 = te2->args[0];
}
if (te1 == te2) {
return;
}
if (te1->constr == TypeExpr::te_ForAll) {
TypeExpr* te = te1;
if (!TypeExpr::remove_forall(te)) {
throw UnifyError{te1, te2, "cannot remove universal type quantifier while performing type unification"};
}
unify(te, te2);
return;
}
if (te2->constr == TypeExpr::te_ForAll) {
TypeExpr* te = te2;
if (!TypeExpr::remove_forall(te)) {
throw UnifyError{te2, te1, "cannot remove universal type quantifier while performing type unification"};
}
unify(te1, te);
return;
}
if (te1->constr == TypeExpr::te_Unknown) {
if (te2->constr == TypeExpr::te_Unknown) {
assert(te1->value != te2->value);
}
if (!TypeExpr::remove_indirect(te2, te1)) {
throw UnifyError{te1, te2, "type unification results in an infinite cyclic type"};
}
check_update_widths(te1, te2);
te1->replace_with(te2);
te1 = te2;
return;
}
if (te2->constr == TypeExpr::te_Unknown) {
if (!TypeExpr::remove_indirect(te1, te2)) {
throw UnifyError{te2, te1, "type unification results in an infinite cyclic type"};
}
check_update_widths(te2, te1);
te2->replace_with(te1);
te2 = te1;
return;
}
if (te1->constr != te2->constr || te1->value != te2->value || te1->args.size() != te2->args.size()) {
throw UnifyError{te1, te2};
}
for (std::size_t i = 0; i < te1->args.size(); i++) {
unify(te1->args[i], te2->args[i]);
}
if (te1->constr == TypeExpr::te_Tensor) {
if (!te1->recompute_width()) {
throw UnifyError{te1, te2, "type unification incompatible with known width of first type"};
}
if (!te2->recompute_width()) {
throw UnifyError{te2, te1, "type unification incompatible with known width of first type"};
}
check_update_widths(te1, te2);
}
te1->replace_with(te2);
te1 = te2;
}
} // namespace funC