1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-02-12 11:12:16 +00:00
ton/crypto/tl/tlbc-gen-cpp.cpp
SpyCheese 710514b8f1
Validate Merkle proofs and updates in TLB validate (#1479)
* Validate Merkle proofs and updates in TLB validate

* Fix out-of-bound access in tl_jni_object.cpp
2025-01-16 09:42:05 +03:00

3464 lines
108 KiB
C++

/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2020 Telegram Systems LLP
*/
#include "tlbc-gen-cpp.h"
#include "td/utils/bits.h"
#include "td/utils/filesystem.h"
namespace tlbc {
/*
*
* C++ CODE GENERATION
*
*/
CppIdentSet global_cpp_ids;
std::vector<std::unique_ptr<CppTypeCode>> cpp_type;
bool add_type_members;
std::set<std::string> forbidden_cpp_idents, local_forbidden_cpp_idents;
std::vector<std::string> const_type_expr_cpp_idents;
std::vector<bool> const_type_expr_simple;
void init_forbidden_cpp_idents() {
std::set<std::string>& f = forbidden_cpp_idents;
f.insert("true");
f.insert("false");
f.insert("int");
f.insert("bool");
f.insert("unsigned");
f.insert("long");
f.insert("short");
f.insert("char");
f.insert("void");
f.insert("class");
f.insert("struct");
f.insert("enum");
f.insert("union");
f.insert("public");
f.insert("private");
f.insert("protected");
f.insert("extern");
f.insert("static");
f.insert("final");
f.insert("if");
f.insert("else");
f.insert("while");
f.insert("do");
f.insert("for");
f.insert("break");
f.insert("continue");
f.insert("return");
f.insert("virtual");
f.insert("explicit");
f.insert("override");
f.insert("new");
f.insert("delete");
f.insert("operator");
f.insert("Ref");
f.insert("Cell");
f.insert("CellSlice");
f.insert("Anything");
f.insert("RefAnything");
f.insert("Nat");
f.insert("t_Nat");
f.insert("t_RefCell");
f.insert("t_Anything");
f.insert("TLB");
f.insert("TLB_Complex");
f.insert("PrettyPrinter");
std::set<std::string>& l = local_forbidden_cpp_idents;
l.insert("cons_len");
l.insert("cons_len_exact");
l.insert("cons_tag");
l.insert("skip");
l.insert("validate_skip");
l.insert("get_size");
l.insert("pack");
l.insert("unpack");
l.insert("ops");
l.insert("cs");
l.insert("cb");
l.insert("cell_ref");
l.insert("type_class");
l.insert("pp");
l.insert("weak");
}
std::string CppIdentSet::compute_cpp_ident(std::string orig_ident, int count) {
std::ostringstream os;
int a, r = 0, cnt = 0;
bool prev_skip = false;
for (int c : orig_ident) {
bool pp = prev_skip;
prev_skip = true;
if (c & 0x80) {
if ((c & 0xe0) == 0xc0) {
a = (c & 0x1f);
r = 1;
continue;
} else if ((c & 0xf0) == 0xe0) {
a = (c & 0x0f);
r = 2;
continue;
}
if ((c & 0xc0) != 0x80) {
continue;
}
if (!r) {
continue;
}
a = (a << 6) | (c & 0x3f);
if (--r) {
continue;
}
c = a;
}
prev_skip = false;
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') {
os << (char)c;
cnt++;
continue;
}
if (c >= '0' && c <= '9') {
if (!cnt) {
os << '_';
cnt++;
}
os << (char)c;
cnt++;
continue;
}
if (c >= 0x410 && c < 0x450) {
os << (char)(0xc0 + (c >> 6)) << (char)(0x80 + (c & 0x3f));
cnt++;
continue;
}
prev_skip = true;
if (!pp) {
os << '_';
}
}
if (!cnt) {
os << '_';
}
if (count) {
os << count;
}
return os.str();
}
bool CppIdentSet::is_good_ident(std::string ident) {
return !defined(ident) && !forbidden_cpp_idents.count(ident) &&
!(extra_forbidden_idents && extra_forbidden_idents->count(ident));
}
std::string CppIdentSet::new_ident(std::string orig_ident, int count, std::string suffix) {
while (true) {
std::string ident = compute_cpp_ident(orig_ident, count) + suffix;
if (is_good_ident(ident)) {
cpp_idents.insert(ident);
return ident;
}
++count;
}
}
struct SizeWriter {
int sz;
explicit SizeWriter(int _sz) : sz(_sz) {
}
void write(std::ostream& os) const;
};
void SizeWriter::write(std::ostream& os) const {
if (sz < 0x10000) {
os << sz;
} else {
os << "0x" << std::hex << sz << std::dec;
}
}
std::ostream& operator<<(std::ostream& os, SizeWriter w) {
w.write(os);
return os;
}
unsigned long long CppTypeCode::compute_selector_mask() const {
unsigned long long z = 0, w = 1;
int c = 0;
for (int v : cons_tag_map) {
if (v > c) {
c = v;
z |= w;
}
w <<= 1;
}
return z;
}
struct HexConstWriter {
unsigned long long mask;
explicit HexConstWriter(unsigned long long _mask) : mask(_mask){};
void write(std::ostream& os) const;
};
void HexConstWriter::write(std::ostream& os) const {
if (mask < 32) {
os << mask;
} else {
os << "0x" << std::hex << mask << std::dec;
}
if (mask >= (1ULL << 31)) {
os << (mask >= (1ULL << 32) ? "ULL" : "U");
}
}
std::ostream& operator<<(std::ostream& os, HexConstWriter w) {
w.write(os);
return os;
}
cpp_val_type detect_cpp_type(const TypeExpr* expr) {
if (expr->tp == TypeExpr::te_Ref) {
return ct_cell;
}
if (expr->is_nat) {
return ct_int32;
}
MinMaxSize sz = expr->compute_size();
int l = sz.fixed_bit_size();
if (expr->is_nat_subtype) {
return l == 1 ? ct_bool : ct_int32;
}
if (expr->tp == TypeExpr::te_CondType) {
cpp_val_type subtype = detect_cpp_type(expr->args.at(1));
if (subtype == ct_slice || subtype == ct_cell || subtype == ct_integer || subtype == ct_bitstring ||
subtype == ct_enum) {
return subtype;
}
if ((subtype == ct_int32 || subtype == ct_int64) && expr->args[1]->is_integer() > 0) {
return subtype;
}
return ct_slice;
}
int x = expr->is_integer();
if (sz.max_size() & 0xff) {
return ct_slice;
}
if (!x) {
const Type* ta = expr->type_applied;
if (expr->tp == TypeExpr::te_Apply && ta && ta->is_simple_enum) {
return ct_enum;
}
if (expr->tp == TypeExpr::te_Apply && ta && ta->type_idx < builtin_types_num &&
(ta == Bits_type || ta->get_name().at(0) == 'b')) {
return (l >= 0 && l <= 256) ? ct_bits : ct_bitstring;
}
if (expr->tp == TypeExpr::te_Tuple && expr->args[1]->tp == TypeExpr::te_Apply &&
expr->args[1]->type_applied->is_bool) {
return (l >= 0 && l <= 256) ? ct_bits : ct_bitstring;
}
return ct_slice;
}
l = (sz.max_size() >> 8);
if (x > 0 && l == 1) {
return ct_bool;
}
if (l < 32) {
return ct_int32;
}
if (l == 32) {
return (x < 0 ? ct_int32 : ct_uint32);
}
if (l < 64) {
return ct_int64;
}
if (l == 64) {
return (x < 0 ? ct_int64 : ct_uint64);
}
return ct_integer;
}
cpp_val_type detect_field_cpp_type(const Field& field) {
return field.subrec ? ct_subrecord : detect_cpp_type(field.type);
}
void show_valtype(std::ostream& os, cpp_val_type x, int size = -1, bool pass_value = false) {
switch (x) {
case ct_void:
os << "void";
break;
case ct_slice:
os << "Ref<CellSlice>";
break;
case ct_cell:
os << "Ref<Cell>";
break;
case ct_typeptr:
os << "const TLB*";
break;
case ct_typeref:
os << "const TLB&";
break;
case ct_bitstring:
os << "Ref<td::BitString>";
break;
case ct_bits:
if (pass_value) {
os << "const ";
}
os << "td::BitArray<" << size << ">";
if (pass_value) {
os << "&";
}
break;
case ct_integer:
os << "RefInt256";
break;
case ct_bool:
os << "bool";
break;
case ct_enum:
os << "char";
break;
case ct_int32:
os << "int";
break;
case ct_uint32:
os << "unsigned";
break;
case ct_int64:
os << "long long";
break;
case ct_uint64:
os << "unsigned long long";
break;
case ct_subrecord:
if (pass_value) {
os << "const ";
}
os << "<unknown-cpp-type>::Record";
if (pass_value) {
os << "&";
}
break;
default:
os << "<unknown-cpp-scalar-type>";
}
}
bool CppValType::needs_move() const {
return (vt == ct_cell || vt == ct_slice || vt == ct_bitstring || vt == ct_integer);
}
void CppValType::show(std::ostream& os, bool pass_value) const {
show_valtype(os, vt, size, pass_value);
}
std::ostream& operator<<(std::ostream& os, CppValType cvt) {
cvt.show(os);
return os;
}
void CppTypeCode::ConsField::print_type(std::ostream& os, bool pass_value) const {
if (ctype != ct_subrecord) {
get_cvt().show(os, pass_value);
} else {
assert(subrec);
if (pass_value) {
os << "const ";
}
subrec->print_full_name(os);
if (pass_value) {
os << "&";
}
}
}
void CppTypeCode::ConsRecord::print_full_name(std::ostream& os) const {
os << cpp_type.cpp_type_class_name << "::" << cpp_name;
}
void CppTypeCode::assign_class_name() {
std::string type_name = type.get_name();
sym_idx_t name = type.type_name;
if (!name && type.parent_type_idx >= 0) {
int i = type.parent_type_idx;
while (true) {
name = types.at(i).type_name;
if (name || types.at(i).parent_type_idx < 0) {
break;
}
i = types.at(i).parent_type_idx;
}
if (name) {
type_name = sym::symbols.get_name(name) + "_aux";
}
}
cpp_type_class_name = global_cpp_ids.new_ident(type_name);
if (params) {
cpp_type_template_name = global_cpp_ids.new_ident(cpp_type_class_name + "T");
} else {
cpp_type_var_name = global_cpp_ids.new_ident(std::string{"t_"} + cpp_type_class_name);
}
}
void CppTypeCode::assign_cons_names() {
cons_enum_name.resize(cons_num);
for (int i = 0; i < cons_num; i++) {
sym_idx_t cons = type.constructors.at(i)->constr_name;
if (cons) {
cons_enum_name[i] = local_cpp_ids.new_ident(sym::symbols.get_name(cons));
} else if (type.const_param_idx >= 0) {
int pv = type.constructors[i]->get_const_param(type.const_param_idx);
cons_enum_name[i] = local_cpp_ids.new_ident(pv ? "cons" : "cons0", pv);
} else {
cons_enum_name[i] = local_cpp_ids.new_ident("cons", i + 1);
}
}
}
void CppTypeCode::assign_cons_values() {
std::vector<std::pair<unsigned long long, int>> a;
a.reserve(cons_num);
for (int i = 0; i < cons_num; i++) {
a.emplace_back(type.constructors[i]->begins_with.min(), i);
}
std::sort(a.begin(), a.end());
cons_enum_value.resize(cons_num);
cons_idx_by_enum.resize(cons_num);
int i = 0;
for (auto z : a) {
cons_enum_value[z.second] = i;
cons_idx_by_enum[i++] = z.second;
}
}
std::vector<std::string> std_field_names = {"x", "y", "z", "t", "u", "v", "w"};
void CppTypeCode::assign_record_cons_names() {
for (int i = 0; i < cons_num; i++) {
const Constructor& ctor = *type.constructors.at(i);
records.emplace_back(*this, ctor, i);
ConsRecord& record = records.back();
record.has_trivial_name = (cons_num <= 1 || !ctor.constr_name);
record.declared = false;
record.cpp_name = local_cpp_ids.new_ident(cons_num <= 1 ? "Record" : std::string{"Record_"} + cons_enum_name[i]);
CppIdentSet rec_cpp_ids;
rec_cpp_ids.insert("type_class");
rec_cpp_ids.insert(record.cpp_name);
// maybe : add field identifiers from type class context (?)
for (int j = 0; j < ctor.fields_num; j++) {
const Field& field = ctor.fields.at(j);
if (field.constraint) {
} else if (!field.implicit) {
MinMaxSize sz = field.type->compute_size();
if (!sz.max_size()) {
continue;
}
std::string field_name;
const ConsRecord* subrec = nullptr;
if (field.name) {
field_name = rec_cpp_ids.new_ident(field.get_name());
} else if (field.subrec) {
field_name = rec_cpp_ids.new_ident("r", 1);
subrec = &cpp_type.at(field.type->args.at(0)->type_applied->type_idx)->records.at(0);
} else if (field.type->tp == TypeExpr::te_Ref) {
field_name = rec_cpp_ids.new_ident("ref", 1);
}
record.cpp_fields.emplace_back(field, field_name, detect_field_cpp_type(field), sz.fixed_bit_size(), j, subrec);
} else if (field.used && (add_type_members || field.type->is_nat_subtype)) {
std::string field_name = rec_cpp_ids.new_ident(field.get_name());
record.cpp_fields.emplace_back(field, field_name, field.type->is_nat_subtype ? ct_int32 : ct_typeptr, -1, j,
nullptr, true);
}
}
auto q = std_field_names.cbegin();
for (auto& fi : record.cpp_fields) {
if (fi.name.empty()) {
bool is_ok = false;
while (q < std_field_names.cend()) {
if (!rec_cpp_ids.defined(*q)) {
fi.name = rec_cpp_ids.new_ident(*q++);
is_ok = true;
break;
}
}
if (!is_ok) {
fi.name = rec_cpp_ids.new_ident("f", 1);
}
}
}
record.is_trivial = (record.cpp_fields.size() <= 1);
record.is_small = (record.cpp_fields.size() <= 3);
record.inline_record = (record.cpp_fields.size() <= 2);
cpp_val_type t = ct_unknown;
if (record.is_trivial) {
t = (record.cpp_fields.size() == 1) ? record.cpp_fields.at(0).ctype : ct_void;
}
std::vector<cpp_val_type> tv;
for (const auto& f : record.cpp_fields) {
if (f.ctype == ct_subrecord) {
record.is_trivial = record.is_small = false;
} else if (!f.implicit) {
tv.push_back(f.ctype);
}
}
record.equiv_cpp_type = t;
record.equiv_cpp_types = tv;
record.triv_conflict = false;
for (int j = 0; j < i; j++) {
if (records[j].equiv_cpp_types == tv) {
record.triv_conflict = records[j].triv_conflict = true;
break;
}
}
}
}
bool CppTypeCode::ConsRecord::recover_idents(CppIdentSet& idents) const {
bool is_ok = idents.insert(cpp_name) && idents.insert("type_class");
for (const auto& f : cpp_fields) {
is_ok &= idents.insert(f.name);
}
return is_ok;
}
void CppTypeCode::assign_class_field_names() {
char cn = 'm', ct = 'X';
int c = 0;
for (int z : type.args) {
bool f = z & Type::_IsNat;
bool neg = (z & Type::_IsNeg);
type_param_is_nat.push_back(f);
type_param_is_neg.push_back(neg);
std::string id;
if (!neg && !c++) {
template_args += ", ";
constructor_args += ", ";
}
if (f) {
id = local_cpp_ids.new_ident(std::string{cn}, 0, "_");
if (cn != 't') {
++cn;
}
if (!neg) {
template_args += "int ";
constructor_args += "int ";
} else {
skip_extra_args += ", int& ";
skip_extra_args_pass += ", ";
}
} else {
id = local_cpp_ids.new_ident(std::string{ct}, 0, "_");
if (ct != 'Z') {
++ct;
} else {
ct = 'T';
}
assert(!neg);
template_args += "typename ";
constructor_args += "const TLB& ";
}
type_param_name.push_back(id);
if (!neg) {
template_args += id;
constructor_args += id;
} else {
skip_extra_args += id;
skip_extra_args_pass += id;
}
}
}
bool CppTypeCode::compute_simple_cons_tags() {
if (!type.is_pfx_determ || type.useful_depth > 8) {
return false;
}
int d = type.useful_depth;
int n = (1 << d);
cons_tag_map.resize(n, 0);
//std::cerr << "compute_simple_cons_tags() for `" << type.get_name() << "` (d=" << d << ")\n";
for (int i = 0; i < cons_num; i++) {
int t = cons_enum_value.at(i) + 1;
for (unsigned long long z : type.constructors[i]->begins_with.pfx) {
int l = std::min(63 - td::count_trailing_zeroes_non_zero64(z), d);
assert(l <= d);
int a = d ? (int)((z & (z - 1)) >> (64 - d)) : 0;
int b = (1 << (d - l));
while (b-- > 0) {
assert(!cons_tag_map.at(a) || cons_tag_map[a] == t);
cons_tag_map[a++] = t;
}
}
}
int c = 0;
for (int v : cons_tag_map) {
if (v && v != c && v != ++c) {
return false;
}
}
return true;
}
bool CppTypeCode::check_incremental_cons_tags() const {
if (!cons_num || common_cons_len < 0) {
return false;
}
int l = common_cons_len;
if (!l || l > 32) {
return true;
}
for (int i = 0; i < cons_num; i++) {
unsigned long long tag = (type.constructors.at(i)->tag >> (64 - l));
if (tag != (unsigned)cons_enum_value.at(i)) {
return false;
}
}
return true;
}
bool CppTypeCode::init() {
builtin = type.is_builtin;
cons_num = type.constr_num;
params = ret_params = tot_params = 0;
for (int z : type.args) {
if ((z & Type::_IsNeg)) {
++ret_params;
} else {
++params;
}
++tot_params;
}
assign_class_name();
assign_cons_names();
assign_class_field_names();
assign_cons_values();
assign_record_cons_names();
simple_get_size = type.has_fixed_size;
inline_skip = simple_get_size;
inline_validate_skip = (inline_skip && type.any_bits && !(type.size.min_size() & 0xff));
inline_get_tag = (type.is_pfx_determ && type.useful_depth <= 6);
simple_cons_tags = compute_simple_cons_tags();
common_cons_len = type.cons_common_len();
incremental_cons_tags = check_incremental_cons_tags();
return true;
}
void CppTypeCode::generate_cons_enum(std::ostream& os) {
os << " enum { ";
for (int i = 0; i < cons_num; i++) {
if (i) {
os << ", ";
}
int k = cons_idx_by_enum.at(i);
os << cons_enum_name.at(k);
assert(cons_enum_value.at(k) == i);
}
os << " };\n";
}
void CppTypeCode::generate_cons_len_array(std::ostream& os, std::string nl, int options) {
bool f = (options & 2);
os << nl << (f ? "" : "static ") << ((options & 3) ? "constexpr " : "") << "char ";
if (f) {
os << cpp_type_class_name << "::";
}
os << "cons_len[" << cons_num << "]";
if (f) {
os << ";\n";
return;
}
os << " = { ";
for (int i = 0; i < cons_num; i++) {
int k = cons_idx_by_enum.at(i);
const Constructor& constr = *type.constructors.at(k);
if (i > 0) {
os << ", ";
}
os << constr.tag_bits;
}
os << " };\n";
}
void CppTypeCode::generate_cons_tag_array(std::ostream& os, std::string nl, int options) {
int m = -1;
for (int i = 0; i < cons_num; i++) {
int k = cons_idx_by_enum.at(i);
const Constructor& constr = *type.constructors.at(k);
if (constr.tag_bits > m) {
m = constr.tag_bits;
}
}
bool f = (options & 2);
os << nl << (f ? "" : "static ") << ((options & 3) ? "constexpr " : "");
if (m <= 8) {
os << "unsigned char ";
} else if (m <= 16) {
os << "unsigned short ";
} else if (m <= 32) {
os << "unsigned ";
} else {
os << "unsigned long long ";
}
if (f) {
os << cpp_type_class_name << "::";
}
os << "cons_tag[" << cons_num << "]";
if (f) {
os << ";\n";
return;
}
os << " = { ";
for (int i = 0; i < cons_num; i++) {
int k = cons_idx_by_enum.at(i);
const Constructor& constr = *type.constructors.at(k);
if (i > 0) {
os << ", ";
}
os << HexConstWriter{constr.tag_bits ? (constr.tag >> (64 - constr.tag_bits)) : 0};
}
os << " };\n";
}
void CppTypeCode::generate_cons_tag_info(std::ostream& os, std::string nl, int options) {
if (cons_num) {
if (common_cons_len == -1) {
generate_cons_len_array(os, nl, options);
} else if (options & 1) {
os << " static constexpr int cons_len_exact = " << common_cons_len << ";\n";
}
if (common_cons_len != 0 && !incremental_cons_tags) {
generate_cons_tag_array(os, nl, options);
}
}
}
void CppTypeCode::generate_get_tag_subcase(std::ostream& os, std::string nl, const BinTrie* trie, int depth) const {
if (!trie || !trie->down_tag) {
os << nl << "return -1; // ???";
return;
}
if (trie->is_unique()) {
os << nl << "return " << cons_enum_name.at(trie->unique_value()) << ";";
return;
}
if (!trie->useful_depth) {
generate_get_tag_param(os, nl, trie->down_tag);
return;
}
assert(trie->left || trie->right);
if (!trie->right) {
generate_get_tag_subcase(os, nl, trie->left.get(), depth + 1);
return;
}
if (!trie->left) {
generate_get_tag_subcase(os, nl, trie->right.get(), depth + 1);
return;
}
if (trie->left->is_unique() && trie->right->is_unique()) {
os << nl << "return cs.bit_at(" << depth << ") ? ";
int a = trie->right->unique_value(), b = trie->left->unique_value();
os << (a >= 0 ? cons_enum_name.at(a) : "-1") << " : ";
os << (b >= 0 ? cons_enum_name.at(b) : "-1") << ";";
return;
}
os << nl << "if (cs.bit_at(" << depth << ")) {";
generate_get_tag_subcase(os, nl + " ", trie->right.get(), depth + 1);
os << nl << "} else {";
generate_get_tag_subcase(os, nl + " ", trie->left.get(), depth + 1);
os << nl << "}";
}
void CppTypeCode::generate_get_tag_param(std::ostream& os, std::string nl, unsigned long long tag,
unsigned long long tag_params) const {
if (!tag) {
os << nl << "return -1; // ???";
return;
}
if (!(tag & (tag - 1))) {
os << nl << "return " << cons_enum_name.at(td::count_trailing_zeroes64(tag)) << ";";
return;
}
int cnt = td::count_bits64(tag);
DCHECK(cnt >= 2);
int mdim = 0, mmdim = 0;
for (int c = 0; c < 64; c++) {
if ((tag >> c) & 1) {
int dim = type.constructors.at(c)->admissible_params.dim;
if (dim > mdim) {
mmdim = mdim;
mdim = dim;
} else if (dim > mmdim) {
mmdim = dim;
}
}
}
assert(mmdim > 0);
for (int p1 = 0; p1 < mmdim; p1++) {
char A[4];
std::memset(A, 0, sizeof(A));
int c;
for (c = 0; c < 64; c++) {
if ((tag >> c) & 1) {
if (!type.constructors[c]->admissible_params.extract1(A, (char)(c + 1), p1)) {
break;
}
}
}
if (c == 64) {
std::string param_name = get_nat_param_name(p1);
generate_get_tag_param1(os, nl, A, &param_name);
return;
}
}
for (int p2 = 0; p2 < mmdim; p2++) {
for (int p1 = 0; p1 < p2; p1++) {
char A[4][4];
std::memset(A, 0, sizeof(A));
int c;
for (c = 0; c < 64; c++) {
if ((tag >> c) & 1) {
if (!type.constructors[c]->admissible_params.extract2(A, (char)(c + 1), p1, p2)) {
break;
}
}
}
if (c == 64) {
std::string param_names[2];
param_names[0] = get_nat_param_name(p1);
param_names[1] = get_nat_param_name(p2);
generate_get_tag_param2(os, nl, A, param_names);
return;
}
}
}
for (int p3 = 0; p3 < mmdim; p3++) {
for (int p2 = 0; p2 < p3; p2++) {
for (int p1 = 0; p1 < p2; p1++) {
char A[4][4][4];
std::memset(A, 0, sizeof(A));
int c;
for (c = 0; c < 64; c++) {
if ((tag >> c) & 1) {
if (!type.constructors[c]->admissible_params.extract3(A, (char)(c + 1), p1, p2, p3)) {
break;
}
}
}
if (c == 64) {
std::string param_names[3];
param_names[0] = get_nat_param_name(p1);
param_names[1] = get_nat_param_name(p2);
param_names[2] = get_nat_param_name(p3);
generate_get_tag_param3(os, nl, A, param_names);
return;
}
}
}
}
os << nl << "// ??? cannot distinguish constructors for this type using up to three parameters\n";
throw src::Fatal{std::string{"cannot generate `"} + cpp_type_class_name + "::get_tag()` method for type `" +
type.get_name() + "`"};
}
bool CppTypeCode::match_param_pattern(std::ostream& os, std::string nl, const char A[4], int mask, std::string pattern,
std::string param_name) const {
int v = 0, w = 0;
for (int i = 0; i < 4; i++) {
if (A[i]) {
if ((mask >> i) & 1) {
v = (v && v != A[i] ? -1 : A[i]);
} else {
w = (w && w != A[i] ? -1 : A[i]);
}
}
}
if (v <= 0 || w <= 0) {
return false;
}
os << nl << "return ";
for (char c : pattern) {
if (c != '#') {
os << c;
} else {
os << param_name;
}
}
os << " ? " << cons_enum_name.at(v - 1) << " : " << cons_enum_name.at(w - 1) << ";";
return true;
}
void CppTypeCode::generate_get_tag_param1(std::ostream& os, std::string nl, const char A[4],
const std::string param_names[1]) const {
os << nl << "// distinguish by parameter `" << param_names[0] << "` using";
for (int i = 0; i < 4; i++) {
os << ' ' << (int)A[i];
}
if (match_param_pattern(os, nl, A, 14, "#", param_names[0]) ||
match_param_pattern(os, nl, A, 2, "# == 1", param_names[0]) ||
match_param_pattern(os, nl, A, 3, "# <= 1", param_names[0]) ||
match_param_pattern(os, nl, A, 10, "(# & 1)", param_names[0]) ||
match_param_pattern(os, nl, A, 4, "# && !(# & 1)", param_names[0]) ||
match_param_pattern(os, nl, A, 8, "# > 1 && (# & 1)", param_names[0])) {
return;
}
os << nl << "// static inline size_t nat_abs(int x) { return (x > 1) * 2 + (x & 1); }";
os << nl << "static signed char ctab[4] = { ";
for (int i = 0; i < 4; i++) {
if (i > 0) {
os << ", ";
}
os << (A[i] ? cons_enum_name.at(A[i] - 1) : "-1");
}
os << " };" << nl << "return ctab[nat_abs(" << param_names[0] << ")];";
}
void CppTypeCode::generate_get_tag_param2(std::ostream& os, std::string nl, const char A[4][4],
const std::string param_names[2]) const {
os << nl << "// distinguish by parameters `" << param_names[0] << "`, `" << param_names[1] << "` using";
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
os << ' ' << (int)A[i][j];
}
}
os << nl << "// static inline size_t nat_abs(int x) { return (x > 1) * 2 + (x & 1); }";
os << nl << "static signed char ctab[4][4] = { ";
for (int i = 0; i < 16; i++) {
if (i > 0) {
os << ", ";
}
int v = A[i >> 2][i & 3];
os << (v ? cons_enum_name.at(v - 1) : "-1");
}
os << " };" << nl << "return ctab[nat_abs(" << param_names[0] << ")][nat_abs(" << param_names[1] << ")];";
}
void CppTypeCode::generate_get_tag_param3(std::ostream& os, std::string nl, const char A[4][4][4],
const std::string param_names[3]) const {
os << nl << "// distinguish by parameters `" << param_names[0] << "`, `" << param_names[1] << "`, `" << param_names[2]
<< "` using";
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
for (int k = 0; k < 4; k++) {
os << ' ' << (int)A[i][j][k];
}
}
}
os << nl << "// static inline size_t nat_abs(int x) { return (x > 1) * 2 + (x & 1); }";
os << nl << "static signed char ctab[4][4][4] = { ";
for (int i = 0; i < 64; i++) {
if (i > 0) {
os << ", ";
}
int v = A[i >> 4][(i >> 2) & 3][i & 3];
os << (v ? cons_enum_name.at(v - 1) : "-1");
}
os << " };" << nl << "return ctab[nat_abs(" << param_names[0] << ")][nat_abs(" << param_names[1] << ")][nat_abs("
<< param_names[2] << ")];";
}
std::string CppTypeCode::get_nat_param_name(int idx) const {
for (int i = 0; i < tot_params; i++) {
if (!type_param_is_neg.at(i) && type_param_is_nat.at(i) && !idx--) {
return type_param_name.at(i);
}
}
return "???";
}
void CppTypeCode::generate_tag_pfx_selector(std::ostream& os, std::string nl, const BinTrie& trie, int d,
int min_size) const {
assert(d >= 0 && d <= 6);
int n = (1 << d);
unsigned long long A[64];
int c[65];
unsigned long long mask = trie.build_submap(d, A);
int l = 1;
c[0] = -1;
for (int i = 0; i < n; i++) {
assert(!(A[i] & (A[i] - 1)));
if ((mask >> l) & 1) {
c[l++] = A[i] ? td::count_trailing_zeroes_non_zero64(A[i]) : -1;
}
}
bool simple = (l > n / 2);
if (simple) {
l = n + 1;
for (int i = 0; i < n; i++) {
c[i + 1] = A[i] ? td::count_trailing_zeroes_non_zero64(A[i]) : -1;
}
}
os << nl << "static signed char ctab[" << l << "] = {";
for (int i = 0; i < l; i++) {
if (i > 0) {
os << ", ";
}
if (c[i] < 0) {
os << c[i];
} else {
os << cons_enum_name.at(c[i]);
}
}
os << "};" << nl << "return ctab[1 + ";
if (simple) {
os << "(long long)cs.prefetch_ulong(" << d << ")];";
} else {
os << "(long long)cs.bselect" << (d >= min_size ? "(" : "_ext(") << d << ", " << HexConstWriter{mask} << ")];";
}
}
bool CppTypeCode::generate_get_tag_pfx_distinguisher(std::ostream& os, std::string nl,
const std::vector<int>& constr_list, bool in_block) const {
if (constr_list.empty()) {
os << nl << " return -1;";
return false;
}
if (constr_list.size() == 1) {
os << nl << " return " << cons_enum_name.at(constr_list[0]) << ";";
return false;
}
std::unique_ptr<BinTrie> trie;
for (int i : constr_list) {
trie = BinTrie::insert_paths(std::move(trie), type.constructors.at(i)->begins_with, 1ULL << i);
}
if (!trie) {
os << nl << " return -1;";
return false;
}
int d = trie->compute_useful_depth();
bool is_pfx_determ = !trie->find_conflict_path();
assert(is_pfx_determ);
if (!in_block) {
os << " {";
}
generate_tag_pfx_selector(os, nl, *trie, d, (int)(type.size.min_size() >> 8));
return !in_block;
}
void CppTypeCode::generate_get_tag_body(std::ostream& os, std::string nl) {
int d = type.useful_depth;
cons_tag_exact.resize(cons_num, false);
if (type.is_pfx_determ) {
if (!cons_num) {
os << nl << "return -1;";
return;
}
if (!d) {
assert(simple_cons_tags && cons_num == 1);
cons_tag_exact[0] = !(type.constructors.at(0)->tag_bits);
os << nl << "return 0;";
return;
}
int min_size = (int)(type.size.min_size() >> 8);
bool always_has = (d <= min_size);
if (d <= 6 && simple_cons_tags) {
unsigned long long sm = compute_selector_mask();
if (always_has && sm + 1 == (2ULL << ((1 << d) - 1))) {
for (int i = 0; i < cons_num; i++) {
cons_tag_exact[i] = (type.constructors.at(i)->tag_bits <= d);
}
os << nl << "return (int)cs.prefetch_ulong(" << d << ");";
return;
}
for (int i = 0; i < cons_num; i++) {
unsigned long long tag = type.constructors.at(i)->tag;
int l = 63 - td::count_trailing_zeroes_non_zero64(tag);
if (l <= d) {
int a = (int)((tag & (tag - 1)) >> (64 - d));
int b = a + (1 << (d - l));
cons_tag_exact[i] = ((sm >> a) & 1) && (b == (1 << d) || ((sm >> b) & 1));
}
}
os << nl << "return cs.bselect" << (always_has ? "(" : "_ext(") << d << ", " << HexConstWriter{sm} << ");";
return;
}
if (d <= 6) {
generate_tag_pfx_selector(os, nl, *(type.cs_trie), d, min_size);
return;
}
}
if (type.is_const_param_determ || type.is_const_param_pfx_determ) {
int p = type.const_param_idx;
assert(p >= 0);
std::vector<int> param_values = type.get_all_param_values(p);
assert(param_values.size() > 1 && param_values.at(0) >= 0);
os << nl << "switch (" << type_param_name.at(p) << ") {";
for (int pv : param_values) {
assert(pv >= 0);
os << nl << "case " << pv << ":";
std::vector<int> constr_list = type.get_constr_by_param_value(p, pv);
assert(!constr_list.empty());
if (constr_list.size() == 1) {
os << nl << " return " << cons_enum_name.at(constr_list[0]) << ";";
continue;
}
bool opbr = generate_get_tag_pfx_distinguisher(os, nl + " ", constr_list, false);
if (opbr) {
os << nl << "}";
}
}
os << nl << "default:" << nl << " return -1;" << nl << "}";
return;
}
if (d) {
int d1 = std::min(6, d);
int n = (1 << d1);
bool always_has = (d1 <= (int)(type.size.min_size() >> 8));
unsigned long long A[64], B[64];
unsigned long long mask = type.cs_trie->build_submap(d1, A);
int l = td::count_bits64(mask);
bool simple = (l > n / 2 || n <= 8);
if (!simple) {
int j = 0;
for (int i = 0; i < n; i++) {
if ((mask >> i) & 1) {
//std::cerr << i << ',' << std::hex << A[i] << std::dec << std::endl;
B[j] = (2 * i + 1ULL) << (63 - d1);
A[j++] = A[i];
}
}
assert(j == l);
} else {
for (int i = 0; i < n; i++) {
B[i] = (2 * i + 1ULL) << (63 - d1);
}
l = n;
}
os << nl << "switch (";
if (simple) {
os << "(int)cs.prefetch_ulong(" << d1;
} else {
os << "cs.bselect" << (always_has ? "(" : "_ext(") << d1 << ", " << HexConstWriter{mask};
}
os << ")) {";
for (int i = 0; i < l; i++) {
if (A[i] != 0) {
if ((long long)A[i] > 0) {
int j;
for (j = 0; j < i; j++) {
if (A[j] == A[i]) {
break;
}
}
if (j < i) {
continue;
}
}
os << nl << "case " << i << ":";
if ((long long)A[i] > 0) {
int j;
for (j = i + 1; j < l; j++) {
if (A[j] == A[i]) {
os << " case " << j << ":";
}
}
if (!(A[i] & (A[i] - 1))) {
os << nl << " return " << cons_enum_name.at(td::count_trailing_zeroes_non_zero64(A[i])) << ";";
}
} else {
generate_get_tag_subcase(os, nl + " ", type.cs_trie->lookup_node_const(B[i]), d1);
}
}
}
os << nl << "default:" << nl << " return -1;" << nl << "}";
} else {
generate_get_tag_subcase(os, nl, type.cs_trie.get(), 0);
}
}
void CppTypeCode::generate_type_fields(std::ostream& os, int options) {
int st = -1;
for (int i = 0; i < tot_params; i++) {
if (type_param_is_neg[i]) {
continue;
}
int nst = type_param_is_nat[i];
if (st != nst) {
if (st >= 0) {
os << ";\n";
}
os << (nst ? " int " : " const TLB ");
st = nst;
} else {
os << ", ";
}
if (!nst) {
os << '&';
}
os << type_param_name[i];
}
if (st >= 0) {
os << ";\n";
}
}
static std::string constr_arg_name(std::string type_field_name) {
if (type_field_name.size() <= 1 || type_field_name.back() != '_') {
return std::string{"_"} + type_field_name;
} else {
return {type_field_name, 0, type_field_name.size() - 1};
}
}
void CppTypeCode::generate_type_constructor(std::ostream& os, int options) {
os << " " << cpp_type_class_name << "(";
for (int i = 0, j = 0; i < tot_params; i++) {
if (type_param_is_neg[i]) {
continue;
}
if (j++ > 0) {
os << ", ";
}
os << (type_param_is_nat[i] ? "int " : "const TLB& ");
os << constr_arg_name(type_param_name[i]);
}
os << ")";
for (int i = 0, j = 0; i < tot_params; i++) {
if (type_param_is_neg[i]) {
continue;
}
if (j++ > 0) {
os << ", ";
} else {
os << " : ";
}
os << type_param_name[i] << "(" << constr_arg_name(type_param_name[i]) << ")";
}
os << " {}\n";
}
void Action::show(std::ostream& os) const {
if (fixed_size >= 0) {
if (!fixed_size) {
os << "true";
} else if (fixed_size < 0x10000) {
os << "cs.advance(" << fixed_size << ")";
} else if (!(fixed_size & 0xffff)) {
os << "cs.advance_refs(" << (fixed_size >> 16) << ")";
} else {
os << "cs.advance_ext(0x" << std::hex << fixed_size << std::dec << ")";
}
} else {
os << action;
}
}
bool Action::may_combine(const Action& next) const {
return !fixed_size || !next.fixed_size || (fixed_size >= 0 && next.fixed_size >= 0);
}
bool Action::operator+=(const Action& next) {
if (!next.fixed_size) {
return true;
}
if (!fixed_size) {
fixed_size = next.fixed_size;
action = next.action;
return true;
}
if (fixed_size >= 0 && next.fixed_size >= 0) {
fixed_size += next.fixed_size;
return true;
}
return false;
}
void operator+=(std::vector<Action>& av, const Action& next) {
if (av.empty() || !(av.back() += next)) {
if (next.is_constraint && !av.empty() && av.back().fixed_size >= 0) {
Action last = av.back();
av.pop_back();
av.push_back(next);
av.push_back(last);
} else {
av.push_back(next);
}
}
}
void CppTypeCode::clear_context() {
actions.clear();
incomplete = 0;
tmp_ints = 0;
needs_tmp_cell = false;
tmp_vars.clear();
field_vars.clear();
field_var_set.clear();
param_var_set.clear();
param_constraint_used.clear();
tmp_cpp_ids.clear();
tmp_cpp_ids.new_ident("cs");
tmp_cpp_ids.new_ident("cb");
tmp_cpp_ids.new_ident("cell_ref");
tmp_cpp_ids.new_ident("t");
}
std::string CppTypeCode::new_tmp_var() {
char buffer[16];
while (true) {
snprintf(buffer, sizeof(buffer), "t%d", ++tmp_ints);
if (tmp_cpp_ids.is_good_ident(buffer) && local_cpp_ids.is_good_ident(buffer)) {
break;
}
}
std::string s{buffer};
s = tmp_cpp_ids.new_ident(s);
tmp_vars.push_back(s);
return s;
}
std::string CppTypeCode::new_tmp_var(std::string hint) {
if (hint.empty() || hint == "_") {
return new_tmp_var();
}
int count = 0;
while (true) {
std::string s = local_cpp_ids.compute_cpp_ident(hint, count++);
if (tmp_cpp_ids.is_good_ident(s) && local_cpp_ids.is_good_ident(s)) {
s = tmp_cpp_ids.new_ident(s);
tmp_vars.push_back(s);
return s;
}
}
}
std::string compute_type_class_name(const Type* typ, int& fake_arg) {
fake_arg = -1;
int idx = typ->type_idx;
if (idx >= builtin_types_num) {
return cpp_type[idx]->cpp_type_class_name;
} else if (typ->produces_nat) {
if (typ == Nat_type) {
return "Nat";
} else if (typ == NatWidth_type) {
return "NatWidth";
} else if (typ == NatLeq_type) {
return "NatLeq";
} else if (typ == NatLess_type) {
return "NatLess";
}
// ...
} else if (typ == Any_type) {
return "Anything";
} else if (typ->has_fixed_size) {
fake_arg = (typ->size.min_size() >> 8);
int c = typ->get_name()[0];
return (c == 'b') ? "Bits" : ((c == 'u') ? "UInt" : "Int");
} else if (typ == Int_type) {
return "Int";
} else if (typ == UInt_type) {
return "UInt";
} else if (typ == Bits_type) {
return "Bits";
}
return "<Unknown_Builtin_Type>";
}
std::string compute_type_expr_class_name(const TypeExpr* expr, int& fake_arg) {
switch (expr->tp) {
case TypeExpr::te_Apply:
return compute_type_class_name(expr->type_applied, fake_arg);
case TypeExpr::te_Ref:
return "RefT";
case TypeExpr::te_Tuple:
return "TupleT";
case TypeExpr::te_CondType:
return "CondT";
}
return "<Unknown-Type-Class>";
}
void CppTypeCode::output_cpp_expr(std::ostream& os, const TypeExpr* expr, int prio, bool allow_type_neg) const {
if (expr->negated) {
if (!allow_type_neg || expr->tp != TypeExpr::te_Apply) {
throw src::Fatal{static_cast<std::ostringstream&&>(std::ostringstream{} << "cannot convert negated expression `"
<< expr << "` into C++ code")
.str()};
}
}
int pos_args = 0;
for (const TypeExpr* arg : expr->args) {
pos_args += !arg->negated;
}
switch (expr->tp) {
case TypeExpr::te_Param: {
int i = expr->value;
assert(field_var_set.at(i));
std::string fv = field_vars.at(i);
assert(!fv.empty());
os << fv;
return;
}
case TypeExpr::te_Apply:
if (!pos_args && expr->type_applied->type_idx >= builtin_types_num) {
int type_idx = expr->type_applied->type_idx;
const CppTypeCode& cc = *cpp_type.at(type_idx);
assert(!cc.cpp_type_var_name.empty());
os << cc.cpp_type_var_name;
return;
}
// fall through
case TypeExpr::te_Ref:
case TypeExpr::te_CondType:
case TypeExpr::te_Tuple:
if (expr->is_constexpr > 0) {
os << const_type_expr_cpp_idents.at(expr->is_constexpr);
return;
} else {
int fake_arg = -1;
os << compute_type_expr_class_name(expr, fake_arg);
os << "{";
int c = 0;
if (fake_arg >= 0) {
os << fake_arg;
c = 1;
}
for (const TypeExpr* arg : expr->args) {
if (!arg->negated) {
os << (c++ ? ", " : "");
output_cpp_expr(os, arg);
}
}
os << '}';
return;
}
case TypeExpr::te_Add:
if (prio > 10) {
os << "(";
}
output_cpp_expr(os, expr->args[0], 10);
os << " + ";
output_cpp_expr(os, expr->args[1], 10);
if (prio > 10) {
os << ")";
}
return;
case TypeExpr::te_MulConst:
if (prio > 20) {
os << "(";
}
os << expr->value;
os << " * ";
output_cpp_expr(os, expr->args[0], 20);
if (prio > 20) {
os << ")";
}
return;
case TypeExpr::te_GetBit:
if (prio > 0) {
os << "(";
}
output_cpp_expr(os, expr->args[0], 5);
os << " & ";
if (expr->args[1]->tp == TypeExpr::te_IntConst && (unsigned)expr->args[1]->value <= 31) {
int v = expr->args[1]->value;
if (v > 1024) {
os << "0x" << std::hex << (1 << v) << std::dec;
} else {
os << (1 << v);
}
} else {
os << "(1 << ";
output_cpp_expr(os, expr->args[1], 5);
os << ")";
}
if (prio > 0) {
os << ")";
}
return;
case TypeExpr::te_IntConst:
os << expr->value;
return;
}
os << "<unknown-expression>";
}
bool CppTypeCode::can_compute_sizeof(const TypeExpr* expr) const {
if (expr->negated || expr->is_nat) {
return false;
}
MinMaxSize sz = expr->compute_size();
if (sz.is_fixed()) {
return !(sz.min_size() & 0xff);
}
if (expr->tp == TypeExpr::te_Apply && (expr->type_applied == Int_type || expr->type_applied == UInt_type ||
expr->type_applied == NatWidth_type || expr->type_applied == Bits_type)) {
return true;
}
if (expr->tp != TypeExpr::te_CondType && expr->tp != TypeExpr::te_Tuple) {
return false;
}
return can_compute_sizeof(expr->args[1]);
}
void CppTypeCode::output_cpp_sizeof_expr(std::ostream& os, const TypeExpr* expr, int prio) const {
if (expr->negated) {
throw src::Fatal{static_cast<std::ostringstream&&>(std::ostringstream{}
<< "cannot compute size of negated type expression `" << expr
<< "` in C++ code")
.str()};
}
if (expr->is_nat) {
throw src::Fatal{static_cast<std::ostringstream&&>(std::ostringstream{}
<< "cannot compute size of non-type expression `" << expr
<< "` in C++ code")
.str()};
}
MinMaxSize sz = expr->compute_size();
if (sz.is_fixed()) {
os << SizeWriter{(int)sz.convert_min_size()};
return;
}
switch (expr->tp) {
case TypeExpr::te_CondType:
if (prio > 5) {
os << '(';
}
output_cpp_expr(os, expr->args[0], 5);
os << " ? ";
output_cpp_sizeof_expr(os, expr->args[1], 6);
os << " : 0";
if (prio > 5) {
os << ')';
}
return;
case TypeExpr::te_Tuple:
if (expr->args[0]->tp == TypeExpr::te_IntConst && expr->args[0]->value == 1) {
output_cpp_sizeof_expr(os, expr->args[1], prio);
return;
}
sz = expr->args[1]->compute_size();
if (sz.is_fixed() && sz.convert_min_size() == 1) {
output_cpp_expr(os, expr->args[0], prio);
return;
}
if (prio > 20) {
os << '(';
}
output_cpp_expr(os, expr->args[0], 20);
os << " * ";
output_cpp_sizeof_expr(os, expr->args[1], 20);
if (prio > 20) {
os << ')';
}
return;
case TypeExpr::te_Apply:
if (expr->type_applied == Int_type || expr->type_applied == UInt_type || expr->type_applied == NatWidth_type ||
expr->type_applied == Bits_type) {
output_cpp_expr(os, expr->args[0], prio);
return;
}
// no break
}
os << "<unknown-expression>";
}
bool CppTypeCode::can_compute(const TypeExpr* expr) const {
if (expr->negated) {
return false;
}
if (expr->tp == TypeExpr::te_Param) {
return field_var_set.at(expr->value);
}
for (const TypeExpr* arg : expr->args) {
if (!can_compute(arg)) {
return false;
}
}
return true;
}
bool CppTypeCode::can_use_to_compute(const TypeExpr* expr, int i) const {
if (!expr->negated || !expr->is_nat) {
return false;
}
if (expr->tp == TypeExpr::te_Param) {
return expr->value == i;
}
for (const TypeExpr* arg : expr->args) {
if (!(arg->negated ? can_use_to_compute(arg, i) : can_compute(arg))) {
return false;
}
}
return true;
}
void CppTypeCode::add_compute_actions(const TypeExpr* expr, int i, std::string bind_to) {
assert(expr->negated && expr->is_nat);
switch (expr->tp) {
case TypeExpr::te_MulConst: {
assert(expr->args.size() == 1 && expr->value > 0);
const TypeExpr* x = expr->args[0];
assert(x->negated);
std::string tmp;
if (x->tp != TypeExpr::te_Param || (x->value != i && i >= 0)) {
tmp = new_tmp_var();
} else {
i = x->value;
tmp = field_vars.at(i);
assert(!tmp.empty());
assert(!field_var_set[i]);
field_var_set[i] = true;
x = nullptr;
}
std::ostringstream ss;
ss << "mul_r1(" << tmp << ", " << expr->value << ", " << bind_to << ")";
actions += Action{std::move(ss), true};
if (x) {
add_compute_actions(x, i, tmp);
}
return;
}
case TypeExpr::te_Add: {
assert(expr->args.size() == 2);
const TypeExpr *x = expr->args[0], *y = expr->args[1];
assert(x->negated ^ y->negated);
if (!x->negated) {
std::swap(x, y);
}
std::string tmp;
if (x->tp != TypeExpr::te_Param || (x->value != i && i >= 0)) {
tmp = new_tmp_var();
} else {
i = x->value;
tmp = field_vars.at(i);
assert(!tmp.empty());
assert(!field_var_set[i]);
field_var_set[i] = true;
x = nullptr;
}
std::ostringstream ss;
ss << "add_r1(" << tmp << ", ";
output_cpp_expr(ss, y);
ss << ", " << bind_to << ")";
actions += Action{std::move(ss), true};
if (x) {
add_compute_actions(x, i, tmp);
}
return;
}
case TypeExpr::te_Param:
assert(expr->value == i || i < 0);
i = expr->value;
assert(!field_vars.at(i).empty());
if (!field_var_set.at(i)) {
actions += Action{std::string{"("} + field_vars.at(i) + " = " + bind_to + ") >= 0"};
field_var_set[i] = true;
} else {
actions += Action{field_vars.at(i) + " == " + bind_to};
}
return;
}
throw src::Fatal{static_cast<std::ostringstream&&>(std::ostringstream{} << "cannot use expression `" << expr << "` = "
<< bind_to << " to set field variable "
<< (i >= 0 ? field_vars.at(i) : "<unknown>"))
.str()};
}
bool CppTypeCode::is_self(const TypeExpr* expr, const Constructor& constr) const {
if (expr->tp != TypeExpr::te_Apply || expr->type_applied != &type || (int)expr->args.size() != tot_params) {
return false;
}
assert(constr.params.size() == expr->args.size());
for (int i = 0; i < tot_params; i++) {
assert(type_param_is_neg[i] == expr->args[i]->negated);
assert(type_param_is_neg[i] == constr.param_negated[i]);
if (!type_param_is_neg[i] && !expr->args[i]->equal(*constr.params[i])) {
return false;
}
}
return true;
}
void CppTypeCode::init_cons_context(const Constructor& constr) {
clear_context();
field_vars.resize(constr.fields.size());
field_var_set.resize(constr.fields.size(), false);
param_var_set.resize(params + ret_params, false);
param_constraint_used.resize(params + ret_params, false);
}
void CppTypeCode::identify_cons_params(const Constructor& constr, int options) {
int j = 0;
for (const TypeExpr* pexpr : constr.params) {
if (pexpr->tp == TypeExpr::te_Param) {
if (!type_param_is_neg.at(j)) {
int i = pexpr->value;
if (field_var_set.at(i)) {
// field i and parameter j must be equal
actions += Action{type_param_name.at(j) + " == " + field_vars.at(i)};
param_constraint_used[j] = true;
} else if (field_vars.at(i).empty()) {
// identify field i with parameter j
field_vars[i] = type_param_name.at(j);
field_var_set[i] = true;
param_constraint_used[j] = true;
}
} else if (!(options & 2)) {
tmp_vars.push_back(type_param_name.at(j));
}
}
j++;
}
}
void CppTypeCode::identify_cons_neg_params(const Constructor& constr, int options) {
int j = 0;
for (const TypeExpr* pexpr : constr.params) {
if (pexpr->tp == TypeExpr::te_Param && type_param_is_neg.at(j)) {
int i = pexpr->value;
if (!field_var_set.at(i) && field_vars.at(i).empty()) {
// identify field i with parameter j
field_vars[i] = type_param_name.at(j);
param_constraint_used[j] = true;
}
}
j++;
}
}
void CppTypeCode::add_cons_tag_check(const Constructor& constr, int cidx, int options) {
if (constr.tag_bits) {
if ((options & 1) && ((options & 8) || cons_num == 1 || !cons_tag_exact.at(cidx))) {
std::ostringstream ss;
int l = constr.tag_bits;
unsigned long long tag = (constr.tag >> (64 - l));
if (l < 64) {
ss << "cs.fetch_ulong(" << l << ") == " << HexConstWriter{tag};
} else {
ss << "cs.begins_with_skip(" << l << ", " << HexConstWriter{tag} << ")";
}
actions.emplace_back(std::move(ss));
} else {
actions.emplace_back(constr.tag_bits);
}
}
}
void CppTypeCode::add_cons_tag_store(const Constructor& constr, int cidx) {
if (constr.tag_bits) {
std::ostringstream ss;
int l = constr.tag_bits;
unsigned long long tag = (constr.tag >> (64 - l));
ss << "cb.store_long_bool(" << HexConstWriter{tag} << ", " << l << ")";
actions.emplace_back(std::move(ss));
}
}
void CppTypeCode::add_remaining_param_constraints_check(const Constructor& constr, int options) {
int j = 0;
for (const TypeExpr* pexpr : constr.params) {
if (!param_constraint_used.at(j)) {
std::ostringstream ss;
if (!type_param_is_neg.at(j)) {
ss << type_param_name.at(j) << " == ";
output_cpp_expr(ss, pexpr);
actions += Action{std::move(ss)};
} else if (options & 2) {
ss << "(" << type_param_name.at(j) << " = ";
output_cpp_expr(ss, pexpr);
ss << ") >= 0";
actions += Action{std::move(ss), true};
}
}
++j;
}
}
void CppTypeCode::output_actions(std::ostream& os, std::string nl, int options) {
bool opbr = false;
if (tmp_vars.size() || needs_tmp_cell) {
if (!(options & 4)) {
opbr = true;
os << " {";
}
if (tmp_vars.size()) {
os << nl << "int";
int c = 0;
for (auto t : tmp_vars) {
if (c++) {
os << ",";
}
os << " " << t;
}
os << ";";
}
if (needs_tmp_cell) {
os << nl << "Ref<vm::Cell> tmp_cell;";
}
}
if (!actions.size()) {
os << nl << "return true;";
} else {
for (std::size_t i = 0; i < actions.size(); i++) {
os << nl << (i ? " && " : "return ");
actions[i].show(os);
}
os << ";";
}
if (incomplete) {
os << nl << "// ???";
}
if (opbr) {
os << nl << "}";
}
}
void CppTypeCode::compute_implicit_field(const Constructor& constr, const Field& field, int options) {
int i = field.field_idx;
if (field_vars.at(i).empty()) {
assert(!field_var_set.at(i));
assert(field.type->is_nat_subtype);
std::string id = new_tmp_var(field.get_name());
field_vars[i] = id;
}
int j = -1;
for (const TypeExpr* pexpr : constr.params) {
++j;
if (!param_constraint_used.at(j) && !type_param_is_neg.at(j)) {
// std::cerr << "can_use_to_compute(" << pexpr << ", " << i << ") = " << can_use_to_compute(pexpr, i) << std::endl;
if (!field_var_set.at(i) && pexpr->tp == TypeExpr::te_Param && pexpr->value == i) {
std::ostringstream ss;
if (field.type->is_nat_subtype) {
ss << "(" << field_vars[i] << " = " << type_param_name.at(j) << ") >= 0";
} else {
ss << "(" << field_vars[i] << " = &" << type_param_name.at(j) << ")";
}
actions += Action{std::move(ss)};
field_vars[i] = type_param_name[j];
field_var_set[i] = true;
param_constraint_used[j] = true;
} else if (can_compute(pexpr)) {
std::ostringstream ss;
ss << type_param_name.at(j) << " == ";
output_cpp_expr(ss, pexpr);
actions += Action{std::move(ss), true};
param_constraint_used[j] = true;
} else if (!field_var_set.at(i) && can_use_to_compute(pexpr, i)) {
add_compute_actions(pexpr, i, type_param_name.at(j));
param_constraint_used[j] = true;
}
}
}
}
bool CppTypeCode::add_constraint_check(const Constructor& constr, const Field& field, int options) {
const TypeExpr* expr = field.type;
if (expr->tp == TypeExpr::te_Apply &&
(expr->type_applied == Eq_type || expr->type_applied == Less_type || expr->type_applied == Leq_type)) {
assert(expr->args.size() == 2);
const TypeExpr *x = expr->args[0], *y = expr->args[1];
if (x->negated || y->negated) {
assert(expr->type_applied == Eq_type);
assert(x->negated ^ y->negated);
if (!x->negated) {
std::swap(x, y);
}
std::ostringstream ss;
output_cpp_expr(ss, y);
add_compute_actions(x, -1, ss.str());
} else {
std::ostringstream ss;
output_cpp_expr(ss, x);
ss << (expr->type_applied == Eq_type ? " == " : (expr->type_applied == Less_type ? " < " : " <= "));
output_cpp_expr(ss, y);
actions += Action{std::move(ss), true};
}
return true;
} else {
// ...
++incomplete;
actions += Action{"check_constraint_incomplete"};
return false;
}
}
void CppTypeCode::output_negative_type_arguments(std::ostream& os, const TypeExpr* expr) {
assert(expr->tp == TypeExpr::te_Apply);
for (const TypeExpr* arg : expr->args) {
if (arg->negated) {
int j = arg->value;
if (arg->tp == TypeExpr::te_Param && !field_var_set.at(j)) {
assert(!field_vars.at(j).empty());
os << ", " << field_vars.at(j);
field_var_set[j] = true;
} else {
std::string tmp = new_tmp_var();
os << ", " << tmp;
postponed_equate.emplace_back(tmp, arg);
}
}
}
}
void CppTypeCode::add_postponed_equate_actions() {
for (const auto& p : postponed_equate) {
add_compute_actions(p.second, -1, p.first);
}
postponed_equate.clear();
}
std::string CppTypeCode::add_fetch_nat_field(const Constructor& constr, const Field& field, int options) {
const TypeExpr* expr = field.type;
int i = field.field_idx;
std::string id = field_vars.at(i);
if (id.empty()) {
field_vars[i] = id = new_tmp_var(field.get_name());
}
const Type* ta = expr->type_applied;
assert(expr->tp == TypeExpr::te_Apply &&
(ta == Nat_type || ta == NatWidth_type || ta == NatLeq_type || ta == NatLess_type));
std::ostringstream ss;
ss << "cs.";
if (ta == Nat_type) {
ss << "fetch_uint_to(32, " << id << ")";
} else if (ta == NatWidth_type && expr->args.at(0)->tp == TypeExpr::te_IntConst && expr->args[0]->value == 1) {
ss << "fetch_bool_to(" << id << ")";
} else {
if (ta == NatWidth_type) {
ss << "fetch_uint_to(";
} else if (ta == NatLeq_type) {
ss << "fetch_uint_leq(";
} else if (ta == NatLess_type) {
ss << "fetch_uint_less(";
}
output_cpp_expr(ss, expr->args[0]);
ss << ", " << id << ")";
}
actions += Action{std::move(ss)};
field_var_set[i] = true;
return id;
}
void CppTypeCode::add_store_nat_field(const Constructor& constr, const Field& field, int options) {
const TypeExpr* expr = field.type;
int i = field.field_idx;
std::string id = field_vars.at(i);
assert(!id.empty());
const Type* ta = expr->type_applied;
assert(expr->tp == TypeExpr::te_Apply &&
(ta == Nat_type || ta == NatWidth_type || ta == NatLeq_type || ta == NatLess_type));
std::ostringstream ss;
ss << "cb.";
if (ta == Nat_type) {
ss << "store_ulong_rchk_bool(" << id << ", 32)";
} else if (ta == NatWidth_type) {
if (expr->args.at(0)->tp == TypeExpr::te_IntConst && expr->args[0]->value == 1) {
ss << "store_ulong_rchk_bool(" << id << ", 1)";
} else {
ss << "store_ulong_rchk_bool(" << id << ", ";
output_cpp_expr(ss, expr->args[0]);
ss << ")";
}
} else if (ta == NatLeq_type) {
ss << "store_uint_leq(";
output_cpp_expr(ss, expr->args[0]);
ss << ", " << id << ")";
} else if (ta == NatLess_type) {
ss << "store_uint_less(";
output_cpp_expr(ss, expr->args[0]);
ss << ", " << id << ")";
} else {
ss << "<store-unknown-nat-subtype>(" << id << ")";
}
actions += Action{std::move(ss)};
field_var_set[i] = true;
}
void CppTypeCode::generate_skip_field(const Constructor& constr, const Field& field, int options) {
const TypeExpr* expr = field.type;
MinMaxSize sz = expr->compute_size();
bool any_bits = expr->compute_any_bits();
bool validating = (options & 1);
// std::cerr << "field `" << field.get_name() << "` size is " << sz << "; fixed=" << sz.is_fixed() << "; any=" << any_bits << std::endl;
if (field.used || (validating && expr->is_nat_subtype && !any_bits)) {
// an explicit field of type # or ## which is used later or its value is not arbitrary
// (must load the value into an integer variable and check)
assert(expr->is_nat_subtype && "cannot use fields of non-`#` type");
add_fetch_nat_field(constr, field, options);
return;
}
if (sz.is_fixed() && (!validating || (!(sz.min_size() & 0xff) && any_bits))) {
// field has fixed size, and either its bits can have arbitrary values (and it has no references)
// ... or we are not validating
// simply skip the necessary amount of bits
// NB: if the field is a reference, and we are not validating, we arrive here
actions += Action{(int)sz.convert_min_size()};
return;
}
if (expr->negated) {
// the field type has some "negative" parameters, which will be computed while checking this field
// must invoke the correct validate_skip or skip method for the type in question
std::ostringstream ss;
if (!is_self(expr, constr)) {
output_cpp_expr(ss, expr, 100, true);
ss << '.';
}
ss << (validating ? "validate_skip(ops, cs, weak" : "skip(cs");
output_negative_type_arguments(ss, expr);
ss << ")";
actions += Action{std::move(ss)};
add_postponed_equate_actions();
return;
}
// at this point, if the field type is a reference, we must be validating
if (expr->tp == TypeExpr::te_Ref && expr->args[0]->tp == TypeExpr::te_Apply &&
(expr->args[0]->type_applied == Cell_type || expr->args[0]->type_applied == Any_type)) {
// field type is a reference to a cell with arbitrary contents
actions += Action{0x10000};
return;
}
// remaining case: general positive type expression
std::ostringstream ss;
std::string tail;
while (expr->tp == TypeExpr::te_CondType) {
// optimization for (chains of) conditional types ( x?type )
assert(expr->args.size() == 2);
ss << "(!";
output_cpp_expr(ss, expr->args[0], 30);
ss << " || ";
expr = expr->args[1];
tail = std::string{")"} + tail;
}
if ((!validating || any_bits) && can_compute_sizeof(expr)) {
// field size can be computed at run-time, and either the contents is arbitrary, or we are not validating
ss << "cs.advance(";
output_cpp_sizeof_expr(ss, expr, 0);
ss << ")" << tail;
actions += Action{std::move(ss)};
return;
}
if (expr->tp != TypeExpr::te_Ref) {
// field type is not a reference, generate a type expression and invoke skip/validate_skip method
if (!is_self(expr, constr)) {
output_cpp_expr(ss, expr, 100);
ss << '.';
}
ss << (validating ? "validate_skip(ops, cs, weak)" : "skip(cs)") << tail;
actions += Action{std::move(ss)};
return;
}
// the (remaining) field type is a reference
if (!validating || (expr->args[0]->tp == TypeExpr::te_Apply &&
(expr->args[0]->type_applied == Cell_type || expr->args[0]->type_applied == Any_type))) {
// the subcase when the field type is either a reference to a cell with arbitrary contents
// or it is a reference, and we are not validating, so we simply skip the reference
ss << "cs.advance_refs(1)" << tail;
actions += Action{std::move(ss)};
return;
}
// general reference type, invoke validate_skip_ref()
// (notice that we are necessarily validating at this point)
expr = expr->args[0];
if (!is_self(expr, constr)) {
output_cpp_expr(ss, expr, 100);
ss << '.';
}
ss << "validate_skip_ref(ops, cs, " << (constr.is_special ? "true" : "weak") << ")" << tail;
actions += Action{ss.str()};
}
void CppTypeCode::generate_skip_cons_method(std::ostream& os, std::string nl, int cidx, int options) {
const Constructor& constr = *(type.constructors.at(cidx));
init_cons_context(constr);
identify_cons_params(constr, options);
identify_cons_neg_params(constr, options);
add_cons_tag_check(constr, cidx, options);
for (const Field& field : constr.fields) {
if (!field.implicit) {
generate_skip_field(constr, field, options);
} else if (!field.constraint) {
compute_implicit_field(constr, field, options);
} else {
add_constraint_check(constr, field, options);
}
}
add_remaining_param_constraints_check(constr, options);
output_actions(os, nl, options);
clear_context();
}
void CppTypeCode::generate_skip_method(std::ostream& os, int options) {
bool validate = options & 1;
bool ret_ext = options & 2;
os << "\nbool " << cpp_type_class_name
<< "::" << (validate ? "validate_skip(int* ops, vm::CellSlice& cs, bool weak" : "skip(vm::CellSlice& cs");
if (ret_ext) {
os << skip_extra_args;
}
os << ") const {";
if (cons_num > 1) {
os << "\n switch (get_tag(cs)) {\n";
for (int i = 0; i < cons_num; i++) {
os << " case " << cons_enum_name[i] << ":";
generate_skip_cons_method(os, "\n ", i, options & ~4);
os << "\n";
}
os << " }\n return false;\n";
} else if (cons_num == 1) {
generate_skip_cons_method(os, "\n ", 0, options | 4);
os << "\n";
} else {
os << "\n return false;\n";
}
os << "}\n";
}
void CppTypeCode::generate_cons_tag_check(std::ostream& os, std::string nl, int cidx, bool force) {
const Constructor& constr = *(type.constructors.at(cidx));
if (!constr.tag_bits) {
os << nl << "return " << cons_enum_name[cidx] << ";";
} else if (force || cons_num == 1 || !cons_tag_exact.at(cidx)) {
os << nl << "return ";
int l = constr.tag_bits;
unsigned long long tag = (constr.tag >> (64 - l));
if (l < 64 || tag != ~0ULL) {
os << "cs.prefetch_ulong(" << l << ") == " << HexConstWriter{tag};
} else {
os << "cs.begins_with(" << l << ", " << HexConstWriter{tag} << ")";
}
os << " ? " << cons_enum_name[cidx] << " : -1;";
} else {
os << nl << "return cs.have(" << constr.tag_bits << ") ? " << cons_enum_name[cidx] << " : -1;";
}
}
void CppTypeCode::generate_check_tag_method(std::ostream& os) {
os << "\nint " << cpp_type_class_name << "::check_tag(const vm::CellSlice& cs) const {";
if (cons_num > 1) {
os << "\n switch (get_tag(cs)) {\n";
for (int i = 0; i < cons_num; i++) {
os << " case " << cons_enum_name[i] << ":";
generate_cons_tag_check(os, "\n ", i);
os << "\n";
}
os << " }\n return -1;\n";
} else if (cons_num == 1) {
generate_cons_tag_check(os, "\n ", 0);
os << "\n";
} else {
os << "\n return -1;\n";
}
os << "}\n";
}
bool CppTypeCode::output_print_simple_field(std::ostream& os, const Field& field, std::string field_name,
const TypeExpr* expr) {
cpp_val_type cvt = detect_cpp_type(expr);
MinMaxSize sz = expr->compute_size();
int i = expr->is_integer();
int l = (sz.is_fixed() ? sz.convert_min_size() : -1);
switch (cvt) {
case ct_bitstring:
case ct_bits:
assert(!(sz.max_size() & 0xff));
os << "pp.fetch_bits_field(cs, ";
output_cpp_sizeof_expr(os, expr, 0);
if (!field_name.empty()) {
os << ", \"" << field_name << '"';
}
os << ")";
return true;
case ct_bool:
case ct_int32:
case ct_uint32:
case ct_int64:
case ct_uint64:
assert(i && l <= 64);
os << "pp.fetch_" << (i > 0 ? "u" : "") << "int_field(cs, ";
output_cpp_sizeof_expr(os, expr, 0);
if (!field_name.empty()) {
os << ", \"" << field_name << '"';
}
os << ")";
return true;
case ct_integer:
assert(i);
os << "pp.fetch_" << (i > 0 ? "u" : "") << "int256_field(cs, ";
output_cpp_sizeof_expr(os, expr, 0);
if (!field_name.empty()) {
os << ", \"" << field_name << '"';
}
os << ")";
return true;
default:
break;
}
return false;
}
void CppTypeCode::generate_print_field(const Constructor& constr, const Field& field, int options) {
const TypeExpr* expr = field.type;
MinMaxSize sz = expr->compute_size();
cpp_val_type cvt = detect_cpp_type(expr);
bool any_bits = expr->compute_any_bits();
bool is_simple = (cvt >= ct_bits && cvt <= ct_uint64 && cvt != ct_enum);
// std::cerr << "field `" << field.get_name() << "` size is " << sz << "; fixed=" << sz.is_fixed() << "; any=" << any_bits << std::endl;
std::string field_name = field.name ? field.get_name() : "";
if (field.used || expr->is_nat_subtype) {
// an explicit field of type # or ##
assert(expr->is_nat_subtype && "cannot use fields of non-`#` type");
std::ostringstream ss;
ss << "pp.field_int(" << add_fetch_nat_field(constr, field, options);
if (field.name) {
ss << ", \"" << field_name << '"';
}
ss << ')';
actions += Action{std::move(ss)};
return;
}
if (sz.is_fixed() && !(sz.min_size() & 0xff) && any_bits) {
// field has fixed size, and either its bits can have arbitrary values (and it has no references)
std::ostringstream ss;
if (output_print_simple_field(ss, field, field_name, expr)) {
actions += Action{std::move(ss)};
return;
}
}
bool cond_chain = (expr->tp == TypeExpr::te_CondType);
if (!cond_chain && !is_simple) {
if (field.name) {
actions += Action{std::string{"pp.field(\""} + field_name + "\")"};
} else {
actions += Action{"pp.field()"};
}
}
if (expr->negated) {
// the field type has some "negative" parameters, which will be computed while checking this field
// must invoke the correct validate_skip or skip method for the type in question
assert(!cond_chain);
std::ostringstream ss;
if (!is_self(expr, constr)) {
output_cpp_expr(ss, expr, 100, true);
ss << '.';
}
ss << "print_skip(pp, cs";
output_negative_type_arguments(ss, expr);
ss << ")";
actions += Action{std::move(ss)};
add_postponed_equate_actions();
return;
}
// remaining case: general positive type expression
std::ostringstream ss;
std::string tail;
while (expr->tp == TypeExpr::te_CondType) {
// optimization for (chains of) conditional types ( x?type )
assert(expr->args.size() == 2);
ss << "(!";
output_cpp_expr(ss, expr->args[0], 30);
ss << " || ";
expr = expr->args[1];
tail += ')';
}
if (output_print_simple_field(ss, field, field_name, expr)) {
ss << tail;
actions += Action{std::move(ss)};
return;
}
if (cond_chain) {
if (field.name) {
ss << "(pp.field(\"" + field_name + "\") && ";
} else {
ss << "(pp.field() && ";
}
tail += ')';
}
if (expr->tp != TypeExpr::te_Ref) {
// field type is not a reference, generate a type expression and invoke skip/validate_skip method
if (!is_self(expr, constr)) {
output_cpp_expr(ss, expr, 100);
ss << '.';
}
ss << "print_skip(pp, cs)" << tail;
actions += Action{std::move(ss)};
return;
}
// general reference type, invoke print_ref()
expr = expr->args[0];
if (!is_self(expr, constr)) {
output_cpp_expr(ss, expr, 100);
ss << '.';
}
ss << "print_ref(pp, cs.fetch_ref())" << tail;
actions += Action{ss.str()};
}
void CppTypeCode::generate_print_cons_method(std::ostream& os, std::string nl, int cidx, int options) {
const Constructor& constr = *(type.constructors.at(cidx));
init_cons_context(constr);
identify_cons_params(constr, options);
identify_cons_neg_params(constr, options);
add_cons_tag_check(constr, cidx, options);
bool do_open = !constr.is_enum;
if (do_open) {
if (constr.constr_name) {
actions += Action{std::string{"pp.open(\""} + constr.get_name() + "\")"};
} else {
actions += Action{"pp.open()"};
}
} else {
actions += Action{std::string{"pp.cons(\""} + constr.get_name() + "\")"};
}
for (const Field& field : constr.fields) {
if (!field.implicit) {
generate_print_field(constr, field, options);
} else if (!field.constraint) {
compute_implicit_field(constr, field, options);
} else {
add_constraint_check(constr, field, options);
}
}
add_remaining_param_constraints_check(constr, options);
if (do_open) {
actions += Action{"pp.close()"};
}
output_actions(os, nl, options);
clear_context();
}
void CppTypeCode::generate_print_method(std::ostream& os, int options) {
bool ret_ext = options & 2;
os << "\nbool " << cpp_type_class_name << "::print_skip(PrettyPrinter& pp, vm::CellSlice& cs";
if (ret_ext) {
os << skip_extra_args;
}
os << ") const {";
if (cons_num > 1) {
os << "\n switch (get_tag(cs)) {\n";
for (int i = 0; i < cons_num; i++) {
os << " case " << cons_enum_name[i] << ":";
generate_print_cons_method(os, "\n ", i, options & ~4);
os << "\n";
}
os << " }\n return pp.fail(\"unknown constructor for " << type.get_name() << "\");\n";
} else if (cons_num == 1) {
generate_print_cons_method(os, "\n ", 0, options | 4);
os << "\n";
} else {
os << "\n return pp.fail(\"no constructors for " << type.get_name() << "\");\n";
}
os << "}\n";
}
void CppTypeCode::bind_record_fields(const CppTypeCode::ConsRecord& rec, int options) {
bool direct = options & 8;
bool read_only = options & 32;
for (const ConsField& fi : rec.cpp_fields) {
int i = fi.orig_idx;
assert(field_vars.at(i).empty() && !field_var_set.at(i));
if (!read_only || !rec.constr.fields.at(i).implicit) {
field_vars[i] = direct ? fi.name : std::string{"data."} + fi.name;
field_var_set[i] = read_only;
}
}
}
void CppTypeCode::output_fetch_field(std::ostream& os, std::string field_var, const TypeExpr* expr, cpp_val_type cvt) {
int i = expr->is_integer();
MinMaxSize sz = expr->compute_size();
int l = (sz.is_fixed() ? sz.convert_min_size() : -1);
switch (cvt) {
case ct_slice:
os << "cs.fetch_subslice_" << (sz.max_size() & 0xff ? "ext_" : "") << "to(";
output_cpp_sizeof_expr(os, expr, 0);
os << ", " << field_var << ")";
return;
case ct_bitstring:
assert(!(sz.max_size() & 0xff));
os << "cs.fetch_bitstring_to(";
output_cpp_sizeof_expr(os, expr, 0);
os << ", " << field_var << ")";
return;
case ct_bits:
assert(l >= 0 && l < 0x10000);
os << "cs.fetch_bits_to(" << field_var << ".bits(), " << l << ")";
return;
case ct_cell:
assert(l == 0x10000);
os << "cs.fetch_ref_to(" << field_var << ")";
return;
case ct_bool:
assert(i > 0 && l == 1);
os << "cs.fetch_bool_to(" << field_var << ")";
return;
case ct_int32:
case ct_uint32:
case ct_int64:
case ct_uint64:
assert(i && l <= 64);
os << "cs.fetch_" << (i > 0 ? "u" : "") << "int_to(";
output_cpp_sizeof_expr(os, expr, 0);
os << ", " << field_var << ")";
return;
case ct_integer:
assert(i);
os << "cs.fetch_" << (i > 0 ? "u" : "") << "int256_to(";
output_cpp_sizeof_expr(os, expr, 0);
os << ", " << field_var << ")";
return;
default:
break;
}
throw src::Fatal{"cannot fetch a field of unknown scalar type"};
}
void CppTypeCode::output_fetch_subrecord(std::ostream& os, std::string field_name, const ConsRecord* subrec) {
assert(subrec);
os << subrec->cpp_type.cpp_type_var_name << ".cell_unpack(cs.fetch_ref(), " << field_name << ")";
}
void CppTypeCode::generate_unpack_field(const CppTypeCode::ConsField& fi, const Constructor& constr, const Field& field,
int options) {
int i = field.field_idx;
const TypeExpr* expr = field.type;
MinMaxSize sz = expr->compute_size();
bool any_bits = expr->compute_any_bits();
bool validating = (options & 1);
cpp_val_type cvt = fi.ctype;
// std::cerr << "field `" << field.get_name() << "` size is " << sz << "; fixed=" << sz.is_fixed() << "; any=" << any_bits << std::endl;
if (field.used || expr->is_nat_subtype) {
assert(expr->is_nat_subtype && "cannot use fields of non-`#` type");
assert(cvt == ct_int32 || cvt == ct_bool);
add_fetch_nat_field(constr, field, options);
return;
}
if (sz.is_fixed() && cvt != ct_enum && (!validating || (!(sz.min_size() & 0xff) && any_bits))) {
// field has fixed size, and either its bits can have arbitrary values (and it has no references)
// ... or we are not validating
// simply skip the necessary amount of bits
// NB: if the field is a reference, and we are not validating, we arrive here
if (cvt == ct_cell) {
assert(sz.min_size() == 1);
}
std::ostringstream ss;
if (cvt == ct_subrecord && field.subrec) {
output_fetch_subrecord(ss, field_vars.at(i), fi.subrec);
} else {
output_fetch_field(ss, field_vars.at(i), expr, cvt);
}
actions += Action{std::move(ss)};
field_var_set[i] = true;
return;
}
if (expr->negated) {
// the field type has some "negative" parameters, which will be computed while checking this field
// must invoke the correct validate_skip or skip method for the type in question
std::ostringstream ss;
assert(cvt == ct_slice);
if (!is_self(expr, constr)) {
output_cpp_expr(ss, expr, 100, true);
ss << '.';
}
ss << (validating ? "validate_fetch_to(ops, cs, weak, " : "fetch_to(cs, ") << field_vars.at(i);
output_negative_type_arguments(ss, expr);
ss << ")";
actions += Action{std::move(ss)};
add_postponed_equate_actions();
field_var_set[i] = true;
return;
}
// at this point, if the field type is a reference, we must be validating
if (expr->tp == TypeExpr::te_Ref && expr->args[0]->tp == TypeExpr::te_Apply &&
(expr->args[0]->type_applied == Cell_type || expr->args[0]->type_applied == Any_type)) {
// field type is a reference to a cell with arbitrary contents
assert(cvt == ct_cell);
actions += Action{"cs.fetch_ref_to(" + field_vars.at(i) + ")"};
field_var_set[i] = true;
return;
}
// remaining case: general positive type expression
std::ostringstream ss;
std::string tail;
while (expr->tp == TypeExpr::te_CondType) {
// optimization for (chains of) conditional types ( x?type )
assert(expr->args.size() == 2);
ss << "(!";
output_cpp_expr(ss, expr->args[0], 30);
ss << " || ";
expr = expr->args[1];
tail = std::string{")"} + tail;
}
if ((!validating || any_bits) && can_compute_sizeof(expr) && cvt != ct_enum) {
// field size can be computed at run-time, and either the contents is arbitrary, or we are not validating
output_fetch_field(ss, field_vars.at(i), expr, cvt);
field_var_set[i] = true;
ss << tail;
actions += Action{std::move(ss)};
return;
}
if (expr->tp != TypeExpr::te_Ref) {
// field type is not a reference, generate a type expression and invoke skip/validate_skip method
assert(cvt == ct_slice || cvt == ct_enum);
if (!is_self(expr, constr)) {
output_cpp_expr(ss, expr, 100);
ss << '.';
}
ss << (validating ? "validate_" : "") << "fetch_" << (cvt == ct_enum ? "enum_" : "")
<< (validating ? "to(ops, cs, weak, " : "to(cs, ") << field_vars.at(i) << ")" << tail;
field_var_set[i] = true;
actions += Action{std::move(ss)};
return;
}
// the (remaining) field type is a reference
if (!validating || (expr->args[0]->tp == TypeExpr::te_Apply &&
(expr->args[0]->type_applied == Cell_type || expr->args[0]->type_applied == Any_type))) {
// the subcase when the field type is either a reference to a cell with arbitrary contents
// or it is a reference, and we are not validating, so we simply skip the reference
assert(cvt == ct_cell);
ss << "cs.fetch_ref_to(" << field_vars.at(i) << ")" << tail;
field_var_set[i] = true;
actions += Action{std::move(ss)};
return;
}
// general reference type, invoke validate_skip_ref()
// (notice that we are necessarily validating at this point)
expr = expr->args[0];
assert(cvt == ct_cell);
ss << "(cs.fetch_ref_to(" << field_vars.at(i) << ") && ";
if (!is_self(expr, constr)) {
output_cpp_expr(ss, expr, 100);
ss << '.';
}
ss << "validate_ref(ops, " << field_vars.at(i) << "))" << tail;
actions += Action{ss.str()};
}
void CppTypeCode::generate_unpack_method(std::ostream& os, CppTypeCode::ConsRecord& rec, int options) {
std::ostringstream tmp;
if (!rec.declare_record_unpack(tmp, "", options)) {
return;
}
tmp.clear();
os << "\n";
bool res = rec.declare_record_unpack(os, "", options | 3072);
DCHECK(res);
if (options & 16) {
// cell unpack version
os << "\n if (cell_ref.is_null()) { return false; }"
<< "\n auto cs = load_cell_slice(std::move(cell_ref));"
<< "\n return " << (options & 1 ? "validate_" : "") << "unpack";
if (!(options & 8)) {
os << "(";
if (options & 1) {
os << "ops, ";
}
os << "cs, data";
} else {
os << "_" << cons_enum_name.at(rec.cons_idx) << "(cs";
for (const auto& f : rec.cpp_fields) {
os << ", " << f.name;
}
}
if (options & 2) {
os << skip_extra_args_pass;
}
os << ") && cs.empty_ext();\n}\n";
return;
}
init_cons_context(rec.constr);
bind_record_fields(rec, options);
identify_cons_params(rec.constr, options);
identify_cons_neg_params(rec.constr, options);
add_cons_tag_check(rec.constr, rec.cons_idx, 9 /* (options & 1) | 8 */);
auto it = rec.cpp_fields.cbegin(), end = rec.cpp_fields.cend();
for (const Field& field : rec.constr.fields) {
if (field.constraint) {
add_constraint_check(rec.constr, field, options);
continue;
}
if (!field.implicit) {
assert(it < end && it->orig_idx == field.field_idx);
generate_unpack_field(*it++, rec.constr, field, options);
} else {
if (it < end && it->orig_idx == field.field_idx) {
++it;
}
compute_implicit_field(rec.constr, field, options);
}
}
assert(it == end);
add_remaining_param_constraints_check(rec.constr, options);
output_actions(os, "\n ", options | 4);
clear_context();
os << "\n}\n";
}
void CppTypeCode::output_store_field(std::ostream& os, std::string field_var, const TypeExpr* expr, cpp_val_type cvt) {
int i = expr->is_integer();
MinMaxSize sz = expr->compute_size();
int l = (sz.is_fixed() ? sz.convert_min_size() : -1);
switch (cvt) {
case ct_slice:
os << "cb.append_cellslice_chk(" << field_var << ", ";
output_cpp_sizeof_expr(os, expr, 0);
os << ")";
return;
case ct_bitstring:
assert(!(sz.max_size() & 0xff));
os << "cb.append_bitstring_chk(" << field_var << ", ";
output_cpp_sizeof_expr(os, expr, 0);
os << ")";
return;
case ct_bits:
assert(l >= 0 && l < 0x10000);
os << "cb.store_bits_bool(" << field_var << ".cbits(), " << l << ")";
return;
case ct_cell:
assert(l == 0x10000);
os << "cb.store_ref_bool(" << field_var << ")";
return;
case ct_bool:
assert(i > 0 && l == 1);
// os << "cb.store_bool(" << field_var << ")";
// return;
// fall through
case ct_int32:
case ct_uint32:
case ct_int64:
case ct_uint64:
assert(i && l <= 64);
os << "cb.store_" << (i > 0 ? "u" : "") << "long_rchk_bool(" << field_var << ", ";
output_cpp_sizeof_expr(os, expr, 0);
os << ")";
return;
case ct_integer:
assert(i);
os << "cb.store_int256_bool(" << field_var << ", ";
output_cpp_sizeof_expr(os, expr, 0);
os << (i > 0 ? ", false)" : ")");
return;
default:
break;
}
throw src::Fatal{"cannot store a field of unknown scalar type"};
}
void CppTypeCode::add_store_subrecord(std::string field_name, const ConsRecord* subrec) {
assert(subrec);
needs_tmp_cell = true;
std::ostringstream ss;
ss << subrec->cpp_type.cpp_type_var_name << ".cell_pack(tmp_cell, " << field_name << ")";
actions += Action{std::move(ss)};
actions += Action{"cb.store_ref_bool(std::move(tmp_cell))"};
}
void CppTypeCode::generate_pack_field(const CppTypeCode::ConsField& fi, const Constructor& constr, const Field& field,
int options) {
int i = field.field_idx;
const TypeExpr* expr = field.type;
MinMaxSize sz = expr->compute_size();
bool any_bits = expr->compute_any_bits();
bool validating = (options & 1);
cpp_val_type cvt = fi.ctype;
// std::cerr << "field `" << field.get_name() << "` size is " << sz << "; fixed=" << sz.is_fixed() << "; any=" << any_bits << std::endl;
if (field.used || expr->is_nat_subtype) {
assert(expr->is_nat_subtype && "cannot use fields of non-`#` type");
assert(cvt == ct_int32 || cvt == ct_bool);
add_store_nat_field(constr, field, options);
return;
}
if (sz.is_fixed() && cvt != ct_enum && (!validating || (!(sz.min_size() & 0xff) && any_bits))) {
// field has fixed size, and either its bits can have arbitrary values (and it has no references)
// ... or we are not validating
// simply skip the necessary amount of bits
// NB: if the field is a reference, and we are not validating, we arrive here
if (cvt == ct_cell) {
assert(sz.min_size() == 1);
}
if (cvt == ct_subrecord && field.subrec) {
add_store_subrecord(field_vars.at(i), fi.subrec);
} else {
std::ostringstream ss;
output_store_field(ss, field_vars.at(i), expr, cvt);
actions += Action{std::move(ss)};
}
field_var_set[i] = true;
return;
}
if (expr->negated) {
// the field type has some "negative" parameters, which will be computed while checking this field
// must invoke the correct validate_skip or skip method for the type in question
std::ostringstream ss;
assert(cvt == ct_slice);
ss << "tlb::" << (validating ? "validate_" : "") << "store_from(cb, ";
if (!is_self(expr, constr)) {
output_cpp_expr(ss, expr, 5, true);
} else {
ss << "*this";
}
ss << ", " << field_vars.at(i);
output_negative_type_arguments(ss, expr);
ss << ")";
actions += Action{std::move(ss)};
add_postponed_equate_actions();
field_var_set[i] = true;
return;
}
// at this point, if the field type is a reference, we must be validating
if (expr->tp == TypeExpr::te_Ref && expr->args[0]->tp == TypeExpr::te_Apply &&
(expr->args[0]->type_applied == Cell_type || expr->args[0]->type_applied == Any_type)) {
// field type is a reference to a cell with arbitrary contents
assert(cvt == ct_cell);
actions += Action{"cb.store_ref_bool(" + field_vars.at(i) + ")"};
field_var_set[i] = true;
return;
}
// remaining case: general positive type expression
std::ostringstream ss;
std::string tail;
while (expr->tp == TypeExpr::te_CondType) {
// optimization for (chains of) conditional types ( x?type )
assert(expr->args.size() == 2);
ss << "(!";
output_cpp_expr(ss, expr->args[0], 30);
ss << " || ";
expr = expr->args[1];
tail = std::string{")"} + tail;
}
if ((!validating || any_bits) && can_compute_sizeof(expr) && cvt != ct_enum) {
// field size can be computed at run-time, and either the contents is arbitrary, or we are not validating
output_store_field(ss, field_vars.at(i), expr, cvt);
field_var_set[i] = true;
ss << tail;
actions += Action{std::move(ss)};
return;
}
if (expr->tp != TypeExpr::te_Ref) {
// field type is not a reference, generate a type expression and invoke skip/validate_skip method
assert(cvt == ct_slice || cvt == ct_enum);
if (!is_self(expr, constr)) {
output_cpp_expr(ss, expr, 100);
ss << '.';
}
ss << (validating ? "validate_" : "") << "store_" << (cvt == ct_enum ? "enum_" : "") << "from(cb, "
<< field_vars.at(i) << ")" << tail;
field_var_set[i] = true;
actions += Action{std::move(ss)};
return;
}
// the (remaining) field type is a reference
if (!validating || (expr->args[0]->tp == TypeExpr::te_Apply &&
(expr->args[0]->type_applied == Cell_type || expr->args[0]->type_applied == Any_type))) {
// the subcase when the field type is either a reference to a cell with arbitrary contents
// or it is a reference, and we are not validating, so we simply skip the reference
assert(cvt == ct_cell);
ss << "cb.store_ref_bool(" << field_vars.at(i) << ")" << tail;
field_var_set[i] = true;
actions += Action{std::move(ss)};
return;
}
// general reference type, invoke validate_skip_ref()
// (notice that we are necessarily validating at this point)
expr = expr->args[0];
assert(cvt == ct_cell);
ss << "(cb.store_ref_bool(" << field_vars.at(i) << ") && ";
if (!is_self(expr, constr)) {
output_cpp_expr(ss, expr, 100);
ss << '.';
}
ss << "validate_ref(ops, " << field_vars.at(i) << "))" << tail;
actions += Action{ss.str()};
}
void CppTypeCode::generate_pack_method(std::ostream& os, CppTypeCode::ConsRecord& rec, int options) {
std::ostringstream tmp;
if (!rec.declare_record_pack(tmp, "", options)) {
return;
}
tmp.clear();
os << "\n";
bool res = rec.declare_record_pack(os, "", options | 3072);
DCHECK(res);
if (options & 16) {
// cell pack version
os << "\n vm::CellBuilder cb;"
<< "\n return " << (options & 1 ? "validate_" : "") << "pack";
if (!(options & 8)) {
os << "(cb, data";
} else {
os << "_" << cons_enum_name.at(rec.cons_idx) << "(cb";
for (const auto& f : rec.cpp_fields) {
// skip SOME implicit fields ???
if (f.implicit) {
} else if (f.get_cvt().needs_move()) {
os << ", std::move(" << f.name << ")";
} else {
os << ", " << f.name;
}
}
}
if (options & 2) {
os << skip_extra_args_pass;
}
os << ") && std::move(cb).finalize_to(cell_ref);\n}\n";
return;
}
init_cons_context(rec.constr);
bind_record_fields(rec, options | 32);
identify_cons_params(rec.constr, options);
identify_cons_neg_params(rec.constr, options);
add_cons_tag_store(rec.constr, rec.cons_idx);
auto it = rec.cpp_fields.cbegin(), end = rec.cpp_fields.cend();
for (const Field& field : rec.constr.fields) {
if (field.constraint) {
add_constraint_check(rec.constr, field, options);
continue;
}
if (!field.implicit) {
assert(it < end && it->orig_idx == field.field_idx);
generate_pack_field(*it++, rec.constr, field, options);
} else {
if (it < end && it->orig_idx == field.field_idx) {
++it;
}
compute_implicit_field(rec.constr, field, options);
}
}
assert(it == end);
add_remaining_param_constraints_check(rec.constr, options);
output_actions(os, "\n ", options | 4);
clear_context();
os << "\n}\n";
}
void CppTypeCode::generate_ext_fetch_to(std::ostream& os, int options) {
std::string validate = (options & 1) ? "validate_" : "";
os << "\nbool " << cpp_type_class_name << "::" << validate << "fetch_to(vm::CellSlice& cs, Ref<vm::CellSlice>& res"
<< skip_extra_args << ") const {\n"
<< " res = Ref<vm::CellSlice>{true, cs};\n"
<< " return " << validate << "skip(cs" << skip_extra_args_pass << ") && res.unique_write().cut_tail(cs);\n"
<< "}\n";
}
void CppTypeCode::ConsRecord::declare_record(std::ostream& os, std::string nl, int options) {
bool force = options & 1024;
if (declared) {
return;
}
if (!force) {
os << nl << "struct " << cpp_name;
if (!inline_record) {
os << ";\n";
return;
}
} else {
os << "\n" << nl << "struct " << cpp_type.cpp_type_class_name << "::" << cpp_name;
}
os << " {\n";
os << nl << " typedef " << cpp_type.cpp_type_class_name << " type_class;\n";
CppIdentSet rec_cpp_ids;
recover_idents(rec_cpp_ids);
std::size_t n = cpp_fields.size();
for (const ConsField& fi : cpp_fields) {
os << nl << " ";
fi.print_type(os);
os << " " << fi.name << "; \t// ";
if (fi.field.name) {
os << fi.field.get_name() << " : ";
}
fi.field.type->show(os, &constr);
os << std::endl;
}
if (n) {
os << nl << " " << cpp_name << "() = default;\n";
std::vector<std::string> ctor_args;
os << nl << " " << cpp_name << "(";
int i = 0, j = 0;
for (const ConsField& fi : cpp_fields) {
if (!fi.implicit) {
std::string arg = rec_cpp_ids.new_ident(std::string{"_"} + fi.name);
ctor_args.push_back(arg);
if (i++) {
os << ", ";
}
fi.print_type(os, true);
os << " " << arg;
}
}
os << ") : ";
i = 0;
for (const ConsField& fi : cpp_fields) {
if (i++) {
os << ", ";
}
os << fi.name << "(";
if (fi.implicit) {
os << (fi.ctype == ct_int32 ? "-1" : "nullptr");
} else if (fi.get_cvt().needs_move()) {
os << "std::move(" << ctor_args.at(j++) << ")";
} else {
os << ctor_args.at(j++);
}
os << ")";
}
os << " {}\n";
}
os << nl << "};\n";
declared = true;
}
bool CppTypeCode::ConsRecord::declare_record_unpack(std::ostream& os, std::string nl, int options) {
bool is_ok = false;
bool cell = options & 16;
std::string slice_arg = cell ? "Ref<vm::Cell> cell_ref" : "vm::CellSlice& cs";
std::string fun_name = (options & 1) ? "validate_unpack" : "unpack";
if (cell) {
fun_name = std::string{"cell_"} + fun_name;
}
std::string class_name;
if (options & 2048) {
class_name = cpp_type.cpp_type_class_name + "::";
}
if (!(options & 8)) {
os << nl << "bool " << class_name << fun_name << "(" << slice_arg << ", " << class_name << cpp_name << "& data";
is_ok = true;
} else if (is_small) {
os << nl << "bool " << class_name << fun_name << "_" << cpp_type.cons_enum_name.at(cons_idx) << "(" << slice_arg;
for (const auto& f : cpp_fields) {
os << ", " << f.get_cvt() << "& " << f.name;
}
is_ok = true;
}
if (is_ok) {
if (options & 2) {
os << cpp_type.skip_extra_args;
}
os << ") const" << (options & 1024 ? " {" : ";\n");
}
return is_ok;
}
bool CppTypeCode::ConsRecord::declare_record_pack(std::ostream& os, std::string nl, int options) {
bool is_ok = false;
bool cell = options & 16;
std::string builder_arg = cell ? "Ref<vm::Cell>& cell_ref" : "vm::CellBuilder& cb";
std::string fun_name = (options & 1) ? "validate_pack" : "pack";
if (cell) {
fun_name = std::string{"cell_"} + fun_name;
}
std::string class_name;
if (options & 2048) {
class_name = cpp_type.cpp_type_class_name + "::";
}
if (!(options & 8)) {
os << nl << "bool " << class_name << fun_name << "(" << builder_arg << ", const " << class_name << cpp_name
<< "& data";
is_ok = true;
} else if (is_small) {
os << nl << "bool " << class_name << fun_name << "_" << cpp_type.cons_enum_name.at(cons_idx) << "(" << builder_arg;
for (const auto& f : cpp_fields) {
// skip SOME implicit fields ???
if (!f.implicit) {
os << ", " << f.get_cvt() << " " << f.name;
}
}
is_ok = true;
}
if (is_ok) {
if (options & 2) {
os << cpp_type.skip_extra_args;
}
os << ") const" << (options & 1024 ? " {" : ";\n");
}
return is_ok;
}
void CppTypeCode::generate_fetch_enum_method(std::ostream& os, int options) {
int minl = type.size.convert_min_size(), maxl = type.size.convert_max_size();
bool exact = type.cons_all_exact();
std::string ctag = incremental_cons_tags ? "(unsigned)t" : "cons_tag[t]";
os << "\nbool " << cpp_type_class_name << "::fetch_enum_to(vm::CellSlice& cs, char& value) const {\n";
if (!cons_num) {
os << " value = -1;\n"
" return false;\n";
} else if (!maxl) {
os << " value = 0;\n"
" return true;\n";
} else if (cons_num == 1) {
const Constructor& constr = *type.constructors.at(0);
os << " value = (cs.fetch_ulong(" << minl << ") == " << HexConstWriter{constr.tag >> (64 - constr.tag_bits)}
<< ") ? 0 : -1;\n";
os << " return !value;\n";
} else if (minl == maxl) {
if (exact) {
os << " value = (char)cs.fetch_ulong(" << minl << ");\n";
os << " return value >= 0;\n";
} else {
os << " int t = get_tag(cs);\n";
os << " value = (char)t;\n";
os << " return t >= 0 && cs.fetch_ulong(" << minl << ") == " << ctag << ";\n";
}
} else if (exact) {
os << " int t = get_tag(cs);\n";
os << " value = (char)t;\n";
os << " return t >= 0 && cs.advance(cons_len[t]);\n";
} else {
os << " int t = get_tag(cs);\n";
os << " value = (char)t;\n";
os << " return t >= 0 && cs.fetch_ulong(cons_len[t]) == " << ctag << ";\n";
}
os << "}\n";
}
void CppTypeCode::generate_store_enum_method(std::ostream& os, int options) {
int minl = type.size.convert_min_size(), maxl = type.size.convert_max_size();
bool exact = type.cons_all_exact();
std::string ctag = incremental_cons_tags ? "value" : "cons_tag[value]";
os << "\nbool " << cpp_type_class_name << "::store_enum_from(vm::CellBuilder& cb, int value) const {\n";
if (!cons_num) {
os << " return false;\n";
} else if (!maxl) {
os << " return !value;\n";
} else if (cons_num == 1) {
const Constructor& constr = *type.constructors.at(0);
os << " return !value && cb.store_long_bool(" << HexConstWriter{constr.tag >> (64 - constr.tag_bits)} << ", "
<< minl << ");\n";
} else if (minl == maxl) {
if (exact) {
os << " return cb.store_long_rchk_bool(value, " << minl << ");\n";
} else if (incremental_cons_tags && cons_num > (1 << (minl - 1))) {
os << " return cb.store_uint_less(" << cons_num << ", value);\n";
} else {
os << " return (unsigned)value < " << cons_num << " && cb.store_long_bool(" << ctag << ", " << minl << ");\n";
}
} else {
os << " return (unsigned)value < " << cons_num << " && cb.store_long_bool(" << ctag << ", cons_len[value]);\n";
}
os << "}\n";
}
void CppTypeCode::generate_print_type_body(std::ostream& os, std::string nl) {
std::string name = type.type_name ? type.get_name() : cpp_type_class_name;
if (!tot_params) {
os << nl << "return os << \"" << name << "\";";
return;
}
os << nl << "return os << \"(" << name;
for (int i = 0; i < tot_params; i++) {
if (type_param_is_neg[i]) {
os << " ~" << type_param_name[i];
} else {
os << " \" << " << type_param_name[i] << " << \"";
}
}
os << ")\";";
}
void CppTypeCode::generate_header(std::ostream& os, int options) {
os << "\nstruct " << cpp_type_class_name << " final : TLB_Complex {\n";
generate_cons_enum(os);
generate_cons_tag_info(os, " ", 1);
if (params) {
generate_type_fields(os, options);
generate_type_constructor(os, options);
}
for (int i = 0; i < cons_num; i++) {
records.at(i).declare_record(os, " ", options);
}
if (type.is_special) {
os << " bool always_special() const override {\n";
os << " return true;\n }\n";
}
int sz = type.size.min_size();
sz = ((sz & 0xff) << 16) | (sz >> 8);
if (simple_get_size) {
os << " int get_size(const vm::CellSlice& cs) const override {\n";
os << " return " << SizeWriter{sz} << ";\n }\n";
}
os << " bool skip(vm::CellSlice& cs) const override";
if (!inline_skip) {
os << ";\n";
} else if (sz) {
os << " {\n return cs.advance" << (sz < 0x10000 ? "(" : "_ext(") << SizeWriter{sz} << ");\n }\n";
} else {
os << " {\n return true;\n }\n";
}
if (ret_params) {
os << " bool skip(vm::CellSlice& cs" << skip_extra_args << ") const;\n";
}
os << " bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override";
if (!inline_validate_skip) {
os << ";\n";
} else if (sz) {
os << " {\n return cs.advance(" << SizeWriter{sz} << ");\n }\n";
} else {
os << " {\n return true;\n }\n";
}
if (ret_params) {
os << " bool validate_skip(int *ops, vm::CellSlice& cs, bool weak" << skip_extra_args << ") const;\n";
os << " bool fetch_to(vm::CellSlice& cs, Ref<vm::CellSlice>& res" << skip_extra_args << ") const;\n";
}
if (type.is_simple_enum) {
os << " bool fetch_enum_to(vm::CellSlice& cs, char& value) const;\n";
os << " bool store_enum_from(vm::CellBuilder& cb, int value) const;\n";
}
for (int i = 0; i < cons_num; i++) {
records[i].declare_record_unpack(os, " ", 2);
records[i].declare_record_unpack(os, " ", 10);
records[i].declare_record_unpack(os, " ", 18);
records[i].declare_record_unpack(os, " ", 26);
records[i].declare_record_pack(os, " ", 2);
records[i].declare_record_pack(os, " ", 10);
records[i].declare_record_pack(os, " ", 18);
records[i].declare_record_pack(os, " ", 26);
}
os << " bool print_skip(PrettyPrinter& pp, vm::CellSlice& cs) const override;\n";
if (ret_params) {
os << " bool print_skip(PrettyPrinter& pp, vm::CellSlice& cs" << skip_extra_args << ") const;\n";
}
os << " std::ostream& print_type(std::ostream& os) const override {";
generate_print_type_body(os, "\n ");
os << "\n }\n";
os << " int check_tag(const vm::CellSlice& cs) const override;\n";
os << " int get_tag(const vm::CellSlice& cs) const override";
if (inline_get_tag) {
os << " {";
generate_get_tag_body(os, "\n ");
os << "\n }\n";
} else {
os << ";\n";
}
os << "};\n";
for (int i = 0; i < cons_num; i++) {
records.at(i).declare_record(os, "", options | 1024);
}
if (!cpp_type_var_name.empty()) {
os << "\nextern const " << cpp_type_class_name << " " << cpp_type_var_name << ";\n";
}
}
void CppTypeCode::generate_body(std::ostream& os, int options) {
generate_cons_tag_info(os, "", 2);
if (!inline_get_tag) {
os << "\nint " << cpp_type_class_name << "::get_tag(const vm::CellSlice& cs) const {";
generate_get_tag_body(os, "\n ");
os << "\n}\n";
}
generate_check_tag_method(os);
options &= -4;
if (!inline_skip) {
generate_skip_method(os, options);
}
if (ret_params) {
generate_skip_method(os, options + 2);
}
if (!inline_validate_skip) {
generate_skip_method(os, options + 1);
}
if (ret_params) {
generate_skip_method(os, options + 3);
generate_ext_fetch_to(os, options);
}
if (type.is_simple_enum) {
generate_fetch_enum_method(os, options);
generate_store_enum_method(os, options);
}
for (int i = 0; i < cons_num; i++) {
ConsRecord& rec = records.at(i);
generate_unpack_method(os, rec, 2);
generate_unpack_method(os, rec, 10);
generate_unpack_method(os, rec, 18);
generate_unpack_method(os, rec, 26);
}
for (int i = 0; i < cons_num; i++) {
ConsRecord& rec = records.at(i);
generate_pack_method(os, rec, 2);
generate_pack_method(os, rec, 10);
generate_pack_method(os, rec, 18);
generate_pack_method(os, rec, 26);
}
generate_print_method(os, options + 1);
if (ret_params) {
generate_print_method(os, options + 3);
}
if (!cpp_type_var_name.empty()) {
os << "\nconst " << cpp_type_class_name << " " << cpp_type_var_name << ";";
}
os << std::endl;
}
void CppTypeCode::generate(std::ostream& os, int options) {
std::string type_name = type.get_name();
if (!type.type_name && type.is_auto) {
type_name = cpp_type_class_name;
}
if (options & 1) {
os << "\n//\n// headers for " << (type.is_auto ? "auxiliary " : "") << "type `" << type_name << "`\n//\n";
generate_header(os, options);
} else if (options & 2) {
std::ostringstream tmp;
generate_header(tmp, options | 1);
}
if (options & 2) {
os << "\n//\n// code for " << (type.is_auto ? "auxiliary " : "") << "type `" << type_name << "`\n//\n";
generate_body(os, options);
}
}
void generate_type_constant(std::ostream& os, int i, TypeExpr* expr, std::string cpp_name, int mode) {
if (!mode) {
os << "// " << expr << std::endl;
os << "extern ";
}
std::string cls_name = "TLB";
int fake_arg = -1;
cls_name = compute_type_expr_class_name(expr, fake_arg);
os << "const " << cls_name << ' ' << cpp_name;
if (!mode) {
os << ";\n";
return;
}
int c = 0;
if (fake_arg >= 0) {
os << '{' << fake_arg;
c++;
}
for (const TypeExpr* arg : expr->args) {
if (!arg->negated) {
assert(arg->is_constexpr);
os << (c++ ? ", " : "{");
if (arg->is_nat) {
os << arg->value;
} else {
os << const_type_expr_cpp_idents.at(arg->is_constexpr);
}
}
}
if (c) {
os << '}';
}
os << ";\n";
}
void generate_type_constants(std::ostream& os, int mode) {
os << "\n// " << (mode ? "definitions" : "declarations") << " of constant types used\n\n";
for (int i = 1; i <= const_type_expr_num; i++) {
TypeExpr* expr = const_type_expr[i];
if (!expr->is_nat && !const_type_expr_simple[i]) {
generate_type_constant(os, i, expr, const_type_expr_cpp_idents[i], mode);
}
}
}
void generate_register_function(std::ostream& os, int mode) {
os << "\n// " << (mode ? "definition" : "declaration") << " of type name registration function\n";
if (!mode) {
os << "extern bool register_simple_types(std::function<bool(const char*, const TLB*)> func);\n";
return;
}
os << "bool register_simple_types(std::function<bool(const char*, const TLB*)> func) {\n";
os << " return ";
int k = 0;
for (int i = builtin_types_num; i < types_num; i++) {
Type& type = types[i];
CppTypeCode& cc = *cpp_type[i];
if (!cc.cpp_type_var_name.empty() && type.type_name) {
if (k++) {
os << "\n && ";
}
os << "func(\"" << type.get_name() << "\", &" << cc.cpp_type_var_name << ")";
}
}
if (!k) {
os << "true";
}
os << ";\n}\n\n";
}
void assign_const_type_cpp_idents() {
const_type_expr_cpp_idents.resize(const_type_expr_num + 1, "");
const_type_expr_simple.resize(const_type_expr_num + 1, false);
for (int i = 1; i <= const_type_expr_num; i++) {
const TypeExpr* expr = const_type_expr[i];
if (!expr->is_nat) {
if (expr->tp == TypeExpr::te_Ref && expr->args[0]->tp == TypeExpr::te_Apply &&
(expr->args[0]->type_applied == Any_type || expr->args[0]->type_applied == Cell_type)) {
const_type_expr_cpp_idents[i] = "t_RefCell";
const_type_expr_simple[i] = true;
continue;
}
if (expr->tp == TypeExpr::te_Apply) {
const Type* typ = expr->type_applied;
int idx = typ->type_idx;
if (typ == Any_type || typ == Cell_type || typ == Nat_type) {
const_type_expr_cpp_idents[i] = (typ == Nat_type ? "t_Nat" : "t_Anything");
const_type_expr_simple[i] = true;
continue;
}
if (idx >= builtin_types_num && idx < types_num && !cpp_type[idx]->params) {
const_type_expr_cpp_idents[i] = cpp_type[idx]->cpp_type_var_name;
const_type_expr_simple[i] = true;
continue;
}
}
std::ostringstream ss;
ss << "t";
expr->const_type_name(ss);
const_type_expr_cpp_idents[i] = global_cpp_ids.new_ident(ss.str());
}
}
}
std::string cpp_namespace = "tlb";
std::vector<std::string> cpp_namespace_list;
std::string tlb_library_header_name = "tl/tlblib.hpp";
void split_namespace_id() {
auto prev_it = cpp_namespace.cbegin();
for (auto it = cpp_namespace.cbegin(); it != cpp_namespace.cend(); ++it) {
if (it[0] == ':' && it + 2 != cpp_namespace.cend() && it[1] == ':') {
if (prev_it != it) {
cpp_namespace_list.emplace_back(prev_it, it);
}
++it;
prev_it = it + 1;
}
}
if (prev_it != cpp_namespace.cend()) {
cpp_namespace_list.emplace_back(prev_it, cpp_namespace.cend());
}
}
std::vector<int> type_gen_order;
void prepare_generate_cpp(int options = 0) {
std::vector<std::pair<int, int>> pairs;
pairs.reserve(types_num - builtin_types_num);
for (int i = builtin_types_num; i < types_num; i++) {
pairs.emplace_back(types.at(i).last_declared, i);
}
std::sort(pairs.begin(), pairs.end());
type_gen_order.reserve(pairs.size());
for (auto z : pairs) {
type_gen_order.push_back(z.second);
}
cpp_type.resize(types_num);
for (int i : type_gen_order) {
Type& type = types[i];
cpp_type[i] = std::make_unique<CppTypeCode>(type);
CppTypeCode& cc = *cpp_type[i];
if (!cpp_type[i] || !cc.is_ok()) {
throw src::Fatal{std::string{"cannot generate c++ code for type `"} + type.get_name() + "`"};
}
}
split_namespace_id();
assign_const_type_cpp_idents();
}
bool generate_prepared;
bool gen_cpp;
bool gen_hpp;
bool append_suffix;
void generate_cpp_output_to(std::ostream& os, int options = 0, std::vector<std::string> include_files = {}) {
if (!generate_prepared) {
prepare_generate_cpp(options);
generate_prepared = true;
}
if (options & 1) {
os << "#pragma once\n";
}
for (auto s : include_files) {
if (s.size() >= 10 && s.substr(s.size() - 10) == "tlblib.hpp") {
os << "#include <" << s << ">\n";
} else {
os << "#include \"" << s << "\"\n";
}
}
os << "/*\n *\n * AUTO-GENERATED FROM";
for (auto s : source_list) {
if (s.empty()) {
os << " stdin";
} else {
os << " `" << s << "`";
}
}
os << "\n *\n */\n";
for (int i = 0; i < builtin_types_num; i++) {
Type& type = types[i];
if (type.used) {
os << "// uses built-in type `" << type.get_name() << "`\n";
}
}
for (auto cpp_nsp : cpp_namespace_list) {
os << "\nnamespace " << cpp_nsp << " {" << std::endl;
};
if (cpp_namespace != "tlb") {
os << "using namespace ::tlb;\n";
}
os << "using td::Ref;\n"
<< "using vm::CellSlice;\n"
<< "using vm::Cell;\n"
<< "using td::RefInt256;\n";
for (int pass = 1; pass <= 2; pass++) {
if (options & pass) {
for (int i : type_gen_order) {
CppTypeCode& cc = *cpp_type[i];
cc.generate(os, (options & -4) | pass);
}
generate_type_constants(os, pass - 1);
generate_register_function(os, pass - 1);
}
}
for (auto it = cpp_namespace_list.rbegin(); it != cpp_namespace_list.rend(); ++it) {
os << "\n} // namespace " << *it << std::endl;
}
}
void generate_cpp_output_to(std::string filename, int options = 0, std::vector<std::string> include_files = {}) {
std::stringstream ss;
generate_cpp_output_to(ss, options, std::move(include_files));
auto new_content = ss.str();
auto r_old_content = td::read_file_str(filename);
if (r_old_content.is_ok() && r_old_content.ok() == new_content) {
return;
}
std::ofstream os{filename};
if (!os) {
throw src::Fatal{std::string{"cannot create output file `"} + filename + "`"};
}
os << new_content;
}
void generate_cpp_output(std::string filename = "", int options = 0) {
if (!gen_cpp && !gen_hpp) {
gen_cpp = gen_hpp = true;
}
options &= ~3;
options |= (gen_hpp ? 1 : 0) | (gen_cpp << 1);
if (filename.empty()) {
generate_cpp_output_to(std::cout, options, {tlb_library_header_name});
} else if (!append_suffix) {
generate_cpp_output_to(filename, options, {tlb_library_header_name});
} else {
if (gen_hpp) {
generate_cpp_output_to(filename + ".h", options & ~2, {tlb_library_header_name});
}
if (gen_cpp) {
generate_cpp_output_to(filename + ".cpp", options & ~1, {filename + ".h"});
}
}
}
} // namespace tlbc