mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
initial commit
This commit is contained in:
commit
c2da007f40
1610 changed files with 398047 additions and 0 deletions
492
crypto/func/abscode.cpp
Normal file
492
crypto/func/abscode.cpp
Normal 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
846
crypto/func/analyzer.cpp
Normal 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
329
crypto/func/asmops.cpp
Normal 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
974
crypto/func/builtins.cpp
Normal 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
689
crypto/func/codegen.cpp
Normal 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
248
crypto/func/func.cpp
Normal 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
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
325
crypto/func/gen-abscode.cpp
Normal 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
115
crypto/func/keywords.cpp
Normal 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
539
crypto/func/optimize.cpp
Normal 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
1105
crypto/func/parse-func.cpp
Normal file
File diff suppressed because it is too large
Load diff
826
crypto/func/stack-transform.cpp
Normal file
826
crypto/func/stack-transform.cpp
Normal 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
85
crypto/func/test/a6.fc
Normal 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
75
crypto/func/test/a6.fp
Normal 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
6
crypto/func/test/a6_1.fc
Normal 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
130
crypto/func/test/a6_2.fc
Normal 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
12
crypto/func/test/a7.fc
Normal 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
28
crypto/func/test/a8.fc
Normal 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
23
crypto/func/test/a9.fc
Normal 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
28
crypto/func/test/a9_1.fc
Normal 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
63
crypto/func/test/b1.fc
Normal 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
60
crypto/func/test/b2.fc
Normal 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
63
crypto/func/test/b2_0.fc
Normal 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
62
crypto/func/test/b2_1.fc
Normal 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
58
crypto/func/test/b2_2.fc
Normal 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
384
crypto/func/unify-types.cpp
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue